<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"><channel><title>LINICKX.com</title><link>https://www.linickx.com/</link><description></description><lastBuildDate>Wed, 30 Aug 2017 19:50:00 +0100</lastBuildDate><item><title>Automated Cisco WLC backup with a Python Script</title><link>https://www.linickx.com/automated-cisco-wlc-backup-with-a-python-script</link><description>&lt;p&gt;As part of my ISE work I generally make changes to WLCs, I'm not really a Wi-Fi Guy so don't ask me about channel width but I do a lot with authentication and in ISE's case that usually includes an ACL or two on the WLC!&lt;/p&gt;
&lt;p&gt;There are a number of options for &lt;a href="http://www.google.co.uk/search?q=cisco+wlc+backup+config"&gt;backing up a WLC&lt;/a&gt; but they're all a bit &lt;em&gt;clunky&lt;/em&gt; if you have say 10 of the blighters to backup!!&lt;/p&gt;
&lt;p&gt;Using python (and the &lt;a href="https://github.com/ktbyers/netmiko"&gt;Netmiko&lt;/a&gt; module), I've written &lt;a href="(/files/2017/08/backup_wlc_py.txt)"&gt;a python script to do the job&lt;/a&gt;. If you're a windows user, I recommend you look at &lt;a href="https://msdn.microsoft.com/commandline/wsl/about"&gt;MicroSoft's Subsystem for linux&lt;/a&gt; as setting up python &amp;amp; netmiko is very straight forward... i.e. the same as linux!&lt;/p&gt;
&lt;p&gt;Here's what it looks like when you run it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;linickx:~ $ python3 backup_wlc.py
Username: admin
Password:
[INFO] 2017-08-28 16:51:11,065 Connecting to 10.10.10.9
[INFO] 2017-08-28 16:51:16,502 Connected (version 2.0, client CISCO_WLC)
[INFO] 2017-08-28 16:51:17,620 Authentication (password) successful!
[INFO] 2017-08-28 16:51:24,366 Working on CHWC01
[INFO] 2017-08-28 16:51:24,367 Filename: CHWC01_170828-165124.txt
[INFO] 2017-08-28 16:51:24,367 Sending cmd: show run-config commands
[INFO] 2017-08-28 16:51:25,773 Connecting to 10.10.11.10
[INFO] 2017-08-28 16:51:31,205 Connected (version 2.0, client CISCO_WLC)
[INFO] 2017-08-28 16:51:32,322 Authentication (password) successful!
[INFO] 2017-08-28 16:51:39,124 Working on CHWC02
[INFO] 2017-08-28 16:51:39,125 Filename: CHWC02_170828-165139.txt
[INFO] 2017-08-28 16:51:39,125 Sending cmd: show run-config commands
[INFO] 2017-08-28 16:51:40,530 Connecting to 10.11.18.5
[INFO] 2017-08-28 16:51:45,647 Connected (version 2.0, client CISCO_WLC)
[INFO] 2017-08-28 16:51:45,998 Authentication (password) successful!
[INFO] 2017-08-28 16:51:51,782 Working on DEWC01
[INFO] 2017-08-28 16:51:51,783 Filename: DEWC01_170828-165151.txt
Finished:
 CHWC01_170828-165124.txt
 CHWC02_170828-165139.txt
 DEWC01_170828-165151.txt
linickx:~ $ 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each WLC gets it's own text file, time/date stamped, of "show run-config commands" which you can use to compare before and after changes... potentially useful for nightly backups if you want to use cron.&lt;/p&gt;
&lt;p&gt;The &lt;a href="/files/2017/08/backup_wlc_py.txt"&gt;script can be downloaded here&lt;/a&gt; (rename to &lt;code&gt;.py&lt;/code&gt;), I've pasted it below for your reading pleasure &lt;code&gt;;-)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Please take note of the comments at the top, the WLC IP addresses are stored in the script, you'll need to update and change as appropriate. By default the script prompts you for credentials which works for runs manually but if used with cron you'll need insecure hardcoded variables (&lt;em&gt;use and secure wisely!&lt;/em&gt;)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;&amp;quot;&amp;quot;
    Script to backup Cisco WLCs using netmiko

    # Version 1.0 28.Aug.2017
    # Nick Bettison - Linickx.com

    # External Credit...
    # https://github.com/AlexMunoz905/Cisco-Backup-Config/blob/master/WLC.py
    # Thanks to ^that^ for getting me started! :)
&amp;quot;&amp;quot;&amp;quot;
import logging
import getpass
import re
import datetime
import sys

try:
    import netmiko
except:
    print(&amp;quot;Error Netmiko not installed - https://github.com/ktbyers/netmiko&amp;quot;)
    sys.exit()

&amp;quot;&amp;quot;&amp;quot;
    This array stores a list if IPS to connect to
    eg.
        one device: `ips = [&amp;quot;10.1.2.12&amp;quot;]`
        two devices: `ips = [&amp;quot;10.1.2.12&amp;quot;, &amp;quot;10.1.3.13&amp;quot;]
`
&amp;quot;&amp;quot;&amp;quot;
ips = [&amp;quot;10.1.2.12&amp;quot;, &amp;quot;10.1.3.13&amp;quot;]

&amp;quot;&amp;quot;&amp;quot;
    By default, ths scipt prompts for credentials.
    If you're feeling insecure and want to store them in the file, replace below with...
    um = &amp;quot;admin&amp;quot;
    pw = &amp;quot;insecure_password&amp;quot;
&amp;quot;&amp;quot;&amp;quot;
un = input('Username: ')
pw = getpass.getpass()

&amp;quot;&amp;quot;&amp;quot;
    No Need to change below here!
&amp;quot;&amp;quot;&amp;quot;
logging.basicConfig(format='[%(levelname)s] %(asctime)s %(message)s', level=logging.INFO)
logger = logging.getLogger(&amp;quot;wlc_backup&amp;quot;)
devices = [] # Empty array to store wlcs
files = [] # Empty array to store filenames

for ip in ips:
    # device definition
    cisco_wlc = {
        'device_type': 'cisco_wlc',
        'ip': ip,
        'username': un,
        'password': pw,
    }
    devices.append(cisco_wlc)

for device in devices:
    logger.info(&amp;quot;Connecting to %s&amp;quot;, device['ip'])
    # connect to the device w/ netmiko
    try:
        net_connect = netmiko.ConnectHandler(**device)
    except:
        logger.error(&amp;quot;Failed to connect to %s&amp;quot;, device['ip'])
        logger.debug(&amp;quot;Exception: %s&amp;quot;, sys.exc_info()[0])
        continue

    # get the prompt as a string
    prompt = net_connect.find_prompt()

    logger.debug(&amp;quot;prompt: %s&amp;quot;, prompt)

    regex = r'^\((.*)\)[\s]&amp;gt;'

    regmatch = re.match(regex, prompt)
    if regmatch:
        hostname = regmatch.group(1)
        logger.info(&amp;quot;Working on %s&amp;quot;, hostname)
    else:
        logger.error(&amp;quot;Hostname Not Found!&amp;quot;)
        logger.debug(regmatch)

    filetime = datetime.datetime.now().strftime(&amp;quot;%y%m%d-%H%M%S&amp;quot;) # File timestamp
    config_filename = hostname + &amp;quot;_&amp;quot; + filetime + &amp;quot;.txt&amp;quot; # Filname with hostname
    files.append(config_filename)
    logger.info(&amp;quot;Filename: %s&amp;quot;, config_filename)

    commands = ['show run-config commands'] # commands to run

    for cmd in commands:
        logger.info(&amp;quot;Sending cmd: %s&amp;quot;, cmd)
        this_cmd = net_connect.send_command(cmd)
        config_filename_f = open(config_filename, 'a')
        config_filename_f.write(this_cmd)
        config_filename_f.write('\n')
        config_filename_f.close()

print(&amp;quot;Finished:&amp;quot;)
for fname in files:
    print(&amp;quot; %s &amp;quot; % fname)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enjoy your backups!&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Nick Bettison</dc:creator><pubDate>Wed, 30 Aug 2017 19:50:00 +0100</pubDate><guid isPermaLink="false">tag:www.linickx.com,2017-08-30:automated-cisco-wlc-backup-with-a-python-script</guid><category>Cisco</category><category>ISE</category><category>WLC</category><category>Python</category></item><item><title>snmpwalk v3 and snmpget v3 examples</title><link>https://www.linickx.com/snmpwalk-v3-and-snmpget-v3-examples</link><description>&lt;p&gt;I always forget the syntax for snmpwalk/snmpget v3; so posting here to remember.&lt;/p&gt;
&lt;h3&gt;snmpwalk version 3&lt;/h3&gt;
&lt;p&gt;The command is: &lt;code&gt;snmpwalk -v3  -l authPriv -u snmp-poller -a SHA -A "PASSWORD1"  -x AES -X "PASSWORD1" 10.10.60.50&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Example output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[nick@server ~]$  snmpwalk -v3  -l authPriv -u snmp-poller -a SHA -A &amp;quot;PASSWORD1&amp;quot;  -x AES -X &amp;quot;PASSWORD1&amp;quot; 10.10.60.50
SNMPv2-MIB::sysDescr.0 = STRING: Cisco Adaptive Security Appliance Version 9.6(2)11
SNMPv2-MIB::sysObjectID.0 = OID: SNMPv2-SMI::enterprises.9.1.1199
DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (201155400) 23 days, 6:45:54.00
SNMPv2-MIB::sysContact.0 = STRING:
SNMPv2-MIB::sysName.0 = STRING: fw01.local
SNMPv2-MIB::sysLocation.0 = STRING:
SNMPv2-MIB::sysServices.0 = INTEGER: 4
IF-MIB::ifNumber.0 = INTEGER: 1
IF-MIB::ifIndex.1 = INTEGER: 1
IF-MIB::ifDescr.1 = STRING: Adaptive Security Appliance 'v101' interface
IF-MIB::ifType.1 = INTEGER: ethernetCsmacd(6)
IF-MIB::ifMtu.1 = INTEGER: 1500
IF-MIB::ifSpeed.1 = Gauge32: 1000000000
IF-MIB::ifPhysAddress.1 = STRING: aa:11:22:33:44:55
IF-MIB::ifAdminStatus.1 = INTEGER: up(1)
IF-MIB::ifOperStatus.1 = INTEGER: up(1)
IF-MIB::ifLastChange.1 = Timeticks: (6600) 0:01:06.00
IF-MIB::ifInOctets.1 = Counter32: 56388261
IF-MIB::ifInUcastPkts.1 = Counter32: 316701
...
[nick@server ~]$ 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;snmpget version 3&lt;/h3&gt;
&lt;p&gt;A command for just getting the hostname: &lt;code&gt;snmpget -v3  -l authPriv -u snmp-poller -a SHA -A "PASSWORD1"  -x AES -X "PASSWORD1" 10.10.60.50 sysName.0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Example output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[nick@server ~]$ snmpget -v3  -l authPriv -u snmp-poller -a SHA -A &amp;quot;PASSWORD1&amp;quot;  -x AES -X &amp;quot;PASSWORD1&amp;quot; 10.10.60.50 sysName.0
SNMPv2-MIB::sysName.0 = STRING: fw01.local
[nick@server ~]$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A command for getting the hostname and the uptime: &lt;code&gt;snmpget -v3  -l authPriv -u snmp-poller -a SHA -A "PASSWORD1"  -x AES -X "PASSWORD1" 10.10.60.50 sysName.0 system.sysUpTime.0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Example output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[nick@server ~]$ snmpget -v3  -l authPriv -u snmp-poller -a SHA -A &amp;quot;PASSWORD1&amp;quot;  -x AES -X &amp;quot;PASSWORD1&amp;quot; 10.10.60.50 sysName.0 system.sysUpTime.0
SNMPv2-MIB::sysName.0 = STRING: fw01.local
DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (14100) 0:02:21.00
[nick@server ~]$
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;These tests are on a Cisco ASA.&lt;/h3&gt;
&lt;p&gt;This is the ASA snmp v3 config used:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;snmp-server group the-noc v3 priv
snmp-server user snmp-poller the-noc v3 auth sha PASSWORD1 priv aes 128 PASSWORD1
snmp-server host v101 10.10.62.100 version 3 snmp-poller
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I've used the same password for authentication &amp;amp; encryption to make it easy. The username is "snmp-poller", the source of my polling is "10.10.61.100", the group "the-noc" is for if you have more than one user account.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Nick Bettison</dc:creator><pubDate>Sat, 01 Apr 2017 15:22:00 +0100</pubDate><guid isPermaLink="false">tag:www.linickx.com,2017-04-01:snmpwalk-v3-and-snmpget-v3-examples</guid><category>Cisco</category><category>Security</category><category>Linux</category><category>SNMP</category><category>snmpget</category><category>snmpwalk</category></item><item><title>Ansible Cisco - Primer 2 - Making Changes</title><link>https://www.linickx.com/ansible-cisco-primer-2-making-changes</link><description>&lt;p&gt;This is my second post, documenting some Ansible/Cisco examples, please check out my &lt;a href="/ansible-cisco-primer-1-hello-world"&gt;Ansible Cisco Primer 1&lt;/a&gt; first; in this example we'll make some changes to devices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt;; Some example files are on github: &lt;a href="https://github.com/linickx/ansible-cisco"&gt;https://github.com/linickx/ansible-cisco&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;NTP &amp;amp; DNS, an easy example&lt;/h3&gt;
&lt;p&gt;This is a play to set NTP &amp;amp; DNS servers, it's a simple "&lt;em&gt;quick win&lt;/em&gt;" because the syntax is straight forward and typically in enterprises the settings are the same on all devices... well DNS might not be, but let's pretend for a second that it is :)&lt;/p&gt;
&lt;p&gt;Here's the play book file: &lt;code&gt;set_ntp_enable.yml&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
- hosts: ios_devices*
  gather_facts: no
  connection: local
  strategy: debug

  tasks:
  - name: Include Login Credentials
    include_vars: secrets.yml

  - name: Define Provider
    set_fact:
      provider:
        host: &amp;quot;{{ ansible_host }}&amp;quot;
        username: &amp;quot;{{ creds['username'] }}&amp;quot;
        password: &amp;quot;{{ creds['password'] }}&amp;quot;

  - name: Define Provider (Enable)
    when: inventory_hostname in groups.ios_devices_enable
    set_fact:
        enable:
            auth_pass: &amp;quot;{{ creds['auth_pass'] }}&amp;quot;
            authorize: yes

  - name: Update Provider (Enable)
    when: inventory_hostname in groups.ios_devices_enable
    set_fact:
        provider: &amp;quot;{{ provider | combine(enable) }}&amp;quot;

  - name: BACKUP
    ios_config:
      provider: &amp;quot;{{ provider }}&amp;quot;
      backup: yes

  - name: RUN 'Set DNS'
    ios_config:
      provider: &amp;quot;{{ provider }}&amp;quot;
      lines:
        - ip name-server 8.8.8.8
        - ip name-server 8.8.4.4
    register: set_dns

  - name: CHECK CHANGE - dns
    when: &amp;quot;(set_dns.changed == true)&amp;quot;
    set_fact: configured=true

  - name: RUN 'Set NTP'
    ios_config:
      provider: &amp;quot;{{ provider }}&amp;quot;
      lines:
        - ntp server uk.pool.ntp.org
        - ntp server time.apple.com
    register: set_ntp

  - name: CHECK CHANGE - ntp
    when: &amp;quot;(set_ntp.changed == true)&amp;quot;
    set_fact: configured=true

  - name: RUN 'wr mem'
    when: &amp;quot;(configured is defined) and (configured == true)&amp;quot;
    register: save_config
    ios_command:
      provider: &amp;quot;{{ provider }}&amp;quot;
      commands:
        - &amp;quot;write memory&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="/ansible-cisco-primer-1-hello-world"&gt;As before&lt;/a&gt;,  I've used a wildcard host group to match both &lt;code&gt;[ios_devices]&lt;/code&gt; and &lt;code&gt;[ios_devices_enable]&lt;/code&gt;; in my lab I have two types of router, r1 &amp;amp; r2 which drop you directly into Priv15 and r3 which requires you to type &lt;code&gt;enable&lt;/code&gt; before being able to make any changes, I did this because this matches the real world, some places I visit force admins to type &lt;em&gt;enable&lt;/em&gt; others don't.&lt;/p&gt;
&lt;p&gt;The router configs can be found here: &lt;a href="https://github.com/linickx/ansible-cisco/tree/master/lab_rtr_configs"&gt;github.com/linickx/ansible-cisco/lab_rtr_configs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The first task imports &lt;code&gt;secrets.yml&lt;/code&gt;, this is less secure than my &lt;code&gt;show_clock_prompt.yml&lt;/code&gt; example but shows something significant, the &lt;em&gt;file read&lt;/em&gt; is part of a task, not before them, that's because &lt;a href="http://docs.ansible.com/ansible/include_vars_module.html"&gt;include_vars is a task module&lt;/a&gt;; once this is read the 2nd task is to define the provider for all devices. &lt;/p&gt;
&lt;h3&gt;Task 3 is different - Define Provider (Enable)&lt;/h3&gt;
&lt;p&gt;The third task, &lt;code&gt;Define Provider (Enable)&lt;/code&gt; is our first example of limiting a task to a specific group: &lt;code&gt;when: inventory_hostname in groups.ios_devices_enable&lt;/code&gt; I think the syntax is quite self explanatory, as such we're only expecting Tasks 3 &amp;amp; 4 to run on r3. &lt;/p&gt;
&lt;h3&gt;Backup!&lt;/h3&gt;
&lt;p&gt;The backup task uses the &lt;a href="https://docs.ansible.com/ansible/ios_config_module.html"&gt;ios_config&lt;/a&gt;'s built in backup feature. By default a local &lt;code&gt;./backup/&lt;/code&gt; directory will be created with a config per device saved... &lt;em&gt;BE WARNED&lt;/em&gt; ... the backups are overwritten (replaced) each time you run &lt;code&gt;ansible-playbook&lt;/code&gt; so if you need per change copies move the files first before running the playbook again.&lt;/p&gt;
&lt;h3&gt;Onto making changes&lt;/h3&gt;
&lt;p&gt;In the playbook, there are two sets of similar tasks, this is the DNS pair that is repeated for NTP.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: RUN 'Set DNS'
  ios_config:
    provider: &amp;quot;{{ provider }}&amp;quot;
    lines:
      - ip name-server 8.8.8.8
      - ip name-server 8.8.4.4
  register: set_dns

- name: CHECK CHANGE - dns
  when: &amp;quot;(set_dns.changed == true)&amp;quot;
  set_fact: configured=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some things to know here. Ansible will add 8.8.8.8/8.8.4.4 to any device that doesn't have them, it will also skip any deivice that has them already, ansible will not remove any erroneous entries.&lt;br /&gt;
&lt;code&gt;register: set_dns&lt;/code&gt; creates a variable for the task, in the previous &lt;em&gt;show clock&lt;/em&gt; example &lt;code&gt;ios_command&lt;/code&gt; created in an output, with &lt;code&gt;ios_config&lt;/code&gt; we have a &lt;code&gt;changed&lt;/code&gt; property; if the device is skipped then change = false, if a change is made it is set to True.&lt;/p&gt;
&lt;p&gt;Typically, if we've made a change, one would want to &lt;code&gt;wr mem&lt;/code&gt;, but that's slow; instead of writing the config after every task, I create a new fact called &lt;code&gt;configured&lt;/code&gt;, then later after all the tasks are complete I have another task that says "if we made a change, then write", like this....&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: RUN 'wr mem'
  when: &amp;quot;(configured is defined) and (configured == true)&amp;quot;
  register: save_config
  ios_command:
    provider: &amp;quot;{{ provider }}&amp;quot;
    commands:
      - &amp;quot;write memory&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's an example of running the play.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;linickx:ansible $ ansible-playbook set_ntp_enable.yml

PLAY [ios_devices*] ************************************************************

TASK [Include Login Credentials] ***********************************************
ok: [r1]
ok: [r2]
ok: [r3]

TASK [Define Provider] *********************************************************
ok: [r1]
ok: [r2]
ok: [r3]

TASK [Define Provider (Enable)] ************************************************
skipping: [r1]
ok: [r3]
skipping: [r2]

TASK [Update Provider (Enable)] ************************************************
skipping: [r1]
skipping: [r2]
ok: [r3]

TASK [BACKUP] ******************************************************************
ok: [r3]
ok: [r2]
ok: [r1]

TASK [RUN 'Set DNS'] ***********************************************************
ok: [r2]
ok: [r3]
changed: [r1]

TASK [CHECK CHANGE - dns] ******************************************************
ok: [r1]
skipping: [r2]
skipping: [r3]

TASK [RUN 'Set NTP'] ***********************************************************
ok: [r2]
ok: [r3]
changed: [r1]

TASK [CHECK CHANGE - ntp] ******************************************************
skipping: [r2]
ok: [r1]
skipping: [r3]

TASK [RUN 'wr mem'] ************************************************************
skipping: [r2]
skipping: [r3]
ok: [r1]

PLAY RECAP *********************************************************************
r1                         : ok=8    changed=2    unreachable=0    failed=0
r2                         : ok=5    changed=0    unreachable=0    failed=0
r3                         : ok=7    changed=0    unreachable=0    failed=0

linickx:ansible $
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice how only &lt;code&gt;r1&lt;/code&gt; was changed?&lt;/p&gt;
&lt;h3&gt;So, how do I standardise NTP and DNS?&lt;/h3&gt;
&lt;p&gt;I mentioned that erroneous or incorrect entries are not removed; to fix that we move our lines of config into variables. With our variables we then have two tasks... add the config we need, remove anything that's not defined.&lt;/p&gt;
&lt;p&gt;Here's the playbook:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
- hosts: ios_devices*
  gather_facts: no
  connection: local
  strategy: debug
  #check_mode: yes

  vars:
    dns_servers:
      - ip name-server 8.8.8.8
      - ip name-server 8.8.4.4
    ntp_servers:
      - ntp server uk.pool.ntp.org
      - ntp server time.apple.com

  tasks:
  - name: Include Login Credentials
    include_vars: secrets.yml

  - name: Define Provider
    set_fact:
      provider:
        host: &amp;quot;{{ ansible_host }}&amp;quot;
        username: &amp;quot;{{ creds['username'] }}&amp;quot;
        password: &amp;quot;{{ creds['password'] }}&amp;quot;

  - name: Define Provider (Enable)
    when: inventory_hostname in groups.ios_devices_enable
    set_fact:
        enable:
            auth_pass: &amp;quot;{{ creds['auth_pass'] }}&amp;quot;
            authorize: yes

  - name: Update Provider (Enable)
    when: inventory_hostname in groups.ios_devices_enable
    set_fact:
        provider: &amp;quot;{{ provider | combine(enable) }}&amp;quot;

  - name: BACKUP
    ios_config:
      provider: &amp;quot;{{ provider }}&amp;quot;
      backup: yes

  - name: &amp;quot;GET CONFIG&amp;quot;
    ios_command:
      provider: &amp;quot;{{ provider }}&amp;quot;
      commands:
        - &amp;quot;show running-config | include ip name-server&amp;quot;
        - &amp;quot;show running-config | include ntp server&amp;quot;
    register: get_config

  - debug: var=get_config.stdout_lines

  - name: RUN 'Set DNS'
    with_items: &amp;quot;{{ dns_servers }}&amp;quot;
    ios_config:
      provider: &amp;quot;{{ provider }}&amp;quot;
      lines:
        - &amp;quot;{{ item }}&amp;quot;
    register: set_dns

  - name: RUN 'Remove DNS'
    when: &amp;quot;(get_config.stdout_lines[0][0] != '') and (item not in dns_servers)&amp;quot;
    with_items: &amp;quot;{{ get_config.stdout_lines[0] }}&amp;quot;
    register: remove_dns
    ios_config:
      provider: &amp;quot;{{ provider }}&amp;quot;
      lines:
        - &amp;quot;no {{ item }}&amp;quot;

  - name: CHECK CHANGE - dns
    when: &amp;quot;(set_dns.changed == true) or (remove_dns.changed == true)&amp;quot;
    set_fact: configured=true

  - name: RUN 'Set NTP'
    with_items: &amp;quot;{{ ntp_servers }}&amp;quot;
    ios_config:
      provider: &amp;quot;{{ provider }}&amp;quot;
      lines:
          - &amp;quot;{{ item }}&amp;quot;
    register: set_ntp

  - name: RUN 'Remove NTP'
    when: &amp;quot;(get_config.stdout_lines[1][0] != '') and (item not in ntp_servers)&amp;quot;
    with_items: &amp;quot;{{ get_config.stdout_lines[1] }}&amp;quot;
    register: remove_ntp
    ios_config:
      provider: &amp;quot;{{ provider }}&amp;quot;
      lines:
        - &amp;quot;no {{ item }}&amp;quot;

  - name: CHECK CHANGE - ntp
    when: &amp;quot;(set_ntp.changed == true) or (remove_ntp.changed == true)&amp;quot;
    set_fact: configured=true

  - name: RUN 'wr mem'
    when: &amp;quot;(configured is defined) and (configured == true)&amp;quot;
    register: save_config
    ios_command:
      provider: &amp;quot;{{ provider }}&amp;quot;
      commands:
        - &amp;quot;write memory&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The check change tasks and final &lt;code&gt;wr mem&lt;/code&gt; should make a lot more sense now. Oh, and what does &lt;code&gt;check_mode&lt;/code&gt; do? Well, you can enable that for a dry-run or do-no-harm kind of thing.&lt;br /&gt;
Look closely at the &lt;code&gt;RUN 'Set NTP'&lt;/code&gt; Task. The &lt;code&gt;with_items&lt;/code&gt; says "&lt;em&gt;loop through the variable ntp_servers&lt;/em&gt;", when ansible loops it automagically creates an &lt;code&gt;{{ item }}&lt;/code&gt; variable, so that's the line of config we apply.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;RUN 'Remove NTP'&lt;/code&gt; is a little more complicated, so I'll pull out two non-contiguous tasks below...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: &amp;quot;GET CONFIG&amp;quot;
  ios_command:
    provider: &amp;quot;{{ provider }}&amp;quot;
    commands:
      - &amp;quot;show running-config | include ip name-server&amp;quot;
      - &amp;quot;show running-config | include ntp server&amp;quot;
  register: get_config

- name: RUN 'Remove NTP'
    when: &amp;quot;(get_config.stdout_lines[1][0] != '') and (item not in ntp_servers)&amp;quot;
    with_items: &amp;quot;{{ get_config.stdout_lines[1] }}&amp;quot;
    register: remove_ntp
    ios_config:
      provider: &amp;quot;{{ provider }}&amp;quot;
      lines:
        - &amp;quot;no {{ item }}&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before we can remove anything, we need to examine what's there, that's what &lt;code&gt;GET CONIG&lt;/code&gt; does, it creates a variable &lt;code&gt;get_config&lt;/code&gt; with two reults &lt;code&gt;[0]&lt;/code&gt; and &lt;code&gt;[1]&lt;/code&gt;, the former being the list of name servers, the later being the list of NTP servers.&lt;br /&gt;
In the &lt;code&gt;Remote NTP&lt;/code&gt; task, we have a &lt;code&gt;when:&lt;/code&gt; condition... &lt;code&gt;and (item not in ntp_servers)&lt;/code&gt; is self explanatory but &lt;code&gt;(get_config.stdout_lines[1][0] != '')&lt;/code&gt; means config "[1]", i.e. out NTP servers, the "[0]" means the output content and &lt;code&gt;!= ''&lt;/code&gt; not blank... so this task only runs when the content is not blank and the item in question is not in {{ ntp_servers }}. So what is the item in question? Well, its &lt;code&gt;with_items: "{{ get_config.stdout_lines[1] }}"&lt;/code&gt; ... it the current lines of config (&lt;em&gt;found with "show run | inc ntp"&lt;/em&gt;). Assuming all this succeeds, i.e. the line of config is not a pre-defined NTP server, then we "no" the line/item.&lt;/p&gt;
&lt;p&gt;Here's what happens when we run it...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;linickx:ansible $ ansible-playbook standard_ntp_enable.yml

PLAY [ios_devices*] ************************************************************

TASK [Include Login Credentials] ***********************************************
ok: [r2]
ok: [r3]
ok: [r1]

TASK [Define Provider] *********************************************************
ok: [r1]
ok: [r2]
ok: [r3]

TASK [Define Provider (Enable)] ************************************************
skipping: [r2]
skipping: [r1]
ok: [r3]

TASK [Update Provider (Enable)] ************************************************
skipping: [r1]
skipping: [r2]
ok: [r3]

TASK [BACKUP] ******************************************************************
ok: [r1]
ok: [r3]
ok: [r2]

TASK [GET CONFIG] **************************************************************
ok: [r2]
ok: [r3]
ok: [r1]

TASK [debug] *******************************************************************
ok: [r3] =&amp;gt; {
    &amp;quot;get_config.stdout_lines&amp;quot;: [
        [
            &amp;quot;ip name-server 8.8.8.8&amp;quot;,
            &amp;quot;ip name-server 8.8.4.4&amp;quot;
        ],
        [
            &amp;quot;ntp server uk.pool.ntp.org&amp;quot;,
            &amp;quot;ntp server time.apple.com&amp;quot;
        ]
    ]
}
ok: [r2] =&amp;gt; {
    &amp;quot;get_config.stdout_lines&amp;quot;: [
        [
            &amp;quot;ip name-server 8.8.8.8&amp;quot;,
            &amp;quot;ip name-server 8.8.4.4&amp;quot;,
            &amp;quot;ip name-server 208.67.222.222&amp;quot;,
            &amp;quot;ip name-server 208.67.220.220&amp;quot;
        ],
        [
            &amp;quot;ntp server 1.uk.pool.ntp.org&amp;quot;,
            &amp;quot;ntp server 0.uk.pool.ntp.org&amp;quot;,
            &amp;quot;ntp server time.apple.com&amp;quot;,
            &amp;quot;ntp server uk.pool.ntp.org&amp;quot;
        ]
    ]
}
ok: [r1] =&amp;gt; {
    &amp;quot;get_config.stdout_lines&amp;quot;: [
        [
            &amp;quot;ip name-server 8.8.8.8&amp;quot;,
            &amp;quot;ip name-server 8.8.4.4&amp;quot;
        ],
        [
            &amp;quot;ntp server uk.pool.ntp.org&amp;quot;,
            &amp;quot;ntp server time.apple.com&amp;quot;
        ]
    ]
}

TASK [RUN 'Set DNS'] ***********************************************************
ok: [r3] =&amp;gt; (item=ip name-server 8.8.8.8)
ok: [r1] =&amp;gt; (item=ip name-server 8.8.8.8)
ok: [r2] =&amp;gt; (item=ip name-server 8.8.8.8)
ok: [r1] =&amp;gt; (item=ip name-server 8.8.4.4)
ok: [r2] =&amp;gt; (item=ip name-server 8.8.4.4)
ok: [r3] =&amp;gt; (item=ip name-server 8.8.4.4)

TASK [RUN 'Remove DNS'] ********************************************************
skipping: [r2] =&amp;gt; (item=ip name-server 8.8.8.8)
skipping: [r2] =&amp;gt; (item=ip name-server 8.8.4.4)
skipping: [r1] =&amp;gt; (item=ip name-server 8.8.8.8)
skipping: [r3] =&amp;gt; (item=ip name-server 8.8.8.8)
skipping: [r1] =&amp;gt; (item=ip name-server 8.8.4.4)
skipping: [r3] =&amp;gt; (item=ip name-server 8.8.4.4)
changed: [r2] =&amp;gt; (item=ip name-server 208.67.222.222)
changed: [r2] =&amp;gt; (item=ip name-server 208.67.220.220)

TASK [CHECK CHANGE - dns] ******************************************************
skipping: [r1]
ok: [r2]
skipping: [r3]

TASK [RUN 'Set NTP'] ***********************************************************
ok: [r2] =&amp;gt; (item=ntp server uk.pool.ntp.org)
ok: [r3] =&amp;gt; (item=ntp server uk.pool.ntp.org)
ok: [r1] =&amp;gt; (item=ntp server uk.pool.ntp.org)
ok: [r2] =&amp;gt; (item=ntp server time.apple.com)
ok: [r1] =&amp;gt; (item=ntp server time.apple.com)
ok: [r3] =&amp;gt; (item=ntp server time.apple.com)

TASK [RUN 'Remove NTP'] ********************************************************
skipping: [r1] =&amp;gt; (item=ntp server uk.pool.ntp.org)
skipping: [r1] =&amp;gt; (item=ntp server time.apple.com)
skipping: [r3] =&amp;gt; (item=ntp server uk.pool.ntp.org)
skipping: [r3] =&amp;gt; (item=ntp server time.apple.com)
changed: [r2] =&amp;gt; (item=ntp server 1.uk.pool.ntp.org)
changed: [r2] =&amp;gt; (item=ntp server 0.uk.pool.ntp.org)
skipping: [r2] =&amp;gt; (item=ntp server time.apple.com)
skipping: [r2] =&amp;gt; (item=ntp server uk.pool.ntp.org)

TASK [CHECK CHANGE - ntp] ******************************************************
skipping: [r3]
skipping: [r1]
ok: [r2]

TASK [RUN 'wr mem'] ************************************************************
skipping: [r1]
skipping: [r3]
ok: [r2]

PLAY RECAP *********************************************************************
r1                         : ok=7    changed=0    unreachable=0    failed=0
r2                         : ok=12   changed=2    unreachable=0    failed=0
r3                         : ok=9    changed=0    unreachable=0    failed=0

linickx:ansible $
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, I ran this playbook straight after the other one, so all the routers had the correct settings to start with, but notice &lt;code&gt;r2&lt;/code&gt; was different, the opendns and additional time servers have been removed.&lt;/p&gt;
&lt;h3&gt;That was a long post!&lt;/h3&gt;
&lt;p&gt;There's a lot covered in this and my &lt;a href="/ansible-cisco-primer-1-hello-world"&gt;last post&lt;/a&gt;, hopefully it helps. My ansible files are on github, feel free to download them: &lt;a href="https://github.com/linickx/ansible-cisco"&gt;https://github.com/linickx/ansible-cisco&lt;/a&gt;&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Nick Bettison</dc:creator><pubDate>Wed, 29 Mar 2017 17:35:00 +0100</pubDate><guid isPermaLink="false">tag:www.linickx.com,2017-03-29:ansible-cisco-primer-2-making-changes</guid><category>Cisco</category><category>Ansible</category></item><item><title>Ansible Cisco - Primer 1 - Hello World!</title><link>https://www.linickx.com/ansible-cisco-primer-1-hello-world</link><description>&lt;p&gt;&lt;a href="https://www.ansible.com"&gt;Anisble&lt;/a&gt; documentation or examples for Cisco devices appear to be a bit hit-n-miss, so I'm documenting my "Hello World Primer" with hope it'll be helpful to others; as this is quite long it will be two posts, one for getting started with a simple "show clock" and a 2nd for making changes. &lt;a href="/ansible-cisco-primer-2-making-changes"&gt;Part two &lt;em&gt;making changes&lt;/em&gt; is available here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt;; Some example files are on github: &lt;a href="https://github.com/linickx/ansible-cisco"&gt;https://github.com/linickx/ansible-cisco&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To get started, you need ansible installed on your Laptop/PC, I'm running OSX with &lt;a href="https://brew.sh"&gt;homebrew&lt;/a&gt; so installation is a simple &lt;code&gt;brew install ansible&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Getting Started - A sane ansible.cfg&lt;/h3&gt;
&lt;p&gt;Once anisble is installed, the first thing you need an &lt;a href="http://docs.ansible.com/ansible/intro_inventory.html"&gt;inventory/hosts's file&lt;/a&gt;... a lot of the linux examples will get you to start fiddling about with &lt;code&gt;/etc/ansible&lt;/code&gt;, firstly this folder doesn't exist on OSX and secondly this is a bad idea on linux as this is a system wide change. &lt;/p&gt;
&lt;p&gt;IMHO a better start is to create an ansible directory in your home: &lt;code&gt;mkdir ~/ansible;cd ~/ansible&lt;/code&gt; &lt;br /&gt;
In your personal ansible directory, create &lt;a href="http://docs.ansible.com/ansible/intro_configuration.html"&gt;ansible.cfg&lt;/a&gt;; in this file you can set a local inventory file and some sane defaults, mine looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;linickx:ansible $ cat ansible.cfg
[defaults]
hostfile = ./inventory.txt
retry_files_enabled = False
timeout = 5
host_key_checking = False
#log_path= ./log.txt
linickx:ansible $
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see I have a few other thing set, firstly my local hosts file (&lt;em&gt;inventory.txt&lt;/em&gt;) and  secondly ansible fails it creates a local &lt;em&gt;retry file&lt;/em&gt;, I don't want these. Timeout is ssh timeout, use whatever you like, something between 1 and 10 works for me.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;host_key_checking&lt;/code&gt; has security implications. The above is set to &lt;code&gt;False&lt;/code&gt; because this is my personal test machine, &lt;em&gt;in Production&lt;/em&gt; I would set this to &lt;code&gt;True&lt;/code&gt; as I want to know if an SSH &lt;a href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack"&gt;MitM&lt;/a&gt; is happening.&lt;/p&gt;
&lt;p&gt;Finally, notice the &lt;code&gt;#&lt;/code&gt;, this is a comment; I have commented out the log_path directive, if you enable it, ansible will log all screen output to a file, useful for searching for something instead of scrolling through your terminal.&lt;/p&gt;
&lt;h3&gt;The Inventory File - Your personal list of devices&lt;/h3&gt;
&lt;p&gt;The inventory defines the devices you want to manage, anisble expects all devices to have resolvable FQDNs, which is unlikely in most enterprises for Cisco devices, if you're a fan of remembering IP addresses you can have a simple list of IP's but if you're like me an like to know the names of devices you can do something like this...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;linickx:ansible $ cat inventory.txt

[all:vars]
# OSX / Homebrew hack, as I have python3 installed.
ansible_python_interpreter=/usr/local/Cellar/python/2.7.13/bin/python2.7

[ios_devices]
r1 ansible_host=10.10.10.135
r2 ansible_host=10.10.10.136

[ios_devices_enable]
r3 ansible_host=10.10.10.137

linickx:ansible $
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my inventory file, the &lt;code&gt;[]&lt;/code&gt; are group names, I have two custom groups &lt;code&gt;[ios_devices]&lt;/code&gt; and &lt;code&gt;[ios_devices_enable]&lt;/code&gt;, you can call these anything you want, my naming structure will make more sense as this post continues. &lt;br /&gt;
All devices belong to a default &lt;code&gt;[all]&lt;/code&gt; group that doesn't need to be defeined in the file, but in my config file I have &lt;code&gt;[all:vars]&lt;/code&gt; this means: &lt;em&gt;set the following variables for the all group&lt;/em&gt;; I have python3 installed for &lt;a href="https://www.linickx.com/tag/python"&gt;my other python projects&lt;/a&gt;, so  &lt;code&gt;ansible_python_interpreter&lt;/code&gt; is a hack to point ansible to the correct version of python (&lt;em&gt;Python 3 is not supported - yet - for ansible&lt;/em&gt;)&lt;br /&gt;
For each device listed in my file (&lt;em&gt;r1,r2,r3, etc&lt;/em&gt;) I have &lt;code&gt;ansible_host&lt;/code&gt; set to an IP address, that's because my test devices do not have resolvable FQDNs.&lt;/p&gt;
&lt;h3&gt;Hello World! ... or Show Clock play&lt;/h3&gt;
&lt;p&gt;Ansible is primarily about &lt;em&gt;Play Books&lt;/em&gt;, a Play Book is a list of tasks (instructions) that you want to perform on one or many devices; the idea is that you create repeatable plays to standardise configuration and such in your environment.&lt;/p&gt;
&lt;p&gt;For my &lt;a href="https://en.wikipedia.org/wiki/%22Hello,_World!%22_program"&gt;Hello World!&lt;/a&gt; example, I'm not going to make a change but show you how to run &lt;code&gt;show clock&lt;/code&gt; on many devices (&lt;em&gt;using the ios_command module&lt;/em&gt;), in ansible world this is a &lt;em&gt;play&lt;/em&gt;.&lt;br /&gt;
Start by creating a file called &lt;code&gt;show_clock.yml&lt;/code&gt; with the following contents ( &lt;em&gt;including the first ---&lt;/em&gt; ):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
- hosts: ios_devices*
  gather_facts: no
  connection: local

  tasks:
  - name: Include Login Credentials
    include_vars: secrets.yml

  - name: Define Provider
    set_fact:
      provider:
        host: &amp;quot;{{ ansible_host }}&amp;quot;
        username: &amp;quot;{{ creds['username'] }}&amp;quot;
        password: &amp;quot;{{ creds['password'] }}&amp;quot;

  - name: RUN 'Show Clock'
    ios_command:
      provider: &amp;quot;{{ provider }}&amp;quot;
      commands:
        - show clock
    register: clock

  - debug: var=clock.stdout_lines
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This playbook has three tasks:
1. Include Login Credentials, i.e. Read login credentials from a file
2. Define Provider, i.e. Use the credentials in something called a "&lt;a href="http://docs.ansible.com/ansible/playbooks_variables.html"&gt;fact&lt;/a&gt;" that is named "&lt;a href="http://docs.ansible.com/ansible/intro_networking.html#connecting-to-networking-devices"&gt;provider&lt;/a&gt;"
3. Run &lt;em&gt;show clock&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Read login credentials from a file&lt;/h3&gt;
&lt;p&gt;You'll need a credential file, so create &lt;code&gt;secrets.yml&lt;/code&gt; with contents like this (&lt;em&gt;update as appropriate&lt;/em&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
creds:
  username: nick
  password: my_password
  auth_pass: my_enable
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Something called a "fact" that is named "provider"&lt;/h3&gt;
&lt;p&gt;The provider is what tells ansible how to connect to a device, all networking equipment needs one, so the provider includes the ip address and credentials, a fact is a device specific variable, so this task is assigning connectivity details to the device. Anything inside &lt;code&gt;{{ }}&lt;/code&gt; is a variable.&lt;/p&gt;
&lt;h3&gt;Run &lt;em&gt;show clock&lt;/em&gt; ...&lt;/h3&gt;
&lt;p&gt;To execute the Play Book or &lt;em&gt;play&lt;/em&gt;, you use the &lt;code&gt;ansible-playbook&lt;/code&gt; command, like this...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;linickx:ansible $ ansible-playbook show_clock.yml 

PLAY [ios_devices*] ************************************************************

TASK [Include Login Credentials] ***********************************************
ok: [r1]
ok: [r2]
ok: [r3]

TASK [Define Provider] *********************************************************
ok: [r2]
ok: [r1]
ok: [r3]

TASK [RUN 'Show Clock'] ********************************************************
ok: [r1]
ok: [r3]
ok: [r2]

TASK [debug] *******************************************************************
ok: [r1] =&amp;gt; {
    &amp;quot;clock.stdout_lines&amp;quot;: [
        [
            &amp;quot;18:58:24.812 UTC Mon Mar 27 2017&amp;quot;
        ]
    ]
}
ok: [r2] =&amp;gt; {
    &amp;quot;clock.stdout_lines&amp;quot;: [
        [
            &amp;quot;18:58:23.490 UTC Mon Mar 27 2017&amp;quot;
        ]
    ]
}
ok: [r3] =&amp;gt; {
    &amp;quot;clock.stdout_lines&amp;quot;: [
        [
            &amp;quot;.18:58:25.064 UTC Mon Mar 27 2017&amp;quot;
        ]
    ]
}

PLAY RECAP *********************************************************************
r1                         : ok=4    changed=0    unreachable=0    failed=0   
r2                         : ok=4    changed=0    unreachable=0    failed=0   
r3                         : ok=4    changed=0    unreachable=0    failed=0   

linickx:ansible $ 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;What happened there then?!&lt;/h3&gt;
&lt;p&gt;In my &lt;code&gt;ansible-playbook show_clock.yml&lt;/code&gt; you'll see the three tasks ran against three routers; in my &lt;code&gt;show_clock.yml&lt;/code&gt; the first line &lt;code&gt;hosts: ios_devices*&lt;/code&gt; says run against any wildcard group, starting with ios_devices, which in my &lt;code&gt;inventory.txt&lt;/code&gt; is both &lt;code&gt;ios_devices&lt;/code&gt; and &lt;code&gt;ios_devices_enable&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Notice how there where four tasks not three, the &lt;code&gt;-debug&lt;/code&gt; entry was an unnamed task which simply outputs the results of the previous task :)&lt;/p&gt;
&lt;h3&gt;But storing credentials in a file is INSECURE!!&lt;/h3&gt;
&lt;p&gt;Anyone that's been on &lt;a href="https://www.linickx.com"&gt;linickx.com&lt;/a&gt; before will find that example of storing a username/password in a plain text file highly out of character, so lets look at how to prompt for the credentials instead; create &lt;code&gt;show_clock_prompt.yml&lt;/code&gt; with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
- hosts: ios_devices
  gather_facts: no
  connection: local

  vars_prompt:
  - name: &amp;quot;mgmt_username&amp;quot;
    prompt: &amp;quot;Username&amp;quot;
    private: no
  - name: &amp;quot;mgmt_password&amp;quot;
    prompt: &amp;quot;Password&amp;quot;

  vars:
    provider:
      host: &amp;quot;{{ ansible_host }}&amp;quot;
      username: &amp;quot;{{ mgmt_username }}&amp;quot;
      password: &amp;quot;{{ mgmt_password }}&amp;quot;

  tasks:
  - name: RUN 'Show Clock'
    ios_command:
      provider: &amp;quot;{{ provider }}&amp;quot;
      commands:
        - show clock
    register: clock

  - debug: var=clock.stdout_lines
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice a few changes, firstly no &lt;code&gt;*&lt;/code&gt;, this play will be run just against one inventory group &lt;code&gt;[ios_devices]&lt;/code&gt;, secondly one&lt;em&gt;-ish&lt;/em&gt; task: &lt;code&gt;RUN 'Show Clock'&lt;/code&gt;. In this example, before running any tasks we prompt for some input and save the provider variables, once we have those we can then run the tasks. Run the play.. &lt;code&gt;ansible-playbook show_clock_prompt.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;linickx:ansible $ ansible-playbook show_clock_prompt.yml
Username: nick
Password:

PLAY [ios_devices] *************************************************************

TASK [RUN 'Show Clock'] ********************************************************
ok: [r2]
ok: [r1]

TASK [debug] *******************************************************************
ok: [r1] =&amp;gt; {
    &amp;quot;clock.stdout_lines&amp;quot;: [
        [
            &amp;quot;19:12:37.863 UTC Mon Mar 27 2017&amp;quot;
        ]
    ]
}
ok: [r2] =&amp;gt; {
    &amp;quot;clock.stdout_lines&amp;quot;: [
        [
            &amp;quot;19:12:39.742 UTC Mon Mar 27 2017&amp;quot;
        ]
    ]
}

PLAY RECAP *********************************************************************
r1                         : ok=2    changed=0    unreachable=0    failed=0
r2                         : ok=2    changed=0    unreachable=0    failed=0

linickx:ansible $
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This time, we're prompted for credentials and less tasks take place.&lt;/p&gt;
&lt;h3&gt;Download the files from github!&lt;/h3&gt;
&lt;p&gt;My ansible files are on github, feel free to download them: &lt;a href="https://github.com/linickx/ansible-cisco"&gt;https://github.com/linickx/ansible-cisco&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Once you're happy with this, check out &lt;a href="/ansible-cisco-primer-2-making-changes"&gt; Primer 2 &lt;em&gt;making changes&lt;/em&gt; .&lt;/a&gt;&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Nick Bettison</dc:creator><pubDate>Mon, 27 Mar 2017 16:07:00 +0100</pubDate><guid isPermaLink="false">tag:www.linickx.com,2017-03-27:ansible-cisco-primer-1-hello-world</guid><category>Cisco</category><category>Ansible</category></item><item><title>pip install crassh</title><link>https://www.linickx.com/pip-install-crassh</link><description>&lt;div style="float:right"&gt;&lt;a href="/files/2016/02/atom_screenshot.png"&gt;&lt;img src="/files/2016/02/atom_screenshot.png" alt="atom screenshot" height="93px" width="168px"/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Recently myself (&lt;em&gt;and a maybe colleague or two&lt;/em&gt;) has been copy/pasting parts of &lt;a href="https://github.com/linickx/crassh/"&gt;C.R.A.SSH&lt;/a&gt; (crassh) my python script for automating commands on Cisco IOS devices into personal scripts to get something done; knowing there must be a better a way I decided to turn crassh into a module which can/should be easy to install on linux or OSX systems with &lt;a href="https://pypi.python.org/pypi/pip"&gt;pip&lt;/a&gt;. PIP is a python (cross platform) package manager that now crassh is available in, i.e. &lt;code&gt;pip install crassh&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Below is an output from my machine, as you can see pip will install any necessary dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pip install crassh
Collecting crassh
  Downloading CraSSH-2.02.tar.gz
Collecting paramiko&amp;gt;=1.10 (from crassh)
  Using cached paramiko-1.16.0-py2.py3-none-any.whl
Collecting pycrypto!=2.4,&amp;gt;=2.1 (from paramiko&amp;gt;=1.10-&amp;gt;crassh)
Collecting ecdsa&amp;gt;=0.11 (from paramiko&amp;gt;=1.10-&amp;gt;crassh)
  Using cached ecdsa-0.13-py2.py3-none-any.whl
Building wheels for collected packages: crassh
  Running setup.py bdist_wheel for crassh
Successfully built crassh
Installing collected packages: pycrypto, ecdsa, paramiko, crassh
Successfully installed crassh-2.2 ecdsa-0.13 paramiko-1.16.0 pycrypto-2.6.1
$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I'm about to start work on some proper documentation , but to get you started there are four functions that most people will want/use: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;crassh.connect()&lt;/li&gt;
&lt;li&gt;crassh.send_command()&lt;/li&gt;
&lt;li&gt;crassh.disconnect()&lt;/li&gt;
&lt;li&gt;crassh.readtxtfile()&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first three are self explanatory, the last one is very useful as it'll read in a plain text file (&lt;em&gt;one line at at time&lt;/em&gt;) and use it as an array, typically the plain text file would be a list of IP address to connect to or a list of commands to execute.&lt;/p&gt;
&lt;p&gt;To show how crassh can be used, below is a simple example to audit to switches for the SNMP community string &lt;code&gt;public&lt;/code&gt; - &lt;strong&gt;A security no-no!&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python
# coding=utf-8

import crassh

# Variables
routers = [&amp;quot;10.159.83.135&amp;quot;, &amp;quot;10.159.83.136&amp;quot;]
username = &amp;quot;nick&amp;quot;
password = &amp;quot;nick&amp;quot;

# Loop
for device in routers:

    hostname = crassh.connect(device, username, password)
    output = crassh.send_command(&amp;quot;show run | inc snmp-server community&amp;quot;, hostname)
    crassh.disconnect()

    # Split the output by spaces so we can search the response
    words = output.split()

    # Look for &amp;quot;public&amp;quot; in the output
    for x in words:
        if x == &amp;quot;public&amp;quot;:
            print(&amp;quot;DANGER: Public SNMP Community set on %s [%s]&amp;quot; % (hostname, device))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I've saved the file as &lt;code&gt;no_public.py&lt;/code&gt; and this is the response...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python no_public.py 
Connecting to 10.159.83.135 ... 
Connecting to 10.159.83.136 ... 
DANGER: Public SNMP Community set on r2 [10.159.83.136]
$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Obviously you could do a lot more than just output but hopefully this'll give you an idea.&lt;/p&gt;
&lt;p&gt;NOTE: Instead of routers being an array, I could have done &lt;code&gt;routers = crassh.readtxtfile("./routers.txt")&lt;/code&gt; where routers.txt was a plain text file with one IP address per line.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Nick Bettison</dc:creator><pubDate>Sun, 14 Feb 2016 11:23:00 +0000</pubDate><guid isPermaLink="false">tag:www.linickx.com,2016-02-14:pip-install-crassh</guid><category>python</category><category>crassh</category><category>linux</category><category>cisco</category><category>osx</category></item><item><title>Travis-CI and Dynamips a cloud router for testing scripts against Cisco IOS</title><link>https://www.linickx.com/travis-ci-and-dynamips-a-cloud-router-for-testing-scripts-against-cisco-ios</link><description>&lt;p&gt;&lt;img alt="Crassh build status" src="https://travis-ci.org/linickx/crassh.svg?branch=master" /&gt;&lt;/p&gt;
&lt;p&gt;I think I have started to get my head around unit tests for &lt;a href="https://github.com/linickx/crassh"&gt;CRASSH&lt;/a&gt;; recently my initial implementation of paramiko's “ssh_session.recv_ready" was ballsed  up, python3 was forgiving python2 would bork, creating an issue for many.  My &lt;a href="https://github.com/linickx/crassh/blob/master/tests/test_crassh.py#L13"&gt;first unit test&lt;/a&gt; was a basic "print" which I ran on &lt;a href="https://travis-ci.org"&gt;Travis-CI&lt;/a&gt; against both python2 and python3 if the build passed then syntax checking for both versions would pass, nice!&lt;/p&gt;
&lt;p&gt;Unit tests that check the &lt;em&gt;internal&lt;/em&gt; functions of CRASSH are all well and good, but the purpose of the script is to log into routers and &lt;em&gt;do stuff&lt;/em&gt;, so I really wanted to spin up a router and check the script against that.&lt;/p&gt;
&lt;p&gt;Travis-CI's infrastructure comes in &lt;a href="https://docs.travis-ci.com/user/ci-environment/#Virtualization-environments"&gt;two flavors&lt;/a&gt;, Standard and Container Based, which essential boils down to one that allows &lt;code&gt;sudo&lt;/code&gt; and one that doesn't&lt;a href="#star"&gt;*&lt;/a&gt;; using the &lt;em&gt;standard&lt;/em&gt; environment I have managed to spin up a dynamips router for testing CRASSH against.&lt;/p&gt;
&lt;p&gt;If you take a look at &lt;a href="https://github.com/linickx/crassh/blob/master/.travis.yml"&gt;my .travis.yml&lt;/a&gt;, I'll step you through how this works:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo: required
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default all builds are &lt;em&gt;Container Based&lt;/em&gt;, this forces the &lt;em&gt;Standard&lt;/em&gt; build.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; python:
- '2.6'
- '3.5'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Currently tests all run on linux, I plan to add OSX tests in the future but here we instruct Travis-CI to perform the tests twice, once against each version.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;before_install:
- ifconfig -a
- sudo /sbin/modprobe tun
- sudo apt-get update -qq
- sudo apt-get install -qq dynamips
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first thing I do is prepare the &lt;a href="https://en.wikipedia.org/wiki/Operating_system"&gt;OS&lt;/a&gt;, the &lt;code&gt;ifconfig -a&lt;/code&gt; is for troublehooting so I can see the network stack before I start; then I install the Tun/Tap kernel driver, update the OS and install dynamips.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; install:
- pip install .
- pip install paramiko
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next I install CRASSH onto Travis-CI and it's dependency &lt;a href="http://www.paramiko.org"&gt;paramiko&lt;/a&gt;. The &lt;code&gt;pip install .&lt;/code&gt; basically reads &lt;a href="https://github.com/linickx/crassh/blob/master/setup.py"&gt;setup.py&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now all the dependencies are installed, the router needs to be loaded...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;before_script:
- wget -q $IOS_URL
- ls -l
- sudo dynamips -s 0:0:tap:tap0 -P 3725 -T 2001 --idle-pc=0x607081e0 -C tests/r1.txt c3725-advsecurityk9-mz.124-25c.bin &amp;amp;
- sleep 90
- sudo ifconfig tap0 inet 1.1.1.1 netmask 255.255.255.252
- ifconfig -a
- ping -c 4 1.1.1.2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;$IOS_URL&lt;/code&gt; is &lt;a href="https://docs.travis-ci.com/user/environment-variables/#Encrypted-Variables"&gt;a secure variable&lt;/a&gt; so that charlatan's cannot download the Cisco IOS; so wget downloads the IOS from my secret location and dynamips starts up (and backrounds, note: &lt;code&gt;&amp;amp;&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Once dynamips is running we wait 90secs to give time for the router to boot, we configure an interface to connect to the router with &lt;code&gt;sudo ifconfig tap0&lt;/code&gt; and I &lt;code&gt;ping&lt;/code&gt; it to make sure it works.&lt;/p&gt;
&lt;p&gt;Now that the router is running, standard &lt;a href="http://pytest.org/"&gt;python unit tests&lt;/a&gt; start with &lt;code&gt;script: py.test --cisco&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Inside &lt;a href="https://github.com/linickx/crassh/blob/master/tests/conftest.py"&gt;the test config&lt;/a&gt; you'll see that &lt;code&gt;--cisco&lt;/code&gt; is an optional switch that runs &lt;code&gt;test_cisco_shver&lt;/code&gt; inside &lt;a href="https://github.com/linickx/crassh/blob/master/tests/test_crassh.py"&gt;test_crassh.py&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The magic of &lt;code&gt;test_cisco_shver&lt;/code&gt; is that dynamips was booted with the config in &lt;a href="https://github.com/linickx/crassh/blob/master/tests/r1.txt"&gt;r1.txt&lt;/a&gt; the test logs into the r1 router and compares a &lt;code&gt;show ver&lt;/code&gt; to &lt;a href="https://github.com/linickx/crassh/blob/master/tests/cisco_shver_output.txt"&gt;an expected output &lt;/a&gt; if they match (&lt;em&gt;line for line&lt;/em&gt;) then the test passes and Travis-CI shuts everything down; if the test fails I get notified.&lt;/p&gt;
&lt;p&gt;Status of builds can be reviewed on the &lt;a href="https://travis-ci.org/linickx/crassh"&gt;CRASSH Travis Page&lt;/a&gt;: https://travis-ci.org/linickx/crassh or is shown in the readme. &lt;/p&gt;
&lt;p&gt;&lt;a name="star"&gt;[*]&lt;/a&gt; I have &lt;a href="https://github.com/travis-ci/apt-source-whitelist/issues/223"&gt;an issue logged&lt;/a&gt; to see if this could be done on a Container Based server&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Nick Bettison</dc:creator><pubDate>Fri, 22 Jan 2016 11:59:00 +0000</pubDate><guid isPermaLink="false">tag:www.linickx.com,2016-01-22:travis-ci-and-dynamips-a-cloud-router-for-testing-scripts-against-cisco-ios</guid><category>crassh</category><category>python</category><category>travis-ci</category><category>Cisco</category><category>Dynamips</category></item><item><title>Cisco CMNA</title><link>https://www.linickx.com/cisco-cmna</link><description>&lt;p&gt;&lt;a href="/files/2016/01/Cisco_CMNA.png"&gt;&lt;img alt="Cisco CMNA" src="/files/2016/01/Cisco_CMNA-300.png" title="Cisco CMNA" /&gt;&lt;/a&gt;&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Nick Bettison</dc:creator><pubDate>Thu, 14 Jan 2016 01:11:00 +0000</pubDate><guid isPermaLink="false">tag:www.linickx.com,2016-01-14:cisco-cmna</guid><category>certificates</category><category>Cisco</category><category>CMNA</category><category>Meraki</category></item><item><title>Cisco CCNP Security</title><link>https://www.linickx.com/cisco-ccnp-security</link><description>&lt;p&gt;&lt;a href="/files/2015/12/Cisco_CCNP-Sec.png"&gt;&lt;img alt="Cisco Certificate Network Professional Security" src="/files/2015/12/Cisco_CCNP-Sec-300.png" title="Cisco CCNP Security" /&gt;&lt;/a&gt;&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Nick Bettison</dc:creator><pubDate>Mon, 21 Dec 2015 01:01:00 +0000</pubDate><guid isPermaLink="false">tag:www.linickx.com,2015-12-21:cisco-ccnp-security</guid><category>certificates</category><category>Cisco</category><category>CCNP</category><category>Security</category></item><item><title>Multi-Context HTTPS backups of Cisco ASA Script</title><link>https://www.linickx.com/multi-context-https-backups-of-cisco-asa-script</link><description>&lt;p&gt;If you look in the Cisco forums for scripts to backup ASAs you'll find various SSH / Expect , complicated examples... not sure why since &lt;a href="https://www.linickx.com/https-backups-of-cisco-asa"&gt;in 2006 I showed it can be done with a single wget command&lt;/a&gt; ;-)&lt;/p&gt;
&lt;p&gt;Recently I needed something that would support Multi-Context firewalls, so I pimped my one line command into the &lt;a href="#below"&gt;below&lt;/a&gt; shell script.&lt;/p&gt;
&lt;p&gt;Copy/paste into a new file as &lt;code&gt;backup_cisco_asa.sh&lt;/code&gt; then &lt;code&gt;chmod 700&lt;/code&gt; the file as necessary. &lt;/p&gt;
&lt;p&gt;Run the file with no options &lt;code&gt;./backup_cisco_asa.sh&lt;/code&gt; and it'll ask you for IP address, username and password to make the connection.&lt;/p&gt;
&lt;p&gt;For this to work the ASA needs appropriate HTTP statements (&lt;em&gt;i.e. allow ASDM access from where you are running the script&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;The file supports in-line backup of a single device such as &lt;code&gt;./backup_cisco_asa.sh 10.10.10.10&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Multi-context support is via an environment variable or a config file &lt;code&gt;~/.asa_config&lt;/code&gt;. You must set an array containing entries for each context you want to backup. e.g.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ASA_CONTEXTS=( 
 &amp;quot;172.31.9.10:system&amp;quot;
 &amp;quot;172.31.9.10:admin&amp;quot;
 &amp;quot;172.31.9.10:Edge&amp;quot;
 &amp;quot;172.31.2.254:system&amp;quot;
 &amp;quot;172.31.2.254:admin&amp;quot;
 &amp;quot;172.31.2.254:Core&amp;quot; )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you're feeling &lt;em&gt;insecure&lt;/em&gt; you can also save your username/password variables into the &lt;code&gt;~/.asa_config&lt;/code&gt; as &lt;code&gt;ASA_UID&lt;/code&gt; and &lt;code&gt;ASA_PW&lt;/code&gt; respectively (&lt;em&gt;or as environment variables&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;Given that the script is a bash shell script I assume that SCP isn't required (&lt;em&gt;because you are probably already on your linux SSH/SCP server running the script&lt;/em&gt;) but to keep the &lt;em&gt;router team&lt;/em&gt; happy you might need to copy the files up via TFTP, this can be set with the &lt;code&gt;ASA_TFTP_IP&lt;/code&gt; variable in &lt;code&gt;~/.asa_config&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;By default the backup files will be saved to &lt;code&gt;./&lt;/code&gt; (&lt;em&gt;i.e. where ever you run the script from&lt;/em&gt;) and you can change that with the &lt;code&gt;ASA_FILEPATH&lt;/code&gt; variable.&lt;/p&gt;
&lt;p&gt;&lt;a name="below"&gt; &lt;/a&gt; &lt;/p&gt;
&lt;h3&gt;backup_cisco_asa.sh&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

# Nick Bettison - LINICKX.com - 2015 - v1
# Bash Shell Scipt for backing up Cisco ASA's via HTTPS (i.e. ASDM)
# 
# Read the file comments, you can setup a config file ~/.asa_config to store variables.
# ASA_UID &amp;amp; ASA_PW for credentials (if you're feeling insecure)
# ASA_CONTEXTS for multi-context support
# and ASA_TFTP_IP for copying the files via TFTP (for insecure router boys)
#
# Tested on.
# Cisco Adaptive Security Appliance Software Version 9.2(3)4 &amp;lt;context&amp;gt;
# Device Manager Version 7.4(2)

# Timestamp for file names
TIMESTAMP=`date &amp;quot;+%Y%m%d-%H%M%S&amp;quot;`

# Allow single ASA IP address to be passed from CLI
# e.g ./backup_cisco_asa.sh 10.10.10.10
if [ -n &amp;quot;$1&amp;quot; ]
    then
    ASA_IP=&amp;quot;$1&amp;quot;
fi

# Check for Curl
type curl &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || { echo &amp;gt;&amp;amp;2 &amp;quot;I require curl but it's not installed.  Aborting.&amp;quot;; exit 1; }

# Read Variables from a config file (if it exits)
# The config file can be used for storing multi-context configurations... and for the insecure username/password ;-)
if [ -e ~/.asa_config ]
then
  . ~/.asa_config
fi

# Default File Path
if [ -z &amp;quot;$ASA_FILEPATH&amp;quot; ]
    then
    ASA_FILEPATH=&amp;quot;./&amp;quot;
fi

# Check for UID/PW Variables - Ask if not found
if [ -z &amp;quot;$ASA_UID&amp;quot; ]
        then
        read -p &amp;quot;ASA Username:&amp;quot; ASA_UID
fi
if [ -z &amp;quot;$ASA_PW&amp;quot; ]
        then
        read -s -p &amp;quot;ASA Password:&amp;quot; ASA_PW
        echo
fi

# Check to see if single or multi-context mode.
if [ -z &amp;quot;$ASA_CONTEXTS&amp;quot; ]
        then
        if [ -z &amp;quot;$ASA_IP&amp;quot; ]
                then
                read -p &amp;quot;ASA IP Address:&amp;quot; ASA_IP
        fi

        ASA_tFILE=&amp;quot;$ASA_FILEPATH.$TIMESTAMP.asaconfig.txt&amp;quot;

        # Download the &amp;quot;show run&amp;quot; via the unofficial CLI.
        curl -s -k -o $ASA_tFILE -u $ASA_UID:$ASA_PW &amp;quot;https://$ASA_IP/admin/exec/show%20running-config%20asdm/show%20running-config&amp;quot;

        if [ -e $ASA_tFILE ]
            then
            # Look for hostname in config file
            ASA_HOSTNAME=`grep ^hostname $ASA_tFILE | awk '{print $2}'`
            # rename the temp file to something sensible.
            mv $ASA_tFILE &amp;quot;$ASA_FILEPATH$TIMESTAMP.$ASA_HOSTNAME.txt&amp;quot;
            # Setup an array for TFTP later.
            ASA_FILES=(&amp;quot;${ASA_FILES[@]}&amp;quot; &amp;quot;$ASA_FILEPATH$TIMESTAMP.$ASA_HOSTNAME.txt&amp;quot;)
            # Done.
            echo &amp;quot;DONE: $ASA_FILEPATH$TIMESTAMP.$ASA_HOSTNAME.txt&amp;quot;
        else
            echo &amp;quot;FAILED: $ASA_IP&amp;quot;
            exit 1
        fi
else
    # Example ASA_CONTEXTS array:
    # 172.31.9.10 is the admin context IP, admin &amp;amp; Edge are the names of the two contexts to backup.
    # 172.31.2.254 is the admin context IP, admin &amp;amp; Core are the names of the two contexts to backup.
    # &amp;quot;system is obviously the system context&amp;quot;
    # ASA_CONTEXTS=( 
    #     &amp;quot;172.31.9.10:system&amp;quot;
    #     &amp;quot;172.31.9.10:admin&amp;quot;
    #     &amp;quot;172.31.9.10:Edge&amp;quot;
    #     &amp;quot;172.31.2.254:system&amp;quot;
    #     &amp;quot;172.31.2.254:admin&amp;quot;
    #     &amp;quot;172.31.2.254:Core&amp;quot; )

    # Loop through the array
    for firewall in ${ASA_CONTEXTS[@]} ; do
         ASA_IP=${firewall%%:*}
         ASA_CONTEXT=${firewall##*:}

         # Feedback on progress
         printf &amp;quot;Connecting to %s for %s \n&amp;quot; $ASA_IP $ASA_CONTEXT
         # Filename
         ASA_FILE=&amp;quot;$ASA_FILEPATH$TIMESTAMP.$ASA_IP.$ASA_CONTEXT.txt&amp;quot;

         # Download the CONTEXT &amp;quot;show run&amp;quot; via the unofficial API.
         curl -s -k -o $ASA_FILE -u $ASA_UID:$ASA_PW &amp;quot;https://$ASA_IP/admin/exec/changeto%20context%20$ASA_CONTEXT/show%20running-config/show%20running-config%20asdm&amp;quot;

         if [ -e $ASA_FILE ]
             then
             # Setup an array for TFTP later.
             ASA_FILES=(&amp;quot;${ASA_FILES[@]}&amp;quot; &amp;quot;$ASA_FILE&amp;quot;)
             # Done!
             echo &amp;quot;DONE: $ASA_FILE&amp;quot;
         else
             echo &amp;quot;FAILED: $ASA_IP&amp;quot;
         fi
     done
fi

# Optional Backup to insecure tftp - you know, to keep the router boys happy!
if [ -n &amp;quot;$ASA_TFTP_IP&amp;quot; ]
    then
    # Check for TFTP binary
    type tftp &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || { echo &amp;gt;&amp;amp;2 &amp;quot;tftp client not installed.  Aborting.&amp;quot;; exit 1; }
    type wc &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || { echo &amp;gt;&amp;amp;2 &amp;quot;wc not installed (needed for counting stuff).  Aborting.&amp;quot;; exit 1; }
    type awk &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || { echo &amp;gt;&amp;amp;2 &amp;quot;awk not installed.... seriously?!?!  Aborting.&amp;quot;; exit 1; }

    # Loop through array
    for file in ${ASA_FILES[@]} ; do
        LOCAL_FILE=$file
        # backup separator
        OIFS=$IFS
        # Change separator to /
        IFS='/'
        # Split the filename from the path
        AWK_POSITION=`echo $file | wc -w`
        REMOTE_FILE=`echo $file | awk -v w=$AWK_POSITION '{print $w}'`
        # restore separator
        IFS=$OIFS
        # TFTP the file.
        tftp -v $ASA_TFTP_IP -c put $LOCAL_FILE $REMOTE_FILE
    done
fi
&lt;/code&gt;&lt;/pre&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Nick Bettison</dc:creator><pubDate>Tue, 28 Jul 2015 16:04:00 +0100</pubDate><guid isPermaLink="false">tag:www.linickx.com,2015-07-28:multi-context-https-backups-of-cisco-asa-script</guid><category>Cisco</category><category>Security</category><category>ASA</category><category>Firewall</category></item><item><title>Running Cisco ISE (1.4) in VirtualBox</title><link>https://www.linickx.com/running-cisco-ise-14-in-virtualbox</link><description>&lt;p&gt;I've been meaning to do this for a while, but I've finally &lt;em&gt;hacked&lt;/em&gt; &lt;a href="http://www.cisco.com/go/ise"&gt;Cisco ISE&lt;/a&gt; into VirtualBox for home lab learning and experimentation.  &lt;/p&gt;
&lt;p&gt;For those familiar with my &lt;a href="https://www.linickx.com/cisco-acs-5-1-in-virtualbox"&gt;ACS in VirtualBox post&lt;/a&gt; you should notice a very similar theme.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Copy the .iso to a Web server directory that allows page indexing&lt;/li&gt;
&lt;li&gt;Hack the KickStart file (ks.cfg) to remove Cisco's hardware checks&lt;/li&gt;
&lt;li&gt;Boot the install CD using the custom ks.cfg&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As before, I use the web installation method because it's the easiest way to hack/test/boot multiple times when you're trying to work out why the installer doesn't run... Also ** I will not be providing you an ISO file to download. **&lt;/p&gt;
&lt;h2&gt;Web Server Configuration (nginx)&lt;/h2&gt;
&lt;p&gt;First, I mounted  &lt;code&gt;ise-1.4.0.253.x86_64.iso&lt;/code&gt; and copied the whole CD to &lt;code&gt;~/public_html/ise&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Next, I need to expose this directory to the web server. I'm using &lt;a href="http://nginx.org"&gt;nginx&lt;/a&gt; on &lt;a href="https://www.centos.org"&gt;CentOS&lt;/a&gt;... it's nice and easy, I added this to my config.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;location /ise {
    root /home/nick/public_html/;
    autoindex on;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Replace ks.cfg&lt;/h2&gt;
&lt;p&gt;When you copy the contents of the CD to the local file system you'll notice all files have the RO flag set. Fix with &lt;code&gt;chmod +w ~/public_html/ise/ks.cfg&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;There are a few things in the file you need to change:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Replace &lt;code&gt;cdrom&lt;/code&gt; on line 10 with &lt;code&gt;url --url http://192.168.10.122/ise/&lt;/code&gt; (&lt;em&gt;replace 192.168.10.122 with the IP of your web server&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Optional, replace the encrypted root password with a new one &lt;code&gt;rootpw CISCO_ise_p455w0rd&lt;/code&gt; on line 12&lt;/li&gt;
&lt;li&gt;Comment out with a &lt;code&gt;#&lt;/code&gt; any &lt;code&gt;/sbin/halt -f&lt;/code&gt; statements to stop error messages from halting the installation&lt;/li&gt;
&lt;li&gt;Replace any &lt;code&gt;cars_udi_util&lt;/code&gt; statements with your own versions, so...&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;UDI_PID=`/sbin/cars_udi_util -p`
UDI_VID=`/sbin/cars_udi_util -v`
UDI_SN=`/sbin/cars_udi_util -s`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;becomes...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UDI_PID=&amp;quot;Cisco-VM-SPID&amp;quot;
UDI_VID=&amp;quot;1.0&amp;quot;
UDI_SN=&amp;quot;123456789&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;... and just for good measure, after &lt;code&gt;validate_hwinfo(){&lt;/code&gt; insert (&lt;em&gt;on a new line&lt;/em&gt;) &lt;code&gt;UDI_PID="Cisco-VM-SPID"&lt;/code&gt; to force hardware selection.&lt;/p&gt;
&lt;p&gt;If you want, you can ignore all of that and just use &lt;a href="/files/2015/07/ks.cfg_ise.txt"&gt;my ks.cfg&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;VirtualBox Configuration&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;CPU - 4 &lt;/li&gt;
&lt;li&gt;RAM - 8Gb&lt;/li&gt;
&lt;li&gt;Hard Disk - SCSI - 100Gb (&lt;em&gt;thin space allocation&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;CDRom - &lt;code&gt;ise-1.4.0.253.x86_64.iso&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;NIC 1 - Bridged&lt;/li&gt;
&lt;li&gt;NIC 2 - Host Only (&lt;em&gt;disconnected&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All other defaults left alone.&lt;/p&gt;
&lt;h2&gt;Boot the custom install&lt;/h2&gt;
&lt;p&gt;Boot the virtual machine from the Cisco ISO, but &lt;strong&gt;DO NOT&lt;/strong&gt; select 1,2,3 or 4. Instead type:  &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vmlinuz text ks=http://192.168.10.122/ise/ks.cfg initrd=initrd.img
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;...replacing 192.168.10.122 with your web server IP.&lt;/p&gt;
&lt;p&gt;&lt;a href="/files/2015/07/Custom_ISE_Boot.png"&gt;&lt;img src="/files/2015/07/Custom_ISE_Boot-150x150.png" alt="custom ise boot"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Wait, See, Enjoy!&lt;/h2&gt;
&lt;p&gt;With a little luck, the VM will load your custom KickStart file and begin the install. &lt;/p&gt;
&lt;p&gt;If you tail your web server access logs, you should see stuff like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;192.168.10.116 - - [30/Jun/2015:18:50:49 +0100] &amp;quot;GET /ise/Packages/CARSCmdSched-2.0cars-1.x86_64.rpm HTTP/1.1&amp;quot; 200 28353 &amp;quot;-&amp;quot; &amp;quot;Cisco Identity Services Engine (anaconda)/1.3&amp;quot; &amp;quot;-&amp;quot;
192.168.10.116 - - [30/Jun/2015:18:50:49 +0100] &amp;quot;GET /ise/Packages/CARSICMPUtil-2.0cars-1.x86_64.rpm HTTP/1.1&amp;quot; 200 11425 &amp;quot;-&amp;quot; &amp;quot;Cisco Identity Services Engine (anaconda)/1.3&amp;quot; &amp;quot;-&amp;quot;
192.168.10.116 - - [30/Jun/2015:18:50:49 +0100] &amp;quot;GET /ise/Packages/CARSSysMon-2.0cars-1.x86_64.rpm HTTP/1.1&amp;quot; 200 34919 &amp;quot;-&amp;quot; &amp;quot;Cisco Identity Services Engine (anaconda)/1.3&amp;quot; &amp;quot;-&amp;quot;
192.168.10.116 - - [30/Jun/2015:18:50:49 +0100] &amp;quot;GET /ise/Packages/libdrm-2.4.39-1.el6.x86_64.rpm HTTP/1.1&amp;quot; 200 119408 &amp;quot;-&amp;quot; &amp;quot;Cisco Identity Services Engine (anaconda)/1.3&amp;quot; &amp;quot;-&amp;quot;
192.168.10.116 - - [30/Jun/2015:18:50:49 +0100] &amp;quot;GET /ise/Packages/plymouth-0.8.3-27.el6.centos.x86_64.rpm HTTP/1.1&amp;quot; 200 91056 &amp;quot;-&amp;quot; &amp;quot;Cisco Identity Services Engine (anaconda)/1.3&amp;quot; &amp;quot;-&amp;quot;
192.168.10.116 - - [30/Jun/2015:18:50:50 +0100] &amp;quot;GET /ise/Packages/rsyslog-5.8.10-6.el6.x86_64.rpm HTTP/1.1&amp;quot; 200 663780 &amp;quot;-&amp;quot; &amp;quot;Cisco Identity Services Engine (anaconda)/1.3&amp;quot; &amp;quot;-&amp;quot;
192.168.10.116 - - [30/Jun/2015:18:50:50 +0100] &amp;quot;GET /ise/Packages/cronie-anacron-1.4.4-7.el6.x86_64.rpm HTTP/1.1&amp;quot; 200 29768 &amp;quot;-&amp;quot; &amp;quot;Cisco Identity Services Engine (anaconda)/1.3&amp;quot; &amp;quot;-&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="/files/2015/07/ISE_Instaling.png"&gt;&lt;img src="/files/2015/07/ISE_Instaling-150x150.png" alt="installing"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;At the end of the install the VM should reboot into the &lt;em&gt;normal&lt;/em&gt; setup login screen allowing you to complete the &lt;a href="http://www.cisco.com/c/en/us/td/docs/security/ise/1-4/installation_guide/b_ise_InstallationGuide14/b_ise_InstallationGuide14_chapter_011.html#ID-1415-000000c9"&gt;setup parameters&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The setup will take ages, well over the 15/20mins that Cisco suggest but once it completes and reboots you should find you have a working ISE to play with.  &lt;/p&gt;
&lt;p&gt;... &lt;em&gt;well, for 90days until the eval lic runs out!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/files/2015/07/ISE_1_4_Running_in_VirtualBox.png"&gt;&lt;img src="/files/2015/07/ISE_1_4_Running_in_VirtualBox.png" alt="ISE 1.4 running in VirtualBox"&gt;&lt;/a&gt;&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Nick Bettison</dc:creator><pubDate>Fri, 24 Jul 2015 18:59:00 +0100</pubDate><guid isPermaLink="false">tag:www.linickx.com,2015-07-24:running-cisco-ise-14-in-virtualbox</guid><category>Cisco</category><category>ISE</category><category>Security</category><category>VirtualBox</category></item></channel></rss>