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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34953ce4b1c254e31a40b7b3ff3795124fe98b712722862a35813cceba686759
4
- data.tar.gz: 3d342087b707b3078cedf38038b3ba21c1a1db6ed7c7d189f5912d9cd5adf4e7
3
+ metadata.gz: ca295a9d011d1c64d48612e24df9e8ecb1b377ffde82010532c4bfa893d9f4ed
4
+ data.tar.gz: 808051a8602029e898047a51e4e689e28d3ce50c629a20dc08de3a1349f89b49
5
5
  SHA512:
6
- metadata.gz: c884fbb30a585899e840ab9e3dddd3b3d6ac052d4b10745bc9c582f9e67ca445aa9ef98af7ac8cbba551cc6594f23a9583d3f54cfba6202f3b8595804bd60641
7
- data.tar.gz: 166f8cb4e3e633a79f1f28ddba9a3ff8793cc2927873bdd349e035870273c81ad6a9688f37d2f871413f43451604f64a06631e0813f8e111f7d36c701a168b05
6
+ metadata.gz: 0e5268b157fc283afb2bc830838da9bba8b04f6055396123422d8bed43b97797f02cee6974f01b6d6db4658b00c99e77cc13474cc60ba8fdd9fa72dfa87e4f18
7
+ data.tar.gz: 5681064618614505ce74388725a03f8ab2f3e442d5feaa8ee1be4539ed9e9170f9f8e374fe6f9ce8f2ebeaeafc72624814f51f0280799b2820b1334aa955da62
@@ -6,21 +6,22 @@ jobs:
6
6
  build:
7
7
  runs-on: ubuntu-latest
8
8
  steps:
9
- - uses: actions/checkout@v2
9
+ - uses: actions/checkout@v3
10
10
  - name: Set up Ruby
11
11
  uses: ruby/setup-ruby@v1
12
12
  with:
13
- ruby-version: 2.7.2
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.9
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@v2.2.0
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
@@ -19,6 +19,7 @@ Metrics/ClassLength:
19
19
  Metrics/BlockLength:
20
20
  Exclude:
21
21
  - spec/**/*.rb
22
+ - docker/**/*.rb
22
23
  - remotus.gemspec
23
24
  - lib/remotus/ssh_connection.rb
24
25
 
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.0.4
data/CHANGELOG.md CHANGED
@@ -1,4 +1,11 @@
1
- ## [Unreleased]
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.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.2.5)
18
+ connection_pool (2.3.0)
18
19
  diff-lcs (1.5.0)
19
- erubi (1.10.0)
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.21.0)
37
- parser (3.1.0.0)
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.2.1)
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.0)
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.0)
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.0)
56
- rubocop (1.25.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.0.0)
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.15.1, < 2.0)
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.15.2)
66
- parser (>= 3.0.1.1)
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.8.0)
70
- rubocop (~> 1.19)
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.1.0)
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.27)
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.22
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,3 @@
1
+ #!/bin/sh
2
+ ssh-keygen -A
3
+ exec /usr/sbin/sshd -D -e "$@"
@@ -0,0 +1,8 @@
1
+ #!/bin/sh
2
+
3
+ # Script for use with the ssh_integration_spec.rb to validate
4
+ # SSH script functionality.
5
+
6
+ echo -n 'test complete' > /home/testuser/script_file
7
+
8
+ echo -n 'success'
@@ -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
@@ -0,0 +1,10 @@
1
+ version: "3.9"
2
+ services:
3
+ # Accessible gateway host
4
+ gateway:
5
+ build: docker
6
+ ports:
7
+ - "2222:22"
8
+ # Inaccessible gateway target host
9
+ target:
10
+ build: docker
@@ -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
- @pool.reject! { |_hostname, _host_pool| true }
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
- # reap all expired host pools
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
- # Calculate the number of pools reaped
86
- pools_reaped = pre_reap_num_pools - post_reap_num_pools
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
 
@@ -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
- Remotus.logger.debug { "Initializing SSH connection to #{Remotus::Auth.credential(self).user}@#{@host}:#{@port}" }
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
- options = { non_interactive: true, keepalive: true, keepalive_interval: KEEPALIVE_INTERVAL }
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
- password = Remotus::Auth.credential(self).password
85
- private_key_path = Remotus::Auth.credential(self).private_key
86
- private_key_data = Remotus::Auth.credential(self).private_key_data
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
- options[:password] = password if password
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
- @connection = Net::SSH.start(
93
- @host,
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
- return true if Remotus::Auth.credential(self).user != @connection.options[:user]
396
- return true if Remotus::Auth.credential(self).password != @connection.options[:password]
397
- return true if Array(Remotus::Auth.credential(self).private_key) != Array(@connection.options[:keys])
398
- return true if Array(Remotus::Auth.credential(self).private_key_data) != Array(@connection.options[:key_data])
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Remotus
4
4
  # Remotus gem version
5
- VERSION = "0.4.0"
5
+ VERSION = "0.6.0"
6
6
  end
@@ -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.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-06-09 00:00:00.000000000 Z
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.1.4
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.