As of Cisco CDA Patch 2, identity mappings provided via Cisco ISE are natively supported. This means you can authenticate against ISE, which may in turn authenticate against LDAP or Active Directory, and subsequently notify one or more Cisco CDA servers that a new user-to-IP mapping exists. Cisco accomplishes this exchange of authenticated identities via syslog messages. ISE is configured to forward syslog messages to the CDA server(s), and the CDA server(s) have the sending ISE server(s) configured as a syslog “client.”

Visio of Cisco IDFW Identity Digestion from ISE

Cisco IDFW Identity Digestion from ISE via Syslog-NG

We didn’t wait for this native support at our organization since we needed identities consumed by Cisco WLC via ISE to be available before the general release of Patch 2. How’d we manage this? After successful authentication via ISE, we would forward syslog RADIUS accounting messages to a syslog-ng server. We’d filter only the messages we needed and pass off the necessary information to a Python app listening for input on stdin. This app digests the information and creates its own RADIUS accounting packet that gets forwarded to an array of CDA and/or AD agent servers. This method works with their legacy AD agent server and non-Patch 2 CDA appliances.

Why aren’t we using the native solution? We recently encountered problems with proper user-to-IP mappings being overwritten by machine-to-IP mappings forwarded from ISE. Since we’re using ISE for user and machine auth, it does make sense to see user and machine mappings, but because only one user can be affiliated with any one IP address, the desired user-to-IP mappings occasionally get overwritten with machine names or MAC addresses. We reverted back to our own in-house solution and I’m posting it here in hopes it will help anyone else experiencing the problem. There are a few requirements to get this working:

  • Forward syslog entries (filtered at your own discretion) from ISE to any syslog/syslog-ng server
  • Configure the necessary source, filter, and/or destination on your syslog server. This assumes you’re already listening for network sources.
  • Add your syslog server as a “Consumer Device” on all CDA servers
  • Install Python & PyRad on your syslog server
  • Configure the Python app, and associated modules, below:

Install PyRad first. I actually used a fork that had the correct support for cisc0-avpair. Next download the module used by the app being called from syslog-ng:

import random, socket, sys, logging
import pyrad.packet

from pyrad.client import Client
from pyrad.dictionary import Dictionary

logger = logging.getLogger(__name__)

#Logger Console Handler
ch = logging.StreamHandler() #StreamHandler logs to console
ch_format = logging.Formatter('%(asctime)s - %(message)s')

#Logger File Handler
fh = logging.FileHandler(".\{0}.log".format(__name__))
fh_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)-8s - %(message)s')

Now grab the program listening for syslog-ng $MESSAGE input. I’ve split the file into a configuration module (mod_cda.py), and the actual program (update_cda.py). Modify to suite your environment:


import mod_radacct

# Dictionary path (can be relative or absolute):
dictionary = "dictionary"

# If missing a user domain in the syslog msg, this is provided:
default_domain = "DefaultDomain"

# List of CDA identity maintainers. Can include legacy AD Agent servers:
servers = [ "cda1", "cda2", "ada1", "ada2" ]

# RADIUS secret:
secret = "yourRadiusSecret"


#!/usr/bin/env python

import logging, re, sys, time
import mod_cda


logger = logging.getLogger(__name__)

#Logger Console Handler
ch = logging.StreamHandler()
ch_format = logging.Formatter('%(asctime)s - %(message)s')

#Logger File Handler; change to your desired path
fh = logging.FileHandler("/usr/local/cda/{0}.log".format("UpdateCDA"))

syslog-ng.conf (excerpt)

### Filter out ISE hosts that should be sending specific messages for Device/IP association:

filter f_ise_host   {   (
                host("") or
                host("") or
                host("ise01") or

### The username and Framed-IP we're looking for are in the watchdog updates AFTER accounting "start" msgs:
filter f_ise_auth { match(".*RADIUS Accounting watchdog update.*" value ("MESSAGE")); };

destination d_NS_ISE { file("/var/log/syslog-ng/NS_logs/NS_ISE/$SOURCEIP/$SOURCEIP.log"); };

destination d_NS_pythonCDA  { 

log {   source(s_network);

Now you should be able to restart syslog-ng and see your application running (ps -ef | grep update_cda). It will continually listen for new messages and process them as needed. Feel free to change any of the configured logging levels to your preferred verbosity.