Skip to content
Thomas Mangin edited this page Jul 25, 2026 · 3 revisions

Pre-Alpha. This page describes behavior that may change.

Ze authenticates operator logins (SSH, web, MCP) against RADIUS servers (RFC 2865) when the system.authentication.radius block is present. It is a PAP admin-authentication backend in the AAA chain, mirroring the existing TACACS+ backend. 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, and does not touch, the pre-existing 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 Section 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 - IPv4 only; tried in declaration order on failure.
radius.server <ip>.port uint16 (1-65535) 1812 UDP authentication port.
radius.server <ip>.key string ($9$) required Shared secret, stored as 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 names.
radius.default-profile leaf-list none Profiles applied when the Access-Accept carries no profile-attribute.

Chain order and lockout safety

When RADIUS, TACACS+, and local users are all configured, the chain is tried in priority order (lower first): RADIUS, then TACACS+, then local bcrypt.

Backend Priority
RADIUS 50
TACACS+ 100
Local bcrypt 200

The chain distinguishes two failure kinds:

  • Access-Reject (explicit rejection) stops the chain immediately. Local bcrypt is NOT tried, so a wrong password against RADIUS cannot succeed via a stale local hash.
  • Timeout or all servers unreachable is treated as an infrastructure error, so the chain falls through to the next backend and ultimately to local bcrypt. A dead server, a bad source-address, or an unreachable network never locks the operator out of SSH.

The total time one login spends talking to RADIUS is bounded, so a slow server falls through rather than hanging the login. If the RADIUS client cannot even start (for example an unbindable source-address), the backend contributes nothing and logs an error rather than failing the whole AAA bundle, so local and TACACS+ still authenticate.

Profile mapping

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

  • With the default profile-attribute filter-id, each Filter-Id attribute (RFC 2865 Section 5.11) in the Access-Accept supplies one profile name; multiple Filter-Id attributes yield multiple profiles. class reads the Class attribute (Section 5.25) the same way.
  • When the Access-Accept carries no such attribute, the default-profile leaf-list applies.
  • When neither is present the login resolves to no profile and is rejected, with a RADIUS admin auth rejected: no profiles resolved warning naming the user. Ze never authorizes a user it cannot attach a profile to: an authenticated login always carries at least one profile name. The rejection stops the chain, exactly as an Access-Reject does, because the server did answer (this is not the unreachable-server case that falls through to local).

Configure the RADIUS server to return Filter-Id = admin (or your profile name) for operators, and define a matching system.authorization.profile. If your server does not send a profile attribute, set default-profile instead; otherwise no RADIUS user can log in.

A successful login is tagged in the daemon log so you can confirm which backend answered:

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

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

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, because a verifiable response requires the correct shared secret. This check is distinct from the L2TP subscriber path's own doctor-radius-unreachable check.

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

Operational notes

  • Shared secrets are stored as $9$ ciphertext, never plaintext. The CLI never echoes them; ze config dump --strip-private replaces them with /* SECRET-DATA */.
  • PAP only in the MVP. CHAP and EAP admin auth are follow-up work; the L2TP subscriber path already handles CHAP / MS-CHAPv2 for PPP sessions.
  • Same client as the L2TP subscriber path (internal/component/radius), so retransmit, failover, and Response-Authenticator verification behave identically. That client is udp4, so RADIUS admin servers must be IPv4.

See also

  • TACACS+ for the sibling admin-authentication backend this mirrors.
  • Web UI and System for the login surfaces and authorization profiles this backend feeds.
  • RFC 2865: Remote Authentication Dial In User Service (RADIUS).

Adapted from main/docs/guide/radius.md.

Home

About

First Steps

Configuration

Operation

Interfaces

Plugins

Plugin Development

Chaos Testing

Blueprints

Development

Reference

Clone this wiki locally