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.