2025-12-16
v10.31.2
Summary
Fixed auto-save in configuration editor to properly debounce and not lose user input
Improved radiator-client command line parameter aliases.
Added TOTP/HOTP authentication support to radiator and radiator-client with base32 and hex (0x prefix) secret formats. See TOTP/HOTP Authentication article for more information.
Added
min_secret_bitsconfiguration option for TOTP and HOTP defaulting to 128 bits as required by RFC 4226Changed the default timeout for radiator-client from 3 seconds to 5 seconds.
Improved error messages for expression parsing to better indicate missing semicolons
Improved RadSec and RADIUS TCP/TLS connection logging with better error details
Add
--transportflag toradiator-clientsupportingudp(default),tcp, andtls(RadSec) transports with TLS certificate optionsAdd
--repeat-mode failoption toradiator-clientfor testing that all requests must failAdd TCP keepalive and default timeout options to listen blocks
Log grid column visibility, sorting, and other table settings are now persisted to the server and synced across sessions
Fixed a rare race-condition where UI settings could be lost
Improved log table toolbar with condensed settings menu
Add
--repeatoption toradiator-clientfor sending multiple requests with configurable success/failure handlingTemplate number fields now support min, max, step, and input mode configuration
Configuration file timestamps are now preserved when copying files for pending configuration, backups, and deployments
Add environment variable support for numeric configuration values
Log view filters can now be shared via URL query parameters
Fixed column positions being lost when dragging columns outside the grid
Added multivalue template field type for entering multiple values (e.g., IP addresses) that generate repeated configuration lines
SQL query column mappings now support filter expressions for parsing JSON columns
Fixed fallback server selection when primary backend server is unavailable
Extend environment variable support to file path configuration options
Debug logging now shows certificate details (subject, issuer, SAN, validity, serial) when TLS contexts are configured
Added download option to configuration file and template browser context menus
License files are now managed through the pending configuration system
Licenses in the
licenses/directory are auto-loaded without explicit configurationZIP export includes option to include license files
ZIP import merges existing licenses with imported configuration
Configuration ZIP import/export now supports granular control over licenses and management directory inclusion
Fixed an issue where backend call timeouts did not properly stop the authentication pipeline
Add support for spreading JSON objects directly to RADIUS reply attributes using
radius.reply.attr = json_valuesyntaxAdd environment variable support to the
includestatementFixed the
rejectaction to no longer automatically set the reply messageAdd RADIATOR_CLIENT_TIMEOUT environment variable and --expect-timeout option to radiator-client
Added structured clog-based pipeline trace logging for improved AAA request debugging
Message-Authenticator is now enabled by default in radiator-client for improved security
radiator-client now validates Message-Authenticator in responses by default
Fixed bug where Message-Authenticator HMAC could overwrite reply attributes when reusing packet buffers
Added
traceaction for enabling per-request pipeline tracing without global trace logging. See trace action for detailsradiator-client: New
--jsonflag for machine-readable JSON outputradiator-client: Dictionary name resolution for response attributes
Added documentation for
reject_errorsaction. See reject_errors for details.TACACS+ server now uses contextual logging with structured namespace hierarchy
Fixed TOTP authentication to return proper rejection instead of pipeline error
Prometheus metrics now use
::separator for namespace hierarchy instead of_Packet capture
consoleoutput has been replaced withlog, addingloglevelandformatoptions for structured log output.
Configuration Editor Auto-Save Improvements
Fixed several issues with auto-save functionality in the configuration editor, template form, and template designer:
- Auto-save now properly debounces, waiting until the user stops typing before saving (previously it would save repeatedly while typing)
- Fixed a race condition where typed characters could be lost if the user continued typing during a save operation
Template Number Field Configuration
Number fields in configuration templates now support additional properties for better input control:
- min/max: Set minimum and maximum allowed values
- step: Define the increment/decrement step size
- inputMode: Choose between "numeric" (integer input) or "decimal" (decimal input) modes
Number fields no longer have hardcoded constraints, allowing negative and fractional values by default.
Fixed an issue where configuration files in the Management UI showed incorrect modification/creation dates.
The backup list is now sorted by filename in descending order, ensuring the most recent backups appear first.
Environment Variable Support for Numeric Configuration Values
Numeric configuration parameters can now be set using environment variables, providing greater flexibility for containerized and dynamic deployments.
Previously, environment variable substitution was only supported for string values. With this change, any numeric configuration parameter (such as ports, timeouts, and limits) can now reference environment variables.
Example
servers {
http "HTTP_SERVER" {
listen {
protocol tcp;
ip 0.0.0.0;
port env.HTTP_PORT | default(1234);
}
}
}
In this example, the port value is read from the HTTP_PORT environment variable. If the variable is not set, it falls back to the default value of 1234.
URL Query Parameters for Log Filters
The logs view now supports URL query parameters for filters, enabling shareable and bookmarkable filtered views.
Example
/logs?logtab=application&q=error&filter.level.equals=ERROR
DataGrid Column Drag Fix
Fixed an issue where column positions would reset when accidentally dragging a column outside the grid container.
Multivalue Template Field
The template system now supports a multivalue field type, enabling users to input multiple values that are iterated over using Handlebars {{#each}} helpers.
Features
- Enter multiple values separated by comma, newline, or space
- Visual chip-based UI showing each value with delete buttons
- Configurable separator in the template designer
- Values are passed as arrays to Handlebars for iteration
Example Usage
Template:
client
{{CLIENT_NAME}}
{
{{#each IP_ADDRESSES}}
ip
{{this}};
{{/each}}
secret "{{SHARED_SECRET}}"; }
With IP_ADDRESSES containing 192.168.1.1, 192.168.1.2, generates:
client MY_CLIENT {
ip 192.168.1.1;
ip 192.168.1.2;
secret "mysecret";
}
SQL Column Filter Expressions
SQL backend query mappings now support filter pipelines on column values. This enables parsing JSON columns and extracting specific fields directly in the mapping configuration.
See Filter expressions in SQL mappings for usage details.
Fixed Backend Server Fallback Selection
Fixed an issue where the round-robin server selection algorithm would fail to properly fall back to the next available server when a backend server became unavailable.
When using multiple backend servers (e.g., LDAP servers) with round-robin load balancing, if one server went down, subsequent requests could incorrectly skip available servers due to a calculation error in determining the next server position.
Configuration File Download
Added a Download option to the context menu for configuration files and templates in the Management UI. This allows users to download individual configuration files directly from the browser.
Usage
- Navigate to Configuration > Files or Configuration > Templates
- Click the actions button (⋮) on any file or template
- Select Download from the context menu
- The file will be downloaded with its original filename
License File Management Changes
Changes
- License files are now managed through the pending configuration system instead of being written directly to the active configuration
- When uploading or deleting license files via the UI, changes are staged in the pending configuration and must be deployed to take effect
- A notification is shown in the Licenses view when there are pending license changes that need to be deployed
- Auto-load licenses: When using a configuration directory, licenses are automatically loaded from the
licenses/subdirectory if it exists and no explicit license configuration is present - ZIP export: License files are excluded from configuration exports by default. A new "Include licenses in export" checkbox is available in the export dialog to optionally include them
- ZIP import: When importing a ZIP configuration, existing licenses from the active configuration are preserved and merged with the pending configuration. If the imported ZIP contains licenses, they take precedence over existing ones with the same name
Granular Configuration ZIP Import/Export Options
The configuration import and export functionality now provides more control over which directories are included:
Export Options
When exporting configuration as a ZIP archive, you can now choose to include:
- Licenses directory (off by default)
- Management directory (off by default)
Import Options
When importing a configuration ZIP archive:
- Import licenses: When enabled (off by default), license files from the ZIP are merged with the active licenses
- Import management directory: When enabled (off by default), the management directory from the ZIP replaces the management directory (after first copying active management to pending)
Warnings
If you select to import licenses or management directory but the ZIP archive doesn't contain them, a warning is displayed after import completes.
Backend Call Timeout Now Stops Pipeline
Previously, when a backend call (such as LDAP) timed out, the error was only logged but the authentication pipeline would continue processing. This could lead to unexpected behavior where authentication might succeed or fail for the wrong reasons, masking the actual timeout issue.
Now, when a backend call times out and all configured servers have been tried, the error is properly propagated and the pipeline is stopped. This ensures that:
- The timeout error is correctly reported as the reason for authentication failure
- The pipeline does not continue with potentially incomplete or incorrect state
- The
reasonattribute is properly set to reflect the backend timeout
JSON Object Spread to RADIUS Reply Attributes
You can now assign a JSON object directly to radius.reply.attr to set multiple reply attributes at once. The JSON object keys are RADIUS dictionary attribute names (case-insensitive), and the values are set as the corresponding attribute values.
Example
modify {
radius.reply.attr = vars.reply_attrs;
}
Where vars.reply_attrs contains a JSON object like:
{
"Framed-IP-Address": "10.0.10.6",
"Framed-IPv6-Prefix": "2001:db8:abcd::/48",
"Session-Timeout": 7200
}
This is equivalent to manually setting each attribute:
modify {
radius.reply.attr.Framed-IP-Address = "10.0.10.6";
radius.reply.attr.Framed-IPv6-Prefix = "2001:db8:abcd::/48";
radius.reply.attr.Session-Timeout = 7200;
}
This feature is useful when storing reply attributes as JSON in a database column and applying them dynamically without explicit mapping.
Fixed reject action reply message behavior
Previously, when using the reject action with a message, the rejection reason was automatically copied to both aaa.reason and aaa.message. This meant the reject reason would always appear in the RADIUS Reply-Message attribute or TACACS+ server message, which may not be desirable in all scenarios.
Now, the reject action only sets aaa.reason. If you want the rejection reason to also appear in the reply message sent to the client, you must explicitly set the message using message action before the reject action.
authentication {
message "Access denied";
reject "Access denied";
}
Alternatively, you can modify the final authentication block to set the reply message based on the rejection reason:
final-authentication {
modify {
aaa.message = aaa.reason;
}
}
radiator-client Timeout Improvements
RADIATOR_CLIENT_TIMEOUT Environment Variable
The --timeout (-T) option can now be configured via the RADIATOR_CLIENT_TIMEOUT
environment variable. This is useful for setting a default timeout across multiple
invocations without repeating the command-line argument.
Example:
export RADIATOR_CLIENT_TIMEOUT=10s
radiator-client -s 127.0.0.1 ... # Uses 10s timeout
The command-line argument still takes precedence over the environment variable.
New --expect-timeout Option
A new --expect-timeout option has been added for testing scenarios where you need
to verify that a request times out. When this option is set:
- The program exits with code 0 (success) if the request times out
- The program exits with a non-zero code if a response is received
The option accepts an optional timeout value. If no value is provided, it uses the
--timeout value:
# Use --timeout value for expected timeout
radiator-client -s 127.0.0.1 --expect-timeout ...
# Use custom timeout for expected timeout
radiator-client -s 127.0.0.1 --expect-timeout 3s ...
Note: --expect-timeout and --expect-response-code are mutually exclusive.
Pipeline Trace Logging
Added structured pipeline trace logging that provides detailed logs for tracing AAA request processing through handlers and pipelines.
When trace logging is enabled (RADIATOR_LOG_LEVEL=trace), Radiator Server emits structured log entries for each pipeline action and stage completion. Use context_id to correlate all log entries for a single request.
See Logging - Pipeline trace logging for details.
radiator-client: Message-Authenticator improvements and security fixes
Breaking Changes
Message-Authenticator enabled by default
Message-Authenticator is now enabled by default in radiator-client for packet types that support it:
Requests: Access-Request, Disconnect-Request, CoA-Request Responses: Access-Accept, Access-Reject, Access-Challenge, Disconnect-Ack/Nak, CoA-Ack/Nak
Message-Authenticator is NOT used for Accounting-Request/Response as per RADIUS specifications.
Security Note: Message-Authenticator is placed as the first attribute in the request to protect RADIUS servers that don't implement Message-Authenticator validation. This placement breaks chosen-prefix MD5 collision attacks.
Response validation
The client now validates Message-Authenticator in responses by default, protecting against response spoofing and tampering. This can be disabled with --validate-message-authenticator=false.
CLI Options
--message-authenticator- Include Message-Authenticator in requests (default: true)--message-authenticator=false- Disable Message-Authenticator in requests--validate-message-authenticator- Validate Message-Authenticator in responses (default: true)--validate-message-authenticator=false- Skip response validation
Migration
If you need to disable Message-Authenticator for compatibility with older RADIUS servers:
radiator-client -s 127.0.0.1 --secret mysecret --user test --password pass \
--message-authenticator=false \
--validate-message-authenticator=false
However, most modern RADIUS servers support Message-Authenticator and it's recommended to keep it enabled for security.
radiator-client JSON output and dictionary support
A new --json flag has been added to radiator-client for machine-readable JSON output. This is useful for scripting and automation.
Usage
radiator-client -s 127.0.0.1 --secret mysecret --user alice --password pass --json
Output format
The JSON output includes both the request and response packets:
{
"request": {
"code": 1,
"type": "Access-Request",
"identifier": 0,
"length": 61,
"authenticator": "0x12c0aba94293adbd2078f8296a3dcd8d",
"attributes": [...]
},
"response": {
"code": 2,
"type": "Access-Accept",
"identifier": 0,
"length": 38,
"authenticator": "0x31064455474ed06c491c972f03b1be14",
"attributes": [...]
}
}
Hex values (authenticator, binary attributes) are prefixed with 0x for clarity.
Dictionary name resolution
Response attributes are now resolved using the RADIUS dictionary, providing human-readable attribute names and decoded values:
- Attribute names are shown as lowercase kebab-case (e.g.,
framed-ip-addressinstead of raw type8) - Enum values are resolved to their names (e.g.,
framed-userinstead of2for Service-Type) - IP addresses, integers, and strings are properly decoded and formatted
Verbose mode
Use --json --verbose to include additional raw data fields (rawType, rawValueHex, rawValueBytes) for each attribute.
Packet dumps (hexdump and attribute listings) are now only displayed when using the --verbose / -v flag. This provides cleaner output for scripting and automation while still allowing detailed debugging when needed.
Pipeline Error Handling Changes
The default behavior for handling pipeline errors has changed. Previously, when a pipeline error occurred during RADIUS or TACACS+ request processing, the request was rejected with an Access-Reject response. Now, pipeline errors cause the request to be silently ignored (no response sent).
This change was made because RADIUS and TACACS+ do not have a standard way to communicate internal errors back to clients. Proxies typically use timeouts to detect broken servers, so sending an Access-Reject in case of internal errors may lead to unexpected behavior where clients interpret configuration errors as authentication failures.
Migration
If you rely on the previous behavior where pipeline errors resulted in Access-Reject responses, add reject_errors on; to your pipeline configurations:
aaa {
policy "DEFAULT" {
handler "AUTHENTICATION" {
authentication {
reject_errors on;
# existing configuration...
}
}
}
}
See reject_errors for full documentation.
TOTP authentication now returns proper rejection
TOTP authentication failures (invalid OTP, replayed OTP, or OTP from before server boot) now correctly return an authentication rejection instead of a pipeline error. This allows post-authentication actions to run and log the rejection reason properly.
Changes
- Invalid TOTP codes now set
aaa.reasonwith a descriptive message and reject the authentication - Replayed TOTP codes are detected and rejected with "Old TOTP replayed" message
- TOTP codes generated before server boot time are rejected with "TOTP from before server boot" message
Configuration tip
To match on authentication result in conditions, use the string format:
match "%{aaa.result}" {
"accept" => {
count "successful_logins";
}
"reject" => {
count "failed_logins";
}
}
The numeric aaa.result value (0=ignore, 1=accept, 2=reject, 3=challenge) remains
unchanged for backward compatibility.
See Prometheus scraping documentation for updated format and PromQL query examples.
Packet capture log output
The console packet capture output has been renamed to log and now outputs structured JSON log entries instead of unstructured text.
Captures are supported by all server types.
New configuration options
loglevel- Sets the log level (error,warning,info,debug,trace)format- Sets the output format:text(default) - Human-readable dissected packethex- Hex-encoded bytes with0xprefixjson- JSON array of dissected fields
Example
captures {
capture "CAPTURE_DEBUG" {
log {
loglevel debug;
format json;
}
}
}
Future Breaking change
Replace console; with log; in existing configurations, we will be removing support for console output in a future release.
See captures.log for full documentation.
Known Issues
When the /var/lib/radiator directory has 15_certificates.radconf file, and the file has not been edited once, the rpm package upgrade will show a warning warning: file /var/lib/radiator/15_certificates.radconf: remove failed: No such file or directory. The warning is harmless and can be ignored, as the 15_certificates.radconf file has been renamed.