train-juniper 0.7.3 → 0.7.4

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: c7074cafad165e2055edc5be8495a250025f4fc06ad658e740b77248e3d2273c
4
- data.tar.gz: febffacb8ad71ffc912a76b3be460f598e48e20b0c500de48f10c3cefe11c549
3
+ metadata.gz: 6da9bb07db2daaa3ca21d2fef18b0a200ff15597495dece758e0e509d10c13ad
4
+ data.tar.gz: 86e802113118c172748e2187e09db4c95c080f393dc083932a65386d19c82f3c
5
5
  SHA512:
6
- metadata.gz: 4c47e243ab6d5922904bd7898b4c1da8dff8ac4986f3b60629e4662c566e79ace9bbec8a0dfda2ee400e51ec7f060f2c871b11be07d84130ce1f1ac1d23b4d76
7
- data.tar.gz: af8b001261139a75f38da8be9711c3c9f14c8a618bd8195234c750262ef40e4a772cd9a081a59deb2880e1c78ac5ad44846ba7c902cc360420a4d4a4b5092be6
6
+ metadata.gz: 0137dee8f2547f0973d2e513491574e624f55a605993ca8e6211e1c84c77341fe840d45704903e25eaeacb6e043b1846556f2f124ee08863c591357e34c37ecb
7
+ data.tar.gz: ddd016a7cf4cc47d68e9e0f89283d874ea563abab20cc6f2e822d67658694bac3782d3cef0ef4f2a1976bfbf4b2a8652050ff87c9b9d13c18864a5c26a169482
data/.env.example CHANGED
@@ -1,29 +1,16 @@
1
- # Train-Juniper Environment Variables Example
2
- # Copy this file to .env and fill in your actual values
3
- # The plugin automatically detects these environment variables
1
+ # Example .env file for Windows testing
2
+ # Copy this to .env and fill in your actual values
4
3
 
5
- # Juniper Device Connection
6
- JUNIPER_HOST=your.device.hostname.com
7
- JUNIPER_USER=your_username
8
- JUNIPER_PASSWORD=your_password
9
- JUNIPER_PORT=22
10
- JUNIPER_TIMEOUT=60
4
+ # Target Juniper device (behind bastion)
5
+ JUNIPER_HOST=192.168.1.100
6
+ JUNIPER_USER=admin
7
+ JUNIPER_PASSWORD=device_password
11
8
 
12
- # Bastion/Jump Host (optional - only if device is behind jump host)
13
- JUNIPER_BASTION_HOST=your.jumphost.com
14
- JUNIPER_BASTION_USER=your_jump_username
15
- JUNIPER_BASTION_PORT=22
16
- JUNIPER_BASTION_PASSWORD=your_jump_password
9
+ # Bastion/Jump host
10
+ BASTION_HOST=bastion.example.com
11
+ BASTION_USER=jumpuser
12
+ BASTION_PASSWORD=bastion_password
17
13
 
18
- # Custom Proxy Command (alternative to bastion host)
19
- # JUNIPER_PROXY_COMMAND=ssh your.jumphost.com -W %h:%p
20
-
21
- # Usage Examples:
22
- # 1. Basic connection (auto-detects above variables):
23
- # inspec detect -t juniper://
24
- #
25
- # 2. Override specific values:
26
- # inspec detect -t juniper://different_user@different_host --password override_pass
27
- #
28
- # 3. Test connection:
29
- # source .env && inspec detect -t juniper:// -l debug
14
+ # Optional: Custom ports
15
+ # JUNIPER_PORT=22
16
+ # BASTION_PORT=22
data/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.7.4] - 2025-06-24
9
+
10
+ ### Added
11
+
12
+ - Add Windows plink.exe support for bastion authentication
13
+
14
+ ### Documentation
15
+
16
+ - Add Windows bastion setup guide and improve navigation
17
+ - Fix MkDocs link warnings
18
+ - Add host key acceptance instructions and InSpec testing examples
19
+ - Improve Windows documentation and standardize on inspec shell
20
+
21
+ ### Miscellaneous Tasks
22
+
23
+ - Fix linting issues in Windows test script
24
+
25
+ ### Testing
26
+
27
+ - Add Windows testing scripts and guide
28
+ - Add .env file support to Windows test scripts
29
+
8
30
  ## [0.7.3] - 2025-06-23
9
31
 
10
32
  ### Documentation
data/CONTRIBUTING.md CHANGED
@@ -146,7 +146,7 @@ Releases are managed by project maintainers:
146
146
 
147
147
  ## Community
148
148
 
149
- - Follow our [Code of Conduct](CODE_OF_CONDUCT)
149
+ - Follow our [Code of Conduct](CODE_OF_CONDUCT.md)
150
150
  - Be respectful and collaborative
151
151
  - Help others learn and contribute
152
152
 
data/README.md CHANGED
@@ -409,7 +409,7 @@ This gem supports a wide range of platforms to ensure maximum compatibility:
409
409
  | `x86_64-linux-musl` | Alpine Linux | Docker containers |
410
410
  | `x86_64-darwin` | Intel macOS | Older Mac workstations |
411
411
  | `arm64-darwin-*` | Apple Silicon macOS | Modern Mac workstations |
412
- | `x64-mingw-ucrt` | Windows (UCRT) | Windows 10/11 with modern Ruby |
412
+ | `x64-mingw-ucrt` | Windows (UCRT) | Windows 10/11 with modern Ruby ([bastion setup](windows-bastion-setup.md)) |
413
413
  | `x86_64-freebsd` | FreeBSD | Network appliances (JunOS heritage) |
414
414
  | `x86_64-solaris` | Solaris/illumos | Enterprise environments |
415
415
 
@@ -418,10 +418,11 @@ This gem supports a wide range of platforms to ensure maximum compatibility:
418
418
 
419
419
  ### Documentation
420
420
 
421
- - **[Installation Guide](installation)** - Complete installation instructions
422
- - **[Basic Usage](basic-usage)** - Getting started with the plugin
423
- - **[Release Process](RELEASE_PROCESS)** - How to cut releases and publish gems
424
- - **[Project Roadmap](ROADMAP)** - Future development plans and contribution opportunities
421
+ - **[Installation Guide](installation.md)** - Complete installation instructions
422
+ - **[Basic Usage](basic-usage.md)** - Getting started with the plugin
423
+ - **[Windows Bastion Setup](windows-bastion-setup.md)** - Windows bastion/jump host authentication guide
424
+ - **[Release Process](RELEASE_PROCESS.md)** - How to cut releases and publish gems
425
+ - **[Project Roadmap](ROADMAP.md)** - Future development plans and contribution opportunities
425
426
 
426
427
  ### Plugin Development Resources
427
428
 
@@ -438,7 +439,7 @@ We welcome contributions! Here's how to get started:
438
439
  4. Run `bundle exec rake test` to ensure tests pass
439
440
  5. Submit a pull request
440
441
 
441
- Please see our [Contributing Guide](CONTRIBUTING) for more details.
442
+ Please see our [Contributing Guide](CONTRIBUTING.md) for more details.
442
443
 
443
444
  ## Support and Contact
444
445
 
@@ -472,12 +473,12 @@ Special thanks to the Train and InSpec communities for their excellent documenta
472
473
 
473
474
  Licensed under the Apache-2.0 license, except as noted below.
474
475
 
475
- See [LICENSE](LICENSE) for full details.
476
+ See [LICENSE](LICENSE.md) for full details.
476
477
 
477
478
  ### Notice
478
479
 
479
480
  This software was produced for the U.S. Government under contract and is subject to Federal Acquisition Regulation Clause 52.227-14.
480
481
 
481
- See [NOTICE](NOTICE) for full details.
482
+ See [NOTICE](NOTICE.md) for full details.
482
483
 
483
484
  © 2025 The MITRE Corporation.
@@ -1,26 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'train-juniper/constants'
4
+ require 'train-juniper/connection/windows_proxy'
5
+ require 'train-juniper/connection/ssh_askpass'
4
6
 
5
7
  module TrainPlugins
6
8
  module Juniper
7
9
  # Handles bastion host proxy configuration and authentication
8
10
  module BastionProxy
11
+ include WindowsProxy
12
+ include SshAskpass
13
+
9
14
  # Configure bastion proxy for SSH connection
10
15
  # @param ssh_options [Hash] SSH options to modify
11
16
  def configure_bastion_proxy(ssh_options)
12
- require 'net/ssh/proxy/jump' unless defined?(Net::SSH::Proxy::Jump)
13
-
14
- # Build proxy jump string from bastion options
15
17
  bastion_user = @options[:bastion_user] || @options[:user]
16
18
  bastion_port = @options[:bastion_port]
19
+ bastion_password = @options[:bastion_password] || @options[:password]
20
+
21
+ # On Windows with password auth, use plink.exe if available
22
+ if Gem.win_platform? && bastion_password && plink_available?
23
+ configure_plink_proxy(ssh_options, bastion_user, bastion_port, bastion_password)
24
+ else
25
+ configure_standard_proxy(ssh_options, bastion_user, bastion_port)
26
+ end
27
+ end
17
28
 
18
- proxy_jump = if bastion_port == Constants::DEFAULT_SSH_PORT
19
- "#{bastion_user}@#{@options[:bastion_host]}"
20
- else
21
- "#{bastion_user}@#{@options[:bastion_host]}:#{bastion_port}"
22
- end
29
+ private
30
+
31
+ # Configure standard SSH proxy using Net::SSH::Proxy::Jump
32
+ # @param ssh_options [Hash] SSH options to modify
33
+ # @param bastion_user [String] Username for bastion
34
+ # @param bastion_port [Integer] Port for bastion
35
+ def configure_standard_proxy(ssh_options, bastion_user, bastion_port)
36
+ require 'net/ssh/proxy/jump' unless defined?(Net::SSH::Proxy::Jump)
23
37
 
38
+ proxy_jump = build_proxy_jump_string(bastion_user, bastion_port)
24
39
  @logger.debug("Using bastion host: #{proxy_jump}")
25
40
 
26
41
  # Set up automated password authentication via SSH_ASKPASS
@@ -29,49 +44,34 @@ module TrainPlugins
29
44
  ssh_options[:proxy] = Net::SSH::Proxy::Jump.new(proxy_jump)
30
45
  end
31
46
 
32
- # Set up SSH_ASKPASS for bastion password authentication
33
- def setup_bastion_password_auth
34
- bastion_password = @options[:bastion_password] || @options[:password]
35
- return unless bastion_password
36
-
37
- @ssh_askpass_script = create_ssh_askpass_script(bastion_password)
38
- ENV['SSH_ASKPASS'] = @ssh_askpass_script
39
- ENV['SSH_ASKPASS_REQUIRE'] = 'force'
40
- @logger.debug('Configured SSH_ASKPASS for automated bastion authentication')
47
+ # Configure plink.exe proxy for Windows password authentication
48
+ # @param ssh_options [Hash] SSH options to modify
49
+ # @param bastion_user [String] Username for bastion
50
+ # @param bastion_port [Integer] Port for bastion
51
+ # @param bastion_password [String] Password for bastion
52
+ def configure_plink_proxy(ssh_options, bastion_user, bastion_port, bastion_password)
53
+ require 'net/ssh/proxy/command' unless defined?(Net::SSH::Proxy::Command)
54
+
55
+ proxy_cmd = build_plink_proxy_command(
56
+ @options[:bastion_host],
57
+ bastion_user,
58
+ bastion_port,
59
+ bastion_password
60
+ )
61
+
62
+ @logger.debug('Using plink.exe for bastion proxy')
63
+ ssh_options[:proxy] = Net::SSH::Proxy::Command.new(proxy_cmd)
41
64
  end
42
65
 
43
- # Create temporary SSH_ASKPASS script for automated password authentication
44
- # @param password [String] The password to use
45
- # @return [String] Path to the created script
46
- def create_ssh_askpass_script(password)
47
- require 'tempfile'
48
-
49
- if Gem.win_platform?
50
- # :nocov:
51
- # Create Windows PowerShell script
52
- script = Tempfile.new(['ssh_askpass', '.ps1'])
53
- # PowerShell handles escaping better, just escape quotes
54
- escaped_password = password.gsub("'", "''")
55
- script.write("Write-Output '#{escaped_password}'\r\n")
56
- script.close
57
-
58
- # Create a wrapper batch file to execute PowerShell with bypass policy
59
- wrapper = Tempfile.new(['ssh_askpass_wrapper', '.bat'])
60
- wrapper.write("@echo off\r\npowershell.exe -ExecutionPolicy Bypass -File \"#{script.path}\"\r\n")
61
- wrapper.close
62
-
63
- @logger.debug("Created SSH_ASKPASS PowerShell script at #{script.path} with wrapper at #{wrapper.path}")
64
- wrapper.path
65
- # :nocov:
66
+ # Build proxy jump string from bastion options
67
+ # @param bastion_user [String] Username for bastion
68
+ # @param bastion_port [Integer] Port for bastion
69
+ # @return [String] Proxy jump string
70
+ def build_proxy_jump_string(bastion_user, bastion_port)
71
+ if bastion_port == Constants::DEFAULT_SSH_PORT
72
+ "#{bastion_user}@#{@options[:bastion_host]}"
66
73
  else
67
- # Create Unix shell script
68
- script = Tempfile.new(['ssh_askpass', '.sh'])
69
- script.write("#!/bin/bash\necho '#{password}'\n")
70
- script.close
71
- File.chmod(0o755, script.path)
72
-
73
- @logger.debug("Created SSH_ASKPASS script at #{script.path}")
74
- script.path
74
+ "#{bastion_user}@#{@options[:bastion_host]}:#{bastion_port}"
75
75
  end
76
76
  end
77
77
 
@@ -88,11 +88,7 @@ module TrainPlugins
88
88
  end
89
89
 
90
90
  # Use ProxyJump (-J) which handles password authentication properly
91
- jump_host = if bastion_port == Constants::DEFAULT_SSH_PORT
92
- "#{bastion_user}@#{@options[:bastion_host]}"
93
- else
94
- "#{bastion_user}@#{@options[:bastion_host]}:#{bastion_port}"
95
- end
91
+ jump_host = build_proxy_jump_string(bastion_user, bastion_port)
96
92
  args += ['-J', jump_host]
97
93
 
98
94
  # Add SSH keys if specified
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tempfile'
4
+
5
+ module TrainPlugins
6
+ module Juniper
7
+ # SSH_ASKPASS script management for automated password authentication
8
+ module SshAskpass
9
+ # Set up SSH_ASKPASS for bastion password authentication
10
+ def setup_bastion_password_auth
11
+ bastion_password = @options[:bastion_password] || @options[:password]
12
+ return unless bastion_password
13
+
14
+ @ssh_askpass_script = create_ssh_askpass_script(bastion_password)
15
+ ENV['SSH_ASKPASS'] = @ssh_askpass_script
16
+ ENV['SSH_ASKPASS_REQUIRE'] = 'force'
17
+ @logger.debug('Configured SSH_ASKPASS for automated bastion authentication')
18
+ end
19
+
20
+ # Create temporary SSH_ASKPASS script for automated password authentication
21
+ # @param password [String] The password to use
22
+ # @return [String] Path to the created script
23
+ def create_ssh_askpass_script(password)
24
+ if Gem.win_platform?
25
+ create_windows_askpass_script(password)
26
+ else
27
+ create_unix_askpass_script(password)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ # Create Windows PowerShell script for SSH_ASKPASS
34
+ # @param password [String] The password to use
35
+ # @return [String] Path to the wrapper batch file
36
+ def create_windows_askpass_script(password)
37
+ # :nocov:
38
+ # Create Windows PowerShell script
39
+ script = Tempfile.new(['ssh_askpass', '.ps1'])
40
+ # PowerShell handles escaping better, just escape quotes
41
+ escaped_password = password.gsub("'", "''")
42
+ script.write("Write-Output '#{escaped_password}'\r\n")
43
+ script.close
44
+
45
+ # Create a wrapper batch file to execute PowerShell with bypass policy
46
+ wrapper = Tempfile.new(['ssh_askpass_wrapper', '.bat'])
47
+ wrapper.write("@echo off\r\npowershell.exe -ExecutionPolicy Bypass -File \"#{script.path}\"\r\n")
48
+ wrapper.close
49
+
50
+ @logger.debug("Created SSH_ASKPASS PowerShell script at #{script.path} with wrapper at #{wrapper.path}")
51
+ wrapper.path
52
+ # :nocov:
53
+ end
54
+
55
+ # Create Unix shell script for SSH_ASKPASS
56
+ # @param password [String] The password to use
57
+ # @return [String] Path to the created script
58
+ def create_unix_askpass_script(password)
59
+ script = Tempfile.new(['ssh_askpass', '.sh'])
60
+ script.write("#!/bin/bash\necho '#{password}'\n")
61
+ script.close
62
+ File.chmod(0o755, script.path)
63
+
64
+ @logger.debug("Created SSH_ASKPASS script at #{script.path}")
65
+ script.path
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+
5
+ module TrainPlugins
6
+ module Juniper
7
+ # Windows-specific proxy handling using plink.exe
8
+ # This pattern is used by various Ruby projects for Windows SSH support,
9
+ # including hglib.rb (Mercurial) and follows Net::SSH::Proxy::Command patterns
10
+ module WindowsProxy
11
+ # Check if plink.exe is available on Windows
12
+ # @return [Boolean] true if plink.exe is found in PATH
13
+ def plink_available?
14
+ return false unless Gem.win_platform?
15
+
16
+ ENV['PATH'].split(File::PATH_SEPARATOR).any? do |path|
17
+ File.exist?(File.join(path, 'plink.exe'))
18
+ end
19
+ end
20
+
21
+ # Build plink.exe proxy command for Windows bastion authentication
22
+ # @param bastion_host [String] Bastion hostname
23
+ # @param user [String] Username for bastion
24
+ # @param port [Integer] Port for bastion
25
+ # @param password [String] Password for bastion
26
+ # @return [String] Complete plink command string
27
+ def build_plink_proxy_command(bastion_host, user, port, password)
28
+ parts = []
29
+ parts << 'plink.exe'
30
+ parts << '-batch' # Non-interactive mode
31
+ parts << '-ssh' # Force SSH protocol (not telnet)
32
+ parts << '-pw'
33
+ parts << (password.include?(' ') ? "\"#{password}\"" : password)
34
+
35
+ if port && port != 22
36
+ parts << '-P'
37
+ parts << port.to_s
38
+ end
39
+
40
+ parts << "#{user}@#{bastion_host}"
41
+ parts << '-nc'
42
+ parts << '%h:%p' # Netcat mode for proxying
43
+
44
+ parts.join(' ')
45
+ end
46
+ end
47
+ end
48
+ end
@@ -8,6 +8,6 @@
8
8
  module TrainPlugins
9
9
  module Juniper
10
10
  # Version number of the train-juniper plugin
11
- VERSION = '0.7.3'
11
+ VERSION = '0.7.4'
12
12
  end
13
13
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: train-juniper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.3
4
+ version: 0.7.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - MITRE Corporation
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-06-23 00:00:00.000000000 Z
11
+ date: 2025-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: train-core
@@ -227,8 +227,10 @@ files:
227
227
  - lib/train-juniper/connection/bastion_proxy.rb
228
228
  - lib/train-juniper/connection/command_executor.rb
229
229
  - lib/train-juniper/connection/error_handling.rb
230
+ - lib/train-juniper/connection/ssh_askpass.rb
230
231
  - lib/train-juniper/connection/ssh_session.rb
231
232
  - lib/train-juniper/connection/validation.rb
233
+ - lib/train-juniper/connection/windows_proxy.rb
232
234
  - lib/train-juniper/constants.rb
233
235
  - lib/train-juniper/file_abstraction/juniper_file.rb
234
236
  - lib/train-juniper/helpers/environment.rb