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 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.