First, I have the habit of working in virtual environments. I create one and activate it:

Wassim@linux:~/private/labscripts$ python -m venv venv
Wassim@linux:~/private/labscripts$ ls
venv
Wassim@linux:~/private/labscripts$ source venv/bin/activate
((venv) ) Wassim@linux:~/private/labscripts$

I upgrade the pip package:

((venv) ) Wassim@linux:~/private/labscripts$ pip install --upgrade pip
Requirement already satisfied: pip in ./venv/lib64/python3.12/site-packages (23.3.2)
Collecting pip
  Downloading pip-26.2.1-py3-none-any.whl.metadata (4.6 kB)
Downloading pip-26.2.1-py3-none-any.whl (1.8 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.8/1.8 MB 19.3 MB/s eta 0:00:00
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 23.3.2
    Uninstalling pip-23.3.2:
      Successfully uninstalled pip-23.3.2
Successfully installed pip-26.2.1
((venv) ) Wassim@linux:~/private/labscripts$ 

I only have the pip package installed. I need pyEZ, Netmiko.

Installing pyEZ:


((venv) ) Wassim@linux:~/private/labscripts$ pip install junos-eznc
Collecting junos-eznc
  Downloading junos_eznc-2.8.2-py2.py3-none-any.whl.metadata (1.9 kB)
Collecting lxml>=3.2.4 (from junos-eznc)
  Downloading lxml-6.1.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl.metadata (3.3 kB)
Collecting ncclient==0.7.0 (from junos-eznc)
  Using cached ncclient-0.7.0-py3-none-any.whl
Collecting scp>=0.7.0 (from junos-eznc)
  Downloading scp-0.16.1-py2.py3-none-any.whl.metadata (4.6 kB)
Collecting jinja2>=3.1.6 (from junos-eznc)
  Using cached jinja2-3.1.6-py3-none-any.whl.metadata (2.9 kB)
Collecting PyYAML>=5.1 (from junos-eznc)
  Using cached pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (2.4 kB)
Collecting paramiko>=3.5.0 (from junos-eznc)
  Downloading paramiko-5.0.0-py3-none-any.whl.metadata (3.7 kB)
Collecting six (from junos-eznc)
  Using cached six-1.17.0-py2.py3-none-any.whl.metadata (1.7 kB)
Collecting pyserial (from junos-eznc)
  Using cached pyserial-3.5-py2.py3-none-any.whl.metadata (1.6 kB)
Collecting yamlloader (from junos-eznc)
  Using cached yamlloader-1.6.0-py3-none-any.whl.metadata (8.5 kB)
Collecting pyparsing (from junos-eznc)
  Downloading pyparsing-3.3.2-py3-none-any.whl.metadata (5.8 kB)
Collecting transitions (from junos-eznc)
  Using cached transitions-0.9.3-py2.py3-none-any.whl.metadata (97 kB)
Collecting MarkupSafe>=2.0 (from jinja2>=3.1.6->junos-eznc)
  Using cached markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (2.7 kB)
Collecting bcrypt>=3.2 (from paramiko>=3.5.0->junos-eznc)
  Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl.metadata (10 kB)
Collecting cryptography>=3.3 (from paramiko>=3.5.0->junos-eznc)
  Downloading cryptography-50.0.0-cp311-abi3-manylinux_2_34_x86_64.whl.metadata (4.3 kB)
Collecting invoke>=2.0 (from paramiko>=3.5.0->junos-eznc)
  Downloading invoke-3.0.3-py3-none-any.whl.metadata (3.2 kB)
Collecting pynacl>=1.5 (from paramiko>=3.5.0->junos-eznc)
  Using cached pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl.metadata (10.0 kB)
Collecting cffi>=2.0.0 (from cryptography>=3.3->paramiko>=3.5.0->junos-eznc)
  Downloading cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.5 kB)
Collecting pycparser (from cffi>=2.0.0->cryptography>=3.3->paramiko>=3.5.0->junos-eznc)
  Downloading pycparser-3.0-py3-none-any.whl.metadata (8.2 kB)
Downloading junos_eznc-2.8.2-py2.py3-none-any.whl (217 kB)
Using cached jinja2-3.1.6-py3-none-any.whl (134 kB)
Downloading lxml-6.1.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (5.2 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5.2/5.2 MB 28.0 MB/s  0:00:00
Using cached markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
Downloading paramiko-5.0.0-py3-none-any.whl (208 kB)
Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB)
Downloading cryptography-50.0.0-cp311-abi3-manylinux_2_34_x86_64.whl (4.7 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.7/4.7 MB 27.4 MB/s  0:00:00
Downloading cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (221 kB)
Downloading invoke-3.0.3-py3-none-any.whl (160 kB)
Using cached pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB)
Using cached pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (807 kB)
Downloading scp-0.16.1-py2.py3-none-any.whl (8.9 kB)
Downloading pycparser-3.0-py3-none-any.whl (48 kB)
Downloading pyparsing-3.3.2-py3-none-any.whl (122 kB)
Using cached pyserial-3.5-py2.py3-none-any.whl (90 kB)
Using cached six-1.17.0-py2.py3-none-any.whl (11 kB)
Using cached transitions-0.9.3-py2.py3-none-any.whl (112 kB)
Using cached yamlloader-1.6.0-py3-none-any.whl (7.8 kB)
Installing collected packages: pyserial, six, PyYAML, pyparsing, pycparser, MarkupSafe, lxml, invoke, bcrypt, yamlloader, transitions, jinja2, cffi, pynacl, cryptography, paramiko, scp, ncclient, junos-eznc
Successfully installed MarkupSafe-3.0.3 PyYAML-6.0.3 bcrypt-5.0.0 cffi-2.1.1 cryptography-50.0.0 invoke-3.0.3 jinja2-3.1.6 junos-eznc-2.8.2 lxml-6.1.2 ncclient-0.7.0 paramiko-5.0.0 pycparser-3.0 pynacl-1.6.2 pyparsing-3.3.2 pyserial-3.5 scp-0.16.1 six-1.17.0 transitions-0.9.3 yamlloader-1.6.0
((venv) ) Wassim@linux:~/private/labscripts$ 

I install the netmiko package:


((venv) ) Wassim@linux:~/private/labscripts$ pip install netmiko
Collecting netmiko
  Downloading netmiko-4.7.0-py3-none-any.whl.metadata (8.3 kB)
Collecting ntc-templates>=3.1.0 (from netmiko)
  Downloading ntc_templates-9.2.0-py3-none-any.whl.metadata (4.2 kB)
Collecting paramiko<5.0,>=3.5.0 (from netmiko)
  Using cached paramiko-4.0.0-py3-none-any.whl.metadata (3.9 kB)
Requirement already satisfied: pyserial>=3.3 in ./venv/lib64/python3.12/site-packages (from netmiko) (3.5)
Requirement already satisfied: pyyaml>=6.0.2 in ./venv/lib64/python3.12/site-packages (from netmiko) (6.0.3)
Collecting rich>=13.8 (from netmiko)
  Downloading rich-15.0.0-py3-none-any.whl.metadata (18 kB)
Collecting ruamel.yaml>=0.17 (from netmiko)
  Using cached ruamel_yaml-0.19.1-py3-none-any.whl.metadata (16 kB)
Requirement already satisfied: scp>=0.13.6 in ./venv/lib64/python3.12/site-packages (from netmiko) (0.16.1)
Collecting textfsm>=1.1.3 (from netmiko)
  Using cached textfsm-2.1.0-py2.py3-none-any.whl.metadata (2.7 kB)
Requirement already satisfied: bcrypt>=3.2 in ./venv/lib64/python3.12/site-packages (from paramiko<5.0,>=3.5.0->netmiko) (5.0.0)
Requirement already satisfied: cryptography>=3.3 in ./venv/lib64/python3.12/site-packages (from paramiko<5.0,>=3.5.0->netmiko) (50.0.0)
Requirement already satisfied: invoke>=2.0 in ./venv/lib64/python3.12/site-packages (from paramiko<5.0,>=3.5.0->netmiko) (3.0.3)
Requirement already satisfied: pynacl>=1.5 in ./venv/lib64/python3.12/site-packages (from paramiko<5.0,>=3.5.0->netmiko) (1.6.2)
Requirement already satisfied: cffi>=2.0.0 in ./venv/lib64/python3.12/site-packages (from cryptography>=3.3->paramiko<5.0,>=3.5.0->netmiko) (2.1.1)
Requirement already satisfied: pycparser in ./venv/lib64/python3.12/site-packages (from cffi>=2.0.0->cryptography>=3.3->paramiko<5.0,>=3.5.0->netmiko) (3.0)
Collecting markdown-it-py>=2.2.0 (from rich>=13.8->netmiko)
  Downloading markdown_it_py-4.2.0-py3-none-any.whl.metadata (7.4 kB)
Collecting pygments<3.0.0,>=2.13.0 (from rich>=13.8->netmiko)
  Downloading pygments-2.21.0-py3-none-any.whl.metadata (2.5 kB)
Collecting mdurl~=0.1 (from markdown-it-py>=2.2.0->rich>=13.8->netmiko)
  Using cached mdurl-0.1.2-py3-none-any.whl.metadata (1.6 kB)
Downloading netmiko-4.7.0-py3-none-any.whl (294 kB)
Using cached paramiko-4.0.0-py3-none-any.whl (223 kB)
Downloading ntc_templates-9.2.0-py3-none-any.whl (649 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 649.8/649.8 kB 15.5 MB/s  0:00:00
Downloading rich-15.0.0-py3-none-any.whl (310 kB)
Downloading pygments-2.21.0-py3-none-any.whl (1.3 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.3/1.3 MB 22.0 MB/s  0:00:00
Downloading markdown_it_py-4.2.0-py3-none-any.whl (91 kB)
Using cached mdurl-0.1.2-py3-none-any.whl (10.0 kB)
Using cached ruamel_yaml-0.19.1-py3-none-any.whl (118 kB)
Using cached textfsm-2.1.0-py2.py3-none-any.whl (44 kB)
Installing collected packages: textfsm, ruamel.yaml, pygments, ntc-templates, mdurl, markdown-it-py, rich, paramiko, netmiko
  Attempting uninstall: paramiko
    Found existing installation: paramiko 5.0.0
    Uninstalling paramiko-5.0.0:
      Successfully uninstalled paramiko-5.0.0
Successfully installed markdown-it-py-4.2.0 mdurl-0.1.2 netmiko-4.7.0 ntc-templates-9.2.0 paramiko-4.0.0 pygments-2.21.0 rich-15.0.0 ruamel.yaml-0.19.1 textfsm-2.1.0
((venv) ) Wassim@linux:~/private/labscripts$ 

The list of installed packages grew. This is because junos-eznc and netmiko require additional packages.


((venv) ) Wassim@linux:~/private/labscripts$ pip list
Package        Version
-------------- -------
bcrypt         5.0.0
cffi           2.1.1
cryptography   50.0.0
invoke         3.0.3
Jinja2         3.1.6
junos-eznc     2.8.2
lxml           6.1.2
markdown-it-py 4.2.0
MarkupSafe     3.0.3
mdurl          0.1.2
ncclient       0.7.0
netmiko        4.7.0
ntc_templates  9.2.0
paramiko       4.0.0
pip            26.2.1
pycparser      3.0
Pygments       2.21.0
PyNaCl         1.6.2
pyparsing      3.3.2
pyserial       3.5
PyYAML         6.0.3
rich           15.0.0
ruamel.yaml    0.19.1
scp            0.16.1
six            1.17.0
textfsm        2.1.0
transitions    0.9.3
yamlloader     1.6.0
((venv) ) Wassim@linux:~/private/labscripts$ 

Configuring Junos using Python scripts#

I need to activate NETCONF first. As a reminder, this is a non-productive, isolated virtual lab environment. So I do not mind including the complete output of the key generation process:

root@leaf1:~ # cli
root@leaf1> show configuration system services 

root@leaf1> edit  
Entering configuration mode

[edit]
root@leaf1# set system services netconf ssh 

[edit]
root@leaf1# commit and-quit 
Generating RSA2 key /etc/ssh/ssh_host_rsa_key
Generating public/private rsa key pair.
Your identification has been saved in /etc/ssh/ssh_host_rsa_key
Your public key has been saved in /etc/ssh/ssh_host_rsa_key.pub
The key fingerprint is:
SHA256:56yVAnFp/VYywpu05Z8CMo/RPFRDqT69UZWJvf5kUj0 root@leaf1
The key's randomart image is:
+---[RSA 2048]----+
|           .+.o o|
|         + ..o +.|
|      . + *.+ ..o|
|       + =.O +.Eo|
|      . S.X.+....|
|       . Xo=o..oo|
|        o *..oo+.|
|         +  ..  .|
|        .        |
+----[SHA256]-----+
Generating ECDSA_P256 key /etc/ssh/ssh_host_ecdsa_key
Generating public/private ecdsa key pair.
Your identification has been saved in /etc/ssh/ssh_host_ecdsa_key
Your public key has been saved in /etc/ssh/ssh_host_ecdsa_key.pub
The key fingerprint is:
SHA256:oMEJE28oBNwJMBrQLes10m1RB+usTb/J2c1/nG/goMs root@leaf1
The key's randomart image is:
+---[ECDSA 256]---+
|@+*o.  .o..      |
|o+oO...  o       |
|o .+*....        |
| .o.+oo+         |
| . o.o  S        |
|  .    + .  . .  |
|      . . .. o o.|
|         o.= o..+|
|          E.. oo=|
+----[SHA256]-----+
Generating ECDSA_P384 key /etc/ssh/ssh_host_ec_p384_key
Generating public/private ecdsa key pair.
Your identification has been saved in /etc/ssh/ssh_host_ec_p384_key
Your public key has been saved in /etc/ssh/ssh_host_ec_p384_key.pub
The key fingerprint is:
SHA256:xj0kUalrEB3GOq7g283KG2lcu4S3BwMEZqwVzTZoTZ4 root@leaf1
The key's randomart image is:
+---[ECDSA 384]---+
| .=O.  o+o..     |
| o=.B...o..      |
| + oE. o...      |
|.   . +..+       |
|     o.oS.o      |
|  .. ++oo  .     |
| . .*.++         |
|  .+.* o.        |
|  ..=o=.         |
+----[SHA256]-----+
Generating ECDSA_P521 key /etc/ssh/ssh_host_ec_p521_key
Generating public/private ecdsa key pair.
Your identification has been saved in /etc/ssh/ssh_host_ec_p521_key
Your public key has been saved in /etc/ssh/ssh_host_ec_p521_key.pub
The key fingerprint is:
SHA256:HWLxt+z7oX5NgTVrM7IdiLhqkVV32os9XlKe9pPJG/s root@leaf1
The key's randomart image is:
+---[ECDSA 521]---+
|        .        |
|         o . . + |
|        o = + B o|
|       . = = * O.|
|        S o o BoB|
|       o . . o.B+|
|        o   . ==*|
|       o     o.B=|
|      .    .+o.oE|
+----[SHA256]-----+
Generating ED25519 key /etc/ssh/ssh_host_ed25519_key
Generating public/private ed25519 key pair.
Your identification has been saved in /etc/ssh/ssh_host_ed25519_key
Your public key has been saved in /etc/ssh/ssh_host_ed25519_key.pub
The key fingerprint is:
SHA256:GTnu0CBp8RhQbEv3oT2h2lZgnfLI11/evFxjggC3t8Q root@leaf1
The key's randomart image is:
+--[ED25519 256]--+
|  .++  . .       |
|    +*=.=o       |
|   o==oX*+o      |
|   ...=+B*.E  .  |
|     o.oS.+.oo o |
|    . oo   o...o+|
|     .  .     + +|
|               o |
|                 |
+----[SHA256]-----+
commit complete
Exiting configuration mode

root@leaf1> 
root@leaf1> show configuration system services 
netconf {
    ssh;
}

root@leaf1>

To be able to reach the OoB mgmt addresses of the Junos devices in the virtual lab, I had to set up static routing:

  • added a default route on each Junos device toward the OoB gateway,
  • added a static route on my Linux machine using nmcli connection modify {Conn_Name} +ipv4.routes "{Prefix/MaskLength Next_Hop_IP_addr}"

I verify that I can establish a NETCONF session to a Junos device:

Wassim@linux:~$ nc -v 172.17.81.44 830
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Connected to 172.17.81.44:830.
SSH-2.0-JSSH_4.1
^C
Wassim@linux:~$ 

I will also need SSH service since I might use Netmiko. I needed to add the ssh version and the port number. Otherwise, the connection was still refused by the Junos device:

Wassim@linux:~$ ssh [email protected]
ssh: connect to host 172.17.81.44 port 22: Connection refused
Wassim@linux:~$ 
[edit system services ssh]
root@leaf1# show 
protocol-version v2;
port 22;

[edit system services ssh]
root@leaf1# 
Wassim@linux:~$ ssh [email protected]
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ED25519 key sent by the remote host is
SHA256:GTnu0CBp8RhQbEv3oT2h2lZgnfLI11/evFxjggC3t8Q.
Please contact your system administrator.
Add correct host key in /home/Wassim/.ssh/known_hosts to get rid of this message.
Offending ED25519 key in /home/Wassim/.ssh/known_hosts:107
Host key for 172.17.81.44 has changed and you have requested strict checking.
Host key verification failed.
Wassim@linux:~$ 

My machine seem to have stored an older version of the Junos device fingerprint. I modified the fingerprint entry for the host with the new value:

Wassim@linux:~$ vim /home/Wassim/.ssh/known_hosts

Old value:

172.17.81.44 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPPZFFLO6dRGan/hhXDFOw1u8K9nl7pLWAkrXqzmGkiS

New value:

172.17.81.44 ssh-ed25519 SHA256:GTnu0CBp8RhQbEv3oT2h2lZgnfLI11/evFxjggC3t8Q

The error message disappeared:

Wassim@linux:~$ ssh [email protected]
The authenticity of host '172.17.81.44 (172.17.81.44)' can't be established.
ED25519 key fingerprint is SHA256:GTnu0CBp8RhQbEv3oT2h2lZgnfLI11/evFxjggC3t8Q.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? ^C
Wassim@linux:~$ 
Wassim@linux:~$ nc -v 172.17.81.44 22
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Connected to 172.17.81.44:22.
SSH-2.0-JSSH_4.1
^C
Wassim@linux:~$

When I create Python scripts, I apply some rules, whenever possible:

  • give the owner full permissions,
  • give the group only read and execute permissions,
  • start the first line of the script with the Shebang.

((venv) ) Wassim@linux:~/private/Juniper/devopstrack$ ls -l mylab03-Setup.py 
-rw-r--r--. 1 Wassim Wassim 474 Aug 25 09:30 mylab03-Setup.py
((venv) ) Wassim@linux:~/private/Juniper/devopstrack$
((venv) ) Wassim@linux:~/private/Juniper/devopstrack$ chmod ug+x mylab03-Setup.py 
((venv) ) Wassim@linux:~/private/Juniper/devopstrack$ ls -l mylab03-Setup.py 
-rwxr-xr--. 1 Wassim Wassim 474 Aug 25 09:30 mylab03-Setup.py
((venv) ) Wassim@linux:~/private/Juniper/devopstrack$ 
#!/usr/bin/python3