Practice Questions

Test your knowledge on this topic in the ENAUTO Exam Trainer — 186 questions across 5 interactive modes.

Security Automation Across Controllers – ENAUTO v2.0

Exam Relevance

Topic 3.0 Controller-Based Network Automation (30%) — Security automation spans all four controllers. This guide fills the gaps left by the individual Deep Dives by covering Catalyst Center Command Runner for compliance, Meraki firewall/group policy APIs, and a cross-controller segmentation comparison.

Exam Topics Covered:
3.2 – Python to manage and monitor configurations
3.5 – Security automation (policy enforcement, compliance monitoring, segmentation)
3.6 – Troubleshoot REST API automation solutions
4.4 – Monitor network health (compliance as health dimension)

Table of Contents


1 – Security Automation Landscape

Security automation in the ENAUTO context means using controller APIs to enforce policies, monitor compliance, and segment networks — without manual CLI changes.

Each controller handles security differently:

┌─────────────────────────────────────────────────────────────────┐
│                   Security Automation Scope                     │
│                                                                 │
│  ┌──────────────┐  ┌──────────┐  ┌────────┐  ┌──────────────┐ │
│  │Catalyst Ctr  │  │  Meraki  │  │  ISE   │  │   SD-WAN     │ │
│  │              │  │          │  │        │  │              │ │
│  │• Command     │  │• L3/L7   │  │• SGTs  │  │• VPN seg.    │ │
│  │  Runner      │  │  FW rules│  │• SGACLs│  │• App policies│ │
│  │• SDA fabric  │  │• Group   │  │• ANC   │  │• Data policy │ │
│  │• App policy  │  │  policies│  │• pxGrid│  │• XSRF token  │ │
│  │• IP pools    │  │• Adaptive│  │• ERS   │  │              │ │
│  │              │  │  policy  │  │        │  │              │ │
│  └──────────────┘  └──────────┘  └────────┘  └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘

Security Function → Controller → API Endpoint

Security FunctionControllerAPI Endpoint
Run compliance CLI commandsCatalyst CenterPOST /dna/intent/api/v1/network-device-poller/cli/read-request
Firewall rules (L3)MerakiGET/PUT /networks/{netId}/appliance/firewall/l3FirewallRules
Group-based policyMerakiGET/POST /networks/{netId}/groupPolicies
Adaptive policy ACLsMerakiGET /organizations/{orgId}/adaptivePolicy/acls
SGT assignmentISEGET/POST /ers/config/sgt
SGACL policiesISEGET/POST /ers/config/sgacl
ANC quarantineISEPOST /ers/config/ancendpoint/apply
VPN segmentationSD-WANGET /template/policy/vsmart
Virtual networks (SDA)Catalyst CenterPOST /dna/intent/api/v1/business/sda/virtual-network
Application policyCatalyst CenterPOST /dna/intent/api/v1/app-policy

2 – Catalyst Center: Command Runner for Compliance

Exam Topic: 3.2, 3.5

Command Runner lets you execute read-only CLI commands on managed devices through the Catalyst Center API. This is the primary mechanism for automated compliance checks — run show commands across your fleet, parse output, and flag deviations.

Command Runner Is a 3-Step Async Process

  1. POST the CLI command → receive a taskId
  2. Poll the task → receive a fileId in the task progress field
  3. GET the file → receive the actual CLI output

Forgetting step 2 or 3 is a very common exam trap.

Step 1 – Authenticate and Run a Command

import requests
import time
import json
 
BASE_URL = "https://catalyst-center.example.com"
USERNAME = "admin"
PASSWORD = "C1sco12345"
 
# --- Step 0: Authenticate ---
auth_url = f"{BASE_URL}/dna/system/api/v1/auth/token"
auth_response = requests.post(auth_url, auth=(USERNAME, PASSWORD), verify=False)
token = auth_response.json()["Token"]
 
headers = {
    "X-Auth-Token": token,          # NOT "Authorization: Bearer ..."
    "Content-Type": "application/json"
}
 
# --- Step 1: Send CLI command via Command Runner ---
command_runner_url = f"{BASE_URL}/dna/intent/api/v1/network-device-poller/cli/read-request"
 
payload = {
    "commands": ["show access-lists"],
    "deviceUuids": [
        "aabb-1122-ccdd-3344",       # device UUID from /network-device
        "eeff-5566-7788-9900"
    ]
}
 
response = requests.post(command_runner_url, headers=headers, json=payload, verify=False)
task_id = response.json()["response"]["taskId"]
print(f"Task ID: {task_id}")

Step 2 – Poll the Task for Completion

# --- Step 2: Poll task until completion ---
task_url = f"{BASE_URL}/dna/intent/api/v1/task/{task_id}"
 
for attempt in range(10):
    task_response = requests.get(task_url, headers=headers, verify=False)
    task_data = task_response.json()["response"]
 
    if task_data.get("isError"):
        print(f"Task failed: {task_data.get('failureReason')}")
        break
 
    if "fileId" in task_data.get("progress", ""):
        # progress contains a JSON string with fileId
        progress = json.loads(task_data["progress"])
        file_id = progress["fileId"]
        print(f"File ID: {file_id}")
        break
 
    print(f"Attempt {attempt + 1}: Task still in progress...")
    time.sleep(2)

Step 3 – Retrieve the CLI Output

# --- Step 3: Get the actual CLI output ---
file_url = f"{BASE_URL}/dna/intent/api/v1/file/{file_id}"
file_response = requests.get(file_url, headers=headers, verify=False)
command_outputs = file_response.json()
 
for device_output in command_outputs:
    device_id = device_output["deviceUuid"]
    output = device_output["commandResponses"]["SUCCESS"].get("show access-lists", "")
    print(f"\n--- Device: {device_id} ---")
    print(output)

Full Example – AAA Compliance Check

import requests
import time
import json
 
BASE_URL = "https://catalyst-center.example.com"
 
def get_token(username, password):
    """Authenticate and return X-Auth-Token."""
    url = f"{BASE_URL}/dna/system/api/v1/auth/token"
    resp = requests.post(url, auth=(username, password), verify=False)
    resp.raise_for_status()
    return resp.json()["Token"]
 
def run_command(headers, device_uuids, commands):
    """Run CLI commands via Command Runner and return parsed output."""
    # Step 1: Submit command
    url = f"{BASE_URL}/dna/intent/api/v1/network-device-poller/cli/read-request"
    payload = {"commands": commands, "deviceUuids": device_uuids}
    resp = requests.post(url, headers=headers, json=payload, verify=False)
    task_id = resp.json()["response"]["taskId"]
 
    # Step 2: Poll task
    task_url = f"{BASE_URL}/dna/intent/api/v1/task/{task_id}"
    for _ in range(15):
        task_resp = requests.get(task_url, headers=headers, verify=False)
        task_data = task_resp.json()["response"]
        if task_data.get("isError"):
            raise Exception(f"Task failed: {task_data.get('failureReason')}")
        progress = task_data.get("progress", "")
        if "fileId" in progress:
            file_id = json.loads(progress)["fileId"]
            break
        time.sleep(2)
    else:
        raise TimeoutError("Task did not complete within timeout")
 
    # Step 3: Get output
    file_url = f"{BASE_URL}/dna/intent/api/v1/file/{file_id}"
    return requests.get(file_url, headers=headers, verify=False).json()
 
# --- Main compliance check ---
token = get_token("admin", "C1sco12345")
headers = {"X-Auth-Token": token, "Content-Type": "application/json"}
 
# Get all devices
devices_url = f"{BASE_URL}/dna/intent/api/v1/network-device"
devices = requests.get(devices_url, headers=headers, verify=False).json()["response"]
device_uuids = [d["id"] for d in devices if d["family"] == "Switches and Hubs"]
 
# Run AAA compliance check
outputs = run_command(headers, device_uuids, ["show run | section aaa"])
 
# Parse and flag non-compliant devices
REQUIRED_AAA_LINES = [
    "aaa new-model",
    "aaa authentication login default group tacacs+",
    "aaa authorization exec default group tacacs+"
]
 
non_compliant = []
for device_output in outputs:
    device_id = device_output["deviceUuid"]
    aaa_config = device_output["commandResponses"]["SUCCESS"].get(
        "show run | section aaa", ""
    )
    missing = [line for line in REQUIRED_AAA_LINES if line not in aaa_config]
    if missing:
        non_compliant.append({
            "deviceUuid": device_id,
            "missing_config": missing
        })
 
print(f"\n=== Compliance Report ===")
print(f"Total devices checked: {len(outputs)}")
print(f"Non-compliant devices: {len(non_compliant)}")
for nc in non_compliant:
    print(f"  Device {nc['deviceUuid']}: missing {nc['missing_config']}")

Command Runner Key Points

  • Only read-only commands are allowed (show, display) — no configuration changes
  • Device UUIDs come from GET /dna/intent/api/v1/network-device
  • The progress field in the task response is a JSON string that must be parsed with json.loads()
  • Maximum 5 commands per request, maximum 20 devices per request

3 – Catalyst Center: Network Access Control

Exam Topic: 3.5

Catalyst Center with SDA (Software-Defined Access) provides network segmentation through virtual networks, IP pools, and fabric sites. These are configured through the /dna/intent/api/v1/business/sda/ endpoints.

Key SDA API Endpoints

SDA Endpoint Reference

ResourceMethodEndpoint
Virtual NetworkGET/POST/DELETE/dna/intent/api/v1/business/sda/virtual-network
Fabric SiteGET/POST/DELETE/dna/intent/api/v1/business/sda/fabric-site
IP Pool (in fabric)GET/POST/DELETE/dna/intent/api/v1/business/sda/virtualnetwork/ippool
Auth ProfileGET/POST/dna/intent/api/v1/business/sda/authentication-profile
Border DeviceGET/POST/DELETE/dna/intent/api/v1/business/sda/border-device

Python Example – Create a Virtual Network for Segmentation

import requests
import time
 
BASE_URL = "https://catalyst-center.example.com"
 
# Authenticate (reuse get_token() from Section 2)
token = get_token("admin", "C1sco12345")
headers = {"X-Auth-Token": token, "Content-Type": "application/json"}
 
# --- Create a Virtual Network ---
vn_url = f"{BASE_URL}/dna/intent/api/v1/business/sda/virtual-network"
 
payload = {
    "virtualNetworkName": "GUEST_VN",
    "isGuestVirtualNetwork": True
}
 
response = requests.post(vn_url, headers=headers, json=payload, verify=False)
task_id = response.json()["response"]["taskId"]  # Async — returns taskId
print(f"VN creation task: {task_id}")
 
# Poll task for completion (same pattern as Command Runner)
task_url = f"{BASE_URL}/dna/intent/api/v1/task/{task_id}"
for _ in range(15):
    task_resp = requests.get(task_url, headers=headers, verify=False)
    task_data = task_resp.json()["response"]
    if task_data.get("isError"):
        print(f"Failed: {task_data.get('failureReason')}")
        break
    if task_data.get("endTime"):
        print("Virtual network created successfully")
        break
    time.sleep(2)

Application Policy

Catalyst Center also supports application-aware policies via /dna/intent/api/v1/app-policy. These tie QoS and security policies to application categories (e.g., block peer-to-peer traffic, prioritize voice).

# --- Get existing application policies ---
app_policy_url = f"{BASE_URL}/dna/intent/api/v1/app-policy"
response = requests.get(app_policy_url, headers=headers, verify=False)
policies = response.json()["response"]
 
for policy in policies:
    print(f"Policy: {policy['name']}")
    print(f"  Consumer: {policy.get('consumer', {}).get('scalableGroup', [])}")
    print(f"  Producer: {policy.get('producer', {}).get('scalableGroup', [])}")

SDA API Calls Are Asynchronous

Like all Catalyst Center write operations, SDA endpoint POSTs return a taskId. You must poll the task to confirm success. Never assume a 200 on the initial POST means the resource is created.


4 – Meraki: MX Firewall Rules

Exam Topic: 3.2, 3.5

Meraki MX appliances expose L3 firewall rules through a simple REST API. The critical concept: PUT replaces the entire rule list — it does not append.

Get Current L3 Firewall Rules

import requests
 
API_KEY = "your_meraki_api_key"
BASE_URL = "https://api.meraki.com/api/v1"
NETWORK_ID = "L_123456789012345678"
 
headers = {
    "X-Cisco-Meraki-API-Key": API_KEY,
    "Content-Type": "application/json"
}
 
# --- Get current L3 firewall rules ---
fw_url = f"{BASE_URL}/networks/{NETWORK_ID}/appliance/firewall/l3FirewallRules"
response = requests.get(fw_url, headers=headers)
rules = response.json()["rules"]
 
for i, rule in enumerate(rules):
    print(f"Rule {i}: {rule['policy']} {rule['protocol']} "
          f"src={rule['srcCidr']} dst={rule['destCidr']} port={rule['destPort']}")

Add a Deny Rule (Safely)

PUT Replaces the Entire Rule List

If you PUT only your new rule, all existing rules will be deleted. You must:

  1. GET current rules
  2. Insert your new rule into the list
  3. PUT the complete updated list

The last rule is always the implicit “Default rule” (allow all) — do not include it in your PUT payload; Meraki re-adds it automatically.

import requests
import time
 
API_KEY = "your_meraki_api_key"
BASE_URL = "https://api.meraki.com/api/v1"
NETWORK_ID = "L_123456789012345678"
 
headers = {
    "X-Cisco-Meraki-API-Key": API_KEY,
    "Content-Type": "application/json"
}
 
# Step 1: GET current rules
fw_url = f"{BASE_URL}/networks/{NETWORK_ID}/appliance/firewall/l3FirewallRules"
response = requests.get(fw_url, headers=headers)
current_rules = response.json()["rules"]
 
# Step 2: Remove the default "allow all" rule (Meraki re-adds it automatically)
custom_rules = [r for r in current_rules if r.get("comment") != "Default rule"]
 
# Step 3: Insert new deny rule at the top (rules are evaluated top-down)
new_rule = {
    "comment": "Block torrent traffic",
    "policy": "deny",
    "protocol": "tcp",
    "srcCidr": "Any",
    "srcPort": "Any",
    "destCidr": "Any",
    "destPort": "6881-6889"
}
custom_rules.insert(0, new_rule)
 
# Step 4: PUT the complete rule list
put_response = requests.put(
    fw_url,
    headers=headers,
    json={"rules": custom_rules}
)
 
if put_response.status_code == 200:
    print("Firewall rules updated successfully")
    print(f"Total rules: {len(put_response.json()['rules'])}")
else:
    print(f"Error: {put_response.status_code}{put_response.text}")

Using the Meraki SDK

import meraki
 
dashboard = meraki.DashboardAPI(API_KEY)
 
# Get rules
rules = dashboard.appliance.getNetworkApplianceFirewallL3FirewallRules(NETWORK_ID)
 
# Add a rule (still must include all existing rules)
custom_rules = [r for r in rules["rules"] if r.get("comment") != "Default rule"]
custom_rules.insert(0, {
    "comment": "Block torrent traffic",
    "policy": "deny",
    "protocol": "tcp",
    "srcCidr": "Any",
    "srcPort": "Any",
    "destCidr": "Any",
    "destPort": "6881-6889"
})
 
dashboard.appliance.updateNetworkApplianceFirewallL3FirewallRules(
    NETWORK_ID, rules=custom_rules
)

Rate Limiting Reminder

Meraki enforces 5 API calls per second per organization. The SDK handles 429 retries automatically. With requests, implement exponential backoff or add time.sleep(0.2) between calls.


5 – Meraki: Group Policies

Exam Topic: 3.5

Group policies in Meraki apply a set of restrictions (bandwidth limits, firewall rules, content filtering, scheduling) to groups of clients. Clients are assigned to a group policy either manually or via RADIUS (ISE integration).

Create a Group Policy with Bandwidth Limits

import requests
 
API_KEY = "your_meraki_api_key"
BASE_URL = "https://api.meraki.com/api/v1"
NETWORK_ID = "L_123456789012345678"
 
headers = {
    "X-Cisco-Meraki-API-Key": API_KEY,
    "Content-Type": "application/json"
}
 
# --- Create a group policy ---
gp_url = f"{BASE_URL}/networks/{NETWORK_ID}/groupPolicies"
 
payload = {
    "name": "Guest-Restricted",
    "scheduling": {"enabled": False},
    "bandwidth": {
        "settings": "custom",
        "bandwidthLimits": {
            "limitUp": 1000,       # 1 Mbps up
            "limitDown": 5000      # 5 Mbps down
        }
    },
    "firewallAndTrafficShaping": {
        "settings": "custom",
        "l3FirewallRules": [
            {
                "comment": "Block internal network",
                "policy": "deny",
                "protocol": "any",
                "destCidr": "10.0.0.0/8",
                "destPort": "Any"
            }
        ],
        "trafficShapingRules": []
    },
    "contentFiltering": {
        "allowedUrlPatterns": {"settings": "network default"},
        "blockedUrlPatterns": {"settings": "network default"},
        "blockedUrlCategories": {"settings": "network default"}
    }
}
 
response = requests.post(gp_url, headers=headers, json=payload)
 
if response.status_code == 201:
    group_policy = response.json()
    print(f"Group policy created: {group_policy['name']} (ID: {group_policy['groupPolicyId']})")
else:
    print(f"Error: {response.status_code}{response.text}")

Apply a Group Policy to a Client

# --- Assign group policy to a specific client ---
client_mac = "aa:bb:cc:dd:ee:ff"
client_url = f"{BASE_URL}/networks/{NETWORK_ID}/clients/{client_mac}/policy"
 
policy_payload = {
    "devicePolicy": "Group policy",
    "groupPolicyId": group_policy["groupPolicyId"]
}
 
resp = requests.put(client_url, headers=headers, json=policy_payload)
 
if resp.status_code == 200:
    print(f"Client {client_mac} assigned to policy '{group_policy['name']}'")

Group Policy Elements

A single group policy can combine:

  • Bandwidth limits (up/down per client)
  • L3 firewall rules (within the policy scope)
  • L7 firewall rules (application-layer blocking)
  • Content filtering (URL categories, allowed/blocked patterns)
  • Scheduling (time-based access)
  • VLAN tagging (override client VLAN)
  • Bonjour forwarding rules

6 – Meraki: Adaptive Policy (SGTs in Meraki)

Exam Topic: 3.5

Meraki Adaptive Policy is the Meraki equivalent of Cisco TrustSec SGTs. It allows micro-segmentation within Meraki networks using security groups and ACLs that define allowed traffic between groups.

Key Concepts

TrustSec (ISE) TermMeraki Adaptive Policy Term
SGT (Security Group Tag)Adaptive Policy Group
SGACLAdaptive Policy ACL
TrustSec MatrixAdaptive Policy Settings
SXPNot applicable (cloud-native)

Get Adaptive Policy ACLs

import requests
 
API_KEY = "your_meraki_api_key"
BASE_URL = "https://api.meraki.com/api/v1"
ORG_ID = "123456"
 
headers = {
    "X-Cisco-Meraki-API-Key": API_KEY,
    "Content-Type": "application/json"
}
 
# --- Get adaptive policy ACLs ---
acl_url = f"{BASE_URL}/organizations/{ORG_ID}/adaptivePolicy/acls"
response = requests.get(acl_url, headers=headers)
acls = response.json()
 
for acl in acls:
    print(f"ACL: {acl['name']} (ID: {acl['aclId']})")
    for rule in acl.get("rules", []):
        print(f"  {rule['policy']} {rule['protocol']} "
              f"srcPort={rule.get('srcPort', 'Any')} dstPort={rule.get('dstPort', 'Any')}")

Get Adaptive Policy Groups

# --- Get adaptive policy groups (equivalent of SGTs) ---
groups_url = f"{BASE_URL}/organizations/{ORG_ID}/adaptivePolicy/groups"
response = requests.get(groups_url, headers=headers)
groups = response.json()
 
for group in groups:
    print(f"Group: {group['name']} (SGT: {group.get('sgt', 'N/A')}) "
          f"ID: {group['groupId']}")

Get Adaptive Policy Settings (the Matrix)

# --- Get the adaptive policy matrix (bindings between groups) ---
policies_url = f"{BASE_URL}/organizations/{ORG_ID}/adaptivePolicy/policies"
response = requests.get(policies_url, headers=headers)
policies = response.json()
 
for policy in policies:
    src = policy["sourceGroup"]["name"]
    dst = policy["destinationGroup"]["name"]
    acls = [a["name"] for a in policy.get("acls", [])]
    print(f"{src} -> {dst}: ACLs={acls}")

ISE Integration with Meraki Adaptive Policy

When ISE is integrated with Meraki, SGT values assigned by ISE are mapped to Meraki adaptive policy groups. The SGT number in Meraki corresponds directly to the TrustSec SGT value in ISE, enabling consistent policy across wired (ISE-managed) and wireless (Meraki-managed) networks.


7 – Cross-Controller Segmentation Comparison

Exam Topic: 3.5

This is one of the most important comparison tables for the exam. Segmentation is implemented differently across all four controllers:

Segmentation Comparison Matrix

AspectCatalyst Center (SDA)ISE (TrustSec)Meraki (Adaptive Policy)SD-WAN (VPN Segmentation)
Segmentation modelVirtual Networks + SGTsSGTs + SGACLsAdaptive Policy Groups + ACLsVPN IDs (0-65530)
Identity sourceISE integration requiredLocal DB + AD/LDAP + certificatesDashboard manual or RADIUSNot identity-based
Enforcement pointFabric edge switchesAny TrustSec-capable switchMR access points, MS switches, MX firewallsvEdge / cEdge routers
API for automation/dna/intent/api/v1/business/sda//ers/config/sgt, /ers/config/sgacl/organizations/{orgId}/adaptivePolicy//template/policy/vsmart
GranularityMicro-segmentation (per-user/device)Micro-segmentation (per-user/device)Macro + group-levelVPN-level (coarse)
Auth methodX-Auth-Token (token exchange)Basic Auth (every request)X-Cisco-Meraki-API-Key (every request)JSESSIONID cookie
Write behaviorAsynchronous (taskId polling)SynchronousSynchronousAsynchronous
ScaleCampus/branch fabricEnterprise-wideCloud-managed sitesWAN overlay
DependencyRequires ISE for identityStandalone or with Catalyst CenterStandalone (optional ISE)Standalone vManage

When to Use Which

Decision Tree for Segmentation:

Q: Is it a campus/branch fabric?
  → YES: Catalyst Center SDA + ISE TrustSec

Q: Is it a cloud-managed Meraki site?
  → YES: Meraki Adaptive Policy (with optional ISE for identity)

Q: Is it a WAN overlay?
  → YES: SD-WAN VPN segmentation

Q: Do you need enterprise-wide SGT policy regardless of fabric type?
  → YES: ISE TrustSec (the central policy engine)

8 – Compliance Monitoring Patterns

Exam Topic: 3.5, 4.4

Pattern 1 – Scheduled Compliance Checks with Command Runner

Run Command Runner on a cron schedule to verify device configurations match expected baselines.

# Pseudocode for a scheduled compliance workflow
#
# 1. Get all managed devices from Catalyst Center
# 2. For each batch of 20 devices:
#    a. Run Command Runner: "show run | section aaa"
#    b. Parse output against compliance baseline
#    c. Flag non-compliant devices
# 3. Generate compliance report
# 4. (Optional) Open ServiceNow ticket for non-compliant devices
 
import schedule
import time
 
def compliance_check():
    token = get_token("admin", "C1sco12345")
    headers = {"X-Auth-Token": token, "Content-Type": "application/json"}
 
    devices = get_all_devices(headers)
    # Process in batches of 20 (Command Runner limit)
    for batch in chunks(devices, 20):
        uuids = [d["id"] for d in batch]
        outputs = run_command(headers, uuids, ["show run | section aaa"])
        check_compliance(outputs)
 
# Run every day at 2:00 AM
schedule.every().day.at("02:00").do(compliance_check)
 
while True:
    schedule.run_pending()
    time.sleep(60)

Pattern 2 – Webhook-Triggered Re-Validation

When a configuration change event fires, re-check the changed device immediately.

from flask import Flask, request
 
app = Flask(__name__)
 
@app.route("/webhook/config-change", methods=["POST"])
def config_change_handler():
    event = request.json
    device_id = event.get("deviceId")
 
    # Re-run compliance check on the changed device
    token = get_token("admin", "C1sco12345")
    headers = {"X-Auth-Token": token, "Content-Type": "application/json"}
 
    outputs = run_command(headers, [device_id], ["show run | section aaa"])
    violations = check_compliance(outputs)
 
    if violations:
        # Trigger remediation or alert
        send_alert(f"Device {device_id} is non-compliant after config change")
 
    return {"status": "processed"}, 200

Pattern 3 – ISE ANC (Adaptive Network Control) for Quarantine

ISE ANC lets you quarantine, un-quarantine, or shutdown endpoints via API.

ISE ERS API Must Be Enabled Separately

The ERS (External RESTful Services) API is disabled by default on ISE. Enable it at: Administration → Settings → API Settings → ERS (Read/Write) Also ensure your API user has the ERS Admin role.

import requests
 
ISE_BASE = "https://ise.example.com:9060"
ISE_USER = "ersadmin"
ISE_PASS = "C1sco12345"
 
headers = {
    "Content-Type": "application/json",
    "Accept": "application/json"           # ISE defaults to XML — always set this
}
 
# --- Quarantine an endpoint ---
anc_url = f"{ISE_BASE}/ers/config/ancendpoint/apply"
 
payload = {
    "OperationAdditionalData": {
        "additionalData": [
            {"name": "macAddress", "value": "AA:BB:CC:DD:EE:FF"},
            {"name": "policyName", "value": "ANC_Quarantine"}
        ]
    }
}
 
response = requests.put(
    anc_url,
    auth=(ISE_USER, ISE_PASS),
    headers=headers,
    json=payload,
    verify=False
)
 
if response.status_code == 204:
    print("Endpoint quarantined successfully")
elif response.status_code == 403:
    print("ERROR: ERS API not enabled or user lacks ERS Admin role")
else:
    print(f"Unexpected status: {response.status_code}{response.text}")
# --- Un-quarantine (clear) an endpoint ---
clear_url = f"{ISE_BASE}/ers/config/ancendpoint/clear"
 
clear_payload = {
    "OperationAdditionalData": {
        "additionalData": [
            {"name": "macAddress", "value": "AA:BB:CC:DD:EE:FF"},
            {"name": "policyName", "value": "ANC_Quarantine"}
        ]
    }
}
 
response = requests.put(
    clear_url,
    auth=(ISE_USER, ISE_PASS),
    headers=headers,
    json=clear_payload,
    verify=False
)
 
if response.status_code == 204:
    print("Endpoint un-quarantined successfully")

Pattern 4 – Meraki Alerts API for Security Events

import requests
 
API_KEY = "your_meraki_api_key"
BASE_URL = "https://api.meraki.com/api/v1"
NETWORK_ID = "L_123456789012345678"
 
headers = {
    "X-Cisco-Meraki-API-Key": API_KEY,
    "Content-Type": "application/json"
}
 
# --- Get recent security events ---
events_url = f"{BASE_URL}/networks/{NETWORK_ID}/events"
params = {
    "productType": "appliance",
    "includedEventTypes": ["ids_alerted"],   # IDS/IPS alerts
    "perPage": 50
}
 
response = requests.get(events_url, headers=headers, params=params)
events = response.json()["events"]
 
for event in events:
    print(f"[{event['occurredAt']}] {event['type']}: {event['description']}")
    print(f"  Client: {event.get('clientMac', 'N/A')}")

9 – Common Patterns and Gotchas

Command Runner Is Async (3-Step Process)

Many exam questions test whether you know the full flow:

  1. POST /cli/read-requesttaskId
  2. GET /task/{taskId} → poll until progress contains fileId
  3. GET /file/{fileId} → actual CLI output

If your script gets the taskId but no CLI output, you forgot steps 2 and/or 3.

Meraki Firewall Rules Are REPLACE, Not APPEND

PUT /l3FirewallRules replaces the entire rule list. If you PUT a single rule, all other rules are deleted. Always GET first, merge, then PUT.

ISE ERS Must Be Enabled Separately

The ISE ERS API (/ers/config/...) is disabled by default. It is a separate system from the ISE Open API (/api/...). Enable ERS at Administration → Settings → API Settings.

ISE Defaults to XML Responses

If you forget Accept: application/json in your ISE ERS requests, you get XML back. This breaks response.json() with a JSONDecodeError.

Always Backup Before Modifying Firewall Rules

Before any PUT to Meraki firewall rules or ISE SGACLs, save the current state:

import json
# Backup current rules
current_rules = requests.get(fw_url, headers=headers).json()
with open("firewall_backup.json", "w") as f:
    json.dump(current_rules, f, indent=2)

SD-WAN Writes Require XSRF Token

SD-WAN (vManage) requires a XSRF token for any write operation in addition to the JSESSIONID session cookie:

# Get XSRF token after authentication
token_resp = requests.get(
    f"{VMANAGE_URL}/dataservice/client/token",
    cookies=session_cookies, verify=False
)
xsrf_token = token_resp.text
headers["X-XSRF-TOKEN"] = xsrf_token

10 – Exam-Style Scenarios

Scenario 1 – Meraki Firewall Rules Disappear

A network engineer runs the following script to add a deny rule to a Meraki MX firewall. After execution, the MX has only one rule (the new deny rule) and the default allow rule — all previous rules are gone. What went wrong?

new_rule = {"comment": "Block SSH", "policy": "deny",
            "protocol": "tcp", "destCidr": "Any",
            "srcCidr": "Any", "destPort": "22"}
requests.put(fw_url, headers=headers,
             json={"rules": [new_rule]})

Scenario 2 – Command Runner Returns taskId But No Output

A Python script uses Catalyst Center Command Runner to run show access-lists. The POST returns successfully with a taskId, but the script never gets the CLI output. The script only contains:

resp = requests.post(cmd_url, headers=headers, json=payload)
task_id = resp.json()["response"]["taskId"]
print(f"Done! Task: {task_id}")

What is missing?


Scenario 3 – ISE ANC Quarantine Fails with 403

A script attempts to quarantine an endpoint via ISE ANC API at PUT /ers/config/ancendpoint/apply but receives a 403 Forbidden response. The credentials are correct and the user can log into the ISE GUI. What are two possible causes?


Scenario 4 – Segmentation via VPN IDs

A network architect needs to segment traffic between departments across the WAN. The organization uses Cisco SD-WAN with vManage. Which segmentation model should they use, and what is the primary identifier?


Scenario 5 – Identify the Error in This Compliance Script

The following script attempts to run a compliance check via Catalyst Center but fails with 401 Unauthorized on the Command Runner call. Identify the error.

# Authenticate
auth_resp = requests.post(
    f"{BASE}/dna/system/api/v1/auth/token",
    auth=("admin", "password"), verify=False
)
token = auth_resp.json()["Token"]
 
# Run command
headers = {
    "Authorization": f"Bearer {token}",
    "Content-Type": "application/json"
}
resp = requests.post(cmd_runner_url, headers=headers, json=payload)

Scenario 6 – ISE Returns XML Instead of JSON

A script calls GET /ers/config/sgt on ISE and response.json() throws a JSONDecodeError. The request returns status 200 and data is present in response.text. What is wrong?


Scenario 7 – Cross-Controller Question

Match each segmentation technology to its controller:

  1. Virtual Networks
  2. SGTs/SGACLs
  3. Adaptive Policy Groups
  4. VPN IDs

11 – Quick Reference Cheat Sheet

Security API Endpoints per Controller

CATALYST CENTER
  Auth:     POST /dna/system/api/v1/auth/token     (Basic Auth → Token)
  Command:  POST /dna/intent/api/v1/network-device-poller/cli/read-request
  Task:     GET  /dna/intent/api/v1/task/{taskId}
  File:     GET  /dna/intent/api/v1/file/{fileId}
  SDA VN:   POST /dna/intent/api/v1/business/sda/virtual-network
  App Pol:  POST /dna/intent/api/v1/app-policy

MERAKI
  Auth:     X-Cisco-Meraki-API-Key header (every request)
  L3 FW:    GET/PUT /networks/{netId}/appliance/firewall/l3FirewallRules
  Group:    GET/POST /networks/{netId}/groupPolicies
  Adaptive: GET /organizations/{orgId}/adaptivePolicy/acls
  Events:   GET /networks/{netId}/events

ISE
  Auth:     Basic Auth (every request)
  SGT:      GET/POST /ers/config/sgt
  SGACL:    GET/POST /ers/config/sgacl
  ANC:      PUT /ers/config/ancendpoint/apply
  ANC clr:  PUT /ers/config/ancendpoint/clear

SD-WAN (vManage)
  Auth:     POST /j_security_check → JSESSIONID cookie
  XSRF:     GET  /dataservice/client/token
  Policy:   GET  /template/policy/vsmart
  VPN:      GET  /template/policy/definition/data

Auth Method Quick Reference

ControllerAuth MethodHeader / Mechanism
Catalyst CenterToken exchangeX-Auth-Token: <token>
MerakiAPI keyX-Cisco-Meraki-API-Key: <key>
ISE (ERS)Basic AuthAuthorization: Basic <b64>
SD-WANSession + XSRFJSESSIONID cookie + X-XSRF-TOKEN header

Sync vs Async per Controller

ControllerRead OperationsWrite Operations
Catalyst CenterSynchronousAsynchronous (taskId polling)
MerakiSynchronousSynchronous (immediate response)
ISESynchronousSynchronous
SD-WANSynchronousAsynchronous (check action status)

Key Ansible Collections for Security Automation

cisco.dnac          — Catalyst Center modules (dnac_command_runner, dnac_sda_*)
cisco.meraki        — Meraki modules (meraki_mx_l3_firewall, meraki_group_policy)
cisco.ise           — ISE modules (ise_sgt, ise_sgacl, ise_anc_endpoint)
cisco.sdwan         — SD-WAN modules (sdwan_policy_definition)

ENAUTO security segmentation compliance cross-controller exam-topic-3-5

See Also