Guide

RADIUS admin AAA

Ze authenticates operator logins (SSH, web, MCP) against RADIUS servers (RFC 2865) when the system.authentication.radius block is present. Local bcrypt users keep working as the fallback so an unreachable server cannot lock you out of the device.

This is the operator/admin login path. It is separate from the L2TP subscriber RADIUS path (l2tp.auth.radius), which authenticates PPP sessions and lives under the l2tp config root.

What it does

Function Status Notes
Authentication Production PAP (RFC 2865 User-Password, hidden per §5.2 with the shared secret).
Authorization Via profiles An Access-Accept reply attribute (default Filter-Id) maps the user to local RBAC profiles.
Accounting Not in MVP Admin-session accounting is deferred; subscriber accounting stays in the L2TP path.

Minimal config

system {
    authentication {
        radius {
            server 10.0.0.1 { port 1812; key "$9$encrypted-key"; }
            server 10.0.0.2 { port 1812; key "$9$encrypted-key"; }
            timeout 3
            retries 3
            profile-attribute filter-id
            default-profile read-only
        }
    }
    authorization {
        profile admin     { run { default-action allow; } edit { default-action allow; } }
        profile read-only { run { default-action allow; } edit { default-action deny;  } }
    }
}
Leaf Type Default Notes
radius.server <ip> list, ordered-by-user - IPv4 only (shared udp4 client); tried in declaration order on failure
radius.server <ip>.port uint16 (1-65535) 1812 UDP authentication port
radius.server <ip>.key string (ze:sensitive) required Shared secret, stored as $9$ ciphertext
radius.timeout uint16 (1-60) 3 Per-server request timeout in seconds
radius.retries uint8 (0-10) 3 Transmit attempts per server before failover
radius.source-address ip-address none Local source IP for outbound RADIUS UDP
radius.profile-attribute enum filter-id | class filter-id Access-Accept reply attribute carrying profile name(s)
radius.default-profile leaf-list none Profiles applied when the Access-Accept carries no profile-attribute

Authentication flow

  1. SSH/web/MCP client connects with username + password.
  2. The daemon's AAA chain calls the RADIUS backend first (priority 50; TACACS+ is priority 100, local bcrypt is priority 200).
  3. The backend builds an Access-Request with User-Name, the hidden User-Password, Service-Type=Login, NAS-Identifier (hostname), and (when a source-address is set) NAS-IP-Address. It sends to the first configured server, retransmitting with exponential backoff and failing over to the next server (RFC 2865 §2.5).
  4. Access-Accept -- the login succeeds. Profiles come from the configured reply attribute (see below); the session is tagged source=radius.
  5. Access-Reject -- explicit rejection. The chain stops here; local bcrypt is NOT tried. This prevents a wrong password against RADIUS from succeeding via a stale local hash.
  6. Timeout / all servers unreachable -- treated as an infrastructure error, so the chain falls through to the next backend (local bcrypt). An unreachable RADIUS server never locks the operator out.

The total time one login spends talking to RADIUS is bounded, so a slow or unreachable server falls through to local rather than hanging the login.

Profile mapping

Ze's authorization model is name-based, so a RADIUS-accepted user must be mapped to one or more locally-defined system.authorization.profile entries.

Configure your RADIUS server to return Filter-Id = admin (or your profile name) for operators, and define a matching system.authorization.profile.

Chain order with TACACS+ and local

When RADIUS, TACACS+, and local users are all configured, the chain is tried in priority order: RADIUS (50), then TACACS+ (100), then local bcrypt (200). A reject from any backend stops the chain; an infrastructure error falls through to the next.

Readiness check

ze doctor probes every configured RADIUS admin server with an Access-Request before the daemon starts. When none answers a verifiable response it emits doctor-radius-admin-unreachable (a warning: local fallback still works). A missing or wrong shared key counts as unreachable.

ze doctor --json router.conf
ze explain doctor-radius-admin-unreachable

Verification

The .ci tests in test/plugin/ cover the main behaviours:

Test Asserts
aaa-radius-admin.ci Access-Accept + Filter-Id -> admin profile, log shows source=radius, no local fallback consulted
aaa-radius-fallback.ci Server unreachable -> local bcrypt accepted, log shows source=local, RADIUS did not silently succeed

Unit coverage lives in internal/component/radius/{config,authenticator,aaa,doctor}_test.go.

For ad-hoc verification, point the daemon at a real RADIUS server and run any command via ze cli -c "show bgp summary" -- the daemon log tags the satisfying backend on every login, e.g.:

INFO SSH auth success subsystem=ssh username=alice remote=10.0.0.1:51408 source=radius

source=radius confirms the chain consulted RADIUS and returned Access-Accept. source=local means RADIUS was unreachable (or unconfigured) and the local bcrypt user accepted the credentials.

Operational notes

RFC reference