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_bits configuration option for TOTP and HOTP defaulting to 128 bits as required by RFC 4226

  • Changed 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 --transport flag to radiator-client supporting udp (default), tcp, and tls (RadSec) transports with TLS certificate options

  • Add --repeat-mode fail option to radiator-client for testing that all requests must fail

  • Add 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 --repeat option to radiator-client for sending multiple requests with configurable success/failure handling

  • Template 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 configuration

  • ZIP 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_value syntax

  • Add environment variable support to the include statement

  • Fixed the reject action to no longer automatically set the reply message

  • Add 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 trace action for enabling per-request pipeline tracing without global trace logging. See trace action for details

  • radiator-client: New --json flag for machine-readable JSON output

  • radiator-client: Dictionary name resolution for response attributes

  • Added documentation for reject_errors action. 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 console output has been replaced with log, adding loglevel and format options 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

  1. Navigate to Configuration > Files or Configuration > Templates
  2. Click the actions button (⋮) on any file or template
  3. Select Download from the context menu
  4. 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 reason attribute 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-address instead of raw type 8)
  • Enum values are resolved to their names (e.g., framed-user instead of 2 for 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.reason with 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 packet
    • hex - Hex-encoded bytes with 0x prefix
    • json - 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.