remotus 0.4.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|