In this recipe, we will outline how to connect to Cisco IOS devices from Ansible via SSH in order to start managing devices from Ansible.
Connecting to Cisco IOS devices
Getting ready
In order to follow along with this recipe, an Ansible inventory file should be constructed as per the previous recipe. IP reachability between the Ansible control machine and all the devices in the network must be configured.
How to do it...
- Inside the ch2_ios directory, create the groups_vars folder.
- Inside the group_vars folder, create the network.yml file with the following content:
$cat network.yml
Ansible_network_os: ios
Ansible_connection: network_cli
Ansible_user: lab
Ansible_password: lab123
Ansible_become: yes
Ansible_become_password: admin123
Ansible_become_method: enable
- On all IOS devices, ensure that the following is configured to set up SSH access:
!
hostname <device_hostname>
!
ip domain name <domain_name>
!
username lab secret 5 <password_for_lab_user>.
!
enable secret 5 <enable_password>.
!
line vty 0 4
login local
transport input SSH
!
- Generate SSH keys on the Cisco IOS devices from the config mode, as shown in the following code snippet:
(config)#crypto key generate rsa
Choose the size of the key modulus in the range of 360 to 4096 for your
General Purpose Keys. Choosing a key modulus greater than 512 may take
a few minutes.
How many bits in the modulus [512]: 2048
% Generating 2048 bit RSA keys, keys will be non-exportable...
[OK] (elapsed time was 0 seconds)
- Update the Ansible.cfg file with the following highlighted parameters:
$ cat Ansible.cfg
[defaults]
host_key_checking=False
How it works...
In our sample network, we will use SSH to set up the connection between Ansible and our Cisco devices. In this setup, Ansible will use SSH in order to establish the connection to our Cisco devices with a view to start managing it. We will use username/password authentication in order to authenticate our Ansible control node with our Cisco devices.
On the Cisco devices, we must ensure that SSH keys are present in order to have a functional SSH server on the Cisco devices. The following code snippet outlines the status of the SSH server on the Cisco device prior to generating the SSH keys:
wan01#show ip SSH
SSH Disabled - version 2.0
%Please create RSA keys to enable SSH (and of atleast 768 bits for SSH v2).
Authentication methods:publickey,keyboard-interactive,password
Authentication Publickey Algorithms:x509v3-SSH-rsa,SSH-rsa
Hostkey Algorithms:x509v3-SSH-rsa,SSH-rsa
Encryption Algorithms:aes128-ctr,aes192-ctr,aes256-ctr
MAC Algorithms:hmac-sha2-256,hmac-sha2-512,hmac-sha1,hmac-sha1-96
KEX Algorithms:diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1
Authentication timeout: 120 secs; Authentication retries: 3
Minimum expected Diffie Hellman key size : 2048 bits
IOS Keys in SECSH format(SSH-rsa, base64 encoded): NONE
Once we create the SSH keys, the SSH server on the Cisco device is operational, and is ready to accept an SSH connection from the Ansible control node.
On the Ansible machine, we include all the variables required to establish the SSH connection to the managed devices in the network.yml file. As per our inventory file, the network group includes all the devices within our topology, and so all the attributes that we configure in this file will apply to all the devices in our inventory. The following is a breakdown of the attributes that we included in the file:
- Ansible_connection: This establishes how Ansible connects to the device. In this scenario, we set it to network_cli to indicate that we will use SSH to connect to a network device.
- Ansible_network_os: When using network_cli as the connection plugin to connect to the network device, we must indicate which network OS Ansible will be connecting to, so as to use the correct SSH parameters with the devices. In this scenario, we will set it to ios, since all the devices in our topology are IOS-based devices.
- Ansible_user: This parameter specifies the username that Ansible will use to establish the SSH session with the network device.
- Ansible_password: This parameter specifies the password that Ansible will use to establish the SSH session with the network device.
- Ansible_become: This instructs Ansible to use the enable command to enter privileged mode when configuring or executing show commands on the managed device. We set this to yes in our context, since we will require privileged mode to configure the devices.
- Ansible_become_password: This specifies the enable password to use in order to enter privileged mode on the managed IOS device.
- Ansible_become_method: This option specifies the method to use in order to enter privileged mode. In our scenario, this is the enable command on IOS devices.
On the Cisco devices, we set up the required username and password so that Ansible can open an SSH connection to the managed Cisco IOS devices. We also configure an enable password to be able to enter privileged mode, and to make configuration changes. Once we apply all of these configurations to the devices, we are ready to set up Ansible.
In any SSH connection, when an SSH client (Ansible control node in our case) connects to an SSH server (Cisco devices in our case), the server sends a copy of its public key to the client before the client logs in. This is used to establish the secure channel between the client and the server, and to authenticate the server to the client in order to prevent any man-in-the-middle attacks. So, at the start of a new SSH session involving a new device, we see the following prompt:
$SSH [email protected]
The authenticity of host '172.20.1.18 (172.20.1.18)' can't be established.
RSA key fingerprint is SHA256:KnWOalnENZfPokYYdIG3Ogm9HDnXIwjh/it3cqdiRRQ.
RSA key fingerprint is MD5:af:18:4b:4e:84:19:a6:8d:82:17:51:d5:ee:eb:16:8d.
Are you sure you want to continue connecting (yes/no)?
When the SSH client initiates the SSH connection to the client, the SSH server sends its public key to the client in order to authenticate itself to the client. The client searches for the public key in its local known hosts files (in the ~/.SSH/known_hosts or /etc/SSH/SSH_known_hosts files). In the event that it does not find the public key for this machine in its local known hosts file, it will prompt the user to add this new key to its local database, and this is the prompt that we see when we initiate the SSH connection.
In order to simplify the SSH connection setup between the Ansible control node and its remotely managed hosts, we can disable this host checking. We can do this by telling Ansible to ignore host keys and not to add them to the known hosts files by setting host_key_checking to False in the Ansible.cfg configuration file.
There's more...
If we need to verify the identity of the SSH hosts that we will connect to, and thereby enable host_key_checking, we can automate the addition of the SSH public key of the remote managed hosts to the ~/.SSH/known_hosts file using Ansible. We create a new Ansible playbook that will run on the Ansible control machine to connect to the remote devices using the ssk-keyscan command. We then collect the SSH public keys for the remote machines and add them to the ~/.SSH/known_hosts file. The method is outlined here:
- Create a new playbook pb_gather_SSH_keys.yml file and add the following play:
- name: "Play2: Record Keys in Known Hosts file"
hosts: localhost
vars:
- hosts_file: "~/.SSH/known_hosts"
tasks:
- name: create know hosts file
file:
path: "{{ hosts_file }}"
state: file
changed_when: false
- Update the playbook and add another play within the same playbook to save and store the SSH public keys for the remote managed nodes:
- name: "Play2: Record Keys in Known Hosts file"
hosts: localhost
vars:
- hosts_file: "~/.SSH/known_hosts"
tasks:
- name: create know hosts file
file:
path: "{{ hosts_file }}"
state: file
changed_when: false
- name: Populate the known_hosts file
blockinfile:
block: |
{% for host in groups['all'] if hostvars[host].SSH_keys.stdout != ''
%}
{{ hostvars[host].SSH_keys.stdout}}
{% endfor %}
path: "{{ hosts_file }}"
create: yes
In our new playbook, we have a play that targets all our managed devices by setting the hosts parameter to all. In this play, we have a single task, which we run on the Ansible control node (using the delegate_to localhost) to issue the SSH-keyscan command, which returns the SSH public key for the remote device, as shown in the following code:
$ SSH-keyscan 172.20.1.22
# 172.20.1.22:22 SSH-2.0-Cisco-1.25
172.20.1.22 SSH-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTwrH4phzRnW/RsC8eXMh/accIErRfkgffDWBGSdEX0r9EwAa6p2uFMWj8dq6kvrREuhqpgFyMoWmpgdx5Cr+10kEonr8So5yHhOhqG1SJO9RyzAb93H0P0ro5DXFK8A/Ww+m++avyZ9dShuWGxKj9CDM6dxFLg9ZU/9vlzkwtyKF/+mdWNGoSiCbcBg7LrOgZ7Id7oxnhEhkrVIa+IxxGa5Pwc73eR45Uf7QyYZXPC0RTOm6aH2f9+8oj+vQMsAzXmeudpRgAu151qUH3nEG9HIgUxwhvmi4MaTC+psmsGg2x26PKTOeX9eLs4RHquVS3nySwv4arqVzDqWf6aruJ
We run the second play on the Ansible control host by setting hosts to localhost, and we execute tasks to create the known hosts file (if not already present) and to populate this file with the data that we captured in the first play using the SSH_keys variable. We run this playbook on the Ansible control machine to store the SSH keys from the remotely managed nodes prior to running any of our playbooks.