Chargeable-User-Identity
Chargeable-User-Identity
Use Chargeable-User-Identity (CUI) to correlate authentication and accounting
with an opaque user identifier. The hmac_sha256 filter
generates a stable CUI from the authenticated identity and a secret key.
This example reads the key from an environment variable named CUI_HMAC_KEY;
replace env.CUI_HMAC_KEY with your key expression. Add the assignment at the end
of the RADIUS authentication handler's @execute block, after successful
authentication, with aaa.identity set to the authenticated user's identity.
@execute {
# Existing authentication actions go here.
if all {
# Handle both initial requests and reauthentication with an existing CUI.
radius.request.attr.Chargeable-User-Identity != none;
} then {
radius.reply.attr.Chargeable-User-Identity = aaa.identity | hmac_sha256(env.CUI_HMAC_KEY) | hex;
}
}
The result is a 64-character hexadecimal CUI. Matching identities and keys produce matching CUIs across servers.
With this configuration, Radiator Server supports the home-server CUI behavior in RFC 4372, section 2.1: return a CUI after successful authentication when requested, including during reauthentication. Configure the NAS to include it in Start, Interim and Stop accounting requests. Proxies must forward it unchanged.
Daily Chargeable-User-Identity (CUI) rotation
A stable CUI allows requests for the same user to be correlated indefinitely. You can reduce this long-term linkability by rotating the CUI at a defined interval.
The following example generates one stable CUI per day. Authentication, reauthentication, accounting, diagnostics, and support systems can correlate the user's activity during that day. The same inputs produce a different CUI after the day changes.
In this example, each day follows Coordinated Universal Time (UTC) and runs
from 00:00:00 through 23:59:59 UTC.
Daily rotation limits correlation through the CUI across different days. It does not make the user anonymous when other attributes, logs, or network identifiers can identify or correlate the user.
The validity timestamps form part of the HMAC input. They do not create an expiry time that Radiator enforces, and they cannot be recovered from the generated CUI. Their purpose is to change the HMAC input at each day boundary.
Configure the daily CUI pipeline
Define the reusable pipeline in a separate configuration file, such as
15_pipeline-daily-cui.radconf:
aaa {
@pipeline "ASSIGN_DAILY_CUI" {
if all {
# Generate a CUI only when the client requests one.
radius.request.attr.Chargeable-User-Identity != none;
# The authentication configuration provides this identity.
vars.cui_identity != none;
vars.cui_identity != "";
# The shared HMAC key must be configured.
env.CUI_HMAC_KEY != none;
env.CUI_HMAC_KEY != "";
} then {
# Read the clock once so both boundaries use the same day.
vars.cui_now = datetime.timestamp.utc;
vars.cui_valid_from = vars.cui_now | timestamp_format("[year]-[month]-[day]T00:00:00Z") | timestamp_format("[unix_timestamp]");
vars.cui_valid_to = vars.cui_now | timestamp_format("[year]-[month]-[day]T23:59:59Z") | timestamp_format("[unix_timestamp]");
# Operator-Name is optional.
vars.cui_operator_name = radius.request.attr.Operator-Name | default("UNKNOWN");
# The validity interval rotates the CUI each day.
vars.cui_plain_text = "cui|v1|%{vars.cui_valid_from}|%{vars.cui_valid_to}|%{vars.cui_identity}|%{vars.cui_operator_name}";
vars.cui = vars.cui_plain_text | hmac_sha256(env.CUI_HMAC_KEY) | base64_encode;
radius.reply.attr.Chargeable-User-Identity = vars.cui;
}
}
}
Set vars.cui_identity to a stable authenticated identity before invoking the
pipeline.
Set CUI_HMAC_KEY to a shared secret key. Radiator instances that must generate
matching CUIs must use the same key. Keep the key stable while previous and
current CUIs must remain reproducible.
The HMAC input contains:
- The format identifier
cui - The format version
v1 - The start of the current day as Unix timestamp seconds
- The end of the current day as Unix timestamp seconds
vars.cui_identity- The request
Operator-Name, orUNKNOWNwhen the attribute is absent
Operator-Name scopes the generated value by operator. Matching users produce
different CUIs when their Operator-Name values differ. Requests without the
attribute use the UNKNOWN scope.
The hmac_sha256 filter prevents deriving the CUI without the shared key. The
base64_encode filter only converts the raw HMAC result to text; it does not
provide additional confidentiality.
Invoke the daily CUI pipeline
Invoke ASSIGN_DAILY_CUI after successful processing:
@post_execute {
if all {
"%{aaa.result}" == "accept";
} then {
invoke "ASSIGN_DAILY_CUI";
}
}
Use this @post_execute example when the corresponding @execute block
contains the complete authentication flow.
The pipeline returns a CUI only when:
- Authentication succeeded.
- The request contains
Chargeable-User-Identity. vars.cui_identitycontains a non-empty value.CUI_HMAC_KEYcontains a non-empty value.
Initial authentication and reauthentication use the same generation process. Within one day, matching input values produce the same reply CUI.
A session that crosses the day boundary continues using its previous CUI until authentication or reauthentication supplies a newly rotated CUI.