If you have been managing Cisco network gear for any length of time you have a TACACS+ scar tissue collection. Mine includes a one-evening lockout that taught me three things about IOS-XE AAA. This post is the companion — the “right way” walkthrough that builds the same deployment from scratch without the postmortem story.

The goal: AD-authenticated SSH access to every switch, router, and WLC in the environment, with three privilege tiers (Helpdesk read-only, NetOps safe-troubleshooting, Admins full), every command logged to ISE for audit, and a break-glass local admin that always works regardless of what ISE does.

Why TACACS+ for device admin

The short answer: TACACS+ is what you use for logging into network devices. RADIUS is what you use for getting devices onto the network.

TACACS+ wins for device admin because:

  • AAA is split into three independent transactions. ISE can grant authentication but deny per-command authorization. That granularity makes “this Helpdesk user can show but not configure terminal” possible.
  • The whole payload is encrypted — not just the password. The session is opaque to a passive sniffer on the management VLAN.
  • TCP port 49 instead of UDP — sessions are stateful, retries are predictable, ordering is preserved.
  • Per-command accounting. Every show version, every configure terminal, every write memory lands in the ISE TACACS+ logs with a timestamp, source IP, and username. Audit teams expect this.

RADIUS can do basic device admin (PAP login with a returned privilege level), but the moment you need per-command logging or per-command authorization, TACACS+ is the right choice.

The architecture

End-to-end, the system is three layers:

  • The network device (NAD) — switch, router, WLC, firewall — initiates TACACS+ to ISE on every shell login attempt and every command the user types.
  • ISE Device Admin Service (PSN persona) — listens on TCP 49, validates credentials against AD, matches the policy set, picks the shell profile + command set, and sends the response.
  • Active Directory — the source of truth for user identity and group membership. ISE queries AD to determine which group the authenticating user belongs to (Domain Admins → Admin role, NetworkOps → Ops role, Helpdesk → Read-only).

The TACACS+ banner above shows this topology. The Active Directory layer is shared with the rest of the ISE deployment (see the AD integration post for the identity-store side).

Configuration walkthrough

There are six phases. Do them in this order — skipping ahead causes silent failures that are hard to diagnose.

Phase 1: Enable the Device Admin Service persona

Administration → System → Deployment → <PSN node> → Personas → Enable Device Admin Service

This is the toggle the lockout postmortem called out — without it, every TACACS+ object you create in the rest of the configuration responds 200 OK but port 49 stays closed because the daemon is not running. A nc -zv ise-psn 49 from any NAD will fail until this persona is enabled.

This is also a licensing checkpoint. Device Admin Service requires the Device Admin license tier. Check Administration → System → Licensing to confirm coverage before proceeding.

Phase 2: Add network devices and assign the TACACS+ shared secret

Administration → Network Resources → Network Devices → Add

For each device, configure:

  • Name — match your inventory naming convention
  • IP address — the source IP the device will use when sending TACACS+ packets (usually the management interface)
  • Device Type — assign to a Network Device Group like Device Type#All Device Types#Switch so the policy set can match by type
  • Location — assign to a Location NDG like Location#All Locations#Datacenter
  • TACACS+ Authentication Settings → check the box, enter a Shared Secret (use a strong random string, store in your password vault)

This is the step where the NAD becomes a known principal to ISE. Without it, ISE rejects the TACACS+ request with “Unknown NAS” — a confusing error because the device IS sending the request, it just is not recognized.

Phase 3: Create command sets

Work Centers → Device Administration → Policy Elements → Results → TACACS Command Sets → Add

A command set is a list of permit / deny rules evaluated in order against every command the user types. Three command sets cover most environments:

ReadOnly-Cmds
  PERMIT  show .*
  PERMIT  ping .*
  PERMIT  traceroute .*
  PERMIT  exit
  PERMIT  enable
  PERMIT  disable
  DENY    .*
  Permit any command not in above list: NO

SafeOps-Cmds (NetworkOps)
  PERMIT  show .*
  PERMIT  ping .*
  PERMIT  traceroute .*
  PERMIT  clear counters .*
  PERMIT  clear mac address-table .*
  PERMIT  reload in .*
  PERMIT  no shutdown
  DENY    configure terminal
  DENY    .*
  Permit any command not in above list: NO

PermitAllCmds (Admin)
  Permit any command not in above list: YES

The order matters — ISE evaluates rules top-down and stops at the first match. The final DENY .* is the safety net for Helpdesk and NetOps. For Admin, Permit any command not in above list: YES is the catch-all.

Naming caveat: ISE has historically rejected TACACS command set names with hyphens (Helpdesk-ReadOnly). Use underscores or spaces (Helpdesk_ReadOnly or Helpdesk ReadOnly). The lockout postmortem flagged this as a paper-cut; it is still real in ISE 3.4.

Phase 4: Create shell profiles

Work Centers → Device Administration → Policy Elements → Results → TACACS Profiles → Add

A shell profile is the set of attributes pushed to the NAD when the user lands. Configure three:

ProfileDefault PrivilegeMaximum PrivilegeNotes
Helpdesk_Shell_Priv111Locked to priv 1; cannot escalate via enable
NetOps_Shell_Priv777Custom priv level that the IOS device must define
Admin_Shell_Priv151515Full enable

For the NetOps_Shell_Priv7 to actually work, the IOS device side needs privilege exec level 7 <command> mappings configured — otherwise priv 7 is functionally identical to priv 1. This is the IOS half of the puzzle, not an ISE configuration. Typically the NetOps mappings are deployed via a config-as-code template (your Ansible repo, Catalyst Center, etc.).

The shell profile + command set combination is the actual building block:

Shell profile and command set composition diagram showing three roles - Helpdesk priv 1 with ReadOnly commands, NetOps priv 7 with SafeOps commands, and Full Admin priv 15 with PermitAll

Phase 5: Create the Device Admin policy set

Work Centers → Device Administration → Device Admin Policy Sets → Add

A policy set is the top-level container that scopes a set of AuthN + AuthZ rules to a subset of devices.

Policy Set: DeviceAdmin-Switches
  Condition:  Device Type EQUALS All Device Types#Switch
              OR Device Type EQUALS All Device Types#Router
              OR Device Type EQUALS All Device Types#WLC

  Authentication Policy:
    Default Rule: Use [dcloud_AD] as identity source

  Authorization Policy:
    Rule 1:  Admins
      Condition:  AD Group EQUALS dcloud.cisco.com/Users/Domain Admins
      Shell Profile: Admin_Shell_Priv15
      Command Set:   PermitAllCmds

    Rule 2:  NetOps
      Condition:  AD Group EQUALS dcloud.cisco.com/Users/NetworkOps
      Shell Profile: NetOps_Shell_Priv7
      Command Set:   SafeOps-Cmds

    Rule 3:  Helpdesk
      Condition:  AD Group EQUALS dcloud.cisco.com/Users/Helpdesk
      Shell Profile: Helpdesk_Shell_Priv1
      Command Set:   ReadOnly-Cmds

    Default: DenyAccess

The decision flow looks like this:

ISE TACACS+ policy set flow showing the four-step authentication and authorization process from incoming TACACS+ request through policy set matching to AD authentication and finally to per-group authorization with shell profile and command set assignment

The “Default: DenyAccess” at the bottom of the AuthZ section is critical. Without it, an AD user who is not in any of the three groups would get whatever the catch-all rule says — and forgetting to define a catch-all is how teams accidentally grant admin to every AD user.

Phase 6: Configure the NAD-side AAA — the safe pattern

This is where the lockout postmortem lessons get applied. There are two AAA chain patterns. One is broken. One is safe.

AAA chain configuration comparison showing the broken pattern with TACACS group first leading to no fallback on STATUS_FAIL versus the safe pattern with local first allowing break-glass admin to always work

The safe IOS-XE configuration:

! Define the TACACS+ server and group
tacacs server ISE-PSN
 address ipv4 198.18.133.27
 key 7 <encoded-shared-secret>
 timeout 5

aaa group server tacacs+ ISE-TACACS
 server name ISE-PSN

! Local break-glass user (never let this account be only in ISE)
username break-glass privilege 15 secret <strong-local-password>

! AAA chain — local FIRST, then TACACS
aaa new-model
aaa authentication login default local group ISE-TACACS
aaa authentication enable default enable
aaa authorization exec default local group ISE-TACACS if-authenticated
aaa accounting exec default start-stop group ISE-TACACS
aaa accounting commands 15 default start-stop group ISE-TACACS

! Apply to vty (SSH)
line vty 0 15
 transport input ssh
 login authentication default

What this gets you:

  • Local user break-glass authenticates against the IOS local database first, succeeds, and never queries ISE. This is your safety net — if ISE goes down, gets misconfigured, or rejects every request, the break-glass account still works.
  • AD users not in the local database fall through to TACACS+ (ISE), which validates them against AD and applies the policy-set rules. They get the shell profile + privilege level matching their AD group.
  • Per-command authorization is intentionally skipped (aaa authorization commands is not configured). Authorization happens at exec level via the priv-level returned by ISE; the priv level itself naturally restricts commands. This is the postmortem’s third lesson.
  • Accounting is on for both exec sessions and priv 15 commands — every admin login and every change command lands in ISE’s TACACS+ accounting logs for audit.

Bonus: console line stays separate

line con 0 typically does not have authorization commands applied. This is intentional — physical console access is your last-resort recovery path. Do not put per-command authorization on the console line unless you have a documented out-of-band recovery procedure that does not depend on it.

Verification

After configuring, validate from a fresh SSH session:

1$ ssh helpdesk-user@switch01
2Password: ********
3switch01> show version       # works (Helpdesk_Shell_Priv1 + ReadOnly cmd set)
4switch01> configure terminal
5% Invalid input detected at '^' marker.   # priv 1 cannot enter config mode

Then verify in ISE: Operations → TACACS → Live Logs. You should see:

  • An authentication event for helpdesk-user
  • AuthZ resolution naming Helpdesk_Shell_Priv1 and ReadOnly-Cmds
  • Command accounting events for show version and the rejected configure terminal

If the Live Logs show “Failed Authorization” but the user got in anyway, your AAA chain is misconfigured — TACACS rejected but the chain fell through to a permissive fallback. Re-check the safe-pattern config above.

Operational gotchas

1. Port 49 stays closed if Device Admin Service persona is not enabled. Phase 1 is non-negotiable. If your nc -zv ise-psn 49 fails, this is almost always the cause.

2. The joinDomainWithAllNodes API endpoint returns 500. Documented in the ISE automation post. For automation: do the AD join via the GUI once, then automate everything else.

3. Command sets reject hyphens in names. Use underscores or spaces. The error message ISE returns is unhelpful.

4. IOS-XE 17.09 silently drops address ipv4 when stale TACACS server names exist. Delete any TACACS_SERVER_AUTH_1-style auto-named servers from prior dCloud bootstrap scripts before adding your new ISE-PSN server.

5. The service-type AVPair matters for some platforms. Some Cisco platforms (older WLCs, certain switches running classic IOS) require the shell profile to include Service-Type = Login or Shell:cli=yes as a custom AVPair. If TACACS authentication succeeds but the user lands in privilege 0 with no commands available, this is likely the cause.

6. ISE 3.4 TACACS Live Logs lag by ~5 seconds. Do not panic if a command does not show up immediately. Refresh after 10 seconds.

7. Accounting and authorization are independent. Disabling accounting does not affect command authorization. They are configured by separate aaa accounting and aaa authorization directives.

Common questions answered

“Do I need command authorization for compliance?” — Usually no. Per-command authorization is a high-risk control that delivers marginal benefit over priv-level enforcement. For PCI-DSS, NIST 800-53, ISO 27001 — command accounting (which logs every command) typically satisfies the audit requirement without the lockout risk.

“Can I federate with other identity sources, not just AD?” — Yes. ISE supports LDAP, SAML, OAuth/OpenID Connect (for the portal), and external RADIUS as identity stores for TACACS+ authentication. AD is the most common because most enterprise device-admin users are already AD-joined.

“What about ISE backup admin access if AD is down?” — Two-layered defense: (a) the local IOS break-glass user is always available, (b) ISE supports Internal Users as a fallback identity store. Add a small local-admin Internal User account that has emergency access for when AD itself is unreachable.

“Does this work with non-Cisco devices?” — Yes for any device that speaks TACACS+ (most enterprise networking gear). Configuration details vary; Juniper, Arista, Aruba all have slightly different AAA syntax but the ISE side is unchanged.


Configuration tested against Cisco ISE 3.4 with C9800-CL 17.09 and CSR8000V 17.04 acting as TACACS+ clients, AD identity source via dcloud join point.

🎯 Studying for CCIE Security?

Practice with free flashcards, quizzes, and hands-on lab scenarios at cciesec.it-learn.io — built specifically for the CCIE Security v6.1 written (350-701 SCOR) and lab exam.