A production-ready Train plugin that provides SSH connectivity to Juniper Networks devices running JunOS for InSpec compliance testing and infrastructure inspection.
!!! info "Quick Start"
Install the plugin: inspec plugin install train-juniper
and start testing your Juniper infrastructure immediately.
The plugin supports:
- :material-ssh: SSH Authentication - Secure connections with password or key-based auth
- :material-router-network: JunOS Platform Detection - Automatic version parsing and platform registration
- :material-console: Command Execution - CLI command execution with Juniper-specific prompt handling
- :material-network: Proxy/Bastion Support - Enterprise network connectivity through jump hosts
- :material-file-document: Configuration Inspection - Pseudo-file operations for configuration access
- :material-test-tube: Mock Mode - Complete testing support without requiring real hardware
- :material-speedometer: Performance Optimized - Efficient platform detection with result caching
!!! warning "Prerequisites" You will need InSpec v4.0 or later.
# Search for train plugins
$ inspec plugin search train-
# Install train-juniper (once published)
$ inspec plugin install train-juniper
# Verify installation
$ inspec plugin list
# Clone the repository
$ git clone https://github.com/mitre/train-juniper.git
$ cd train-juniper
# Install dependencies and run tests
$ bundle install
$ bundle exec rake test
# Build and install local gem
$ gem build train-juniper.gemspec
$ inspec plugin install ./train-juniper-0.1.0.gem
# Verify plugin is loaded
$ inspec plugin list
# Detect platform
$ inspec detect -t juniper://[email protected] --password yourpassword
== Platform Details
Name: juniper
Families: bsd
Release: 21.4R3-S1.6
Arch: x86_64
# Interactive shell
$ inspec shell -t juniper://[email protected] --password yourpassword
inspec> command('show version').stdout
=> "Hostname: srx-fw\nModel: SRX340\nJunos: 21.4R3-S1.6\n..."
# Simplified: Same username/password for bastion and device (most common)
$ inspec shell -t juniper://[email protected] --password yourpassword \
--bastion-host jump.example.com
# Different credentials for bastion and device
$ inspec shell -t juniper://[email protected] --password device_password \
--bastion-host jump.example.com --bastion-user netadmin \
--bastion-password jump_password
# With custom port
$ inspec shell -t juniper://[email protected] --password yourpassword \
--bastion-host jump.example.com --bastion-port 2222
# Using environment variables (recommended for automation)
export JUNIPER_BASTION_HOST=jump.example.com
export JUNIPER_PASSWORD=shared_password # Used for both bastion and device
$ inspec shell -t juniper://[email protected]
# Different passwords via environment
export JUNIPER_BASTION_HOST=jump.example.com
export JUNIPER_BASTION_USER=netadmin
export JUNIPER_BASTION_PASSWORD=jump_password
export JUNIPER_PASSWORD=device_password
$ inspec shell -t juniper://[email protected]
# Using SSH ProxyCommand syntax
$ inspec shell -t "juniper://[email protected]?proxy_command=ssh%20jump.host%20-W%20%h:%p"
# Complex corporate network scenario
$ inspec detect -t "juniper://[email protected]?bastion_host=jump.dmz.corp&bastion_user=svc_inspec"
The plugin automatically detects and uses standard environment variables, eliminating the need to pass connection flags:
# Basic connection with auto-detection
export JUNIPER_HOST=192.168.1.1
export JUNIPER_USER=admin
export JUNIPER_PASSWORD=yourpassword
inspec detect -t juniper:// # No flags needed!
# With bastion host auto-detection
export JUNIPER_HOST=internal.device.corp
export JUNIPER_USER=netadmin
export JUNIPER_PASSWORD=devicepass
export JUNIPER_BASTION_HOST=jump.corp.com
export JUNIPER_BASTION_USER=admin
export JUNIPER_BASTION_PASSWORD=bastionpass
inspec detect -t juniper:// # Automatically uses bastion!
# Using .env file (recommended for development)
# Create .env file with your credentials:
source .env
inspec detect -t juniper:// # Reads from .env automatically
The plugin uses the following priority order for configuration values:
- Command-line flags (highest priority) - e.g.,
--bastion-user
- Environment variables - e.g.,
JUNIPER_BASTION_USER
- Defaults/Fallbacks (lowest priority) - e.g., bastion_user falls back to main user
This allows maximum flexibility while providing sensible defaults for common scenarios.
Option | Description | Default | Environment Variable |
---|---|---|---|
host |
Juniper device hostname/IP | - | JUNIPER_HOST |
user |
SSH username | - | JUNIPER_USER |
password |
SSH password | - | JUNIPER_PASSWORD |
port |
SSH port | 22 | JUNIPER_PORT |
timeout |
Connection timeout (seconds) | 30 | JUNIPER_TIMEOUT |
keepalive |
SSH keepalive enabled | true | - |
keepalive_interval |
SSH keepalive interval (seconds) | 60 | - |
Option | Description | Default | Environment Variable |
---|---|---|---|
bastion_host |
SSH bastion/jump host | - | JUNIPER_BASTION_HOST |
bastion_user |
SSH bastion username | Falls back to main user |
JUNIPER_BASTION_USER |
bastion_port |
SSH bastion port | 22 | JUNIPER_BASTION_PORT |
bastion_password |
Password for bastion authentication | - | JUNIPER_BASTION_PASSWORD |
proxy_command |
Custom SSH ProxyCommand | - | JUNIPER_PROXY_COMMAND |
key_files |
SSH private key files | - | - |
keys_only |
Use only specified keys | false | - |
!!! note "Important Configuration Notes"
- Cannot specify both bastion_host
and proxy_command
simultaneously
- If bastion_user
not provided, falls back to using main user
for bastion authentication
- If bastion_password
not provided, falls back to using main password
for bastion authentication
- Supports automated password authentication via SSH_ASKPASS mechanism
Create ~/.inspec/config.json
:
{
"credentials": {
"juniper-lab": {
"target": "juniper://[email protected]",
"password": "yourpassword",
"insecure": true
}
}
}
Then use: inspec detect --config=juniper-lab
The train-juniper plugin supports Train-standard proxy/bastion connections for enterprise environments where Juniper devices are behind jump hosts or in isolated network segments.
Important: Train does not have a separate --bastion-password
option. Here are the standard authentication patterns:
Use the same --password
for both bastion host and Juniper device:
# Same username/password for jump host and device
inspec detect -t "juniper://[email protected]?bastion_host=jump.corp.com&bastion_user=admin" --password "shared_password"
# With environment variables
export JUNIPER_PASSWORD="shared_password"
inspec shell -t "juniper://[email protected]?bastion_host=jump.corp.com&bastion_user=admin"
Use SSH keys for both connections:
# SSH keys for both bastion and device
inspec detect -t "juniper://[email protected]?bastion_host=jump.corp.com&bastion_user=admin" -i ~/.ssh/id_rsa
# With multiple keys
inspec shell -t "juniper://[email protected]?bastion_host=jump.corp.com" --key-files ~/.ssh/bastion_key ~/.ssh/device_key
Use SSH ProxyCommand when bastion and device require different authentication:
# Bastion uses one password, device uses another (embed bastion auth in proxy command)
inspec detect -t "juniper://[email protected]?proxy_command=sshpass%20-p%20bastionpass%20ssh%[email protected]%20-W%20%h:%p" --password "device_password"
# Bastion uses SSH key, device uses password
inspec shell -t "juniper://[email protected]?proxy_command=ssh%20-i%20~/.ssh/bastion_key%[email protected]%20-W%20%h:%p" --password "device_password"
# Corporate network with dedicated jump host
inspec exec profile -t "juniper://[email protected]?bastion_host=jump.corp.com&bastion_user=netadmin" --password "shared_password"
# Cloud environment with bastion instance
inspec exec profile -t "juniper://[email protected]?bastion_host=bastion.aws.company.com&bastion_port=2222" --key-files ~/.ssh/aws_key
# DMZ access pattern
inspec detect -t "juniper://[email protected]?bastion_host=jump.dmz.corp&bastion_user=svc_account" --password "corporate_password"
# SSH ProxyCommand for complex routing
inspec shell -t "juniper://admin@device?proxy_command=ssh%20-o%20StrictHostKeyChecking=no%20jump%20nc%20%h%20%p"
# Multi-hop proxy (SSH chain)
inspec exec profile -t "juniper://admin@target?proxy_command=ssh%20-J%20first-jump,second-jump%20-W%20%h:%p"
# In Ruby code or configuration
Train.create('juniper', {
host: 'secure.device.corp',
user: 'admin',
bastion_host: 'jump.corp.com',
bastion_user: 'automation',
key_files: ['/path/to/private/key'],
keys_only: true
})
Solution: Train doesn't have --bastion-password
. Use one of these patterns:
# Same password for both (most common)
inspec detect -t "juniper://user@device?bastion_host=jump" --password "shared_pass"
# SSH keys (recommended)
inspec detect -t "juniper://user@device?bastion_host=jump" --key-files ~/.ssh/id_rsa
# Different passwords (use proxy command)
inspec detect -t "juniper://user@device?proxy_command=sshpass%20-p%20jumppass%20ssh%20jumpuser@jump%20-W%20%h:%p" --password "device_pass"
Solutions:
# Verify bastion connection first
ssh [email protected]
# Test direct device connection (if accessible)
ssh [email protected]
# Use verbose SSH for debugging
inspec detect -t "juniper://user@device?bastion_host=jump&proxy_command=ssh%20-v%20jump%20-W%20%h:%p" --password "pass"
# Use InSpec debug mode for detailed logging
inspec detect -t "juniper://user@device?bastion_host=jump" --password "pass" -l debug
Solutions:
# Increase timeouts
inspec detect -t "juniper://user@device?bastion_host=jump&connection_timeout=60" --password "pass"
# Check network connectivity
ping device.internal # From bastion host
telnet device.internal 22 # Test SSH port
The train-juniper plugin includes a comprehensive mock mode for testing profiles without requiring physical Juniper hardware:
# Use mock mode with InSpec detect
inspec detect -t "juniper://admin@mock-device?mock=true"
# Output shows mocked JunOS platform:
# Name: juniper
# Families: bsd
# Release: 12.1X47-D15.4
For programmatic usage in tests:
require 'train'
connection = Train.create('juniper',
host: 'test-device',
user: 'admin',
mock: true
)
# Mock mode returns predefined responses
result = connection.run_command('show version')
# => Returns mock JunOS version output
Mock mode provides:
- ✅ Realistic JunOS command outputs
- ✅ Platform detection (JunOS 12.1X47-D15.4)
- ✅ Error simulation for negative testing
- ✅ Fast execution for CI/CD pipelines
- Ruby 3.1+
- Bundler
- InSpec 4.0+ (for testing)
git clone https://github.com/mitre/train-juniper.git
cd train-juniper
bundle install
# Run all tests
bundle exec rake test
# Run individual test suites
bundle exec ruby test/unit/connection_test.rb
bundle exec ruby test/functional/juniper_test.rb
# Lint code
bundle exec rubocop
This plugin implements the Train Plugin V1 API with:
- Transport (
lib/train-juniper/transport.rb
) - Plugin registration and factory - Connection (
lib/train-juniper/connection.rb
) - SSH connectivity and command execution - Platform (
lib/train-juniper/platform.rb
) - JunOS platform detection - Version (
lib/train-juniper/version.rb
) - Plugin version management
This gem supports a wide range of platforms to ensure maximum compatibility:
Platform | Description | Use Case |
---|---|---|
ruby |
Platform-independent | Pure Ruby installations |
x86_64-linux |
Standard Linux | Most Linux servers and CI/CD |
aarch64-linux |
ARM64 Linux | AWS Graviton, Raspberry Pi |
x86_64-linux-musl |
Alpine Linux | Docker containers |
x86_64-darwin |
Intel macOS | Older Mac workstations |
arm64-darwin-* |
Apple Silicon macOS | Modern Mac workstations |
x64-mingw-ucrt |
Windows (UCRT) | Windows 10/11 with modern Ruby (bastion setup) |
x86_64-freebsd |
FreeBSD | Network appliances (JunOS heritage) |
x86_64-solaris |
Solaris/illumos | Enterprise environments |
!!! note "Platform Compatibility" This comprehensive platform support ensures the plugin works wherever InSpec runs, from developer workstations to CI/CD pipelines to production jump hosts. The FreeBSD support is particularly relevant given that JunOS is based on FreeBSD.
- Installation Guide - Complete installation instructions
- Basic Usage - Getting started with the plugin
- Windows Bastion Setup - Windows bastion/jump host authentication guide
- Release Process - How to cut releases and publish gems
- Project Roadmap - Future development plans and contribution opportunities
- Train Plugin Development Guide - Comprehensive tutorial for Train plugin development
- How This Plugin Was Built - See modules 20-22 in the development guide for our research methodology, implementation approach, and containerlab testing environment
We welcome contributions! Here's how to get started:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Make your changes with tests
- Run
bundle exec rake test
to ensure tests pass - Submit a pull request
Please see our Contributing Guide for more details.
For questions, feature requests, or general support:
- Email: [email protected]
- GitHub Issues: https://github.com/mitre/train-juniper/issues
For security issues or vulnerabilities:
- Email: [email protected]
- GitHub Security: https://github.com/mitre/train-juniper/security
This project was inspired by and references several excellent community Train plugins:
- train-rest by Thomas Heinen (Prospectra) - REST API transport patterns
- train-awsssm by Thomas Heinen (Prospectra) - AWS Systems Manager transport
- train-pwsh by MITRE SAF Team - PowerShell/Windows automation transport
- train-k8s-container by InSpec Team - Kubernetes container platform detection
- train-local-rot13 by InSpec Team - Official plugin development example
Special thanks to the Train and InSpec communities for their excellent documentation and plugin examples.
Licensed under the Apache-2.0 license, except as noted below.
See LICENSE for full details.
This software was produced for the U.S. Government under contract and is subject to Federal Acquisition Regulation Clause 52.227-14.
See NOTICE for full details.
© 2025 The MITRE Corporation.