Downloadable ACLs API

Overview

Downloadable ACLs (dACLs) are pushed to network devices during authorization. They provide fine-grained access control per endpoint.

Base URL

/ers/config/downloadableacl

Methods

GET, POST, PUT, DELETE

Key Fields

name, dacl (content), daclType

Setup

dsource d000 dev/network
ISE_HOST="${ISE_PAN_IP}"
ISE_AUTH="${ISE_API_USER}:${ISE_API_PASS}"
BASE_URL="https://${ISE_HOST}:9060/ers/config"

List All dACLs

netapi
netapi ise get-dacls
curl
# List all dACLs
curl -sk -u "${ISE_AUTH}" \
  "${BASE_URL}/downloadableacl" \
  -H "Accept: application/json" | jq '.SearchResult.resources[] | {name, id}'

Get dACL by Name

netapi
netapi ise get-dacl "Linux-Permit-AD-Only"
curl
# Get dACL by name (includes ACL content)
DACL_NAME="Linux-Permit-AD-Only"
curl -sk -u "${ISE_AUTH}" \
  "${BASE_URL}/downloadableacl/name/${DACL_NAME}" \
  -H "Accept: application/json" | jq '.DownloadableAcl'

Get Content Only

# Get just the ACL content
DACL_NAME="Linux-Permit-AD-Only"
curl -sk -u "${ISE_AUTH}" \
  "${BASE_URL}/downloadableacl/name/${DACL_NAME}" \
  -H "Accept: application/json" | jq -r '.DownloadableAcl.dacl'

Create dACL

Permit All

# Create permit-all dACL
curl -sk -u "${ISE_AUTH}" \
  "${BASE_URL}/downloadableacl" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -X POST \
  -d '{
    "DownloadableAcl": {
      "name": "Linux-Permit-All",
      "description": "Full network access for trusted Linux workstations",
      "dacl": "permit ip any any",
      "daclType": "IPV4"
    }
  }'

Zero-Trust AD-Only

# Create AD-only dACL (zero-trust)
# Variables for AD DC
AD_DC_IP="10.50.1.50"

cat > /tmp/dacl-content.txt << EOF
remark ### AD Authentication - Kerberos ###
permit udp any host ${AD_DC_IP} eq 88
permit tcp any host ${AD_DC_IP} eq 88
remark ### LDAP/LDAPS ###
permit tcp any host ${AD_DC_IP} eq 389
permit tcp any host ${AD_DC_IP} eq 636
remark ### DNS ###
permit udp any host ${AD_DC_IP} eq 53
permit tcp any host ${AD_DC_IP} eq 53
remark ### Global Catalog ###
permit tcp any host ${AD_DC_IP} eq 3268
permit tcp any host ${AD_DC_IP} eq 3269
remark ### NTP ###
permit udp any host ${AD_DC_IP} eq 123
remark ### SMB for GPO ###
permit tcp any host ${AD_DC_IP} eq 445
remark ### DENY ALL OTHER ###
deny ip any any
EOF

DACL_CONTENT=$(cat /tmp/dacl-content.txt | jq -Rs .)

curl -sk -u "${ISE_AUTH}" \
  "${BASE_URL}/downloadableacl" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -X POST \
  -d '{
    "DownloadableAcl": {
      "name": "Linux-Permit-AD-Only",
      "description": "Zero-trust: Only AD authentication traffic",
      "dacl": '"${DACL_CONTENT}"',
      "daclType": "IPV4"
    }
  }'

MAB Onboarding

# Create MAB onboarding dACL (DHCP + limited access)
curl -sk -u "${ISE_AUTH}" \
  "${BASE_URL}/downloadableacl" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -X POST \
  -d '{
    "DownloadableAcl": {
      "name": "MAB-Onboard-DACL",
      "description": "Limited access for MAB onboarding",
      "dacl": "remark ### DHCP ###\npermit udp any any eq 67\npermit udp any any eq 68\nremark ### DNS ###\npermit udp any any eq 53\nremark ### ISE for posture ###\npermit tcp any host 10.50.1.20 eq 8443\nremark ### DENY ALL OTHER ###\ndeny ip any any",
      "daclType": "IPV4"
    }
  }'

Using netapi

# Create dACL from file
netapi ise create-dacl "Linux-Permit-AD-Only" \
  --file /tmp/dacl-content.txt \
  --description "Zero-trust: Only AD authentication traffic"

# Or inline
netapi ise create-dacl "Test-DACL" \
  --content "permit ip any any" \
  --description "Test permit all"

Update dACL

# Update dACL content
DACL_NAME="Linux-Permit-AD-Only"

# Get current dACL
DACL=$(curl -sk -u "${ISE_AUTH}" \
  "${BASE_URL}/downloadableacl/name/${DACL_NAME}" \
  -H "Accept: application/json")

DACL_ID=$(echo "$DACL" | jq -r '.DownloadableAcl.id')

# New content with additional rule
NEW_CONTENT="remark ### UPDATED ###
permit udp any host 10.50.1.50 eq 88
permit tcp any host 10.50.1.50 eq 88
permit tcp any host 10.50.1.50 eq 389
permit tcp any host 10.50.1.50 eq 636
permit udp any host 10.50.1.50 eq 53
permit tcp any host 10.50.1.50 eq 53
permit tcp any host 10.50.1.50 eq 3268
permit udp any host 10.50.1.50 eq 123
permit tcp any host 10.50.1.50 eq 445
remark ### Added: ICMP for troubleshooting ###
permit icmp any host 10.50.1.50
deny ip any any"

curl -sk -u "${ISE_AUTH}" \
  "${BASE_URL}/downloadableacl/${DACL_ID}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -X PUT \
  -d '{
    "DownloadableAcl": {
      "id": "'"${DACL_ID}"'",
      "name": "'"${DACL_NAME}"'",
      "dacl": "'"$(echo "$NEW_CONTENT" | jq -Rs .)"'",
      "daclType": "IPV4"
    }
  }'

Delete dACL

netapi
netapi ise delete-dacl "Test-DACL"
curl
# Delete dACL (must not be in use by any authz profile)
DACL_ID="abc123-def456"
curl -sk -u "${ISE_AUTH}" \
  "${BASE_URL}/downloadableacl/${DACL_ID}" \
  -X DELETE

Common Patterns

Compare dACLs

# Compare two dACLs
DACL1="Linux-Permit-AD-Only"
DACL2="Linux-Permit-AD-Only-v2"

echo "=== ${DACL1} ==="
curl -sk -u "${ISE_AUTH}" \
  "${BASE_URL}/downloadableacl/name/${DACL1}" \
  -H "Accept: application/json" | jq -r '.DownloadableAcl.dacl'

echo ""
echo "=== ${DACL2} ==="
curl -sk -u "${ISE_AUTH}" \
  "${BASE_URL}/downloadableacl/name/${DACL2}" \
  -H "Accept: application/json" | jq -r '.DownloadableAcl.dacl'

# Diff them
diff <(curl -sk -u "${ISE_AUTH}" \
  "${BASE_URL}/downloadableacl/name/${DACL1}" \
  -H "Accept: application/json" | jq -r '.DownloadableAcl.dacl') \
  <(curl -sk -u "${ISE_AUTH}" \
  "${BASE_URL}/downloadableacl/name/${DACL2}" \
  -H "Accept: application/json" | jq -r '.DownloadableAcl.dacl')

Find Unused dACLs

# Find dACLs not used by any authz profile
# Get all dACL names
DACLS=$(curl -sk -u "${ISE_AUTH}" \
  "${BASE_URL}/downloadableacl" \
  -H "Accept: application/json" | jq -r '.SearchResult.resources[].name')

# Get all dACLs used in profiles
USED_DACLS=$(curl -sk -u "${ISE_AUTH}" \
  "${BASE_URL}/authorizationprofile" \
  -H "Accept: application/json" | \
  jq -r '.SearchResult.resources[].id' | while read ID; do
    curl -sk -u "${ISE_AUTH}" \
      "${BASE_URL}/authorizationprofile/${ID}" \
      -H "Accept: application/json" | jq -r '.AuthorizationProfile.daclName // empty'
  done | sort -u)

# Find unused
echo "Unused dACLs:"
echo "$DACLS" | while read DACL; do
  if ! echo "$USED_DACLS" | grep -q "^${DACL}$"; then
    echo "  $DACL"
  fi
done

Export All dACLs

# Export all dACLs to files
mkdir -p dacl-backup

curl -sk -u "${ISE_AUTH}" \
  "${BASE_URL}/downloadableacl" \
  -H "Accept: application/json" | jq -r '.SearchResult.resources[] | "\(.id) \(.name)"' | \
  while read ID NAME; do
    SAFE_NAME=$(echo "$NAME" | tr ' /' '_')
    curl -sk -u "${ISE_AUTH}" \
      "${BASE_URL}/downloadableacl/${ID}" \
      -H "Accept: application/json" | jq -r '.DownloadableAcl.dacl' > "dacl-backup/${SAFE_NAME}.acl"
    echo "Exported: ${NAME}"
  done

Validate Syntax

# Validate dACL syntax before creating (basic check)
validate_dacl() {
  local content="$1"
  local errors=0

  while IFS= read -r line; do
    # Skip empty lines and remarks
    [[ -z "$line" || "$line" =~ ^remark ]] && continue

    # Check for valid ACL format
    if ! [[ "$line" =~ ^(permit|deny)[[:space:]]+(ip|tcp|udp|icmp) ]]; then
      echo "Invalid line: $line"
      ((errors++))
    fi
  done <<< "$content"

  return $errors
}

# Example usage
DACL_CONTENT="permit ip any any
deny tcp any any eq 22
invalid line here"

if validate_dacl "$DACL_CONTENT"; then
  echo "dACL syntax valid"
else
  echo "dACL has syntax errors"
fi