10. August 2018 · Comments Off on Cisco Firepower Management Center (FMC) bulk modifications of policy rules. · Categories: Cisco, Firewall, Linux, Linux Scripts, Networking · Tags: , , , , , , ,

As stated in my previous post, the Cisco Migration tools are very limited and Cisco has not shared any tools publicly that can perform bulk modifications. When migrating to a firepower unless all the previous access rules on the ASA had logging enabled any rule converted will continue to not have logging enabled. This is a major bummer for auditors, security teams or for anyone looking to eventually migrate L4 rules to L7 rules.

Just because I can, the following example script will bulk enable logBegin & sendEventsToFMC on all policy rules on a Firepower (FMC) appliance. This script is intended to be a reference, however modifying policy rules can broadly be broken down into the following steps:

  1. GET the accessrule
  2. Purge the metadata & link value trees from the json
  3. Change, add or remove whatever values you want
  4. PUT the accessrule
#!/bin/bash
## Example script that can bulk modify policy rules on a Cisco FMC.
## Requires: python:PyYAML,shyaml
## 2018 (v.01) - Script from www.davideaves.com
 
firepower="192.0.2.10"
username="apiuser"
password="api1234"
 
# Convert JSON to YAML
j2y() {
 python -c 'import sys, yaml, json; yaml.safe_dump(json.load(sys.stdin), sys.stdout, default_flow_style=False)' 2> /dev/null
}
 
# Convert YAML to JSON
y2j() {
 python -c 'import sys, yaml, json; y=yaml.load(sys.stdin.read()); print json.dumps(y)' 2> /dev/null
}
 
# Pop forbidden put values (metadata, links)
jpop() {
 python -c 'import sys, json; j=json.load(sys.stdin); j.pop("metadata"); j.pop("links"); print json.dumps(j)' 2> /dev/null
}
 
fmcauth() {
 ### Post credentials and eval header return data.
 
 if [[ -z "${auth_epoch}" || "${auth_epoch}" -lt "$(($(date +%s) - 1500))" ]]
  then
 
   if [ -z "${X_auth_access_toke}" ]
    then eval "auth_epoch=$(date +%s)"
        eval "$(curl -skX POST https://${firepower}/api/fmc_platform/v1/auth/generatetoken \
          -H "Authorization: Basic $(printf "${username}:${password}" | base64)" -D - |\
          awk '/(auth|DOMAIN|global)/{gsub(/[\r|:]/,""); gsub(/-/,"_",$1); print $1"=\""$2"\""}')"
    else eval "auth_epoch=$(date +%s)"
         eval "$(curl -skX POST https://${firepower}/api/fmc_platform/v1/auth/refreshtoken \
          -H "X-auth-access-token: ${X_auth_access_token}" \
          -H "X-auth-refresh-token: ${X_auth_refresh_token}" -D - |\
          awk '/(auth|DOMAIN|global)/{gsub(/[\r|:]/,""); gsub(/-/,"_",$1); print $1"=\""$2"\""}')"
   fi
 
 fi
}
 
# Get a list of all access control policies.
fmcauth && curl -skX GET https://${firepower}/api/fmc_config/v1/domain/${DOMAIN_UUID}/policy/accesspolicies \
 -H "X-auth-access-token: ${X_auth_access_token}" | j2y | shyaml get-value items | awk '/self:/{print $NF}' | while read POLICY
do fmcauth
 
   # Get a list of all access rules in each policy.
   curl -skX GET ${POLICY}/accessrules?limit=10000 -H "X-auth-access-token: ${X_auth_access_token}" |\
   j2y | shyaml get-value items 2> /dev/null | awk '/self:/{print $NF}' | while read RULE
   do
 
      # ID of the access rule.
      UUID="$(basename ${RULE})"
 
      # Collect the access rule resource and pop forbidden values.
      RESOURCE=`curl -skX GET ${RULE} -H "X-auth-access-token: ${X_auth_access_token}" | jpop`
 
      # Modify only if resource exists.
      if [[ ${RESOURCE} != *"Resource not found."* ]]
       then echo -ne "$(date) - PUT:${UUID} - MSG: "
 
       # Use sed to modify values in payload before putting back to FMC.
       curl -skX PUT ${RULE} \
        -H "Content-Type: application/json" \
        -H "X-auth-access-token: ${X_auth_access_token}" \
        -d "$(echo ${RESOURCE} | j2y | sed 's/logBegin: false/logBegin: true/;s/sendEventsToFMC: false/sendEventsToFMC: true/' | y2j)" && echo
      fi
 
   done
 
done
08. August 2018 · Comments Off on Cisco Firepower Management Center (FMC) bulk import & delete objects · Categories: Cisco, Firewall, Linux, Linux Scripts, Networking · Tags: , , , , , ,

I’ve been doing a lot of migration work with the Cisco Firepower. I quickly noticed a general lack of tools to assist with migrations short of a few scattered and limited (hopefully incomplete and will continue to be developed) migration tools provided by Cisco:

If you are looking for tools to perform bulk rule changes or help convert from Layer4 rules to Layer7, like the PaloAlto Migration tool, you are out of luck. That being said, as an engineer trying to use the FMC, I quickly found the experience of working within the firepower interface slow, tedious and generally painful. This has forced me to try to interface with the API’s as much as possible to save time and to avoid using the interface.

First thing; API access needs to be enabled on the FMC; by default they are, but if disabled you can enable them by going to System > REST API Preferences and enabling them. If API’s are enabled the documentation will be accessible via: https://<FMCHOST>/api/api-explorer

API’s on the FMC take an imperative approach and are simple to trigger and depending on the HTTP method you can command the FMC to make changes. There are 4 basic methods the FMC will accept:

  • GET – Retrieves data from the specified object. GET is a read-only operation.
  • PUT – Adds supplied information to the specified object; returns a 404 Resource Not Found error if the object does not exist.
  • POST – Creates the object with the supplied information. POST operations are be followed with a payload consisting of JSON.
  • DELETE – Delete and object.

Concerning the types of objects that can be modified; the following are currently supported:

  • icmpv4objects
  • icmpv6objects
  • interfacegroups
  • networkgroups
  • networks
  • portobjectgroups
  • protocolportobjects
  • ranges
  • securityzones
  • slamonitors
  • urlgroups
  • urls
  • vlangrouptags
  • vlantags

As far as I can tell vpn policies, flexconfig and other objects of the sort must be created by hand. The following json & script are very rudimentary, however it is a working example that uses cURL to perform a bulk import of objects into a firepower. At the very least the curl commands can be used as a reference in your own projects.

fmc-objects.json: ASCII text, with very long lines

[{"value":"10.0.0.0","overridable":false,"name":"10.0.0.0_32","type":"Hosts","description":"Test REST API Object"},{"value":"10.0.0.1","overridable":false,"name":"10.0.0.1_32","type":"Hosts","description":"Test REST API Object"},{"value":"10.0.0.2","overridable":false,"name":"10.0.0.2_32","type":"Hosts","description":"Test REST API Object"},{"value":"10.0.0.3","overridable":false,"name":"10.0.0.3_32","type":"Hosts","description":"Test REST API Object"},{"value":"10.0.0.4","overridable":false,"name":"10.0.0.4_32","type":"Hosts","description":"Test REST API Object"},{"value":"10.0.0.5","overridable":false,"name":"10.0.0.5_32","type":"Hosts","description":"Test REST API Object"},{"value":"10.0.0.6","overridable":false,"name":"10.0.0.6_32","type":"Hosts","description":"Test REST API Object"},{"value":"10.0.0.7","overridable":false,"name":"10.0.0.7_32","type":"Hosts","description":"Test REST API Object"},{"value":"10.0.0.8/29","overridable":false,"name":"10.0.0.8_29","type":"Networks","description":"Test REST API Object"}]

Because I do a lot with Ansible I have a strong preference to work with the YAML data serialization format. The script includes 2 unused functions that converts JSON to YAML and vice versa. I left those functions in this example script because they are handy and provide me flexibility of what and how I import. When posting to the FMC, its important to make sure the data is in JSON format and any metadata & link value trees have been popped. One thing I am lacking is a function to verify the object has been created, its easy to do by simply performing a GET after the POST, it just makes things slower and is simply not in this example.

fmc-objects.sh: Bourne-Again shell script, ASCII text executable

#!/bin/bash
## Example script that can bulk import and delete objects from a Cisco FMC.
## Requires: python:PyYAML,shyaml
## 2018 (v.01) - Script from www.davideaves.com
 
firepower="192.0.2.10"
username="apiuser"
password="api1234"
 
# Convert JSON to YAML
j2y() {
 python -c 'import sys, yaml, json; yaml.safe_dump(json.load(sys.stdin), sys.stdout, default_flow_style=False)' 2> /dev/null
}
 
# Convert YAML to JSON
y2j() {
 python -c 'import sys, yaml, json; y=yaml.load(sys.stdin.read()); print json.dumps(y)' 2> /dev/null
}
 
fmcauth() {
 ### Post credentials and eval header return data.
 
 if [[ -z "${auth_epoch}" || "${auth_epoch}" -lt "$(($(date +%s) - 1500))" ]]
  then
 
   if [ -z "${X_auth_access_toke}" ]
    then eval "auth_epoch=$(date +%s)"
        eval "$(curl -skX POST https://${firepower}/api/fmc_platform/v1/auth/generatetoken \
          -H "Authorization: Basic $(printf "${username}:${password}" | base64)" -D - |\
          awk '/(auth|DOMAIN|global)/{gsub(/[\r|:]/,""); gsub(/-/,"_",$1); print $1"=\""$2"\""}')"
    else eval "auth_epoch=$(date +%s)"
         eval "$(curl -skX POST https://${firepower}/api/fmc_platform/v1/auth/refreshtoken \
          -H "X-auth-access-token: ${X_auth_access_token}" \
          -H "X-auth-refresh-token: ${X_auth_refresh_token}" -D - |\
          awk '/(auth|DOMAIN|global)/{gsub(/[\r|:]/,""); gsub(/-/,"_",$1); print $1"=\""$2"\""}')"
   fi
 
 fi
}
 
 
### Bulk add diffrent type objects into FMC
 
# Cat single line example containing diffrent objects & use awk to extract json stanza.
cat "fmc-objects.json" | awk -F'[}{]' '{ for (i=1; i< =NF; i++) if(length($i) > 5) print "{"$i"}" }' |\
while read LINE
 do fmcauth
 
   # Determine Type & Value.
   TYPE="$(echo ${LINE} | shyaml get-value type | awk '{print tolower($0)}')"
   VALUE="$(echo ${LINE} | shyaml get-value value | awk '{print tolower($0)}')"
 
   echo -ne "$(date) - POST:${TYPE}>${VALUE} - MSG: "
 
   # Post new object to FMC.
   curl -skX POST https://${firepower}/api/fmc_config/v1/domain/${DOMAIN_UUID}/object/${TYPE} \
     -H "Content-Type: application/json" \
     -H "X-auth-access-token: ${X_auth_access_token}" \
     -d "${LINE}" && echo
done
 
### Bulk delete all unused objects from FMC...
 
# Valid objects types are: hosts icmpv4objects icmpv6objects interfacegroups networkgroups networks portobjectgroups protocolportobjects ranges securityzones slamonitors urlgroups urls vlangrouptags vlantags
for TYPE in hosts networks
 do fmcauth
    echo "### DELETEING ${TYPE} OBJECTS ###"
 
    # Get a list of objects.
    curl -skX GET https://${firepower}/api/fmc_config/v1/domain/${DOMAIN_UUID}/object/${TYPE}?limit=10000 \
     -H "X-auth-access-token: ${X_auth_access_token}" | j2y | shyaml get-value items 2> /dev/null | awk '/self:/{print $NF}' | while read SELF
    do  fmcauth
        echo -ne "$(date) - DELETE:${TYPE} - MSG: "
 
        # Delete the object.
        curl -skX DELETE ${SELF} -H "X-auth-access-token: ${X_auth_access_token}" && echo
    done
done
05. August 2018 · Comments Off on Ansible playbook to handle IOS upgrades. · Categories: Ansible, Cisco, Linux, Networking · Tags: , , , ,

The following is an Ansible playbook I created to handle IOS upgrades on against an excessively large number of Cisco routers at a customer site I was doing some work at. I saved a lot of time by staging the IOS images on flash before kicking off the playbook, if I missed anything this playbook would of uploaded the image for me before setting the boot statement. I think moving forward I will start leveraging the NTC (Network to Code) Ansible modules a lot more, its have proven itself to be superior and more feature rich than the built in Cisco Ansible modules.

In addition to the NTC requirements, this playbook also requires 2 directories:

  • ./images: directory that contains IOS images.
  • ./backups: directory repository for config backups.

ansible.cfg: ASCII text

[defaults]
transport = ssh
host_key_checking = false
retry_files_enabled = false
#stdout_callback = unixy
#stdout_callback = actionable
display_skipped_hosts = false
 
timeout = 5
 
inventory = ./hosts
log_path   = ./ansible.log
 
[ssh_connection]
pipelining = True

platform_facts.csv: ASCII text

C3900,IOS,ROUTER,c3900-universalk9-mz.SPA.156-3.M4.bin
C2900,IOS,ROUTER,c2900-universalk9-mz.SPA.156-3.M4.bin
ISR4300,IOS,ROUTER,isr4300-universalk9.16.03.06.SPA.bin

ios_upgrade.yaml: a ansible-playbook script text executable, ASCII text

#!/usr/local/bin/ansible-playbook -f 10
## Ansible playbook to handle IOS upgrades.
# Playbook will not reboot any device unless the variable REBOOT exists.
#
# Requires: https://github.com/networktocode/ntc-ansible
# Example: ansible-playbook --extra-vars "REBOOT=yes" ios_upgrade.yaml
# Example: ansible-playbook ios_upgrade.yaml --skip-tags=change
---
- name: Cisco IOS Upgrade
  hosts: [ "all" ]
  connection: local
  gather_facts: no
  tags: [ "IOS", "upgrade" ]

  vars_prompt:

  - name: "username"
    prompt: "Username"
    private: no

  - name: "password"
    prompt: "Password"

  vars:

  - ansible_connection: "local"
  - ansible_python_interpreter: "/usr/bin/env python"

  - ios_provider:
      username: "{{ username }}"
      password: "{{ password }}"
      authorize: true
      auth_pass: "{{ password }}"
      host: "{{ inventory_hostname }}"
      timeout: 120

  pre_tasks:

  - name: "ios_facts: hardware"
    ios_facts:
      gather_subset: hardware
      provider: "{{ ios_provider }}"
    connection: local
    when: (PLATFORM is not defined)
    tags: [ "pre_task", "ios_facts", "hardware" ]

  - name: "ios_command: boot configuration"
    ios_command:
      provider: "{{ ios_provider }}"
      commands:
        - "show running-config | include ^boot.system"
    connection: local
    register: COMMANDS
    tags: [ "pre_task", "ios_command", "boot", "COMMANDS" ]

  - name: "set_fact: PLATFORM"
    set_fact:
      PLATFORM: "{{ ansible_net_image|upper | regex_replace('.*[:/]') | regex_replace('([A-Z]-|-).*') }}"
    no_log: True
    when: (ansible_net_image is defined) and (PLATFORM is not defined)
    tags: [ "pre_task", "set_fact", "PLATFORM", "ansible_net_image" ]

  - name: "set_fact: SYSTEM"
    set_fact:
      SYSTEM: "{{ lookup('csvfile', PLATFORM + ' file=platform_facts.csv col=1 delimiter=,')|upper }}"
    no_log: True
    when: (PLATFORM is defined) and (SYSTEM is not defined)
    tags: [ "pre_task", "set_fact", "lookup", "platform_facts.csv", "PLATFORM", "SYSTEM" ]

  - name: "set_fact: TYPE"
    set_fact:
      TYPE: "{{ lookup('csvfile', PLATFORM + ' file=platform_facts.csv col=2 delimiter=,')|upper }}"
    no_log: True
    when: (PLATFORM is defined) and (TYPE is not defined)
    tags: [ "pre_task", "set_fact", "lookup", "platform_facts.csv", "PLATFORM", "TYPE" ]

  - name: "set_fact: IMAGE"
    set_fact:
      IMAGE: "{{ lookup('csvfile', PLATFORM + ' file=platform_facts.csv col=3 delimiter=,') }}"
    no_log: True
    when: (PLATFORM is defined) and (IMAGE is not defined)
    tags: [ "pre_task", "set_fact", "lookup", "platform_facts.csv", "PLATFORM", "IMAGE" ]

  - name: "stat: BACKUP_FILE"
    stat: path="backups/{{ inventory_hostname }}.cfg"
    no_log: True
    register: BACKUP_FILE
    tags: [ "pre_task", "stat", "BACKUP_FILE" ]

  - name: "stat: IMAGE_FILE"
    stat: path="images/{{ IMAGE }}"
    no_log: True
    register: IMAGE_FILE
    tags: [ "pre_task", "stat", "IMAGE_FILE" ]

  tasks:

  - name: "fail: missing image"
    fail:
      msg: "Platform image missing: {{ PLATFORM }}"
    when: (IMAGE[0] is undefined)

  - name: "ntc_save_config: host > local" 
    ntc_save_config:     
      platform: cisco_ios_ssh
      local_file: "backups/{{ inventory_hostname }}.cfg"
      provider: "{{ ios_provider }}"
    connection: local
    when: (BACKUP_FILE.stat.exists == False)
    tags: [ "ntc-ansible", "ntc_save_config", "cisco_ios_ssh", "BACKUP_FILE" ]

  - name: "ntc_file_copy: local > host"
    ntc_file_copy:
      platform: cisco_ios_ssh
      local_file: "images/{{ IMAGE }}"
      host: "{{ inventory_hostname }}"
      provider: "{{ ios_provider }}"
    connection: local
    when: (IMAGE_FILE.stat.exists == True) and (PLATFORM is defined) and (IMAGE is defined)
    tags: [ "ntc-ansible", "ntc_file_copy", "cisco_ios_ssh", "IMAGE", "PLATFORM", "IMAGE_FILE" ]

  - name: "ios_config: remove boot system lines"
    ios_config:
      provider: "{{ ios_provider }}"
      lines: "no {{ item }}"
    connection: local
    register: config_boot_rem
    with_items: "{{ COMMANDS.stdout_lines[0] }}"
    when: (PLATFORM is defined) and (IMAGE is defined) and
          not(IMAGE in item) and not(item == '')
    tags: [ "ios_config", "boot", "PLATFORM", "remove", "config_boot_rem", "change" ]
    notify:
      - ios write memory

  - name: "ios_config: add boot system line"
    ios_config:
      provider: "{{ ios_provider }}"
      lines: "boot system flash:{{ IMAGE }}"
      match: line
    connection: local
    register: config_boot_add
    when: (PLATFORM is defined) and (IMAGE is defined)
    tags: [ "ios_config", "boot", "PLATFORM", "IMAGE", "add", "config_boot_add", "change" ]
    notify:
      - ios write memory

  - meta: flush_handlers

  post_tasks:

  - name: "ntc_reboot: when REBOOT is defined"
    ntc_reboot:
      platform: cisco_ios_ssh
      confirm: true
      host: "{{ inventory_hostname }}"
      provider: "{{ ios_provider }}"
    connection: local
    when: (REBOOT is defined) and
          (config_boot_add.changed == true) or (config_boot_rem.changed == true)
    tags: [ "post_task", "ntc-ansible", "ntc_reboot", "REBOOT", "change" ]
    notify:
      - wait for tcp

  handlers:

  - name: "ios write memory"
    ios_command:
      provider: "{{ ios_provider }}"
      commands: "write memory"
    connection: local

  - name: "wait for tcp"
    wait_for:
      port: 22
      host: "{{inventory_hostname}}"
      timeout: 420
    connection: local