Radiator Server Documentation — v10.33.2

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+
  • Transport Options
  • Authentication Types
  • Minimal Listener (TCP)
  • Multistage ASCII Authentication With Username, Password, and TOTP
  • Authorization and Accounting
  • Built-in Authorization Session Cache
  • Operational Notes
  • 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 a short-lived built-in authorization session after login
  • Authorize individual CLI commands per operator (per-command authorization)
  • 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.

Transport Options

Radiator supports two TACACS+ transports on the listener:

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

There is no IANA-assigned port for TACACS+ over TLS.

secret and secret none apply equally on both transports. Even on TLS, the TACACS+ packet body is still obfuscated (or not) per the configured secret mode for compatibility with TACACS+ clients.

PROXY protocol is supported on TCP listeners only. Combining tls and proxy-protocol on the same TACACS+ listener is rejected at configuration load time.

Authentication Types

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

authen_typeRound-tripsOTP follow-up via challenge, hotp, totp
ASCIIMultistage (server may prompt further)Supported
PAPSingle request/replyNot supported
CHAPSingle request/replyNot supported
MSCHAPSingle request/replyNot supported
MSCHAPv2Single request/replyNot supported

Only the ASCII authen_type may use the additional get-user, get-pass, and get-data continue rounds needed for interactive prompts and OTP follow-ups. The PAP, CHAP, MSCHAP, and MSCHAPv2 authen_type values must complete in a single exchange.

This restriction is on the TACACS+ authen_type field itself, not on what Radiator can validate inside an ASCII flow. Inside an ASCII multistage handler you can still:

  • Compare a captured password against the backend with the pap action (basic plaintext password comparison; the multistage example below uses it).
  • Validate a one-time password with totp, hotp or other methods.
  • Combine the above in any order.

If you need a one-time password step, configure the device to start ASCII authentication for the relevant logins.

Minimal Listener (TCP)

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;
        policy "POLICY_TACACS_PLUS";
    }
}

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

See clients.tacacs-plus for the full client reference and servers.tls for the TLS listener variant.

Multistage ASCII Authentication With Username, Password, and TOTP

This is the canonical multi-step interactive login flow. The device starts an ASCII authentication request. Radiator drives a sequence of get-user, get-pass, and get-data prompts using challenge, then validates the captured password with pap and the captured TOTP code with totp.

aaa {
    policy "POLICY_TACACS_PLUS" {
        handler "MULTISTAGE_TOTP" {
            conditions all {
                tacacsplus.authentication == true;
            }

            @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;
                    }
                    challenge "Password";
                }

                # Snapshot the password before the next prompt overwrites the
                # shared TACACS+ message field. `pap response;` parks the
                # captured response so a later `pap;` can validate it.
                pap response;

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

                # Load the user record (password hash + TOTP secret).
                backend {
                    name "TACACS_USERS";
                    query "FIND_USER";
                }

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

                # Replace the parked password with the freshly captured TOTP
                # response, then validate it as a TOTP code.
                pap {
                    attribute tacacsplus.request.message;
                    response;
                }
                totp;

                modify tacacsplus.reply.message = "Welcome";
            }
        }
    }
}

How the rounds map onto TACACS+ packets:

  1. Device sends AuthenticationStart with authen_type = ASCII.
  2. Server replies with status = get-user, prompt "Username". Device sends AuthenticationContinue carrying the username.
  3. Server replies with status = get-pass, prompt "Password". Device sends AuthenticationContinue carrying the password.
  4. Server replies with status = get-data, prompt "TOTP". Device sends AuthenticationContinue carrying the OTP.
  5. Server validates both factors and replies with status = pass or status = fail.

Things to keep in mind:

  • The challenge action returns control to the policy after the next AuthenticationContinue arrives. Conditions evaluated after a challenge call see the new request fields.
  • pap response; (block-less form) does not authenticate. It only stages the current message field as the captured PAP response so a later pap; (or another pap response;) can use it. This pattern is required because tacacsplus.request.message is reused across rounds.
  • The same handler must validate every continue. Do not split a multistage flow across multiple handlers.
  • Per RFC 8907, only ASCII supports AuthenticationContinue. Radiator rejects continue rounds for PAP, CHAP, MSCHAP, and MSCHAPv2.

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

Authorization and Accounting

A single TACACS+ client typically also performs authorization and accounting. Add separate handlers gated on tacacsplus.authorization and tacacsplus.accounting:

The authorization example below expects a restored built-in TACACS+ session and rejects the request if there is no matching session. Accounting 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 carried by 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.

Built-in Authorization Session Cache

After a successful TACACS+ authentication, Radiator stores a sanitized snapshot of the user record so that subsequent authorization requests do not need to re-resolve the backend. The snapshot contains:

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

Authentication-only state (password authenticator, per-user conditions, post-authentication callbacks) is not kept. To carry additional values across authentication and authorization, use a named caches block.

Entries are stored per TACACS+ server, keyed by server name, device IP address, TACACS+ client name, and username. Each successful authentication seeds an entry; each authorization cache hit refreshes its timeout. Lifetime is controlled by session-timeout in the servers { tacacs-plus "NAME" { ... } } block (durations like 30s, 15m, 1h; default 15m). Match it to the device's session timeout for the best end-user experience, or shorten it if you need tighter synchronization with the device's session lifecycle.

The cache is not aware of the device-side session, so an entry can expire while the device session is still active, or persist 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 and either reject the request or re-resolve the user from the backend; re-resolving applies only to that one request and does not seed the cache (only authentication does).

Operational Notes

  • Idle timeout. Per-client timeout overrides the listener timeout. The default is 10s when neither is set. See clients.tacacs-plus for the precedence rules.
  • Keepalive. Enable keepalive on the listener if you expect long-lived control sessions; it complements the idle timeout.
  • ip-accept. Use the listener ip-accept map to drop connections from unexpected source ranges before any TACACS+ processing happens.
  • TLS client identity. When require_client_certificate true; is set, Radiator first matches the client by source IP and then re-resolves it by certificate Subject Alternative Name. The TLS-side client must have protocol tls; in its source block.
  • Diagnostics. Rejecting non-TLS TACACS+ connection. Expecting TLS ClientHello byte 0x16 indicates a plaintext connection arriving on a TLS listener. Rejecting TACACS+ TLS client connection: protocol not allowed means the resolved client does not have protocol tls; set.

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

  • 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

  • Pipeline Directives

  • 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