Automated Operations with Python: Chapter 6 Notes

Chapter 6: Detailed Explanation of the System Batch Operation Manager Paramiko

Paramiko is an SSH2 remote secure connection implemented in Python, supporting both authentication and key-based methods. It can perform remote command execution, file transfer, and intermediate SSH proxy functions. Compared to Pexpect, it has a higher level of encapsulation and is closer to the functionalities of the SSH protocol.

Installing Paramiko

# You can install it using pip (paramiko_env) kevinweng@KevindeMacBook-Air paramiko_test % pip install paramiko
Collecting paramiko  Downloading paramiko-3.5.1-py3-none-any.whl.metadata (4.6 kB)
Collecting bcrypt>=3.2 (from paramiko)  Downloading bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl.metadata (10 kB)
Collecting cryptography>=3.3 (from paramiko)  Downloading cryptography-44.0.2-cp39-abi3-macosx_10_9_universal2.whl.metadata (5.7 kB)
Collecting pynacl>=1.5 (from paramiko)  Downloading PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl.metadata (8.7 kB)
Collecting cffi>=1.12 (from cryptography>=3.3->paramiko)  Downloading cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl.metadata (1.5 kB)
Collecting pycparser (from cffi>=1.12->cryptography>=3.3->paramiko)  Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes)
Downloading paramiko-3.5.1-py3-none-any.whl (227 kB)
Downloading bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl (498 kB)
Downloading cryptography-44.0.2-cp39-abi3-macosx_10_9_universal2.whl (6.7 MB)   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.7/6.7 MB 30.7 MB/s eta 0:00:00
Downloading PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl (349 kB)
Downloading cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl (178 kB)
Downloading pycparser-2.22-py3-none-any.whl (117 kB)
Installing collected packages: pycparser, bcrypt, cffi, pynacl, cryptography, paramiko
Successfully installed bcrypt-4.3.0 cffi-1.17.1 cryptography-44.0.2 paramiko-3.5.1 pycparser-2.22 pynacl-1.5.0
[notice] A new release of pip is available: 25.0 -> 25.1
[notice] To update, run: pip install --upgrade pip

Use a script to quickly verify if Paramiko is installed successfully.

The script logs into the virtual machine via SSH and uses the free command to check memory before exiting.

import paramiko
ip = "192.168.0.107"
username = "kevin"
password = "751225"
# Create an SSH client
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
    # Connect to the remote server
    client.connect(ip, username=username, password=password)
    # Execute the command
    stdin, stdout, stderr = client.exec_command("free -h")
    # Print the command output
    print(stdout.read().decode())
    print(stderr.read().decode())
finally:
    # Explicitly close output streams to avoid __del__ issues
    stdout.close()
    stderr.close()
    stdin.close()
    # Close the SSH connection
    client.close()

Execution result:

# (paramiko_env) kevinweng@KevindeMacBook-Air paramiko_test % python ssh_login.py
               total        used        free      shared  buff/cache   available
Mem:           3.8Gi       416Mi       3.0Gi       1.3Mi       544Mi       3.4Gi
Swap:             0B          0B          0B

Core Components of Paramiko

SSHClient Class

The SSHClient class of Paramiko is one of its core classes, used to establish and manage SSH connections, execute remote commands, and transfer files.

SSHClient provides a high-level interface for connecting to remote servers using the SSH protocol, with main functionalities including:

  • Establishing SSH connections
  • Executing remote commands
  • Managing keys
  • Uploading/Downloading files (in conjunction with SFTPClient)

Method Descriptions

Various parameters may change due to version updates, so there is no need to memorize them; you can look them up when needed.

  • <span>connect(hostname, port, username, password, ...)</span>: Establish an SSH connection
  • <span>exec_command(command)</span>: Execute a remote command
  • <span>load_system_host_keys()</span>: By default, it loads the file that stores known host public keys in the system, usually located at<span>~/.ssh/known_hosts</span>
  • <span>set_missing_host_key_policy(policy)</span>: Set the host key policy (e.g., AutoAddPolicy or RejectPolicy)

SFTPClient Class

paramiko.SFTPClient is the core class for SSH-based file transfer (SFTP) in Paramiko, which can be used to upload, download, list directories, delete files, etc., suitable for remote file operations in automation scripts.

sftp = client.open_sftp()  # Create an SFTP client object
sftp.get('/remote/path/file.txt', 'local.txt')   # Download
sftp.put('local.txt', '/remote/path/file.txt')   # Upload
sftp.close()

Best Practice: Implementing a Bastion Host with Paramiko

What is a Bastion Host?

A Bastion Host is a security device specifically used as a jump server; users must first log into the Bastion Host and then jump or forward to the target server. It serves to isolate internal and external networks and audit operational activities.

Note: You need to prepare two virtual machines, named Bastion Host and Target Machine.

Bastion Host IP address: 192.168.0.115 Target Machine IP address: 192.168.0.116 A local SSH private key file exists, and login is done using the SSH key.

Login Path: Local Machine -> Bastion Host -> Target Machine

SSH Private Key Authentication Setup

SSH Settings from Local Machine to Bastion Host

# Generate public and private key pair (paramiko_env) kevinweng@KevindeMacBook-Air paramiko_test % ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa
Generating public/private rsa key pair.
Enter passphrase for "/Users/kevinweng/.ssh/id_rsa" (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /Users/kevinweng/.ssh/id_rsa
Your public key has been saved in /Users/kevinweng/.ssh/id_rsa.pub
The key fingerprint is:SHA256:bj5q7PGusheGdf+cW7HZAVNmsFOUntgWf0AJRCsohzU [email protected]
The key's randomart image is:+---[RSA 4096]----+|        E  o+++B.||       o o   .Oo ||      o o . .=+.+||      .o.  . .+=o||     o .S.    o..||    . o.  .    =.||     o..o  o .+ .||    . +=.   +.   ||    .*+++.  ..   |+----[SHA256]-----+
# Copy the public key to the Bastion Host (paramiko_env) kevinweng@KevindeMacBook-Air paramiko_test % ssh-copy-id -i ~/.ssh/id_rsa.pub [email protected]
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/Users/kevinweng/.ssh/id_rsa.pub"
The authenticity of host '192.168.0.115 (192.168.0.115)' can't be established.
ED25519 key fingerprint is SHA256:IKVjRXKjyECLNh7QPH89pUcT2YJdvRPFxK7xEEtNTDE.
This host key is known by the following other names/addresses:    ~/.ssh/known_hosts:11: 192.168.0.107
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
[email protected]'s password: 
Number of key(s) added:        1
Now try logging into the machine, with: "ssh -i /Users/kevinweng/.ssh/id_rsa '[email protected]'" and check to make sure that only the key(s) you wanted were added.
# Now you can log in using SSH without a password (paramiko_env) kevinweng@KevindeMacBook-Air paramiko_test % ssh [email protected]
Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.8.0-58-generic aarch64)
 * Documentation:  https://help.ubuntu.com * Management:     https://landscape.canonical.com * Support:        https://ubuntu.com/pro
 System information as of Sun May  4 09:05:16 AM UTC 2025
  System load:             0.08  Usage of /:              31.7% of 9.75GB  Memory usage:            7%  Swap usage:              0%  Temperature:             11758.9 C  Processes:               208  Users logged in:         1  IPv4 address for ens160: 192.168.0.115  IPv6 address for ens160: fd00:4c10:d567:4919::1003  IPv6 address for ens160: fd00:4c10:d567:4919:20c:29ff:fef0:2fec

Expanded Security Maintenance for Applications is not enabled.
75 updates can be applied immediately.
To see these additional updates run: apt list --upgradable
Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status

Last login: Thu May  1 04:35:38 2025 from 192.168.0.109

Generating Key Pair for Target Machine from Bastion Host

kevin@jump-server:~$ ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/kevin/.ssh/id_rsa
Your public key has been saved in /home/kevin/.ssh/id_rsa.pub
The key fingerprint is:SHA256:PR6QjpCw0uNB9Nsi7olBExw4JXbOYrD368TMhfgjSHM kevin@jump-server
The key's randomart image is:+---[RSA 4096]----+|+==.             ||*==+ .   .       ||o**o+   o        ||.+o= = o o       || =+E= + S +      ||oo+* +   . o     ||o o O     .      || + = .           ||. o .            |+----[SHA256]-----+
kevin@jump-server:~$ ssh-copy-id -i ~/.ssh/id_rsa.pub [email protected]
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/kevin/.ssh/id_rsa.pub"
The authenticity of host '192.168.0.116 (192.168.0.116)' can't be established.
ED25519 key fingerprint is SHA256:IKVjRXKjyECLNh7QPH89pUcT2YJdvRPFxK7xEEtNTDE.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
[email protected]'s password: 
Number of key(s) added: 1
Now try logging into the machine, with:   "ssh '[email protected]'" and check to make sure that only the key(s) you wanted were added.
# Now you can log in from the Bastion Host to the Target Machine without a password
kevin@jump-server:~$ ssh 192.168.0.116
Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.8.0-58-generic aarch64)
 * Documentation:  https://help.ubuntu.com * Management:     https://landscape.canonical.com * Support:        https://ubuntu.com/pro
 System information as of Sun May  4 09:07:20 AM UTC 2025
  System load:             0.0  Usage of /:              31.7% of 9.75GB  Memory usage:            7%  Swap usage:              0%  Temperature:             11758.9 C  Processes:               211  Users logged in:         1  IPv4 address for ens160: 192.168.0.116  IPv6 address for ens160: fd00:4c10:d567:4919:20c:29ff:fe0c:54a2

Expanded Security Maintenance for Applications is not enabled.
75 updates can be applied immediately.
To see these additional updates run: apt list --upgradable
Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status

Last login: Thu May  1 04:35:38 2025 from 192.168.0.109
kevin@target-machine:~$ exit
logout
Connection to 192.168.0.116 closed.

Copying Local Machine Public Key to Target Machine

The script runs on the Mac local machine, using the local private key (path is /Users/kevinweng/.ssh/id_rsa). The target machine does not have the corresponding public key for this private key, so it cannot connect successfully through the Bastion Host.

# (paramiko_env) kevinweng@KevindeMacBook-Air paramiko_test % ssh-copy-id -i ~/.ssh/id_rsa.pub [email protected]
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/Users/kevinweng/.ssh/id_rsa.pub"
The authenticity of host '192.168.0.116 (192.168.0.116)' can't be established.
ED25519 key fingerprint is SHA256:IKVjRXKjyECLNh7QPH89pUcT2YJdvRPFxK7xEEtNTDE.
This host key is known by the following other names/addresses:    ~/.ssh/known_hosts:11: 192.168.0.107    ~/.ssh/known_hosts:14: 192.168.0.115
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
[email protected]'s password: 
Number of key(s) added:        1
Now try logging into the machine, with: "ssh -i /Users/kevinweng/.ssh/id_rsa '[email protected]'" and check to make sure that only the key(s) you wanted were added.

Logging into Target Machine via Bastion Host from Local Machine

The code is as follows:

import paramiko
# Parameters
bastion_ip = "192.168.0.115"
bastion_user = "kevin"
target_ip = "192.168.0.116"
target_user = "kevin"
target_port = 22
ssh_key_path = "/Users/kevinweng/.ssh/id_rsa"
# Load SSH private key
private_key = paramiko.RSAKey.from_private_key_file(ssh_key_path)
# Step 1: Log into the Bastion Host
bastion_client = paramiko.SSHClient()
bastion_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
bastion_client.connect(bastion_ip, username=bastion_user, pkey=private_key)
# Get the underlying Transport
bastion_transport = bastion_client.get_transport()
# Step 2: Create a channel to the target machine through the Bastion Host (essentially an SSH tunnel)
dest_addr = (target_ip, target_port)
local_addr = ('127.0.0.1', 0)
channel = bastion_transport.open_channel("direct-tcpip", dest_addr, local_addr)
# Step 3: Connect to the target machine using this channel
target_client = paramiko.SSHClient()
target_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
target_client.connect(
    target_ip,
    username=target_user,
    pkey=private_key,
    sock=channel  # <=== Key point: connect through the Bastion Host channel
)
# Now you are connected to the target machine and can execute commands
stdin, stdout, stderr = target_client.exec_command("hostname")
print("Target machine hostname:", stdout.read().decode().strip())
# Clean up resources
target_client.close()
bastion_client.close()

Execution result is as follows:

# (paramiko_env) kevinweng@KevindeMacBook-Air paramiko_test % python jump-server.py
Target machine hostname: target-machine

Leave a Comment