remotus 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +5 -4
- data/.rubocop.yml +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +8 -1
- data/Gemfile.lock +25 -20
- data/README.md +9 -0
- data/docker/Dockerfile +16 -0
- data/docker/entrypoint.sh +3 -0
- data/docker/ssh_integration_script.sh +8 -0
- data/docker/ssh_integration_spec.rb +87 -0
- data/docker-compose.yml +10 -0
- data/lib/remotus/host_pool.rb +16 -0
- data/lib/remotus/pool.rb +16 -7
- data/lib/remotus/result.rb +1 -1
- data/lib/remotus/ssh_connection.rb +112 -17
- data/lib/remotus/version.rb +1 -1
- data/lib/remotus/winrm_connection.rb +12 -0
- data/remotus.gemspec +1 -0
- metadata +23 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ca295a9d011d1c64d48612e24df9e8ecb1b377ffde82010532c4bfa893d9f4ed
|
|
4
|
+
data.tar.gz: 808051a8602029e898047a51e4e689e28d3ce50c629a20dc08de3a1349f89b49
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0e5268b157fc283afb2bc830838da9bba8b04f6055396123422d8bed43b97797f02cee6974f01b6d6db4658b00c99e77cc13474cc60ba8fdd9fa72dfa87e4f18
|
|
7
|
+
data.tar.gz: 5681064618614505ce74388725a03f8ab2f3e442d5feaa8ee1be4539ed9e9170f9f8e374fe6f9ce8f2ebeaeafc72624814f51f0280799b2820b1334aa955da62
|
data/.github/workflows/main.yml
CHANGED
|
@@ -6,21 +6,22 @@ jobs:
|
|
|
6
6
|
build:
|
|
7
7
|
runs-on: ubuntu-latest
|
|
8
8
|
steps:
|
|
9
|
-
- uses: actions/checkout@
|
|
9
|
+
- uses: actions/checkout@v3
|
|
10
10
|
- name: Set up Ruby
|
|
11
11
|
uses: ruby/setup-ruby@v1
|
|
12
12
|
with:
|
|
13
|
-
ruby-version:
|
|
13
|
+
ruby-version: 3.0.4
|
|
14
14
|
- name: Run unit tests and code linting
|
|
15
15
|
run: |
|
|
16
|
-
gem install bundler -v 2.2.
|
|
16
|
+
gem install bundler -v 2.2.33
|
|
17
17
|
bundle install
|
|
18
18
|
bundle exec rake
|
|
19
|
+
bundle exec rspec docker
|
|
19
20
|
bundle exec yard
|
|
20
21
|
|
|
21
22
|
- name: Deploy documentation
|
|
22
23
|
if: github.ref == 'refs/heads/main'
|
|
23
|
-
uses: crazy-max/ghaction-github-pages@
|
|
24
|
+
uses: crazy-max/ghaction-github-pages@v3.0.0
|
|
24
25
|
with:
|
|
25
26
|
target_branch: gh-pages
|
|
26
27
|
build_dir: doc
|
data/.rubocop.yml
CHANGED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.0.4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [0.6.0] - 2022-09-26
|
|
2
|
+
* Add `#close` method to all connection types
|
|
3
|
+
* Fix SSH gateway connection caching
|
|
4
|
+
* Ensure SSH gateway connections are closed gracefully before reinitializing a connection
|
|
5
|
+
|
|
6
|
+
## [0.5.0] - 2022-09-21
|
|
7
|
+
* Ensure port argument is respected in `Remotus::SshConnection`
|
|
8
|
+
* Add SSH gateway support
|
|
2
9
|
|
|
3
10
|
## [0.4.0] - 2022-06-02
|
|
4
11
|
* Added winrm-elevated gem to solve wirnrm AuthenticationError
|
data/Gemfile.lock
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
remotus (0.
|
|
4
|
+
remotus (0.6.0)
|
|
5
5
|
connection_pool (~> 2.2)
|
|
6
6
|
net-scp (~> 3.0)
|
|
7
7
|
net-ssh (~> 6.1)
|
|
8
|
+
net-ssh-gateway (~> 2.0)
|
|
8
9
|
winrm (~> 2.3)
|
|
9
10
|
winrm-elevated (~> 1.2)
|
|
10
11
|
winrm-fs (~> 1.3)
|
|
@@ -14,9 +15,9 @@ GEM
|
|
|
14
15
|
specs:
|
|
15
16
|
ast (2.4.2)
|
|
16
17
|
builder (3.2.4)
|
|
17
|
-
connection_pool (2.
|
|
18
|
+
connection_pool (2.3.0)
|
|
18
19
|
diff-lcs (1.5.0)
|
|
19
|
-
erubi (1.
|
|
20
|
+
erubi (1.11.0)
|
|
20
21
|
ffi (1.15.5)
|
|
21
22
|
gssapi (1.3.1)
|
|
22
23
|
ffi (>= 1.0.1)
|
|
@@ -24,6 +25,7 @@ GEM
|
|
|
24
25
|
builder (>= 2.1.2)
|
|
25
26
|
rexml (~> 3.0)
|
|
26
27
|
httpclient (2.8.3)
|
|
28
|
+
json (2.6.2)
|
|
27
29
|
little-plugger (1.1.4)
|
|
28
30
|
logging (2.3.1)
|
|
29
31
|
little-plugger (~> 1.1)
|
|
@@ -32,13 +34,15 @@ GEM
|
|
|
32
34
|
net-scp (3.0.0)
|
|
33
35
|
net-ssh (>= 2.6.5, < 7.0.0)
|
|
34
36
|
net-ssh (6.1.0)
|
|
37
|
+
net-ssh-gateway (2.0.0)
|
|
38
|
+
net-ssh (>= 4.0.0)
|
|
35
39
|
nori (2.6.0)
|
|
36
|
-
parallel (1.
|
|
37
|
-
parser (3.1.
|
|
40
|
+
parallel (1.22.1)
|
|
41
|
+
parser (3.1.2.1)
|
|
38
42
|
ast (~> 2.4.1)
|
|
39
43
|
rainbow (3.1.1)
|
|
40
44
|
rake (13.0.6)
|
|
41
|
-
regexp_parser (2.
|
|
45
|
+
regexp_parser (2.5.0)
|
|
42
46
|
rexml (3.2.5)
|
|
43
47
|
rspec (3.11.0)
|
|
44
48
|
rspec-core (~> 3.11.0)
|
|
@@ -46,32 +50,33 @@ GEM
|
|
|
46
50
|
rspec-mocks (~> 3.11.0)
|
|
47
51
|
rspec-core (3.11.0)
|
|
48
52
|
rspec-support (~> 3.11.0)
|
|
49
|
-
rspec-expectations (3.11.
|
|
53
|
+
rspec-expectations (3.11.1)
|
|
50
54
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
51
55
|
rspec-support (~> 3.11.0)
|
|
52
|
-
rspec-mocks (3.11.
|
|
56
|
+
rspec-mocks (3.11.1)
|
|
53
57
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
54
58
|
rspec-support (~> 3.11.0)
|
|
55
|
-
rspec-support (3.11.
|
|
56
|
-
rubocop (1.
|
|
59
|
+
rspec-support (3.11.1)
|
|
60
|
+
rubocop (1.36.0)
|
|
61
|
+
json (~> 2.3)
|
|
57
62
|
parallel (~> 1.10)
|
|
58
|
-
parser (>= 3.1.
|
|
63
|
+
parser (>= 3.1.2.1)
|
|
59
64
|
rainbow (>= 2.2.2, < 4.0)
|
|
60
65
|
regexp_parser (>= 1.8, < 3.0)
|
|
61
|
-
rexml
|
|
62
|
-
rubocop-ast (>= 1.
|
|
66
|
+
rexml (>= 3.2.5, < 4.0)
|
|
67
|
+
rubocop-ast (>= 1.20.1, < 2.0)
|
|
63
68
|
ruby-progressbar (~> 1.7)
|
|
64
69
|
unicode-display_width (>= 1.4.0, < 3.0)
|
|
65
|
-
rubocop-ast (1.
|
|
66
|
-
parser (>= 3.
|
|
70
|
+
rubocop-ast (1.21.0)
|
|
71
|
+
parser (>= 3.1.1.0)
|
|
67
72
|
rubocop-rake (0.6.0)
|
|
68
73
|
rubocop (~> 1.0)
|
|
69
|
-
rubocop-rspec (2.
|
|
70
|
-
rubocop (~> 1.
|
|
74
|
+
rubocop-rspec (2.13.1)
|
|
75
|
+
rubocop (~> 1.33)
|
|
71
76
|
ruby-progressbar (1.11.0)
|
|
72
77
|
rubyntlm (0.6.3)
|
|
73
78
|
rubyzip (2.3.2)
|
|
74
|
-
unicode-display_width (2.
|
|
79
|
+
unicode-display_width (2.3.0)
|
|
75
80
|
webrick (1.7.0)
|
|
76
81
|
winrm (2.3.6)
|
|
77
82
|
builder (>= 2.1.2)
|
|
@@ -91,7 +96,7 @@ GEM
|
|
|
91
96
|
logging (>= 1.6.1, < 3.0)
|
|
92
97
|
rubyzip (~> 2.0)
|
|
93
98
|
winrm (~> 2.0)
|
|
94
|
-
yard (0.9.
|
|
99
|
+
yard (0.9.28)
|
|
95
100
|
webrick (~> 1.7.0)
|
|
96
101
|
|
|
97
102
|
PLATFORMS
|
|
@@ -108,4 +113,4 @@ DEPENDENCIES
|
|
|
108
113
|
yard (~> 0.9)
|
|
109
114
|
|
|
110
115
|
BUNDLED WITH
|
|
111
|
-
2.2.
|
|
116
|
+
2.2.33
|
data/README.md
CHANGED
|
@@ -30,6 +30,15 @@ connection = Remotus.connect("remotehost.local", proto: :ssh, port: 2222)
|
|
|
30
30
|
# Initialize a new connection pool to remotehost.local with a defined protocol and port and arbitrary metadata
|
|
31
31
|
connection = Remotus.connect("remotehost.local", proto: :ssh, port: 2222, company: "Test Corp", location: "Oslo")
|
|
32
32
|
|
|
33
|
+
# Initialize a new connection pool to remotehost.local via a gateway host named gateway.local
|
|
34
|
+
connection = Remotus.connect("remotehost.local", gateway_host: "gateway.local")
|
|
35
|
+
|
|
36
|
+
# Initialize a new connection pool to remotehost.local via a gateway host named gateway.local with a defined protocol and port
|
|
37
|
+
connection = Remotus.connect("remotehost.local", proto: :ssh, port: 2222, gateway_host: "gateway.local", gateway_port: 2222)
|
|
38
|
+
|
|
39
|
+
# Initialize a new connection pool to remotehost.local via a gateway host named gateway.local with arbitrary metadata
|
|
40
|
+
connection = Remotus.connect("remotehost.local", company: "Test Corp", gateway_host: "gateway.local", gateway_metadata: { company: "Other Corp" })
|
|
41
|
+
|
|
33
42
|
# Create a credential for the new connection pool
|
|
34
43
|
connection.credential = Remotus::Auth::Credential.new("username", "password")
|
|
35
44
|
|
data/docker/Dockerfile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
FROM alpine:latest
|
|
2
|
+
|
|
3
|
+
COPY entrypoint.sh /
|
|
4
|
+
|
|
5
|
+
RUN apk add --update --no-cache openssh && \
|
|
6
|
+
sed -i '/GatewayPorts.*/d' /etc/ssh/sshd_config && \
|
|
7
|
+
echo 'GatewayPorts yes' >> /etc/ssh/sshd_config && \
|
|
8
|
+
sed -i '/AllowTcpForwarding.*/d' /etc/ssh/sshd_config && \
|
|
9
|
+
echo 'AllowTcpForwarding yes' >> /etc/ssh/sshd_config && \
|
|
10
|
+
echo 'PasswordAuthentication yes' >> /etc/ssh/sshd_config && \
|
|
11
|
+
adduser -h /home/testuser -s /bin/sh -D testuser && \
|
|
12
|
+
echo 'testuser:testuser' | chpasswd
|
|
13
|
+
|
|
14
|
+
ENTRYPOINT ["/entrypoint.sh"]
|
|
15
|
+
|
|
16
|
+
EXPOSE 22
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Assumes docker-compose is available
|
|
4
|
+
|
|
5
|
+
RSpec.describe "Remotus::SshConnection Integration Tests" do
|
|
6
|
+
before(:all) do
|
|
7
|
+
# docker-compose startup
|
|
8
|
+
`docker-compose -f "docker-compose.yml" up -d --build`
|
|
9
|
+
|
|
10
|
+
# Set up Remotus credentials based on Dockerfile
|
|
11
|
+
Remotus::Auth.cache["localhost"] = Remotus::Auth::Credential.new("testuser", "testuser")
|
|
12
|
+
Remotus::Auth.cache["target"] = Remotus::Auth::Credential.new("testuser", "testuser")
|
|
13
|
+
|
|
14
|
+
# Allow time for sshd to start up
|
|
15
|
+
sleep 1
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
after(:all) do
|
|
19
|
+
# Close all active connections
|
|
20
|
+
Remotus.clear
|
|
21
|
+
|
|
22
|
+
# docker-compose cleanup
|
|
23
|
+
`docker-compose -f "docker-compose.yml" down -v`
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
let(:test_script) { ::File.join(__dir__, "ssh_integration_script.sh") }
|
|
27
|
+
let(:test_script_dest) { ::File.join("/home/testuser", ::File.basename(test_script)) }
|
|
28
|
+
|
|
29
|
+
context "when a gateway and inaccessible target host exist" do
|
|
30
|
+
let(:gateway_hostname) { `docker ps | grep remotus_gateway`.split.first }
|
|
31
|
+
let(:target_hostname) { `docker ps | grep remotus_target`.split.first }
|
|
32
|
+
let(:gateway_connection) { Remotus.connect("localhost", proto: :ssh, port: 2222) }
|
|
33
|
+
let(:target_connection) { Remotus.connect("target", proto: :ssh, port: 22, gateway_host: "localhost", gateway_port: 2222) }
|
|
34
|
+
|
|
35
|
+
it "Connects to the gateway host successfully" do
|
|
36
|
+
expect(gateway_connection.run("hostname").stdout.chomp).to eq(gateway_hostname)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "Can run a script against the gateway host successfully" do
|
|
40
|
+
result = gateway_connection.run_script(test_script, test_script_dest)
|
|
41
|
+
expect(result.stdout).to eq("success")
|
|
42
|
+
expect(result.success?).to eq(true)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "Can upload a file to the gateway host" do
|
|
46
|
+
result = gateway_connection.upload(test_script, "/home/testuser/upload_test")
|
|
47
|
+
expect(result).to eq("/home/testuser/upload_test")
|
|
48
|
+
expect(gateway_connection.file_exist?("/home/testuser/upload_test")).to eq(true)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "Can download a file from the gateway host" do
|
|
52
|
+
gateway_connection.run('echo -n "test" > /home/testuser/download_test')
|
|
53
|
+
result = gateway_connection.download("/home/testuser/download_test")
|
|
54
|
+
expect(result).to eq("test")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "Can check if a file exists on the gateway host" do
|
|
58
|
+
expect(gateway_connection.file_exist?("/home/testuser")).to eq(true)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "Connects to the target host successfully" do
|
|
62
|
+
expect(target_connection.run("hostname").stdout.chomp).to eq(target_hostname)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "Can run a script against the target host successfully" do
|
|
66
|
+
result = target_connection.run_script(test_script, test_script_dest)
|
|
67
|
+
expect(result.stdout).to eq("success")
|
|
68
|
+
expect(result.success?).to eq(true)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "Can upload a file to the target host" do
|
|
72
|
+
result = target_connection.upload(test_script, "/home/testuser/upload_test")
|
|
73
|
+
expect(result).to eq("/home/testuser/upload_test")
|
|
74
|
+
expect(target_connection.file_exist?("/home/testuser/upload_test")).to eq(true)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "Can download a file from the target host" do
|
|
78
|
+
target_connection.run('echo -n "test" > /home/testuser/download_test')
|
|
79
|
+
result = target_connection.download("/home/testuser/download_test")
|
|
80
|
+
expect(result).to eq("test")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "Can check if a file exists on the target host" do
|
|
84
|
+
expect(target_connection.file_exist?("/home/testuser")).to eq(true)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
data/docker-compose.yml
ADDED
data/lib/remotus/host_pool.rb
CHANGED
|
@@ -42,6 +42,13 @@ module Remotus
|
|
|
42
42
|
# @param [Hash] metadata metadata for this connection. Useful for providing additional information to various authentication stores
|
|
43
43
|
# should be specified using snake_case symbol keys. If keys are not snake_case, they will be converted.
|
|
44
44
|
#
|
|
45
|
+
# To configure a connection gateway, the following metadata entries can be provided to the host pool:
|
|
46
|
+
# :gateway_host
|
|
47
|
+
# :gateway_port
|
|
48
|
+
# :gateway_metadata
|
|
49
|
+
#
|
|
50
|
+
# These function similarly to the host, port, and host_pool metadata fields.
|
|
51
|
+
#
|
|
45
52
|
def initialize(host, size: DEFAULT_POOL_SIZE, timeout: DEFAULT_EXPIRATION_SECONDS, port: nil, proto: nil, **metadata)
|
|
46
53
|
Remotus.logger.debug { "Creating host pool for #{host}" }
|
|
47
54
|
|
|
@@ -80,6 +87,15 @@ module Remotus
|
|
|
80
87
|
@expiration_time = Time.now
|
|
81
88
|
end
|
|
82
89
|
|
|
90
|
+
#
|
|
91
|
+
# Closes all open connections in the pool.
|
|
92
|
+
# @see Remotus::SshConnection#close
|
|
93
|
+
# @see Remotus::WinrmConnection#close
|
|
94
|
+
#
|
|
95
|
+
def close
|
|
96
|
+
@pool.reload(&:close)
|
|
97
|
+
end
|
|
98
|
+
|
|
83
99
|
#
|
|
84
100
|
# Provides an SSH or WinRM connection to a given block of code
|
|
85
101
|
#
|
data/lib/remotus/pool.rb
CHANGED
|
@@ -58,7 +58,11 @@ module Remotus
|
|
|
58
58
|
return 0 unless @pool
|
|
59
59
|
|
|
60
60
|
num_pools = count
|
|
61
|
-
|
|
61
|
+
|
|
62
|
+
# Force connection close
|
|
63
|
+
@pool.each { |_hostname, host_pool| host_pool.close }
|
|
64
|
+
@pool = {}
|
|
65
|
+
|
|
62
66
|
return num_pools
|
|
63
67
|
end
|
|
64
68
|
end
|
|
@@ -77,13 +81,18 @@ module Remotus
|
|
|
77
81
|
# If the pool is not yet initialized, no processes can be reaped
|
|
78
82
|
return 0 unless @pool
|
|
79
83
|
|
|
80
|
-
|
|
81
|
-
pre_reap_num_pools = count
|
|
82
|
-
@pool.reject! { |_hostname, host_pool| host_pool.expired? }
|
|
83
|
-
post_reap_num_pools = count
|
|
84
|
+
pools_reaped = 0
|
|
84
85
|
|
|
85
|
-
#
|
|
86
|
-
|
|
86
|
+
# Force connection close for expired host pools
|
|
87
|
+
@pool.each do |hostname, host_pool|
|
|
88
|
+
next unless host_pool.expired?
|
|
89
|
+
|
|
90
|
+
# Close and delete the host pool if it is expired
|
|
91
|
+
Remotus.logger.debug { "Reaping #{hostname} host pool" }
|
|
92
|
+
host_pool.close
|
|
93
|
+
@pool.delete(hostname)
|
|
94
|
+
pools_reaped += 1
|
|
95
|
+
end
|
|
87
96
|
|
|
88
97
|
Remotus.logger.debug { "Reaped #{pools_reaped} expired host pools" }
|
|
89
98
|
|
data/lib/remotus/result.rb
CHANGED
|
@@ -65,7 +65,7 @@ module Remotus
|
|
|
65
65
|
def error!(accepted_exit_codes = [0])
|
|
66
66
|
return unless error?(accepted_exit_codes)
|
|
67
67
|
|
|
68
|
-
raise Remotus::ResultError, "Error encountered executing #{@command}! Exit code #{@exit_code} was returned "\
|
|
68
|
+
raise Remotus::ResultError, "Error encountered executing #{@command}! Exit code #{@exit_code} was returned " \
|
|
69
69
|
"while a value in #{accepted_exit_codes} was expected.\n#{output}"
|
|
70
70
|
end
|
|
71
71
|
|
|
@@ -6,6 +6,7 @@ require "remotus/result"
|
|
|
6
6
|
require "remotus/auth"
|
|
7
7
|
require "net/scp"
|
|
8
8
|
require "net/ssh"
|
|
9
|
+
require "net/ssh/gateway"
|
|
9
10
|
|
|
10
11
|
module Remotus
|
|
11
12
|
# Class representing an SSH connection to a host
|
|
@@ -21,6 +22,9 @@ module Remotus
|
|
|
21
22
|
# Number of default retries
|
|
22
23
|
DEFAULT_RETRIES = 2
|
|
23
24
|
|
|
25
|
+
# Base options for new SSH connections
|
|
26
|
+
BASE_CONNECT_OPTIONS = { non_interactive: true, keepalive: true, keepalive_interval: KEEPALIVE_INTERVAL }.freeze
|
|
27
|
+
|
|
24
28
|
# @return [Integer] Remote port
|
|
25
29
|
attr_reader :port
|
|
26
30
|
|
|
@@ -33,12 +37,50 @@ module Remotus
|
|
|
33
37
|
# Delegate metadata methods to associated host pool
|
|
34
38
|
def_delegators :@host_pool, :[], :[]=
|
|
35
39
|
|
|
40
|
+
# Internal gateway connection class to allow for the host and metadata to be pulled for the gateway
|
|
41
|
+
# by authentication credentials
|
|
42
|
+
class GatewayConnection
|
|
43
|
+
extend Forwardable
|
|
44
|
+
|
|
45
|
+
# @return [String] host gateway hostname
|
|
46
|
+
attr_reader :host
|
|
47
|
+
|
|
48
|
+
# @return [Integer] Remote port
|
|
49
|
+
attr_reader :port
|
|
50
|
+
|
|
51
|
+
# @return [Net::SSH::Gateway] connection gateway connection
|
|
52
|
+
attr_accessor :connection
|
|
53
|
+
|
|
54
|
+
# Delegate metadata methods to associated hash
|
|
55
|
+
def_delegators :@metadata, :[], :[]=
|
|
56
|
+
|
|
57
|
+
#
|
|
58
|
+
# Creates a GatewayConnection
|
|
59
|
+
#
|
|
60
|
+
# @param [String] host hostname
|
|
61
|
+
# @param [Integer] port remote port
|
|
62
|
+
# @param [Hash] metadata associated metadata for this gateway
|
|
63
|
+
#
|
|
64
|
+
def initialize(host, port = REMOTE_PORT, metadata = {})
|
|
65
|
+
@host = host
|
|
66
|
+
@port = port
|
|
67
|
+
@metadata = metadata
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
36
71
|
#
|
|
37
72
|
# Creates an SshConnection
|
|
38
73
|
#
|
|
39
74
|
# @param [String] host hostname
|
|
40
75
|
# @param [Integer] port remote port
|
|
41
76
|
# @param [Remotus::HostPool] host_pool associated host pool
|
|
77
|
+
# To configure the gateway, the following metadata
|
|
78
|
+
# entries can be provided to the host pool:
|
|
79
|
+
# :gateway_host
|
|
80
|
+
# :gateway_port
|
|
81
|
+
# :gateway_metadata
|
|
82
|
+
#
|
|
83
|
+
# These function similarly to the host, port, and host_pool metadata fields.
|
|
42
84
|
#
|
|
43
85
|
def initialize(host, port = REMOTE_PORT, host_pool: nil)
|
|
44
86
|
Remotus.logger.debug { "Creating SshConnection #{object_id} for #{host}" }
|
|
@@ -77,23 +119,47 @@ module Remotus
|
|
|
77
119
|
def connection
|
|
78
120
|
return @connection unless restart_connection?
|
|
79
121
|
|
|
80
|
-
|
|
122
|
+
# Close any active connections
|
|
123
|
+
close
|
|
124
|
+
|
|
125
|
+
target_cred = Remotus::Auth.credential(self)
|
|
126
|
+
|
|
127
|
+
Remotus.logger.debug { "Initializing SSH connection to #{target_cred.user}@#{@host}:#{@port}" }
|
|
81
128
|
|
|
82
|
-
|
|
129
|
+
target_options = BASE_CONNECT_OPTIONS.dup
|
|
130
|
+
target_options[:password] = target_cred.password if target_cred.password
|
|
131
|
+
target_options[:keys] = [target_cred.private_key] if target_cred.private_key
|
|
132
|
+
target_options[:key_data] = [target_cred.private_key_data] if target_cred.private_key_data
|
|
133
|
+
target_options[:port] = @port || REMOTE_PORT
|
|
83
134
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
135
|
+
if via_gateway?
|
|
136
|
+
@gateway = GatewayConnection.new(@host_pool[:gateway_host], @host_pool[:gateway_port], @host_pool[:gateway_metadata])
|
|
137
|
+
gateway_cred = Remotus::Auth.credential(@gateway)
|
|
138
|
+
gateway_options = BASE_CONNECT_OPTIONS.dup
|
|
139
|
+
gateway_options[:port] = @gateway.port || REMOTE_PORT
|
|
140
|
+
gateway_options[:password] = gateway_cred.password if gateway_cred.password
|
|
141
|
+
gateway_options[:keys] = [gateway_cred.private_key] if gateway_cred.private_key
|
|
142
|
+
gateway_options[:key_data] = [gateway_cred.private_key_data] if gateway_cred.private_key_data
|
|
143
|
+
|
|
144
|
+
Remotus.logger.debug { "Initializing SSH gateway connection to #{gateway_cred.user}@#{@gateway.host}:#{gateway_options[:port]}" }
|
|
145
|
+
|
|
146
|
+
@gateway.connection = Net::SSH::Gateway.new(@gateway.host, gateway_cred.user, **gateway_options)
|
|
147
|
+
@connection = @gateway.connection.ssh(@host, target_cred.user, **target_options)
|
|
148
|
+
else
|
|
149
|
+
@connection = Net::SSH.start(@host, target_cred.user, **target_options)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
#
|
|
154
|
+
# Closes the current SSH connection if it is active
|
|
155
|
+
#
|
|
156
|
+
def close
|
|
157
|
+
@connection&.close
|
|
87
158
|
|
|
88
|
-
|
|
89
|
-
options[:keys] = [private_key_path] if private_key_path
|
|
90
|
-
options[:key_data] = [private_key_data] if private_key_data
|
|
159
|
+
@gateway&.connection&.shutdown! if via_gateway?
|
|
91
160
|
|
|
92
|
-
@
|
|
93
|
-
|
|
94
|
-
Remotus::Auth.credential(self).user,
|
|
95
|
-
**options
|
|
96
|
-
)
|
|
161
|
+
@gateway = nil
|
|
162
|
+
@connection = nil
|
|
97
163
|
end
|
|
98
164
|
|
|
99
165
|
#
|
|
@@ -392,10 +458,30 @@ module Remotus
|
|
|
392
458
|
return true unless @connection
|
|
393
459
|
return true if @connection.closed?
|
|
394
460
|
return true if @host != @connection.host
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
return true if
|
|
461
|
+
|
|
462
|
+
target_cred = Remotus::Auth.credential(self)
|
|
463
|
+
|
|
464
|
+
return true if target_cred.user != @connection.options[:user]
|
|
465
|
+
return true if target_cred.password != @connection.options[:password]
|
|
466
|
+
return true if Array(target_cred.private_key) != Array(@connection.options[:keys])
|
|
467
|
+
return true if Array(target_cred.private_key_data) != Array(@connection.options[:key_data])
|
|
468
|
+
|
|
469
|
+
# Perform gateway checks
|
|
470
|
+
if via_gateway?
|
|
471
|
+
return true unless @gateway&.connection&.active?
|
|
472
|
+
|
|
473
|
+
gateway_session = @gateway.connection.instance_variable_get(:@session)
|
|
474
|
+
|
|
475
|
+
return true if gateway_session.closed?
|
|
476
|
+
return true if @host_pool[:gateway_host] != gateway_session.host
|
|
477
|
+
|
|
478
|
+
gateway_cred = Remotus::Auth.credential(@gateway)
|
|
479
|
+
|
|
480
|
+
return true if gateway_cred.user != gateway_session.options[:user]
|
|
481
|
+
return true if gateway_cred.password != gateway_session.options[:password]
|
|
482
|
+
return true if Array(gateway_cred.private_key) != Array(gateway_session.options[:keys])
|
|
483
|
+
return true if Array(gateway_cred.private_key_data) != Array(gateway_session.options[:key_data])
|
|
484
|
+
end
|
|
399
485
|
|
|
400
486
|
false
|
|
401
487
|
end
|
|
@@ -469,5 +555,14 @@ module Remotus
|
|
|
469
555
|
Remotus.logger.debug { "Generated permission commands #{cmds}" }
|
|
470
556
|
cmds
|
|
471
557
|
end
|
|
558
|
+
|
|
559
|
+
#
|
|
560
|
+
# Whether connecting via an SSH gateway
|
|
561
|
+
#
|
|
562
|
+
# @return [Boolean] true if using a gateway, false otherwise
|
|
563
|
+
#
|
|
564
|
+
def via_gateway?
|
|
565
|
+
host_pool && host_pool[:gateway_host]
|
|
566
|
+
end
|
|
472
567
|
end
|
|
473
568
|
end
|
data/lib/remotus/version.rb
CHANGED
|
@@ -64,6 +64,9 @@ module Remotus
|
|
|
64
64
|
def base_connection(reload: false)
|
|
65
65
|
return @base_connection if !reload && !restart_base_connection?
|
|
66
66
|
|
|
67
|
+
# Close any active connections
|
|
68
|
+
close
|
|
69
|
+
|
|
67
70
|
Remotus.logger.debug { "Initializing WinRM connection to #{Remotus::Auth.credential(self).user}@#{@host}:#{@port}" }
|
|
68
71
|
@base_connection = WinRM::Connection.new(
|
|
69
72
|
endpoint: "http://#{@host}:#{@port}/wsman",
|
|
@@ -88,6 +91,15 @@ module Remotus
|
|
|
88
91
|
@connection = base_connection(reload: true).shell(@shell)
|
|
89
92
|
end
|
|
90
93
|
|
|
94
|
+
#
|
|
95
|
+
# Closes the current WinRM connection if it is active
|
|
96
|
+
#
|
|
97
|
+
def close
|
|
98
|
+
@connection&.close
|
|
99
|
+
@connection = nil
|
|
100
|
+
@base_connection = nil
|
|
101
|
+
end
|
|
102
|
+
|
|
91
103
|
#
|
|
92
104
|
# Whether the remote host's WinRM port is available
|
|
93
105
|
#
|
data/remotus.gemspec
CHANGED
|
@@ -32,6 +32,7 @@ Gem::Specification.new do |spec|
|
|
|
32
32
|
spec.add_dependency "connection_pool", "~> 2.2"
|
|
33
33
|
spec.add_dependency "net-scp", "~> 3.0"
|
|
34
34
|
spec.add_dependency "net-ssh", "~> 6.1"
|
|
35
|
+
spec.add_dependency "net-ssh-gateway", "~> 2.0"
|
|
35
36
|
spec.add_dependency "winrm", "~> 2.3"
|
|
36
37
|
spec.add_dependency "winrm-elevated", "~> 1.2"
|
|
37
38
|
spec.add_dependency "winrm-fs", "~> 1.3"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: remotus
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matthew Newell
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-
|
|
11
|
+
date: 2022-09-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: connection_pool
|
|
@@ -52,6 +52,20 @@ dependencies:
|
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '6.1'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: net-ssh-gateway
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '2.0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '2.0'
|
|
55
69
|
- !ruby/object:Gem::Dependency
|
|
56
70
|
name: winrm
|
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -189,6 +203,7 @@ files:
|
|
|
189
203
|
- ".gitignore"
|
|
190
204
|
- ".rspec"
|
|
191
205
|
- ".rubocop.yml"
|
|
206
|
+
- ".ruby-version"
|
|
192
207
|
- CHANGELOG.md
|
|
193
208
|
- Gemfile
|
|
194
209
|
- Gemfile.lock
|
|
@@ -197,6 +212,11 @@ files:
|
|
|
197
212
|
- Rakefile
|
|
198
213
|
- bin/console
|
|
199
214
|
- bin/setup
|
|
215
|
+
- docker-compose.yml
|
|
216
|
+
- docker/Dockerfile
|
|
217
|
+
- docker/entrypoint.sh
|
|
218
|
+
- docker/ssh_integration_script.sh
|
|
219
|
+
- docker/ssh_integration_spec.rb
|
|
200
220
|
- lib/remotus.rb
|
|
201
221
|
- lib/remotus/auth.rb
|
|
202
222
|
- lib/remotus/auth/credential.rb
|
|
@@ -235,7 +255,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
235
255
|
- !ruby/object:Gem::Version
|
|
236
256
|
version: '0'
|
|
237
257
|
requirements: []
|
|
238
|
-
rubygems_version: 3.
|
|
258
|
+
rubygems_version: 3.2.33
|
|
239
259
|
signing_key:
|
|
240
260
|
specification_version: 4
|
|
241
261
|
summary: Ruby gem for connecting to remote systems seamlessly via WinRM or SSH.
|