A specific feature in the freeradius-api project allows us to create services in JSON files and define parameters like Radius AVPairs that should be added to the freeradius database upon creating a user that references this service. For example instead of creating a brand new FastAPI endpoint and having to write Python for a new speed package, you can simply create a new JSON file with a unique service name and ensure that users created against the /api/v1/services/
endpoint with a reference to the relevant service will be added to the relevant radusergroup table and also user specific attributes like radcheck and radreply avpairs.
The current workflow can be described below:
- Services are loaded from the configured directory (specifically any file with the .json suffix)
- Depending on if a single service or multiple services are configured in the single JSON file, they are attempted to load into the service pydantic model for a pre-validation check
- If the service passes the validation check, it is currently assigned in memory to the services (this will later be moved to redis to share across multiple instances)
- Each service will now run through logic to confirm if there is any duplicate services with the same "service_name" and that any related AVPairs to
radgroupcheck
andradgroupreply
are created in the database already. If they are not then the precheck will fail and the endpoint will throw an error if you attempt to create a user with this service until the attributes are in the database
- Basic valdiation is performed to check the JSON body, ensuring the provided
service_name
is configured already and has passed its prechecks - Username validation will process the service templates defined
username_type
, by default this isstr
. - Any special variables such as
{{username}}
and{{service_name}}
populated in the template will be replaced to create the initial service model - Service User check is now performed to ensure that any
radcheck
andradreply
specific user attributes don't already exist in the database. - Now the user is created with the relevant AVPairs and Groups defined in the Service Template and the user has been provisioned
A service template is just a JSON file that allows you to create a service without writing Python. This is extremely powerful because you can now create a range of services and ensure users that reference the service in the provisioning process will be created with the relevant group assosications, AVPairs and even perform username validiation to adhere to your service (eg. enforcing the username to be a regex string or a mac address)
Example services can be found in this directory.
Variable Name | Description |
---|---|
service_name | Unique Name for the service to reference during provisioning process |
username_type | Used to compare username, valid types are "str | int | uuid | mac_address | regex" |
username_regex | Regex String to use if username_type is set to regex |
radusergroups | List of RadUserGroup that the user is apart of |
radcheck_avpairs | AVPairs that will be added into the radcheck table for this user during provisioning process |
radreply_avpairs | AVPairs that list be added into the radreply table for this user during provisioning process |
radgroupcheck_avpairs | AVPairs that should already be present in the radgroupcheck database table for the relevant group to pass the app startup prechecks |
radgroupreply_avpairs | AVPairs that should already be present in the radgroupreply database table for the relevant group to pass the app startup prechecks |
The following variables can be used as values so that the policy endpoint will automatically populate these fields during the user prechecks and provisioning process:
Variable Name | Description |
---|---|
{{service_name}} | Takes the configured service_name of the service |
{{username}} | Takes the username that was supplied in the body of the provisioning request |
radcheck_avpairs
and radreply_avpairs
Variable Name | Description |
---|---|
username | The name of the user to apply the AVPair to, preferably use {{username}} for template to fill this variable out for you automatically |
attribute | IETF Attribute or Vendor Specific Attribute |
op | Operation "= | += | :=" |
value | Value of the Attribute to set |
radgroupcheck_avpairs
and radgroupreply_avpairs
Variable Name | Description |
---|---|
groupname | The name of the group to check AVPairs, preferably use {{service_name}} for template to fill this variable out for you automatically |
attribute | IETF Attribute or Vendor Specific Attribute |
op | Operation "= | += | :=" |
value | Value of the Attribute to set |
Below is an example of a service that matches usernames based on MAC Addresses, adds a user to a group called VLAN_100_SERVICE
and creates a radcheck and radreply attribute to the FreeRADIUS database.
{
"service_name": "VLAN_100_SERVICE",
"username_type": "mac_address",
"radusergroups": [
{
"username": "{{username}}",
"groupname": "{{service_name}}",
"priority": 100
}
],
"radcheck_avpairs": [
{
"username": "{{username}}",
"attribute": "ClearText-Password",
"op": ":=",
"value": "{{username}}"
}
],
"radreply_avpairs": [
{
"username": "{{username}}",
"attribute": "Framed-Pool",
"op": "=",
"value": "VLAN_100_POOL"
}
],
"radgroupcheck_avpairs": [
{
"groupname": "{{service_name}}",
"attribute": "ClearText-Password",
"op": "=",
"value": "default"
}
],
"radgroupreply_avpairs": [
{
"groupname": "{{service_name}}",
"attribute": "Tunnel-Type",
"op": ":=",
"value": 13
},
{
"groupname": "{{service_name}}",
"attribute": "Tunnel-Medium-Type",
"op": ":=",
"value": 6
},
{
"groupname": "{{service_name}}",
"attribute": "Tunnel-Private-Group-Id",
"op": ":=",
"value": 100
},
{
"groupname": "{{service_name}}",
"attribute": "Fall-Through",
"op": "=",
"value": "Yes"
}
]
}
Cisco AVPairs are a vendor specific attribute which extends the value field to include sub attributes like this:
Cisco-AVPair += 'delegated-prefix=2001:db8::/48
'
If you enable the VALIDATE_AVPAIRS
option in the configuration of this API, then the user precheck performed during the provisioning process will attempt to decode this sub-attribute in the format of '<attribute> <op> <value>
' and perform basic validation against the FreeRADIUS dictionary of attributes. The idea of this is that you provide the API the same dictionary files loaded on the FreeRADIUS server which is used to search, if the sub attribute does not exist then an error is simply thrown because adding an attribute into the database that FreeRADIUS doesn't know how to build is unnecessary information in the database.