train-winrm 0.3.1 → 0.4.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/lib/train-winrm/connection.rb +17 -2
- data/lib/train-winrm/socks_proxy_patch.rb +110 -0
- data/lib/train-winrm/transport.rb +32 -8
- data/lib/train-winrm/version.rb +1 -1
- metadata +10 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b04c6c445681047b4681b59960762a6a4493340596dad7285f5f2f4ea1639e5
|
4
|
+
data.tar.gz: 77cddd410ec5e3053c727674c7338432479bc0b315ffc97ddaace31711435c06
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1edf24433ce19b6e0a778a224b9a882e70e31a07f108d5a9c5d6224ef2b6f4d736502844fbd384d00a88dbc8a97c2dec90885f84804e230e25b5ffff311d4fc
|
7
|
+
data.tar.gz: 0c2127482ba6cd7747871b53c41c3b72b2ed20abe29968e6675bf1e9ff53967b3ae2745a745d2d4349183062b5caaeccd0d3bb16a1fa70b157d56b9e408181d0
|
@@ -31,6 +31,8 @@
|
|
31
31
|
# * marshalling to / from JSON
|
32
32
|
# You don't have to worry about most of this.
|
33
33
|
|
34
|
+
# frozen_string_literal: true
|
35
|
+
|
34
36
|
require "train"
|
35
37
|
require "train/plugins"
|
36
38
|
# This module may need to directly require WinRM to reference its exception classes
|
@@ -50,6 +52,9 @@ module TrainPlugins
|
|
50
52
|
@max_wait_until_ready = @options.delete(:max_wait_until_ready)
|
51
53
|
@operation_timeout = @options.delete(:operation_timeout)
|
52
54
|
@shell_type = @options.delete(:winrm_shell_type)
|
55
|
+
|
56
|
+
# SOCKS proxy patch for HTTPClient
|
57
|
+
apply_socks_proxy_patch if @options[:socks_proxy]
|
53
58
|
end
|
54
59
|
|
55
60
|
# (see Base::Connection#close)
|
@@ -104,7 +109,7 @@ module TrainPlugins
|
|
104
109
|
|
105
110
|
private
|
106
111
|
|
107
|
-
PING_COMMAND = "Write-Host '[WinRM] Established\n'"
|
112
|
+
PING_COMMAND = "Write-Host '[WinRM] Established\n'"
|
108
113
|
|
109
114
|
def file_via_connection(path)
|
110
115
|
Train::File::Remote::Windows.new(self, path)
|
@@ -114,7 +119,7 @@ module TrainPlugins
|
|
114
119
|
return if command.nil?
|
115
120
|
|
116
121
|
logger.debug("[WinRM] #{self} (#{command})")
|
117
|
-
out = ""
|
122
|
+
out = +""
|
118
123
|
response = nil
|
119
124
|
timeout = opts[:timeout]&.to_i
|
120
125
|
|
@@ -234,6 +239,16 @@ module TrainPlugins
|
|
234
239
|
end
|
235
240
|
end
|
236
241
|
|
242
|
+
def apply_socks_proxy_patch
|
243
|
+
require_relative "socks_proxy_patch"
|
244
|
+
|
245
|
+
::SocksProxyPatch.apply(
|
246
|
+
socks_proxy: @options[:socks_proxy],
|
247
|
+
socks_user: @options[:socks_user],
|
248
|
+
socks_password: @options[:socks_password]
|
249
|
+
)
|
250
|
+
end
|
251
|
+
|
237
252
|
# String representation of object, reporting its connection details and
|
238
253
|
# configuration.
|
239
254
|
#
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Patches the `HTTPClient` and `TCPSocket` classes to use a SOCKS5H proxy.
|
4
|
+
# This class modifies the behavior of `HTTPClient` to route HTTP requests
|
5
|
+
# through a SOCKS proxy by overriding the socket creation method.
|
6
|
+
# It also configures `TCPSocket` to use the specified SOCKS proxy server,
|
7
|
+
# and validates that the provided proxy is correct, resolvable, and accessible.
|
8
|
+
|
9
|
+
require "socksify"
|
10
|
+
require "httpclient"
|
11
|
+
require "socket" unless defined?(Socket)
|
12
|
+
|
13
|
+
class SocksProxyPatch
|
14
|
+
# Applies the SOCKS proxy settings to `HTTPClient` and `TCPSocket`.
|
15
|
+
#
|
16
|
+
# @param socks_proxy [String] The SOCKS proxy address in the format `host:port`.
|
17
|
+
# @param socks_user [String, nil] Optional SOCKS proxy username.
|
18
|
+
# @param socks_password [String, nil] Optional SOCKS proxy password.
|
19
|
+
# @example
|
20
|
+
# SocksProxyPatch.apply(socks_proxy: "127.0.0.1:1080", socks_user: "user", socks_password: "pass")
|
21
|
+
def self.apply(socks_proxy:, socks_user: nil, socks_password: nil)
|
22
|
+
new(socks_proxy: socks_proxy, socks_user: socks_user, socks_password: socks_password).apply
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(socks_proxy:, socks_user:, socks_password:)
|
26
|
+
@socks_proxy = socks_proxy
|
27
|
+
@socks_user = socks_user
|
28
|
+
@socks_password = socks_password
|
29
|
+
end
|
30
|
+
|
31
|
+
def apply
|
32
|
+
@proxy_host, @proxy_port = parse_and_validate_proxy(@socks_proxy)
|
33
|
+
configure_socks
|
34
|
+
patch_http_client
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :proxy_host, :proxy_port, :socks_user, :socks_password
|
40
|
+
|
41
|
+
# Parses the proxy string and validates its format, DNS resolution, and port.
|
42
|
+
def parse_and_validate_proxy(socks_proxy)
|
43
|
+
proxy_host, proxy_port = socks_proxy.split(":")
|
44
|
+
|
45
|
+
unless proxy_host && proxy_port
|
46
|
+
raise Train::ClientError, "Invalid SOCKS proxy format: '#{socks_proxy}'. Expected format is 'host:port'."
|
47
|
+
end
|
48
|
+
|
49
|
+
port = validate_port(proxy_port)
|
50
|
+
validate_dns_resolution(proxy_host)
|
51
|
+
|
52
|
+
[proxy_host, port]
|
53
|
+
end
|
54
|
+
|
55
|
+
# Ensures port is a valid integer in range.
|
56
|
+
def validate_port(port_str)
|
57
|
+
port = Integer(port_str)
|
58
|
+
unless port.between?(1, 65_535)
|
59
|
+
raise Train::ClientError, "SOCKS proxy port '#{port}' is out of valid range (1-65535)."
|
60
|
+
end
|
61
|
+
|
62
|
+
port
|
63
|
+
rescue ArgumentError
|
64
|
+
raise Train::ClientError, "Invalid SOCKS proxy port '#{port_str}'. Port must be an integer."
|
65
|
+
end
|
66
|
+
|
67
|
+
# Checks if the hostname is resolvable.
|
68
|
+
def validate_dns_resolution(host)
|
69
|
+
Socket.getaddrinfo(host, nil)
|
70
|
+
rescue SocketError => e
|
71
|
+
raise Train::ClientError, "DNS resolution failed for SOCKS proxy host '#{host}': #{e.message}"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Applies the validated proxy settings to TCPSocket.
|
75
|
+
def configure_socks
|
76
|
+
TCPSocket.socks_username = socks_user if socks_user
|
77
|
+
TCPSocket.socks_password = socks_password if socks_password
|
78
|
+
TCPSocket.socks_server = proxy_host
|
79
|
+
TCPSocket.socks_port = proxy_port
|
80
|
+
end
|
81
|
+
|
82
|
+
# Patches HTTPClient to route all connections through the SOCKS proxy.
|
83
|
+
def patch_http_client
|
84
|
+
HTTPClient::Session.class_eval do
|
85
|
+
unless method_defined?(:original_create_socket)
|
86
|
+
alias_method :original_create_socket, :create_socket
|
87
|
+
|
88
|
+
# Override the `create_socket` method to use `TCPSocket` with SOCKS proxy.
|
89
|
+
def create_socket(host, port, *args)
|
90
|
+
socket = ::TCPSocket.new(host, port)
|
91
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, [5, 0].pack("l_2"))
|
92
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, [5, 0].pack("l_2"))
|
93
|
+
socket
|
94
|
+
rescue HTTPClient::ConnectTimeoutError
|
95
|
+
raise Train::ClientError, "SOCKS proxy connection to #{host}:#{port} timed out (HTTPClient::ConnectTimeoutError)"
|
96
|
+
rescue Errno::ETIMEDOUT
|
97
|
+
raise Train::ClientError, "Connection to #{host}:#{port} timed out through SOCKS proxy."
|
98
|
+
rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH
|
99
|
+
raise Train::ClientError, "Network unreachable when connecting to #{host}:#{port} via SOCKS proxy."
|
100
|
+
rescue Errno::ECONNREFUSED
|
101
|
+
raise Train::ClientError, "Connection refused by SOCKS proxy when connecting to #{host}:#{port}."
|
102
|
+
rescue SocketError => e
|
103
|
+
raise Train::ClientError, "Socket error while connecting to #{host}:#{port} via SOCKS proxy: #{e.message}"
|
104
|
+
rescue StandardError => e
|
105
|
+
raise Train::ClientError, "Unexpected error during SOCKS proxy connection: #{e.class} - #{e.message}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -79,6 +79,11 @@ module TrainPlugins
|
|
79
79
|
option :client_key , default: nil
|
80
80
|
option :client_key_pass , default: nil
|
81
81
|
|
82
|
+
# new socks proxy options
|
83
|
+
option :socks_proxy, default: nil
|
84
|
+
option :socks_user, default: nil
|
85
|
+
option :socks_password, default: nil
|
86
|
+
|
82
87
|
def initialize(opts)
|
83
88
|
super(opts)
|
84
89
|
load_needed_dependencies!
|
@@ -102,24 +107,40 @@ module TrainPlugins
|
|
102
107
|
def validate_options(opts)
|
103
108
|
super(opts)
|
104
109
|
|
105
|
-
#
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
winrm_transport = opts[:winrm_transport]
|
110
|
+
# Normalize to symbols
|
111
|
+
opts[:winrm_transport] = opts[:winrm_transport].to_s.downcase.to_sym if opts[:winrm_transport]
|
112
|
+
opts[:winrm_shell_type] = opts[:winrm_shell_type].to_s.downcase.to_sym if opts[:winrm_shell_type]
|
113
|
+
|
114
|
+
winrm_transport = opts[:winrm_transport]
|
115
|
+
winrm_shell_type = opts[:winrm_shell_type]
|
116
|
+
|
110
117
|
unless SUPPORTED_WINRM_TRANSPORTS.include?(winrm_transport)
|
111
118
|
raise Train::ClientError, "Unsupported transport type: #{winrm_transport.inspect}"
|
112
119
|
end
|
113
120
|
|
114
|
-
winrm_shell_type = opts[:winrm_shell_type].to_sym
|
115
121
|
unless SUPPORTED_WINRM_SHELL_TYPES.include?(winrm_shell_type)
|
116
122
|
raise Train::ClientError, "Unsupported winrm shell type: #{winrm_shell_type.inspect}"
|
117
123
|
end
|
118
124
|
|
119
|
-
#
|
125
|
+
# Set scheme, port, endpoint
|
126
|
+
scheme = opts[:ssl] ? "https" : "http"
|
127
|
+
port = opts[:port] || (opts[:ssl] ? 5986 : 5985)
|
120
128
|
path = (opts[:path] || "").sub(%r{^/+}, "")
|
121
129
|
|
122
130
|
opts[:endpoint] = "#{scheme}://#{opts[:host]}:#{port}/#{path}"
|
131
|
+
|
132
|
+
# Auto-detect realm when not provided
|
133
|
+
if winrm_transport == :kerberos && opts[:kerberos_realm].nil?
|
134
|
+
begin
|
135
|
+
krb_realm = File.read("/etc/krb5.conf")[/default_realm\s*=\s*(\S+)/i, 1]
|
136
|
+
if krb_realm
|
137
|
+
opts[:kerberos_realm] = krb_realm
|
138
|
+
logger.debug("Kerberos realm auto-detected: #{krb_realm}")
|
139
|
+
end
|
140
|
+
rescue => e
|
141
|
+
logger.warn("Could not auto-detect Kerberos realm: #{e.message}")
|
142
|
+
end
|
143
|
+
end
|
123
144
|
end
|
124
145
|
|
125
146
|
WINRM_FS_SPEC_VERSION = ">= 1.3.7".freeze
|
@@ -134,7 +155,7 @@ module TrainPlugins
|
|
134
155
|
def connection_options(opts)
|
135
156
|
{
|
136
157
|
logger: logger,
|
137
|
-
transport: opts[:winrm_transport]
|
158
|
+
transport: opts[:winrm_transport],
|
138
159
|
disable_sspi: opts[:winrm_disable_sspi],
|
139
160
|
basic_auth_only: opts[:winrm_basic_auth_only],
|
140
161
|
hostname: opts[:host],
|
@@ -154,6 +175,9 @@ module TrainPlugins
|
|
154
175
|
client_cert: opts[:client_cert],
|
155
176
|
client_key: opts[:client_key],
|
156
177
|
key_pass: opts[:client_key_pass],
|
178
|
+
socks_proxy: opts[:socks_proxy],
|
179
|
+
socks_user: opts[:socks_user],
|
180
|
+
socks_password: opts[:socks_password],
|
157
181
|
}
|
158
182
|
end
|
159
183
|
|
data/lib/train-winrm/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: train-winrm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chef InSpec Team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: chef-winrm
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 2.4.4
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 2.4.4
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: chef-winrm-elevated
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,28 +44,28 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 1.4.
|
47
|
+
version: 1.4.1
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 1.4.
|
54
|
+
version: 1.4.1
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: socksify
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '1.8'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '1.8'
|
69
69
|
description: Allows applictaions using Train to speak to Windows using Remote Management;
|
70
70
|
handles authentication, cacheing, and SDK dependency management.
|
71
71
|
email:
|
@@ -77,6 +77,7 @@ files:
|
|
77
77
|
- LICENSE
|
78
78
|
- lib/train-winrm.rb
|
79
79
|
- lib/train-winrm/connection.rb
|
80
|
+
- lib/train-winrm/socks_proxy_patch.rb
|
80
81
|
- lib/train-winrm/transport.rb
|
81
82
|
- lib/train-winrm/version.rb
|
82
83
|
homepage: https://github.com/inspec/train-winrm
|