Train Juniper Plugin
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.
Features :material-star:
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
 
Installation :material-download:
!!! warning "Prerequisites" You will need InSpec v4.0 or later.
Production Installation (from RubyGems)
# Search for train plugins
$ inspec plugin search train-
# Install train-juniper (once published)
$ inspec plugin install train-juniper
# Verify installation
$ inspec plugin list
Development Installation (local testing)
# 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
Usage
Basic Connection
# Detect platform
$ inspec detect -t juniper://admin@192.168.1.1 --password yourpassword
== Platform Details
Name:      juniper
Families:  bsd
Release:   21.4R3-S1.6
Arch:      x86_64
# Interactive shell
$ inspec shell -t juniper://admin@192.168.1.1 --password yourpassword
inspec> command('show version').stdout
=> "Hostname: srx-fw\nModel: SRX340\nJunos: 21.4R3-S1.6\n..."
With Bastion Host (Jump Host)
# Simplified: Same username/password for bastion and device (most common)
$ inspec shell -t juniper://admin@10.1.1.1 --password yourpassword \
    --bastion-host jump.example.com
# Different credentials for bastion and device
$ inspec shell -t juniper://admin@10.1.1.1 --password device_password \
    --bastion-host jump.example.com --bastion-user netadmin \
    --bastion-password jump_password
# With custom port
$ inspec shell -t juniper://admin@10.1.1.1 --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://admin@10.1.1.1
# 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://admin@10.1.1.1
With Custom Proxy Command
# Using SSH ProxyCommand syntax  
$ inspec shell -t "juniper://admin@device.internal?proxy_command=ssh%20jump.host%20-W%20%h:%p"
# Complex corporate network scenario
$ inspec detect -t "juniper://netadmin@core-switch.corp?bastion_host=jump.dmz.corp&bastion_user=svc_inspec"
Environment Variables (Auto-Detection)
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
Configuration Options
Option Priority
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.
Connection Options
| 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 | - | 
Proxy/Bastion Options
| 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
InSpec Configuration File
Create ~/.inspec/config.json:
{
  "credentials": {
    "juniper-lab": {
      "target": "juniper://admin@lab-srx.example.com",
      "password": "yourpassword",
      "insecure": true
    }
  }
}
Then use: inspec detect --config=juniper-lab
Proxy Connection Patterns
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.
Authentication Patterns
Important: Train does not have a separate --bastion-password option. Here are the standard authentication patterns:
🔐 Same Credentials (Most Common)
Use the same --password for both bastion host and Juniper device:
# Same username/password for jump host and device
inspec detect -t "juniper://admin@device.internal?bastion_host=jump.corp.com&bastion_user=admin" --password "shared_password"
# With environment variables
export JUNIPER_PASSWORD="shared_password"
inspec shell -t "juniper://admin@device.internal?bastion_host=jump.corp.com&bastion_user=admin"
🔑 SSH Key Authentication (Recommended)
Use SSH keys for both connections:
# SSH keys for both bastion and device
inspec detect -t "juniper://admin@device.internal?bastion_host=jump.corp.com&bastion_user=admin" -i ~/.ssh/id_rsa
# With multiple keys
inspec shell -t "juniper://admin@device.internal?bastion_host=jump.corp.com" --key-files ~/.ssh/bastion_key ~/.ssh/device_key
🔗 Different Credentials (Advanced)
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://deviceuser@device.internal?proxy_command=sshpass%20-p%20bastionpass%20ssh%20bastionuser@jump.corp.com%20-W%20%h:%p" --password "device_password"
# Bastion uses SSH key, device uses password
inspec shell -t "juniper://admin@device.internal?proxy_command=ssh%20-i%20~/.ssh/bastion_key%20admin@jump.corp.com%20-W%20%h:%p" --password "device_password"
Bastion Host Scenarios
# Corporate network with dedicated jump host
inspec exec profile -t "juniper://admin@core-switch.internal?bastion_host=jump.corp.com&bastion_user=netadmin" --password "shared_password"
# Cloud environment with bastion instance  
inspec exec profile -t "juniper://ubuntu@10.0.1.100?bastion_host=bastion.aws.company.com&bastion_port=2222" --key-files ~/.ssh/aws_key
# DMZ access pattern
inspec detect -t "juniper://operator@firewall.dmz?bastion_host=jump.dmz.corp&bastion_user=svc_account" --password "corporate_password"
Custom Proxy Commands
# 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"
SSH Key Authentication with Proxy
# 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
})
Common Authentication Issues
❌ Error: "No bastion password specified"
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"
❌ Error: "Authentication failed"
Solutions:
# Verify bastion connection first
ssh jumpuser@jump.corp.com
# Test direct device connection (if accessible)
ssh deviceuser@device.internal
# 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
❌ Error: "Connection timeout"
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
Mock Mode (Testing Without Hardware)
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
 
Development
Requirements
- Ruby 3.1+
 - Bundler
 - InSpec 4.0+ (for testing)
 
Setup
git clone https://github.com/mitre/train-juniper.git
cd train-juniper
bundle install
Testing
# 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
Architecture
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 
Platform Support
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.
Documentation
- 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
 
Plugin Development Resources
- 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
 
Contributing
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 testto ensure tests pass - Submit a pull request
 
Please see our Contributing Guide for more details.
Support and Contact
General Support
For questions, feature requests, or general support:
- Email: saf@mitre.org
 - GitHub Issues: https://github.com/mitre/train-juniper/issues
 
Security Issues
For security issues or vulnerabilities:
- Email: saf-security@mitre.org
 - GitHub Security: https://github.com/mitre/train-juniper/security
 
Acknowledgments
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.
License
Licensed under the Apache-2.0 license, except as noted below.
See LICENSE for full details.
Notice
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.