Radiator Server Documentation — v10.33.3

TACACS+ Authentication, Authorization, and Accounting

How to configure TACACS+ in Radiator Server for device administration, including ASCII multistage flows with one-time password challenges.

Table of Contents
  • TACACS+ Authentication, Authorization, and Accounting
  • When to Use TACACS+
  • Configuration Flow
  • Start With a Minimal TACACS+ Server
  • Choose the Transport
  • Choose an Authentication Type
  • OTP
  • Add Multistage ASCII Authentication With Username, Password, and TOTP
  • Add Authorization and Accounting
  • How the Built-in Authorization Session Cache Works
  • Logging and Counters
  • Trace-Level Packet Logging
  • Build an RFC 9887-Compatible Profile
  • See Also

TACACS+ Authentication, Authorization, and Accounting

Radiator Server implements TACACS+ as defined by RFC 8907. TACACS+ is the authentication, authorization, and accounting (AAA) protocol used by network devices (switches, routers, firewalls, load balancers) for administrator login and command authorization.

When to Use TACACS+

Use TACACS+ when you need to:

  • Authenticate operators connecting to network devices (SSH, console, web management) and keep an authorization session after login
  • Authorize individual CLI commands
  • Record an audit trail of administrator actions (command and session accounting)
  • Combine device login with multi-factor authentication

TACACS+ is separate from RADIUS. Use RADIUS for end-user network access (Wi-Fi, VPN, 802.1X) and TACACS+ for administrative access to the network devices themselves.

Configuration Flow

Read the TACACS+ configuration in this order:

  1. Define the TACACS+ clients and their shared secrets.
  2. Add a TACACS+ server in servers { tacacs-plus ... }.
  3. Configure the server listen block and choose the transport.
  4. Choose the authentication flow used by the device.
  5. Add authorization and accounting handlers if the device uses them.
  6. Tune session, packet size, logging, and other operational settings.

Start With a Minimal TACACS+ Server

This minimal example defines one TACACS+ client and one TACACS+ server with a TCP listen block:

clients {
    tacacs-plus "TACACS_DEVICES" {
        client "core-switch" {
            source {
                ip 10.10.1.10;
            }
            secret "SuperSecretKey!";
        }
    }
}

servers {
    tacacs-plus "TACACS" {
        listen {
            protocol tcp;
            port 49;
            ip 0.0.0.0;
        }
        clients "TACACS_DEVICES";
        session-timeout 15m;
        max-packet-size 4096;
        policy "POLICY_TACACS_PLUS";
    }
}

This example also shows the built-in TACACS+ authorization session cache. 15m is the default.

Default packet size is max-packet-size 4096;. You can raise it up to 65536 bytes or lower it per TACACS+ server.

See clients.tacacs-plus for TACACS+ client configuration details and servers.tls for the TLS server variant.

Choose the Transport

The minimal example above uses plain TCP in its listen block. Radiator also supports TLS there:

TransportDefault portWhen to use
TCP49Devices on a trusted management network
TLS (TACACS+ over TLS)300Devices that cross untrusted networks or require certificate-based identity

If you need RFC 9887 compatibility, use TLS on port 300.

Choose an Authentication Type

TACACS+ defines four authentication types selected by the device in AuthenticationStart. Radiator implements them per RFC 8907:

authen_typeRound-trips
ASCIIMultistage (server may prompt further)
PAPSingle request/reply
CHAPSingle request/reply
MSCHAPSingle request/reply
MSCHAPv2Single request/reply

Only ASCII may use the additional get-user, get-pass, and get-data continue rounds. PAP, CHAP, MSCHAP, and MSCHAPv2 must finish in a single exchange.

Do not route every TACACS+ authentication request through one generic password handler. Match the expected tacacsplus.request.action, tacacsplus.request.authentication.type, and tacacsplus.request.authentication.service, or reject unsupported combinations before running pap;. This keeps ENABLE, CHPASS, and non-password methods out of a login-specific flow.

OTP

You can use OTP with TACACS+ in two ways:

  • Additional prompts. Use ASCII if Radiator must prompt separately for the OTP with challenge and validate it in a later AuthenticationContinue.
  • Embedded into the password field. If the chosen method has a cleartext password field, validate both parts from that one submission. Use pap plus totp for <password><totp>. Use pap plus yubikey for <password><otp>. In both cases, extract each part from the same field.

If you need Radiator to prompt separately for the OTP, configure the device to start ASCII authentication.

Add Multistage ASCII Authentication With Username, Password, and TOTP

This is the standard interactive login flow. The device starts an ASCII authentication request. Radiator sends get-user, get-pass, and get-data prompts with challenge. It then validates the password with pap and the TOTP code with totp.

aaa {
    policy "POLICY_TACACS_PLUS" {
        handler "MULTISTAGE_TOTP" {
            conditions all {
                tacacsplus.authentication == true;
                tacacsplus.request.action == tacacsplus.AUTHENTICATION_ACTION_LOGIN;
                tacacsplus.request.authentication.type == tacacsplus.AUTHENTICATION_TYPE_ASCII;
                tacacsplus.request.authentication.service == tacacsplus.AUTHENTICATION_SERVICE_LOGIN;
            }

            @execute {
                # Prompt for the username if the client did not include one.
                if any {
                    tacacsplus.request.user == none;
                    tacacsplus.request.user == "";
                } then {
                    modify {
                        tacacsplus.reply.status = "get-user";
                        tacacsplus.reply.noecho = false;
                    }
                    challenge "Username";

                    if any {
                        tacacsplus.request.user == none;
                        tacacsplus.request.user == "";
                    } then {
                        modify tacacsplus.reply.message = "Username is required";
                        reject;
                    }
                }

                # Prompt for the password (no echo).
                if all {
                    tacacsplus.request.password == none;
                } then {
                    modify {
                        tacacsplus.reply.status = "get-pass";
                        tacacsplus.reply.noecho = true;
                    }
                    # The challenge text is sent as TACACS+ server_msg.
                    # It is optional, but most clients need it to know
                    # what to send.
                    challenge "Password";
                }

                # Prompt for the TOTP code (echo on).
                modify {
                    tacacsplus.reply.status = "get-data";
                    tacacsplus.reply.noecho = false;
                }
                challenge "TOTP";

                # Load the user record. This includes user.password and
                # hmac-otp.secret, which is the stored TOTP shared secret.
                backend {
                    name "TACACS_USERS";
                    query "FIND_USER";
                }

                # Validate the password captured in the get-pass round.
                pap;

                # Validate the submitted TOTP code from the current TACACS+
                # message field. The shared secret comes from the backend-
                # loaded hmac-otp.secret value.
                totp {
                    secret tacacsplus.request.message;
                }

                # Optional text sent as TACACS+ server_msg similarly to the challenge.
                modify tacacsplus.reply.message = "Welcome";
            }
        }
    }
}

This handler is intentionally narrow. It accepts only ASCII device-login requests. Keep other TACACS+ request types, such as CHPASS, ENABLE, CHAP, MSCHAP, and MSCHAPv2, in separate handlers or reject them explicitly.

Some devices send AUTHENTICATION_SERVICE_NONE instead of AUTHENTICATION_SERVICE_LOGIN for ordinary logins. If your device does that, adjust the service guard to match the device behavior instead of removing the guard entirely.

How the rounds map onto TACACS+ packets:

The example above shows the full multistage TACACS+ ASCII flow for username, password, and TOTP in one handler.

Add Authorization and Accounting

A TACACS+ client often also uses authorization and accounting. Add separate handlers for tacacsplus.authorization and tacacsplus.accounting:

The authorization example below expects a matching built-in TACACS+ session. It rejects the request if no session is found. The accounting example uses values from the accounting request itself.

handler "SHELL_AUTHORIZATION" {
    conditions all {
        tacacsplus.authorization == true;
    }

    @execute {
        modify {
            vars.args = tacacsplus.request.args;
        }

        if all {
            user.username == none;
        } then {
            reject "no session";
        }

        if all {
            user.group == "network-admins";
            vars.args[0] == "service=shell";
            vars.args[1] == "cmd=ls";
        } then {
            modify {
                tacacsplus.reply.args += "priv-lvl=1";
                tacacsplus.reply.args += "service=shell";
            }
            accept;
        } else {
            reject "command not allowed";
        }
    }
}

handler "TACACS_ACCOUNTING" {
    conditions all {
        tacacsplus.accounting == true;
    }

    @execute {
        log "ACCOUNTING" {
            json {
                "user" tacacsplus.request.user;
                "port" tacacsplus.request.port;
                "address" tacacsplus.request.address;
                "args" tacacsplus.request.args;
            }
        }
        accept;
    }
}

Accounting handlers should use the values in the accounting request itself, such as tacacsplus.request.user, tacacsplus.request.port, tacacsplus.request.address, and tacacsplus.request.args. Radiator does not restore cached user.* fields into accounting requests.

How the Built-in Authorization Session Cache Works

After successful TACACS+ authentication, Radiator stores a sanitized snapshot of the user record. Later authorization requests can use it without another backend lookup. The snapshot contains:

  • user.username
  • user.group
  • user.role
  • user.backend
  • user.privilege

Entries are stored per TACACS+ server. The key contains the server name, device IP address, TACACS+ client name, and username. Each successful authentication creates an entry. Each authorization cache hit refreshes its timeout. Control the lifetime with session-timeout in the servers { tacacs-plus "NAME" { ... } } block. Use values such as 30s, 15m, or 1h. The default is 15m. Match it to the device's session timeout, or shorten it if you need faster expiration.

The cache is not aware of the device-side session. An entry can expire while the device session is still active. It can also remain until timeout after the device session has ended. A few stale entries are inexpensive.

When no entry matches, Radiator still runs the authorization handler with user.* unset. Check user.username == none. Then either reject the request or resolve the user again from the backend. That extra lookup applies only to that one request. It does not seed the cache.

Only the fields listed above are kept. To carry additional values across authentication and authorization, use a named caches block.

Logging and Counters

Server-level TACACS+ logs use the server::tacacs-plus::<server-name> namespace. Logs emitted inside a TACACS+ AAA policy extend that namespace with ::policy::<policy-name>::handler::<handler-name>. The UI and exported counters use the same namespace path. This lets you filter TACACS+ events by server, policy, or handler.

Trace-Level Packet Logging

When the effective TACACS+ server log level is TRACE, Radiator writes one TACACS+ packet record for each incoming and outgoing packet. Each record includes the direction, packet type, session and sequence identifiers, and a structured packet_json object with hex dumps and an RFC-shaped field dissection.

Packet bodies in trace logs are deobfuscated.

The example below shows an incoming ASCII AuthenticationStart trace entry. It starts an interactive login for user alice on tty0 from 127.0.0.1.

{
  "message": "TACACS+ packet",
  "fields": {
    "direction": "incoming",
    "tacacs_plus_encrypted": "true",
    "tacacs_plus_session_id": "16909060",
    "tacacs_plus_sequence_number": "1",
    "tacacs_plus_message_type": "request",
    "tacacs_plus_packet_type": "authentication",
    "packet_json": {
      "header_hex": "c0 01 01 04 01 02 03 04 00 00 00 1a",
      "header": {
        "major_version": { "value": "TAC_PLUS_MAJOR_VER", "raw": 12 },
        "minor_version": { "value": "TAC_PLUS_MINOR_VER_DEFAULT", "raw": 0 },
        "type": { "value": "TAC_PLUS_AUTHEN", "raw": 1 },
        "seq_no": 1,
        "flags": [{ "value": "TAC_PLUS_SINGLE_CONNECT_FLAG", "raw": 4 }],
        "session_id": 16909060,
        "length": 26
      },
      "body_hex": "01 01 01 01 05 04 09 00 61 6c 69 63 65 74 74 79 30 31 32 37 2e 30 2e 30 2e 31",
      "body": {
        "action": { "value": "TAC_PLUS_AUTHEN_LOGIN", "raw": 1 },
        "priv_lvl": 1,
        "authen_type": { "value": "TAC_PLUS_AUTHEN_TYPE_ASCII", "raw": 1 },
        "authen_service": { "value": "TAC_PLUS_AUTHEN_SVC_LOGIN", "raw": 1 },
        "user": "alice",
        "port": "tty0",
        "rem_addr": "127.0.0.1"
      }
    }
  }
}

Treat TACACS+ packet trace logs as sensitive data. They record logical TACACS+ body contents. That can include credentials such as PAP passwords and ASCII AuthenticationContinue message values.

Build an RFC 9887-Compatible Profile

Use this profile when you need a TACACS+ over TLS deployment that matches RFC 9887 expectations.

clients {
    tacacs-plus "TACACS_TLS_CLIENTS" {
        client "router-a" {
            source {
                ip 10.10.1.10;
            }
        }
    }
}

servers {
    tacacs-plus "TACACS_TLS" {
        listen {
            protocol tls;
            port 300;
            ip 0.0.0.0;

            tls {
                certificate "TACACS_SERVER_CERT";
                certificate_key "TACACS_SERVER_KEY";
                require_client_certificate true;
                client_ca_certificate "TACACS_CLIENT_CA";
                min_protocol_version tlsv13;
            }
        }

        clients "TACACS_TLS_CLIENTS";
        obfuscation disabled;
        protocol-error-reply true;
        policy "POLICY_TACACS_PLUS";
    }
}

Set these values for an RFC 9887 TACACS+ over TLS deployment:

  • Set listen { protocol tls; port 300; } on the TACACS+ server.
  • Set tls { certificate ...; certificate_key ...; } so the server presents a TLS certificate.
  • Set require_client_certificate true; and client_ca_certificate ...; to require mutual TLS and validate client certificates.
  • Set min_protocol_version tlsv13; to require TLS 1.3 or later.
  • Set obfuscation disabled; on the TACACS+ server to disable TACACS+ body obfuscation inside the TLS session.
  • When the TACACS+ server sets obfuscation disabled;, it ignores the matched client secret and always uses unobfuscated TACACS+ bodies.
  • Set protocol-error-reply true; when you want malformed or out-of-sequence TACACS+ requests to receive an ERROR reply before the server closes the connection.

RFC 9887 requires TACACS+ data only after the TLS handshake completes. Radiator keeps 0-RTT disabled on TACACS+ TLS listeners and does not accept it.

To limit or disable TLS 1.3 session resumption per RFC 9887 §3.6, set tls13_session_ticket_lifetime (in seconds) on the tls block. Use 0 to disable resumption ticket issuance entirely. See servers.tls for parameter details.

Radiator does not currently support certificate revocation checking, such as CRLs or OCSP, for TACACS+ over TLS.

See Also

Navigation
  • About Radiator software development security

  • Architecture Overview

  • Backend Load Balancing

  • Basic Installation

  • Built-in Environment Variables

  • Comparison Operators

  • Configuration Editor

  • Configuration Import and Export

  • Containers

  • Data Types

  • Duration Units

  • Environment Variables

  • Execution Context

  • Execution Pipelines

  • Filters

  • Getting a Radiator License

  • Health check /live and /ready

  • High Availability and Load Balancing

  • High availability identifiers

  • HTTP Basic Authentication

  • Introduction

  • Linux systemd support

  • Local AAA Backends

  • Log storage and formatting

  • Management API privilege levels

  • Namespaces

  • Password Hashing

  • Probabilistic Sampling

  • Prometheus scraping

  • PROXY Protocol Support

  • Radiator server health and boot up logic

  • Radiator sizing

  • Radiator software releases

  • Rate Limiting

  • Rate Limiting Algorithms

  • Reverse Dynamic Authorization

  • Service Level Objective

  • TACACS+ Authentication, Authorization, and Accounting

  • Template Rendering CLI

  • Tools radiator-client

  • TOTP/HOTP Authentication

  • What is Radiator?

  • YubiKey Authentication

  • YubiKey Context Variables