Network Automation     Archive

Grabbing L3 Firewall Policy Information from Meraki

Had a friend of mine from Cisco ask about how/if Ansible can work with Cisco Meraki. As with most answers with “can Ansible do this” my initial response was, of course!

Quick background: Cisco Meraki is one of the largest LAN SDN infrastructures today. Using their open APIs, we sat down and wrote an Ansible playbook around checking and provisioning policies on a single access point. The goal of this exercise is to eventually tie together other security products and define specific security policies to deploy across a Cisco Meraki Infrastructure.

Here’s a look at the playbook in its entirety:


---
- name: GRAB MERAKI SECURITY POLICY OF US AP DEPOT IN CISCO DEVNET
  hosts: localhost
  connection: local
  gather_facts: no

  vars_files:
    - ./vars.yml

  tasks:
    - name: CHECK CONNECTION
      uri:
        url: https://api.meraki.com/api/v0/organizations
        method: GET
        headers:
          X-Cisco-Meraki-API-Key: "{{ MERAKI_API_KEY }}"
      register: meraki_org

    - name: CHANGE MERAKI_ORG TO LOWER
      set_fact:
        meraki_org_lower: "{{ meraki_org.json | lower }}"

    - name: GRAB DEVNET SANDBOX ORGANIZATION ID
      set_fact:
        meraki_org_id: "{{item.id}}"
      with_items: "{{meraki_org_lower}}"
      when: item.name == 'devnet sandbox'

    - name: GRAB ORGANIZATION'S LIST OF NETWORKS
      uri:
        url: https://api.meraki.com/api/v0/organizations/{{meraki_org_id}}/networks
        method: GET
        headers:
          X-Cisco-Meraki-API-Key: "{{MERAKI_API_KEY}}"
      register: meraki_network

    - name: GRAB ID OF WIRELESS TYPE AND NAME OF US AP DEPOT
      set_fact:
        meraki_network_id: "{{ item.id }}"
      with_items: "{{ meraki_network.json }}"
      when: item.type == 'wireless' and item.name == 'US AP Depot'

    - name: GRAB SSID OF NETWORK
      uri:
        url: https://api.meraki.com/api/v0/networks/{{meraki_network_id}}/ssids
        method: GET
        headers:
          X-Cisco-Meraki-API-Key: "{{MERAKI_API_KEY}}"
      register: meraki_ssid

    - name: GRAB ID OF US AP DEPOT WIFI
      set_fact:
        meraki_ssid_number: "{{item.number}}"
      with_items: "{{ meraki_ssid.json}}"
      when: item.name == 'US AP Depot WiFi'

    - name: GRAB L3 FIREWALL POLICIES
      uri:
        url: https://api.meraki.com/api/v0/networks/{{meraki_network_id}}/ssids/{{meraki_ssid_number}}/l3FirewallRules
        method: GET
        headers:
          X-Cisco-Meraki-API-Key: "{{MERAKI_API_KEY}}"
      register: meraki_l3firewallrules

    - debug:
        msg: "{{meraki_l3firewallrules}}"

Now, let’s step through the tasks!

The first task is to grab the ID of the “DevNet SandBox” organization which is a Cisco provided sandbox environment.


- name: CHECK CONNECTION
  uri:
    url: https://api.meraki.com/api/v0/organizations
    method: GET
    headers:
      X-Cisco-Meraki-API-Key: "{{ MERAKI_API_KEY }}"
  register: meraki_org

- name: CHANGE MERAKI_ORG TO LOWER
  set_fact:
    meraki_org_lower: "{{ meraki_org.json | lower }}"

- name: GRAB DEVNET SANDBOX ORGANIZATION ID
  set_fact:
    meraki_org_id: "{{item.id}}"
  with_items: "{{meraki_org_lower}}"
  when: item.name == 'devnet sandbox'

Here we use the uri module to query the Cisco Meraki API in order to, first, grab the organizations that exist, and from there, grab the organization ID that has the name of “DevNet SandBox.” We have an additional task to utilize the set_fact module to alter the registered variable to be lowercase so we don’t miss the name due to a capitalized letter here and there. With this task, we now have the organization ID, which we will use in the next task to point to the new API endpoint.

NOTE: Many debug statements were used to figure out the key value pair, no animals were harmed.


- name: GRAB ORGANIZATION'S LIST OF NETWORKS
  uri:
    url: https://api.meraki.com/api/v0/organizations/{{meraki_org_id}}/networks
    method: GET
    headers:
      X-Cisco-Meraki-API-Key: "{{MERAKI_API_KEY}}"
  register: meraki_network

- name: GRAB ID OF TYPE WIRELESS AND NAME OF US AP DEPOT
  set_fact:
    meraki_network_id: "{{ item.id }}"
  with_items: "{{ meraki_network.json }}"
  when: item.type == 'wireless' and item.name == 'US AP Depot'

Now, an organization contains a list of networks. For our example, we want to grab a network that is of type “wireless,” but also having the name of “US AP Depot.” The type and name used in this example is specific to the Cisco DevNet Sandbox, but could easily be applied to any Cisco Meraki environment.

We now have the network ID of a specific wireless network named “US AP Depot,” which we will use to get the SSID of a specific Access Point (AP).


- name: GRAB SSID OF NETWORK
  uri:
    url: https://api.meraki.com/api/v0/networks/{{meraki_network_id}}/ssids
    method: GET
    headers:
      X-Cisco-Meraki-API-Key: "{{MERAKI_API_KEY}}"
  register: meraki_ssid

- name: GRAB ID OF US AP DEPOT WIFI
  set_fact:
    meraki_ssid_number: "{{item.number}}"
  with_items: "{{ meraki_ssid.json}}"
  when: item.name == 'US AP Depot WiFi'

Next, we use the meraki_network_id from the previous task to grab the SSID’s for the US AP Depot network. We also add a conditional when statement to ensure that we are grabbing the correct SSID, with the conditional being that the name equals US AP Depot WiFi


- name: GRAB L3 FIREWALL POLICIES
  uri:
    url: https://api.meraki.com/api/v0/networks/{{meraki_network_id}}/ssids/{{meraki_ssid_number}}/l3FirewallRules
    method: GET
    headers:
      X-Cisco-Meraki-API-Key: "{{MERAKI_API_KEY}}"
  register: meraki_l3firewallrules

- debug:
    msg: "{{meraki_l3firewallrules}}"

Lastly, we use the SSID number from the previous task to grab the L3 Firewall Policies from our Cisco Meraki Infrastructure. In summary, we were able to dive down from the organization to the access point and retrieve a security policy. As an extra step, if we change the URI request to a ‘PUT’ method and add a body, we can push/change the security policies.

PLAY [GRAB MERAKI SECURITY POLICY OF US AP DEPOT IN CISCO DEVNET] *****************************************************************

TASK [CHECK CONNECTION] ***********************************************************************************************************
ok: [localhost]

TASK [debug] **********************************************************************************************************************
ok: [localhost] => {
    "msg": {
        "cache_control": "no-cache, no-store, max-age=0, must-revalidate",
        "changed": false,
        "connection": "close",
        "content_type": "application/json; charset=utf-8",
        "cookies": {},
        "cookies_string": "",
        "date": "Mon, 16 Jul 2018 12:50:41 GMT",
        "expires": "Fri, 01 Jan 1990 00:00:00 GMT",
        "failed": false,
        "json": [
            {
                "id": 537758,
                "name": "mCloud Demo",
                "samlConsumerUrl": "https://n149.meraki.com/saml/login/d029Cc/rtFn7bJtIRna",
                "samlConsumerUrls": [
                    "https://n149.meraki.com/saml/login/d029Cc/rtFn7bJtIRna"
                ]
            },
            {
                "id": 549236,
                "name": "DevNet Sandbox"
            },
            {
                "id": 646829496481089033,
                "name": "Test ENV"
            }
        ],
        "msg": "OK (unknown bytes)",
        "pragma": "no-cache",
        "redirected": true,
        "server": "nginx",
        "status": 200,
        "strict_transport_security": "max-age=15552000; includeSubDomains",
        "transfer_encoding": "chunked",
        "url": "https://n149.meraki.com/api/v0/organizations",
        "vary": "Accept-Encoding",
        "x_frame_options": "sameorigin",
        "x_request_id": "ce0f2dbfa4e1bf2a192191b4c652d6a2",
        "x_runtime": "0.206179",
        "x_ua_compatible": "IE=Edge,chrome=1"
    }
}

TASK [CHANGE MERAKI_ORG TO LOWER] *************************************************************************************************
ok: [localhost]

TASK [GRAB DEVNET SANDBOX ORGANIZATION ID] ****************************************************************************************
skipping: [localhost] => (item={u'samlconsumerurls': [u'https://n149.meraki.com/saml/login/d029cc/rtfn7bjtirna'], u'samlconsumerurl': u'https://n149.meraki.com/saml/login/d029cc/rtfn7bjtirna', u'name': u'mcloud demo', u'id': 537758})
ok: [localhost] => (item={u'id': 549236, u'name': u'devnet sandbox'})
skipping: [localhost] => (item={u'id': 646829496481089033, u'name': u'test env'})

TASK [GRAB ORGANIZATION'S LIST OF NETWORKS] ***************************************************************************************
ok: [localhost]

TASK [GRAB ID OF WIRELESS TYPE AND NAME OF US AP DEPOT] ***************************************************************************
skipping: [localhost] => (item={u'name': u'SM - Corp', u'tags': u' Sandbox ', u'organizationId': u'549236', u'timeZone': u'America/Los_Angeles', u'type': u'systems manager', u'id': u'N_646829496481145308'})
skipping: [localhost] => (item={u'name': u'SM - Branch', u'tags': u' Sandbox ', u'organizationId': u'549236', u'timeZone': u'America/Los_Angeles', u'type': u'systems manager', u'id': u'N_646829496481145309'})
skipping: [localhost] => (item={u'disableMyMerakiCom': False, u'name': u'DevNet Always On Read Only', u'tags': u' Sandbox ', u'organizationId': u'549236', u'timeZone': u'America/Los_Angeles', u'type': u'combined', u'id': u'L_646829496481099586'})
skipping: [localhost] => (item={u'disableMyMerakiCom': False, u'name': u'DevNet Small Business Reservable 1', u'tags': u' Sandbox ', u'organizationId': u'549236', u'timeZone': u'America/Los_Angeles', u'type': u'combined', u'id': u'L_646829496481099587'})
skipping: [localhost] => (item={u'disableMyMerakiCom': False, u'name': u'DevNet Small Business Reservable 2', u'tags': u' Sandbox ', u'organizationId': u'549236', u'timeZone': u'America/Los_Angeles', u'type': u'combined', u'id': u'L_646829496481099589'})
skipping: [localhost] => (item={u'disableMyMerakiCom': False, u'name': u'DevNet Small Business Reservable 3', u'tags': u' Sandbox ', u'organizationId': u'549236', u'timeZone': u'America/Los_Angeles', u'type': u'combined', u'id': u'L_646829496481099590'})
skipping: [localhost] => (item={u'disableMyMerakiCom': False, u'name': u'DevNet Small Business Reservable 4', u'tags': u' Sandbox ', u'organizationId': u'549236', u'timeZone': u'America/Los_Angeles', u'type': u'combined', u'id': u'L_646829496481099592'})
skipping: [localhost] => (item={u'disableMyMerakiCom': False, u'name': u'DevNet Small Business Reservable 5', u'tags': u' Sandbox ', u'organizationId': u'549236', u'timeZone': u'America/Los_Angeles', u'type': u'combined', u'id': u'L_646829496481099593'})
skipping: [localhost] => (item={u'name': u'Camera Depot', u'tags': u' Sandbox ', u'organizationId': u'549236', u'timeZone': u'America/Los_Angeles', u'type': u'camera', u'id': u'N_646829496481145361'})
skipping: [localhost] => (item={u'disableMyMerakiCom': False, u'name': u'EU AP Depot', u'tags': u' Sandbox ', u'organizationId': u'549236', u'timeZone': u'America/Los_Angeles', u'type': u'wireless', u'id': u'N_646829496481145363'})
ok: [localhost] => (item={u'disableMyMerakiCom': False, u'name': u'US AP Depot', u'tags': u' Sandbox ', u'organizationId': u'549236', u'timeZone': u'America/Los_Angeles', u'type': u'wireless', u'id': u'N_646829496481145366'})
skipping: [localhost] => (item={u'disableMyMerakiCom': False, u'name': u'PythonChallenge', u'tags': u' tag1 tag2 ', u'organizationId': u'549236', u'timeZone': u'America/Los_Angeles', u'type': u'combined', u'id': u'L_646829496481099579'})
skipping: [localhost] => (item={u'disableMyMerakiCom': False, u'name': u'WW AP Depot', u'tags': None, u'organizationId': u'549236', u'timeZone': u'America/Los_Angeles', u'type': u'wireless', u'id': u'N_646829496481146258'})

TASK [GRAB SSID OF NETWORK] *******************************************************************************************************
ok: [localhost]

TASK [GRAB ID OF US AP DEPOT WIFI] ************************************************************************************************
ok: [localhost] => (item={u'ipAssignmentMode': u'NAT mode', u'splashPage': u'None', u'perClientBandwidthLimitDown': 0, u'enabled': True, u'number': 0, u'perClientBandwidthLimitUp': 0, u'ssidAdminAccessible': False, u'bandSelection': u'Dual band operation', u'minBitrate': 11, u'authMode': u'open', u'name': u'US AP Depot WiFi'})
skipping: [localhost] => (item={u'ipAssignmentMode': u'NAT mode', u'splashPage': u'None', u'perClientBandwidthLimitDown': 0, u'enabled': False, u'number': 1, u'perClientBandwidthLimitUp': 0, u'ssidAdminAccessible': False, u'bandSelection': u'Dual band operation', u'minBitrate': 11, u'authMode': u'open', u'name': u'Unconfigured SSID 2'})
skipping: [localhost] => (item={u'ipAssignmentMode': u'NAT mode', u'splashPage': u'None', u'perClientBandwidthLimitDown': 0, u'enabled': False, u'number': 2, u'perClientBandwidthLimitUp': 0, u'ssidAdminAccessible': False, u'bandSelection': u'Dual band operation', u'minBitrate': 11, u'authMode': u'open', u'name': u'Unconfigured SSID 3'})
skipping: [localhost] => (item={u'ipAssignmentMode': u'NAT mode', u'splashPage': u'None', u'perClientBandwidthLimitDown': 0, u'enabled': False, u'number': 3, u'perClientBandwidthLimitUp': 0, u'ssidAdminAccessible': False, u'bandSelection': u'Dual band operation', u'minBitrate': 11, u'authMode': u'open', u'name': u'Unconfigured SSID 4'})
skipping: [localhost] => (item={u'ipAssignmentMode': u'NAT mode', u'splashPage': u'None', u'perClientBandwidthLimitDown': 0, u'enabled': False, u'number': 4, u'perClientBandwidthLimitUp': 0, u'ssidAdminAccessible': False, u'bandSelection': u'Dual band operation', u'minBitrate': 11, u'authMode': u'open', u'name': u'Unconfigured SSID 5'})
skipping: [localhost] => (item={u'ipAssignmentMode': u'NAT mode', u'splashPage': u'None', u'perClientBandwidthLimitDown': 0, u'enabled': False, u'number': 5, u'perClientBandwidthLimitUp': 0, u'ssidAdminAccessible': False, u'bandSelection': u'Dual band operation', u'minBitrate': 11, u'authMode': u'open', u'name': u'Unconfigured SSID 6'})
skipping: [localhost] => (item={u'ipAssignmentMode': u'NAT mode', u'splashPage': u'None', u'perClientBandwidthLimitDown': 0, u'enabled': False, u'number': 6, u'perClientBandwidthLimitUp': 0, u'ssidAdminAccessible': False, u'bandSelection': u'Dual band operation', u'minBitrate': 11, u'authMode': u'open', u'name': u'Unconfigured SSID 7'})
skipping: [localhost] => (item={u'ipAssignmentMode': u'NAT mode', u'splashPage': u'None', u'perClientBandwidthLimitDown': 0, u'enabled': False, u'number': 7, u'perClientBandwidthLimitUp': 0, u'ssidAdminAccessible': False, u'bandSelection': u'Dual band operation', u'minBitrate': 11, u'authMode': u'open', u'name': u'Unconfigured SSID 8'})
skipping: [localhost] => (item={u'ipAssignmentMode': u'NAT mode', u'splashPage': u'None', u'perClientBandwidthLimitDown': 0, u'enabled': False, u'number': 8, u'perClientBandwidthLimitUp': 0, u'ssidAdminAccessible': False, u'bandSelection': u'Dual band operation', u'minBitrate': 11, u'authMode': u'open', u'name': u'Unconfigured SSID 9'})
skipping: [localhost] => (item={u'ipAssignmentMode': u'NAT mode', u'splashPage': u'None', u'perClientBandwidthLimitDown': 0, u'enabled': False, u'number': 9, u'perClientBandwidthLimitUp': 0, u'ssidAdminAccessible': False, u'bandSelection': u'Dual band operation', u'minBitrate': 11, u'authMode': u'open', u'name': u'Unconfigured SSID 10'})
skipping: [localhost] => (item={u'ipAssignmentMode': u'NAT mode', u'splashPage': u'None', u'perClientBandwidthLimitDown': 0, u'enabled': False, u'number': 10, u'perClientBandwidthLimitUp': 0, u'ssidAdminAccessible': False, u'bandSelection': u'Dual band operation', u'minBitrate': 11, u'authMode': u'open', u'name': u'Unconfigured SSID 11'})
skipping: [localhost] => (item={u'ipAssignmentMode': u'NAT mode', u'splashPage': u'None', u'perClientBandwidthLimitDown': 0, u'enabled': False, u'number': 11, u'perClientBandwidthLimitUp': 0, u'ssidAdminAccessible': False, u'bandSelection': u'Dual band operation', u'minBitrate': 11, u'authMode': u'open', u'name': u'Unconfigured SSID 12'})
skipping: [localhost] => (item={u'ipAssignmentMode': u'NAT mode', u'splashPage': u'None', u'perClientBandwidthLimitDown': 0, u'enabled': False, u'number': 12, u'perClientBandwidthLimitUp': 0, u'ssidAdminAccessible': False, u'bandSelection': u'Dual band operation', u'minBitrate': 11, u'authMode': u'open', u'name': u'Unconfigured SSID 13'})
skipping: [localhost] => (item={u'ipAssignmentMode': u'NAT mode', u'splashPage': u'None', u'perClientBandwidthLimitDown': 0, u'enabled': False, u'number': 13, u'perClientBandwidthLimitUp': 0, u'ssidAdminAccessible': False, u'bandSelection': u'Dual band operation', u'minBitrate': 11, u'authMode': u'open', u'name': u'Unconfigured SSID 14'})
skipping: [localhost] => (item={u'ipAssignmentMode': u'NAT mode', u'splashPage': u'None', u'perClientBandwidthLimitDown': 0, u'enabled': False, u'number': 14, u'perClientBandwidthLimitUp': 0, u'ssidAdminAccessible': False, u'bandSelection': u'Dual band operation', u'minBitrate': 11, u'authMode': u'open', u'name': u'Unconfigured SSID 15'})

TASK [GRAB L3 FIREWALL POLICIES] **************************************************************************************************
ok: [localhost]

TASK [debug] **********************************************************************************************************************
ok: [localhost] => {
    "msg": {
        "cache_control": "no-cache, no-store, max-age=0, must-revalidate",
        "changed": false,
        "connection": "close",
        "content_type": "application/json; charset=utf-8",
        "cookies": {},
        "cookies_string": "",
        "date": "Mon, 16 Jul 2018 12:50:47 GMT",
        "expires": "Fri, 01 Jan 1990 00:00:00 GMT",
        "failed": false,
        "json": [
            {
                "comment": "Wireless clients accessing LAN",
                "destCidr": "Local LAN",
                "destPort": "Any",
                "policy": "deny",
                "protocol": "Any"
            },
            {
                "comment": "Default rule",
                "destCidr": "Any",
                "destPort": "Any",
                "policy": "allow",
                "protocol": "Any"
            }
        ],
        "msg": "OK (unknown bytes)",
        "pragma": "no-cache",
        "redirected": true,
        "server": "nginx",
        "status": 200,
        "strict_transport_security": "max-age=15552000; includeSubDomains",
        "transfer_encoding": "chunked",
        "url": "https://n149.meraki.com/api/v0/networks/N_646829496481145366/ssids/0/l3FirewallRules",
        "vary": "Accept-Encoding",
        "x_frame_options": "sameorigin",
        "x_request_id": "d9ce7b26523317d4eb7f5be62a06f3d0",
        "x_runtime": "0.354606",
        "x_ua_compatible": "IE=Edge,chrome=1"
    }
}

PLAY RECAP ************************************************************************************************************************
localhost                  : ok=10   changed=0    unreachable=0    failed=0

Hopefully this was helpful and it opens your imagination to other things that you can accomplish via Ansible!

Tweet