I’ve implemented some new changes to pyMultiChange and netlib. The biggest change affects both netlib and pyMultiChange. In netlib, I ripped out both the ‘simple_creds’ and ‘simple_yaml’ methods, as both stored user credentials in plain text on the computer that you used them on.

Instead, utilizing the ‘keyring’ python module, I created a new class called ‘KeyRing’. Using ‘KeyRing’ is simple. It implements three methods. These methods are ‘get_creds’, ‘set_creds’, and ‘del_creds’.

‘get_creds’ will attempt to retrieve the credentials for a username, if the credentials exist. If they do not exist, ‘get_creds’ will automatically call the ‘set_creds’ method.

>>> from netlib.user_keyring import KeyRing
>>> creds = KeyRing(username='testuser')
>>> creds.get_creds()
No credentials keyring exist. Creating new credentials.
Enter your user password: 
Confirm your user password: 
Enter your enable password: 
Confirm your enable password: 
>>> print creds.get_creds()
{'username': 'testuser', 'enable': u'test', 'password': u'testpass'}
>>> 

‘set_creds’ does exactly what it sounds like. It allows you to set your credentials. If no user credentials exist, it creates a new keyring. However, if user credentials do exist, it over-writes the credentials.

>>> creds.set_creds()
Enter your user password: 
Confirm your user password: 
Enter your enable password: 
Confirm your enable password: 
>>> print creds.get_creds()
{'username': 'testuser', 'enable': u'enable123', 'password': u'test123'}

Finally, ‘del_creds’ deletes a user’s credentials from an existing keyring.

>>> creds.del_creds()
Enter your user password: 
Deleting keyring credentials for testuser

The ‘keyring‘ python library utilizes your operating systems native methods for storing passwords. For Example, in Mac OS X, it will utilize the KeyChain functionality, In Linux, it will use dbus, and in Microsoft Windows, it utilizes the Credential Vault. There are also methods available for you to create your own backend. Much more secure than the ~/.tacacslogin or ~/.tacacs.yml files that used to be created from the old methods.

As expected, this change was implemented into pyMultiChange. Doing so, required that new command line arguments needed to be implemented. There are actually several new command line arguments, since the last time that I wrote about pyMultiChange. I’ll go over them here.

usage: multi_change.py [-h] -u USERNAME [--delete-creds [DELETE_CREDS]]
                       [--set-creds [SET_CREDS]] [-d DEVICES] [-c COMMANDS]
                       [-s [SSH]] [-t [TELNET]] [-o [OUTPUT]] [-v [VERBOSE]]
                       [--delay DELAY] [--buffer BUFFER]
                       [--threaded [THREADED]] [-m MAXTHREADS]

Managing network devices with python

optional arguments:
  -h, --help            show this help message and exit
  -u USERNAME, --username USERNAME
                        Specify your username.
  --delete-creds [DELETE_CREDS]
                        Delete credentials from keyring.
  --set-creds [SET_CREDS]
                        set keyring credentials.
  -d DEVICES, --devices DEVICES
                        Specifies a host file
  -c COMMANDS, --commands COMMANDS
                        Specifies a commands file
  -s [SSH], --ssh [SSH]
                        Default: Use the SSH protocol
  -t [TELNET], --telnet [TELNET]
                        Use the Telnet protocol
  -o [OUTPUT], --output [OUTPUT]
                        Verbose command output
  -v [VERBOSE], --verbose [VERBOSE]
                        Debug script output
  --delay DELAY         Change the default delay exec between commands
  --buffer BUFFER       Change the default SSH output buffer
  --threaded [THREADED]
                        Enable process threading
  -m MAXTHREADS, --maxthreads MAXTHREADS
                        Define the maximum number of threads

The first, is that multi_change.py now requires that you specify a username for all actions. This allows multi_change.py to interact with the keyring to set, extract, and delete credentials.

usage: multi_change.py [-h] -u USERNAME [--delete-creds [DELETE_CREDS]]
                       [--set-creds [SET_CREDS]] [-d DEVICES] [-c COMMANDS]
                       [-s [SSH]] [-t [TELNET]] [-o [OUTPUT]] [-v [VERBOSE]]
                       [--delay DELAY] [--buffer BUFFER]
                       [--threaded [THREADED]] [-m MAXTHREADS]
multi_change.py: error: argument -u/--username is required

When you specify a username, multi_change.py immediately attempts to extract the credentials for the user. If they don’t exist, multi_change.py will prompt you to set the credentials.

$ multi_change.py -u testuser
No credentials keyring exist. Creating new credentials.
Enter your user password: 
Confirm your user password: 
Enter your enable password: 
Confirm your enable password:

You can also utilize the ‘–set-creds’ command line argument to either set credentials for a new user or over-write the credentials for an existing user.

$ multi_change.py -u testuser --set-creds
Enter your user password: 
Confirm your user password: 
Enter your enable password: 
Confirm your enable password: 

Like wise, you can use the ‘–delete-creds’ to delete existing creds.

$ multi_change.py -u testuser --delete-creds
Enter your user password: 
Deleting keyring credentials for testuser

Beyond that, the option to utilize threading was created. With the threading ability, you also get the ability to specify the number of threads and the delay factor between command execution.

For example, the below series of command line arguments enable threading, utilizing 50 threads, create a delay factor of 5 seconds, and will display the command output.

$ multi_change.py -u testuser -d hosts.txt -c commands.txt --threaded -m 50 --delay 5 -o

This is very handy for running a common command set across a large number of devices very quickly.

Beyond that, there are a few under the hood enhancements.

  1. Protocol failover will no longer happen. Meaning that if a device fails to login via SSH, it will no longer failover to attempt to login via telnet and vise versa.
  2. Login failures are logged in a file called ‘failure.log’. This file is created in the local folder that you’re running ‘multi_change.py’ in.
  3. multi_change.py will now only read the commands file once, rather than reading it for every device that it attempts to make changes on.
  4. pyMultiChange and netlib are now python installable packages. Meaning that you can run their ‘setup.py’ files and they will be installed as native python packages, allowing them to be called from anywhere on the OS.

Both packages are available on github.com:

Share on FacebookTweet about this on TwitterShare on LinkedInShare on RedditEmail this to someone

In the previous blog, I kicked the tires on the ios_command and ios_config Ansible modules. I still had my development environment set up from then, so I decided that I wanted to kick the tires on the ios_template module.

The online documentation currently has several errors, with the module documentation in the same state, which is undesirable. However, after some experimentation, I feel that I can adequately describe what the module does.

You feed the module a candidate configuration for a device, the module will then reach out to the device, pull its current running configuration, compare the running configuration to the candidate configuration, determine what configuration needs to be added to the device based upon the comparison, then add the configuration to the device.

I’ve noted two caveats with this module. First, it will not negate any commands, so if you update your configuration template to remove a swath of configuration, the module will not make those changes. Second, the module isn’t intelligent enough to determine the risk associated with a command, thus it’s unable to take preemptive actions, such as bleeding traffic from a link or device.

With that in mind, let’s get to the playbook.

Here is what my playbook looks like:

---
- hosts: ios
  gather_facts: no
  connection: local

  tasks:
  - name: OBTAIN LOGIN CREDENTIALS
    include_vars: secrets.yaml

  - name: DEFINE PROVIDER
    set_fact:
      provider:
        host: "{{ inventory_hostname }}"
        username: "{{ creds['username'] }}"
        password: "{{ creds['password'] }}"
        auth_pass: "{{ creds['auth_pass'] }}"

  - name: TEST IOS_TEMPLATE 
    ios_template:
      provider: "{{ provider }}"
      authorize: true
      backup: true
      src: "{{ inventory_hostname }}.candidate_config.txt"
    register: template

  - debug: var=template

In this playbook, the src file – “{{ inventory_hostname }}.candidate_config.txt“, which are referenced, contain the running configuration of each of my devices in inventory, with the exception that I’ve added an access-list called TEST. Given what we know of the module, it will compare the running config to the candidate config, determine that the access-list called TEST is missing from the running config, and attempt to add the access-list to the device.

You can see below that there is an access-list at the tail end of both of the configurations.

(env)jtdub-macbook:ansible2.0 jtdub$ tail edge1.candidate_config.txt 
!
ntp source Vlan2
ntp access-group peer NAT kod
ntp master 3
ntp update-calendar
ntp server 17.151.16.21
ntp server 17.151.16.34 prefer
ip access-list standard TEST
 permit host 1.1.1.1
end
(env)jtdub-macbook:ansible2.0 jtdub$ tail aggr1a.candidate_config.txt 
 exec-timeout 15 0
 login authentication auth_local
 transport input ssh
line vty 5 15
!
ntp source Vlan2
ntp server 172.16.1.1 prefer
ip access-list standard TEST
 permit host 1.1.1.1
end

Now, if we run the playbook, you can see the results.

(env)jtdub-macbook:ansible2.0 jtdub$ ansible-playbook -i hosts ios_template.yaml 

PLAY [ios] *********************************************************************

TASK [OBTAIN LOGIN CREDENTIALS] ************************************************
ok: [edge1]
ok: [aggr1a]

TASK [DEFINE PROVIDER] *********************************************************
ok: [aggr1a]
ok: [edge1]

TASK [TEST IOS_TEMPLATE] *******************************************************
changed: [aggr1a]
changed: [edge1]

TASK [debug] *******************************************************************
ok: [edge1] => {
    "template": {
        "changed": true, 
        "responses": [
            "", 
            "", 
            ""
        ], 
        "updates": [
            "clock summer-time CST recurring", 
            "ip access-list standard TEST", 
            "permit host 1.1.1.1"
        ]
    }
}
ok: [aggr1a] => {
    "template": {
        "changed": true, 
        "responses": [
            "", 
            ""
        ], 
        "updates": [
            "ip access-list standard TEST", 
            "permit host 1.1.1.1"
        ]
    }
}

PLAY RECAP *********************************************************************
aggr1a                     : ok=4    changed=1    unreachable=0    failed=0   
edge1                      : ok=4    changed=1    unreachable=0    failed=0   

(env)jtdub-macbook:ansible2.0 jtdub$ 

That is definitely cool! Though, it’s also definitely lacking from a configuration intent perspective. I’m sure that it will improve with time.

Some colleagues and I have been attempting to solve this configuration intent problem. It’s a difficult problem to solve for, but we have a working code base. Soon, I’ll try to write about configuration intent and how we are approaching the problem.

Share on FacebookTweet about this on TwitterShare on LinkedInShare on RedditEmail this to someone