OOB SSH

OOB opens a node console as an SSH shell. OpenSSH, Netmiko, and Ansible talk SSH to eve-iac-agent on port 8787. The agent attaches the existing Console Hub when the first shell opens. The workstation does not listen, and it does not dial Telnet.

The SSH user is always eve-oob. That is not the agent login and not a Unix account on the EVE host. Do not point these clients at port 22.

Each SDK writes the same directory. This page uses the CLI, which calls the Python helper. Python, TypeScript, and Go examples: OOB SSH for developers.

1. Materialize

From the project directory (the folder that contains .eve-iac.yml):

export EVE_IAC_URL=https://eve.example:8787
export EVE_IAC_USERNAME=admin
export EVE_IAC_PASSWORD=...
export EVE_IAC_CA_FILE=$PWD/ca.pem

eve-iac login
eve-iac oob start ./IaC/sample

oob start is idempotent for this agent session. A second start keeps the same keys and does not drop a live SSH session. The command prints the path of ssh_config and does not print the token.

The same action in the IDE is Prepare OOB SSH.

Files, all under IaC/sample/.eve-iac/oob/:

File What it is
ssh_config One Host block per telnet node
id_ed25519 User private key (0600)
known_hosts Canonical node keys and the host public key
session_token The bearer that owns this generation (0600)
agent_context Agent URL and TLS trust. No bearer

The directory is gitignored. It is not part of topology.yml, the UNL, plan, or reconcile.

A node named iol1 with canonical key n_1 becomes:

Host iol1
  HostName n_1
  User eve-oob
  IdentityFile .../.eve-iac/oob/id_ed25519
  UserKnownHostsFile .../.eve-iac/oob/known_hosts
  IdentitiesOnly yes
  StrictHostKeyChecking yes
  ProxyCommand "<python>" "-m" "eveiac.oob.proxy" "--project" ".../IaC/sample" "--node" "n_1"

HostName is the canonical key. The alias is the node name, folded to letters, digits, _, and -. If two nodes fold to the same alias, the greater key gets -<key> (iol1-n_7).

The Python on PATH must be able to import eveiac. A pip install of this project’s eve-iac package is enough. The ProxyCommand does not read EVE_IAC_URL or EVE_IAC_TOKEN.

2. Open a shell

Unset the agent environment so the SSH process cannot fall back to it. Do not pass a remote command: exec is rejected and does not open the console.

unset EVE_IAC_URL EVE_IAC_TOKEN
ssh -F ./IaC/sample/.eve-iac/oob/ssh_config iol1

Host checking stays on. The first shell attaches the Hub. The agent then sends one carriage return so a quiet console shows its prompt. After that, the bytes are the device’s.

ssh -F ./IaC/sample/.eve-iac/oob/ssh_config iol1 show version

That line is an exec channel. The agent refuses it.

3. Netmiko

terminal_server is a raw shell, which matches this console. Pass the generated key and ssh_config.

Paramiko 2.12 treats a ProxyCommand that both starts and ends with " as a single program name. OpenSSH does not. Rewrite that one line to ProxyCommand /bin/sh -c '<original line>' before Paramiko parses the file.

import io
import os
from netmiko import ConnectHandler
import paramiko

os.environ.pop("EVE_IAC_URL", None)
os.environ.pop("EVE_IAC_TOKEN", None)

ssh_config = "IaC/sample/.eve-iac/oob/ssh_config"
key = "IaC/sample/.eve-iac/oob/id_ed25519"
original = paramiko.SSHConfig.parse

def parse(self, file_obj):
    lines = []
    for line in file_obj.read().splitlines():
        stripped = line.strip()
        if stripped.lower().startswith("proxycommand "):
            value = stripped.split(None, 1)[1]
            if value.startswith('"') and value.endswith('"'):
                script = "'" + value.replace("'", "'\\''") + "'"
                line = f"ProxyCommand /bin/sh -c {script}"
        lines.append(line)
    return original(self, io.StringIO("\n".join(lines) + "\n"))

paramiko.SSHConfig.parse = parse

conn = ConnectHandler(
    device_type="terminal_server",
    host="iol1",
    username="eve-oob",
    key_file=key,
    ssh_config_file=ssh_config,
    allow_agent=False,
    use_keys=True,
)
print(conn.find_prompt())
print(conn.send_command_timing("terminal length 0"))
print(conn.send_command_timing("show version"))
conn.disconnect()

terminal length 0 keeps a following Ansible run from attaching while the console is still sitting in --More--.

4. Ansible

network_cli opens a shell. On this Ansible build, ansible-pylibssh is often absent, so the connection falls back to Paramiko and does not load ssh -F. Give it the same ProxyCommand, the private key, and a HOME whose known_hosts is the generated file. ansible_host is the canonical key (n_1), because that is the name in known_hosts.

iol1:
  ansible_host: n_1
  ansible_connection: ansible.netcommon.network_cli
  ansible_network_os: cisco.ios.ios
  ansible_user: eve-oob
  ansible_private_key_file: /path/to/.eve-iac/oob/id_ed25519
  ansible_paramiko_proxy_command: /bin/sh -c '"/usr/bin/python3" "-m" "eveiac.oob.proxy" "--project" "/path/to/IaC/sample" "--node" "n_1"'
- hosts: iol1
  gather_facts: false
  tasks:
    - ansible.netcommon.cli_command:
        command: show version

A blank Cisco IOL has no startup-config. After the agent’s carriage return the prompt appears, then IOS prints:

Autoinstall will terminate if any input is detected on console

The IOS terminal plugin waits until the buffer ends in > or #. Add the initial prompt so Ansible sends a carriage return and the prompt can settle. This is device output. The agent does not parse it.

ansible_terminal_initial_prompt:
  - "Autoinstall will terminate if any input is detected on console"
ansible_terminal_initial_answer:
  - "\r"
ansible_terminal_initial_prompt_newline: false

ansible.netcommon 5.3 treats a false newline flag as true (false or true). The bytes on the wire are \r\r. That still returns the console to Switch>. Do not turn host-key checking off to get past a timeout.

5. Stop

eve-iac oob stop ./IaC/sample

Stop uses the bearer stored in session_token. It deletes .eve-iac/oob/ only after the agent accepts the revoke, or rejects that bearer as expired or unknown. If the agent cannot be reached, the directory stays. Stop OOB SSH in the IDE does the same thing.

eve-iac oob status reports whether this session’s generation is active. eve-iac oob credentials prints key material; do not log that output.

6. Inventories

eve-iac inventory generate writes an Ansible or Netmiko inventory that points at the ssh_config created above. It does not start OOB and it does not copy the ProxyCommand line. See Inventory export.