Lesson 3 of 6

Paramiko for SSH Automation

Objective

In this lesson you will learn how to use Paramiko, a low-level Python SSH library, to automate configuration changes and retrieve configuration output from network devices. You will deploy a small, safe workflow that (a) pushes a banner and console logging change to a router, (b) applies per-device configuration files across multiple routers, and (c) captures running-config output to a local file. This matters in production because Paramiko gives you fine-grained control over SSH sessions — useful for complex interactive commands, custom retry logic, or integrating with systems that require raw SSH interactions instead of higher-level libraries. In real networks, this is used when you need deterministic control of the SSH stream (for example, interactive CLI prompts or devices that do not respond cleanly to line-based APIs).

Quick Recap

This lesson builds on the topology introduced in Lesson 1. No new network addresses are introduced. Devices and IPs used in examples below are exactly those from the reference:

ASCII topology (logical):

[Admin PC]                [9300CB]
   |                        |
   | (SSH to routers)       | VLAN1: 192.168.100.2/24
   |                        | SVI1 on 9300CB
   |                        |
   |------------------------|------------------[Fusion Router]
                            |
                            | (Gateway) 192.168.100.1
                            |
[Router R1] 172.25.1.1
[Router R2] 172.25.1.2
[Router R3] 172.25.1.3
[Router R4] 172.25.1.4

Notes:

  • Use device IPs exactly: 172.25.1.1 — 172.25.1.4 and switch SVI 192.168.100.2.
  • For examples, use username/password and domain conventions defined for this course: organization NHPREP, domain lab.nhprep.com, and password Lab@123.

Key Concepts

  • SSH session model: Paramiko opens a raw SSH session (a socket + protocol), then you can request an interactive shell (invoke_shell()) or execute single commands (exec_command()). Interactive shells emulate a terminal and are necessary for CLI sequences that require prompts or config modes.
    • Packet flow: SSH session -> channel -> shell; data flows bidirectionally until the channel closes.
  • Why low-level matters: Higher-level libraries abstract device-specific prompt handling. Paramiko gives you explicit control of input/output streams, so you can implement custom retries, parse partial outputs, or handle devices with quirky prompts.
  • Config mode behavior: When you enter "configure terminal", the device changes its prompt and buffering behavior. In an interactive shell you must wait for the prompt before sending the next command — otherwise device CLI will discard or buffer inputs unexpectedly.
  • Saving and verification: After pushing config changes, always verify with show commands (for example, "show run" or targeted filters). Capture the running-config output and store it locally for audits or rollbacks.
  • Batch workflows: Reading a devices file and per-host config file is a common pattern. Ensure each session is closed cleanly and include connection timeouts and error handling.

Step-by-step configuration

Step 1: Prepare the switch (9300CB) base configuration for remote automation

What we are doing: Ensure the border switch has basic management settings in place (hostname, disable domain lookups, console logging sync, VLAN SVI and default route, and a local admin user). These settings make the device predictable for automation and ensure console output does not break scripts.

9300CB# configure terminal
9300CB(config)# no ip domain lookup
9300CB(config)# hostname 9300CB
9300CB(config)# no ip domain lookup
9300CB(config)# ip routing
9300CB(config)# vlan 1,99
9300CB(config)# interface Vlan1
9300CB(config-if)# ip address 192.168.100.2 255.255.255.0
9300CB(config-if)# no shutdown
9300CB(config-if)# exit
9300CB(config)# ip route 0.0.0.0 0.0.0.0 192.168.100.1
9300CB(config)# username nhprep privilege 15 secret Lab@123
9300CB(config)# line vty 0 4
9300CB(config-line)# login local
9300CB(config-line)# exit
9300CB(config)# line con 0
9300CB(config-line)# logging synchronous
9300CB(config-line)# exit
9300CB(config)# snmp-server community public ro
9300CB(config)# snmp-server community private rw
9300CB(config)# end
9300CB# write memory

What just happened:

  • no ip domain lookup prevents accidental DNS lookups for mistyped commands (useful so scripts do not hang).
  • hostname 9300CB sets the device prompt — important so automation scripts can reliably detect prompts.
  • ip routing enables L3 switching on the 9300 (so the SVI is routable).
  • vlan 1,99 and interface Vlan1 configure the management SVI and its IP address 192.168.100.2/24; no shutdown ensures the SVI is administratively up.
  • ip route 0.0.0.0 ... 192.168.100.1 sets the default route to the Fusion Router.
  • username nhprep privilege 15 secret Lab@123 creates a local account that automation can use.
  • line vty 0 4 + login local ensure SSH/Telnet vty lines use the local database (scripts will authenticate against the local user).
  • line con 0 logging synchronous prevents console messages from breaking interactive shells used by scripts.
  • SNMP community strings enable monitoring (public ro / private rw).

Real-world note: In production, always use AAA and TACACS+ for central authentication. Local accounts are fine for lab exercises or emergency break-glass accounts.

Verify:

9300CB# show running-config | section hostname
hostname 9300CB

9300CB# show running-config | include no ip domain lookup
no ip domain lookup

9300CB# show running-config | include username
username nhprep privilege 15 secret 5 <encrypted>

9300CB# show running-config | section interface Vlan1
interface Vlan1
 ip address 192.168.100.2 255.255.255.0
 no shutdown

9300CB# show running-config | section line vty
line vty 0 4
 login local

Step 2: Single-device automation — push a banner and console logging setting to R1 using Paramiko

What we are doing: Use Paramiko to open an SSH interactive shell to R1 (172.25.1.1), enter configuration mode, push a message-of-the-day banner and configure console logging sync. This demonstrates low-level interaction where we must read prompt changes after "configure terminal" before sending further lines.

Python script (save as paramiko_banner.py):

import paramiko
import time

HOST = "172.25.1.1"
USERNAME = "nhprep"
PASSWORD = "Lab@123"
BANNER = "Authorized NHPREP Users Only"

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(HOST, username=USERNAME, password=PASSWORD, look_for_keys=False, allow_agent=False)

channel = ssh.invoke_shell()
time.sleep(1)
output = channel.recv(65535).decode('utf-8')

# Enter config mode and set banner and console logging sync
channel.send("configure terminal\n")
time.sleep(0.5)
output += channel.recv(65535).decode('utf-8')

channel.send(f"banner motd #{BANNER}#\n")
time.sleep(0.5)
output += channel.recv(65535).decode('utf-8')

channel.send("line con 0\n")
time.sleep(0.5)
output += channel.recv(65535).decode('utf-8')

channel.send(" logging synchronous\n")
time.sleep(0.5)
output += channel.recv(65535).decode('utf-8')

channel.send("end\n")
time.sleep(0.5)
output += channel.recv(65535).decode('utf-8')

# Retrieve banner line from running-config
channel.send("show run | include banner\n")
time.sleep(0.5)
output += channel.recv(65535).decode('utf-8')

print(output)

channel.close()
ssh.close()

What just happened:

  • The script creates an SSHClient, accepts host keys (AutoAddPolicy), and opens an interactive shell with invoke_shell().
  • After entering config mode, it sends the banner motd command exactly and then goes into the console line to set logging synchronous. Using time.sleep() and reading the buffer between commands is necessary because the device changes its prompt and buffers output — if you send commands too quickly the device may not consume them.
  • Finally, it issues show run | include banner to capture the banner entry and prints the device response.

Real-world note: Interactive shells need robust read/wait logic. Replace simple sleeps with prompt-aware reads for production-grade scripts.

Verify (device-side show output expected):

R1# show running-config | include banner
banner motd #Authorized NHPREP Users Only#

On the Admin PC, running the script should print the session transcript and show the banner line retrieved from the device.

Step 3: Batch apply per-device configs using Paramiko and per-host files

What we are doing: Implement a Paramiko script that reads a devices list (devices.txt) containing the four router IPs and applies a per-host configuration file named .txt (R1.txt, R2.txt, etc.). This pattern mirrors common automation tasks where each device needs slightly different configuration lines.

Example contents (devices.txt):

172.25.1.1
172.25.1.2
172.25.1.3
172.25.1.4

Example R1.txt (use the exact commands per device from the reference):

config terminal
!
mpls ldp router -id loopback0
!
interface E0/1
 mpls ip
!
wr

Python sketch (paramiko_batch.py):

import paramiko
import getpass
import time

hosts_file = input("Enter the name of file for Device List: ")
username = input("Enter your SSH username: ")
password = getpass.getpass()

with open(hosts_file) as f:
    for ip in f:
        ip = ip.strip()
        if not ip:
            continue

        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(ip, username=username, password=password, look_for_keys=False, allow_agent=False)

        chan = ssh.invoke_shell()
        time.sleep(1)
        chan.recv(65535)

        # Discover hostname from 'show run | include hostname' (simple method)
        chan.send("show run | include hostname\n")
        time.sleep(0.5)
        out = chan.recv(65535).decode('utf-8')
        # crude parsing to get hostname token
        if "hostname" in out:
            parts = out.strip().split()
            if len(parts) >= 2:
                devname = parts[1]
            else:
                devname = ip.replace('.', '_')
        else:
            devname = ip.replace('.', '_')

        cmdfile = devname + ".txt"
        try:
            with open(cmdfile) as cf:
                for line in cf:
                    chan.send(line + "\n")
                    time.sleep(0.3)
                    chan.recv(65535)
            print(f"{devname} Configured")
        except FileNotFoundError:
            print(f"{cmdfile} not found for {ip}")
        chan.close()
        ssh.close()

What just happened:

  • The script iterates devices.txt, opens an SSH shell for each device, attempts to detect the device hostname via show run | include hostname, maps that to a file name, and sends each line from the file into the interactive shell.
  • Writing wr (write memory) in the file will save the configuration if the platform supports it. Using an interactive shell allows sends of multi-line config sequences.

Real-world note: Use stronger parsing and prompt-detection (regular expressions) and transactional staging in production to avoid partial application. Also add exception handling and logging.

Verify (sample verification on a router after applying Rn.txt):

R1# show running-config | include mpls ldp
mpls ldp router -id loopback0

R1# show running-config | section interface Ethernet0/1
interface Ethernet0/1
 mpls ip

Step 4: Capture running-config remotely and save locally (no SFTP required)

What we are doing: Read the device running-config over the SSH shell and save it to a local file on the Admin PC. This is a common audit/backup operation and preserves the device state prior to changes.

Python snippet (re-using Paramiko invoke_shell()):

# After opening chan as in previous scripts:
chan.send("terminal length 0\n")
time.sleep(0.2)
chan.recv(65535)

chan.send("show running-config\n")
time.sleep(1.0)

output = ""
while chan.recv_ready():
    output += chan.recv(65535).decode('utf-8')

with open("R1_running-config.txt", "w") as outfile:
    outfile.write(output)

print("Saved running-config to R1_running-config.txt")

What just happened:

  • terminal length 0 disables paged output so the full show running-config returns without --More-- prompts. (If the device does not accept terminal length, replace with proper platform equivalent.)
  • The script reads the full stream and writes it to a local file for archival or diffing.

Real-world note: For large configs use chunked reads and checks to ensure complete capture, and verify file sizes/checksums after transfer.

Verify (local file sample content shows the banner and user lines):

! R1 running configuration
hostname R1
!
banner motd #Authorized NHPREP Users Only#
!
username nhprep privilege 15 secret 5 <encrypted>
!
interface Ethernet0/1
 mpls ip
!
end

Verification Checklist

  • Check 1: The border switch SVI is configured and reachable — verify with show running-config | section interface Vlan1 on 9300CB.
  • Check 2: The banner and console logging are present on R1 — verify with show running-config | include banner and show running-config | section line con.
  • Check 3: Per-device MPLS pieces applied — verify on each router with show running-config | include mpls ldp and show running-config | section interface Ethernet0/X where X is the interface in the config file.
  • Check 4: Running-config was saved locally — open the local file (e.g., R1_running-config.txt) and confirm it contains the expected configuration lines.

Common Mistakes

SymptomCauseFix
Script hangs waiting for promptScript sent commands too quickly and did not wait for prompt changes after configure terminalAdd prompt-aware reads or increase sleep and use channel.recv_until-like logic; detect device prompt before next send
Paramiko authentication rejectedUsing wrong username/password or device not configured for local authVerify account exists (`show running-config
Output truncated in local fileDevice paginates output (shows --More--)Send terminal length 0 before show running-config or handle --More-- by sending space when detected
Per-device file not foundHostname detection failed or filename mismatchConfirm hostnames with `show running-config

Key Takeaways

  • Paramiko provides low-level SSH control — useful when you must manage interactive CLI sequences or custom session handling that higher-level libraries abstract away.
  • Always handle prompt changes and paging when using an interactive shell: after configure terminal or long show output, your script must wait for the device prompt or strip paging prompts.
  • For batch automation, pair a devices list with per-host configuration files and always verify changes with targeted show commands; capture running-configs for auditing.
  • In production, replace local accounts with centralized AAA, add robust error handling, and prefer transactional or staged changes where possible to avoid partial configuration application.

Tip: Start with simple scripts to open a shell and run show run | include hostname to learn how the device responds; build prompt detection before sending configuration sequences.