- FreeRADIUS
- Linux
Using FreeRADIUS to perform EAP-TLS and PAP
In my journey to make CavanaSystems more open I wanted to replace my existing Microsoft NPS farm with a piece of Free Software… and found an amazing one!
Make your requirements
As with any IT project, you have to start with your requirements.
Mine were:
1. High Availability
2. PAP will be used for authenticating and authorizing SSH sessions to network devices
3. EAP-TLS will be used to provide network-level authentication and dynamic VLAN assignment for cable and wireless access
4. Authentication and Authorization decisions must be taken accordingly to a centralized user store (Active Directory Domain Services)
5. Authentication events must be logged on the RADIUS Server locally (for now)
The existing system
These are the policies that where implemented on the outgoing system.
For cable access:

For wireless access:

And at last, SSH CLI Authentication and Authorization for Cisco devices:

The infrastructure was made of two servers, failover managed by the NAS devices (as it’s typical in a RADIUS deployment).
FreeRADIUS basics
This post is not a tutorial, it’s of no use that I go about and rewrite what is already explained very well by FreeRADIUS creators and book publishers; instead, here’s a list of things you have to read to understand how this thing works:
1. FreeRADIUS man pages:
man radiusd
man radiusd.conf
man unlang
man dictionary
2. FreeRADIUS technical guide:
https://networkradius.com/doc/FreeRADIUS-Technical-Guide.pdf
3. The comments in the configuration files (if you have read the manual pages above you already know this)
While I think that FreeRADIUS itself is a very good piece of software, its documentation is quite good but not awesome. It lacks something that ties everything together, which for me was a book: “FreeRADIUS Beginner’s Guide” of Dirk van der Walt, published by Packt Publishing. I highly recommend buying a hard copy of it (you are going to take notes on it).
It’s based on FR 2 but the server hasn’t changed in its core design, just keep handy the /etc/raddb/README.rst
file which documents what’s changed from version 2 to 3.
Designing the solution
By now you already know (because you read the goddamned man
, didn’t you?) that every RADIUS packet that comes into FreeRADIUS goes through a very specific pipeline; the exact shape of it depends on which type of packet the server is processing (Authentication or Accounting) and the decisions you are exerting over that packet (Are you proxying it or authenticating/accounting it locally? Will it go through all the processing sections applicable to that packet or will it be discarded early in the process? …).
In this phase we are designing how our packets will be processed.
PAP Requests:
1. When the server receives a PAP request it should check the Directory via LDAP to gather the user’s group information
2. Then it must set the Auth-Type to pam
(the servers are joined to the AD Domain and as such, capable of Kerberos authentication via the system’s PAMs)
3. Authentication is performed via the appropriate PAM module
4. Additional RADIUS AVPs and VSAs are added via unlang policies, when there is a match from an AD group gathered from the authorize
phase
EAP-TLS Requests:
1. When the server receives an EAP-TLS request it should check the Directory via LDAP to gather the computer’s group information
2. Authentication is performed via FreeRADIUS EAP module, which will check the client certificate revocation information against the CA OCSP responder
3. Additional RADIUS AVPs and VSAs are added via unlang policies, when there is a match from an AD group from the authorize
phase
Making things work
Configuring the LDAP module instances
FreeRADIUS modules support instantiation; this is achieved by putting an arbitrary name after the module name. For the sake of consistency it’s recommended to keep the original module name followed by the instance name of your choice.
The first instance takes care of retrieving group information for a user account:
ldap ldap_users {
server = 'ldaps://<<redacted>>'
server = 'ldaps://<<redacted>>'
base_dn = '<<redacted>>'
identity = '<<redacted>>'
password = '<<redacted>>'
scope= 'sub'
user {
base_dn = '<<redacted>>'
filter = "(userPrincipalName=%{%{Stripped-User-Name}:-%{User-Name}})"
}
group {
base_dn = "${..base_dn}"
filter = '(objectClass=group)'
membership_attribute = 'memberOf'
}
options {
chase_referrals = no
rebind = yes
res_timeout = 10
srv_timelimit = 3
net_timeout = 1
idle = 60
probes = 3
interval = 3
ldap_debug = 0x0028
}
tls {
start_tls = no
ca_file = ${cadir}/<<redacted>>.pem
ca_path = ${cadir}
require_cert = 'hard'
# This looks like a bug. Recheck later after upgrading.
# check_crl = yes
tls_min_version = "1.2"
cipher_list = "DEFAULT"
}
pool {
start = ${thread[pool].start_servers}
min = ${thread[pool].min_spare_servers}
max = ${thread[pool].max_servers}
spare = ${thread[pool].max_spare_servers}
uses = 0
retry_delay = 30
lifetime = 0
idle_timeout = 60
max_retries = 5
}
}
The second instance does the same for a computer account, but the LDAP query is different so it’s the only piece of information reported here.
Be aware that computers account User-Name
attribute is not immediately usable as received by the NAS, so it needs some unlang transformation that will be explained shortly.
user {
base_dn = '<<redacted>>'
filter = "(sAMAccountName=%{%{Stripped-User-Name}:-%{User-Name}})"
}
Configuring the EAP module
It’s quite a standard EAP module configuration with EAP-TLS and OCSP checking with soft-fail enabled.
TLS cache is cleaned by a cron job not shown here.
eap {
default_eap_type = tls
timer_expire = 60
ignore_unknown_eap_types = no
cisco_accounting_username_bug = no
max_sessions = ${max_requests}
tls-config tls-common {
private_key_password = '<<redacted>>'
private_key_file = ${certdir}/<<redacted>>.key
certificate_file = ${certdir}/<<redacted>>.pem
ca_file = ${cadir}/<<redacted>>.pem
ca_path = ${cadir}
cipher_list = "PROFILE=SYSTEM"
cipher_server_preference = no
tls_min_version = "1.2"
tls_max_version = "1.3"
ecdh_curve = ""
cache {
enable = yes
lifetime = 24 # hours
name = "EAP module"
persist_dir = "${logdir}/tlscache"
store {
Tunnel-Private-Group-Id
}
}
verify {
skip_if_ocsp_ok = yes
}
ocsp {
enable = yes
override_cert_url = no
use_nonce = yes
softfail = yes
}
}
tls {
tls = tls-common
}
}
Unlang policies
Policies related to PAP authentication:
It’s quite simple. It goes like this:
– IF you come from a specific subnet (the one where network devices have their management interfaces) AND you are SSH-ing into one of such devices AND you are part of a LDAP group, then you get some VSAs and AVPs (good for you!).
– IF you don’t match the preceding criteria, continue with the packet processing (we need this as we can process some other requests differently).
– ELSE we reject you.
Be mindful that since we have used LDAP module instantiation here, you have to check against the ldap_users-LDAP-Group
AVP (you’ll see that in radiusd
output when run in debug mode with -X).
cavanasystems_netadmins_auth {
if ((&request:NAS-Port-Type == "Virtual") && (&request:NAS-IP-Address =~ /^10.88.0.[0-254]/) && (&request:ldap_users-LDAP-Group == <<redacted>>)) {
update reply {
&Service-Type := "Login"
&Cisco-AVPair := "shell:priv-lvl=15"
&Idle-Timeout := 900
&Session-Timeout := 3600
}
ok
}
elsif (&request:NAS-Port-Type != "Virtual") {
noop
}
else {
update reply { &Reply-Message := "Not authorized" }
reject
}
}
Policies related to EAP-TLS authentication:
We have two kinds of policies here; the first two just perform dynamic VLAN assignment: they put some more AVPs in the reply based on the presence of other AVPs in the request.
Be mindful that because we have used LDAP module instantiation here, you have to check against the ldap_computers-LDAP-Group
AVP.
cavanasystems_vlan_mappings_wired {
if ((&request:EAP-Type == "TLS") && (&request:NAS-Port-Type == "Ethernet") && (&request:ldap_computers-LDAP-Group == <<redacted>>)) {
update reply {
&Tunnel-Type := VLAN
&Tunnel-Medium-Type := IEEE-802
&Tunnel-Private-Group-Id := "112"
}
ok
}
}
cavanasystems_vlan_mappings_wireless {
if ((&request:EAP-Type == "TLS") && (&request:NAS-Port-Type == "Wireless-802.11") && (&request:Called-Station-ID =~ /^[0-9a-f\-]{17}\:CavanaSystems-CORP-802\.1x$/) && (&request:ldap_computers-LDAP-Group == <<redacted>>)) {
update reply {
&Tunnel-Type := VLAN
&Tunnel-Medium-Type := IEEE-802
&Tunnel-Private-Group-Id := "112"
}
ok
}
}
The second kind, it performs a check the EAP module doesn’t do.
This check is already present into the default FreeRADIUS configuration, but it’s disabled as it requires some tweaking for your local site, and specifically if you have to deal with Windows clients.
The Windows 802.1x supplicant will send it’s User-Name
like this: host/UPPERCASE_HOSTNAME.lowercase.domain
.
Who doesn’t love this?
We have to strip the host/
part then catch the rest through a regular expression, then validate it against the hostname in the certificate (which Windows requests in lowercase, just to piss you off. Yes, I checked the .X500 RFC 1617 which is what standardizes the components you see in LDAP based directories and .X509 style certificates such as TLS ones. It’s case insensitive so if Windows wanted it could have requested the certificate with the same uppercase hostname).
This policy works for both the Windows supplicant (and its habits) and also with “sane” supplicants which will send the User-Name
attribute equal to its certificate subject.
cavanasystems_verify_tls_client_common_name {
if (&User-Name =~ /^host\/([A-Z0-9]{13}.corp\.cavanasystems\.com)/) {
update request {
&Sanitized-Host-Name := "%{1}"
}
}
if ((&User-Name !~ /^@/) && &TLS-Client-Cert-Common-Name && ("%{tolower:%{TLS-Client-Cert-Common-Name}}" != "%{tolower:%{%{&Sanitized-Host-Name}:-%{&User-Name}}}")) {
reject
}
}
Don’t forget to declare Sanitized-Host-Name
in FreeRADIUS dictionary as type string
.
This thing is so flexible that you will never stop tinkering.
The site definition
I will summarize things a little for the sake of being on spot, it’s not how it actually looks and it probably won’t work if you paste it verbatim. Or it may. Anyhow, the parts concerning this post are correct.
We make a trick similar to what you just saw to get a usable Stripped-User-Name
to use in the LDAP query to gather computer’s group membership. If the User-Name
does not match our regex then in the LDAP query the User-Name
attribute will be used instead.
Note that User-Name
!= Sanitized-Host-Name
as the latter is just the host name, while the former is what the supplicant provides (which can be horrible in the case of Windows).
authorize {
eap {
ok = return
}
if (&request:User-Password) {
ldap_users
}
if (&request:EAP-Message) {
if (&request:EAP-Type == EAP-TLS) {
if (&request:User-Name =~ /^.*([A-Z0-9]{13}).corp\.cavanasystems\.com/i) {
update request {
&Stripped-User-Name := "%{1}$"
}
ldap_computers
}
}
}
if ((ok || updated) && User-Password && !control:Auth-Type) {
update control {
&Auth-Type := pam
}
}
}
authenticate {
pam
eap
}
post-auth {
# CavanaSystems custom policies
if (&control:Auth-Type == PAM) {
cavanasystems_netadmins_auth
}
#debug_reply
if (&control:Auth-Type == EAP) {
cavanasystems_vlan_mappings_wired
cavanasystems_vlan_mappings_wireless
cavanasystems_verify_tls_client_common_name
}
# debug_all
exec
remove_reply_message_if_eap
Post-Auth-Type REJECT {
attr_filter.access_reject
eap
remove_reply_message_if_eap
}
Post-Auth-Type Challenge {
remove_reply_message_if_eap
attr_filter.access_challenge.post-auth
}
Post-Auth-Type Client-Lost {
}
if (EAP-Key-Name && &reply:EAP-Session-Id) {
update reply {
&EAP-Key-Name := &reply:EAP-Session-Id
}
}
}
If you are wondering why I checked if the packet was EAP before testing for EAP-TLS, it’s because FR will throw a warning in the debug output when doing PAP: if the message being processed is not EAP, all EAP-related AVPs are not present. It will work the same, but it was bothering me.
Testing it
Does it work? Yes it does.
Here’s my debug output when FreeRADIUS processes a PAP message.
(0) Received Access-Request Id 206 from 10.88.0.4:1645 to 10.88.18.9:1812 length 113
(0) User-Name = "<<redacted>>"
(0) User-Password = "<<redacted>>"
(0) NAS-Port = 1
(0) NAS-Port-Id = "tty1"
(0) NAS-Port-Type = Virtual
(0) NAS-IP-Address = 10.88.0.4
(0) # Executing section authorize from file /etc/raddb/sites-enabled/cavanasystems-site
(0) authorize {
(0) policy filter_username {
(0) if (&User-Name) {
(0) if (&User-Name) -> TRUE
(0) if (&User-Name) {
(0) if (&User-Name =~ / /) {
(0) if (&User-Name =~ / /) -> FALSE
(0) if (&User-Name =~ /@[^@]*@/ ) {
(0) if (&User-Name =~ /@[^@]*@/ ) -> FALSE
(0) if (&User-Name =~ /\.\./ ) {
(0) if (&User-Name =~ /\.\./ ) -> FALSE
(0) if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) {
(0) if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) -> FALSE
(0) if (&User-Name =~ /\.$/) {
(0) if (&User-Name =~ /\.$/) -> FALSE
(0) if (&User-Name =~ /@\./) {
(0) if (&User-Name =~ /@\./) -> FALSE
(0) } # if (&User-Name) = notfound
(0) } # policy filter_username = notfound
(0) [preprocess] = ok
(0) eap: No EAP-Message, not doing EAP
(0) [eap] = noop
(0) if (User-Password) {
(0) if (User-Password) -> TRUE
(0) if (User-Password) {
rlm_ldap (ldap_users): Reserved connection (0)
(0) ldap_users: EXPAND (userPrincipalName=%{%{Stripped-User-Name}:-%{User-Name}})
(0) ldap_users: --> (userPrincipalName= <<redacted>>)
(0) ldap_users: Performing search in "<<redacted>>" with filter "(userPrincipalName= <<redacted>>)", scope "sub"
(0) ldap_users: Waiting for search result...
(0) ldap_users: User object found at DN "<<redacted>>"
rlm_ldap (ldap_users): Released connection (0)
Need more connections to reach 10 spares
rlm_ldap (ldap_users): Opening additional connection (5), 1 of 27 pending slots used
rlm_ldap (ldap_users): Connecting to ldaps://<<redacted>>:636 ldaps://<<redacted>>:636
rlm_ldap (ldap_users): Waiting for bind result...
rlm_ldap (ldap_users): Bind successful
(0) [ldap_users] = ok
(0) } # if (User-Password) = ok
(0) if (EAP-Message) {
(0) if (EAP-Message) -> FALSE
(0) if ((ok || updated) && User-Password && !control:Auth-Type) {
(0) if ((ok || updated) && User-Password && !control:Auth-Type) -> TRUE
(0) if ((ok || updated) && User-Password && !control:Auth-Type) {
(0) update control {
(0) &Auth-Type := pam
(0) } # update control = noop
(0) } # if ((ok || updated) && User-Password && !control:Auth-Type) = noop
(0) [expiration] = noop
(0) [logintime] = noop
(0) } # authorize = ok
(0) Found Auth-Type = pam
(0) # Executing group from file /etc/raddb/sites-enabled/cavanasystems-site
(0) authenticate {
(0) pam: Using pamauth string "radiusd" for pam.conf lookup
(0) pam: Authentication succeeded
(0) [pam] = ok
(0) } # authenticate = ok
(0) # Executing section post-auth from file /etc/raddb/sites-enabled/cavanasystems-site
(0) post-auth {
(0) if (control:Auth-Type == PAM) {
(0) if (control:Auth-Type == PAM) -> TRUE
(0) if (control:Auth-Type == PAM) {
(0) policy cavanasystems_netadmins_auth {
(0) if ((&request:NAS-Port-Type == "Virtual") && (&request:NAS-IP-Address =~ /^10.88.0.[0-254]/) && (ldap_users-LDAP-Group == "CN=<<redacted>>")) {
(0) Searching for user in group "<<redacted>>"
rlm_ldap (ldap_users): Reserved connection (1)
(0) Using user DN from request "<<redacted>>"
(0) Checking user object's memberOf attributes
(0) Performing unfiltered search in "<<redacted>>", scope "base"
(0) Waiting for search result...
(0) Processing memberOf value "<<redacted>>" as a DN
(0) Processing memberOf value "<<redacted>>" as a DN
(0) User found in group DN "<<redacted>>".
Comparison between membership: dn, check: dn
rlm_ldap (ldap_users): Released connection (1)
Need more connections to reach 10 spares
rlm_ldap (ldap_users): Opening additional connection (6), 1 of 26 pending slots used
rlm_ldap (ldap_users): Connecting to ldaps://<<redacted>>:636 ldaps://<<redacted>>:636
rlm_ldap (ldap_users): Waiting for bind result...
rlm_ldap (ldap_users): Bind successful
(0) if ((&request:NAS-Port-Type == "Virtual") && (&request:NAS-IP-Address =~ /^10.88.0.[0-254]/) && (ldap_users-LDAP-Group == "CN= <<redacted>>")) -> TRUE
(0) if ((&request:NAS-Port-Type == "Virtual") && (&request:NAS-IP-Address =~ /^10.88.0.[0-254]/) && (ldap_users-LDAP-Group == "<<redacted>>")) {
(0) update reply {
(0) &Service-Type := Login-User
(0) &Cisco-AVPair := "shell:priv-lvl=15"
(0) &Idle-Timeout := 900
(0) &Session-Timeout := 3600
(0) } # update reply = noop
(0) [ok] = ok
(0) } # if ((&request:NAS-Port-Type == "Virtual") && (&request:NAS-IP-Address =~ /^10.88.0.[0-254]/) && (ldap_users-LDAP-Group == "CN= <<redacted>>")) = ok
(0) ... skipping elsif: Preceding "if" was taken
(0) ... skipping else: Preceding "if" was taken
(0) } # policy cavanasystems_netadmins_auth = ok
(0) } # if (control:Auth-Type == PAM) = ok
(0) if (control:Auth-Type == EAP) {
(0) if (control:Auth-Type == EAP) -> FALSE
(0) policy debug_all {
(0) policy debug_control {
(0) if ("%{debug_attr:control:}" == '') {
(0) Attributes matching "control:"
(0) &control:LDAP-UserDN = <<redacted>>
(0) &control:Auth-Type := pam
(0) EXPAND %{debug_attr:control:}
(0) -->
(0) if ("%{debug_attr:control:}" == '') -> TRUE
(0) if ("%{debug_attr:control:}" == '') {
(0) [noop] = noop
(0) } # if ("%{debug_attr:control:}" == '') = noop
(0) } # policy debug_control = noop
(0) policy debug_request {
(0) if ("%{debug_attr:request:}" == '') {
(0) Attributes matching "request:"
(0) &request:User-Name = <<redacted>>
(0) &request:User-Password = <<redacted>>
(0) &request:NAS-Port = 1
(0) &request:NAS-Port-Id = tty1
(0) &request:NAS-Port-Type = Virtual
(0) &request:NAS-IP-Address = 10.88.0.4
(0) &request:Event-Timestamp = Feb 8 2025 20:20:40 CET
(0) EXPAND %{debug_attr:request:}
(0) -->
(0) if ("%{debug_attr:request:}" == '') -> TRUE
(0) if ("%{debug_attr:request:}" == '') {
(0) [noop] = noop
(0) } # if ("%{debug_attr:request:}" == '') = noop
(0) } # policy debug_request = noop
(0) policy debug_coa {
(0) if ("%{debug_attr:coa:}" == '') {
(0) Attributes matching "coa:"
(0) WARNING: List "coa" is not available
(0) EXPAND %{debug_attr:coa:}
(0) -->
(0) if ("%{debug_attr:coa:}" == '') -> TRUE
(0) if ("%{debug_attr:coa:}" == '') {
(0) [noop] = noop
(0) } # if ("%{debug_attr:coa:}" == '') = noop
(0) } # policy debug_coa = noop
(0) policy debug_reply {
(0) if ("%{debug_attr:reply:}" == '') {
(0) Attributes matching "reply:"
(0) &reply:Service-Type := Login-User
(0) &reply:Cisco-AVPair := shell:priv-lvl=15
(0) &reply:Idle-Timeout := 900
(0) &reply:Session-Timeout := 3600
(0) EXPAND %{debug_attr:reply:}
(0) -->
(0) if ("%{debug_attr:reply:}" == '') -> TRUE
(0) if ("%{debug_attr:reply:}" == '') {
(0) [noop] = noop
(0) } # if ("%{debug_attr:reply:}" == '') = noop
(0) } # policy debug_reply = noop
(0) policy debug_session_state {
(0) if ("%{debug_attr:session-state:}" == '') {
(0) Attributes matching "session-state:"
(0) EXPAND %{debug_attr:session-state:}
(0) -->
(0) if ("%{debug_attr:session-state:}" == '') -> TRUE
(0) if ("%{debug_attr:session-state:}" == '') {
(0) [noop] = noop
(0) } # if ("%{debug_attr:session-state:}" == '') = noop
(0) } # policy debug_session_state = noop
(0) } # policy debug_all = noop
(0) [exec] = noop
(0) policy remove_reply_message_if_eap {
(0) if (&reply:EAP-Message && &reply:Reply-Message) {
(0) if (&reply:EAP-Message && &reply:Reply-Message) -> FALSE
(0) else {
(0) [noop] = noop
(0) } # else = noop
(0) } # policy remove_reply_message_if_eap = noop
(0) if (EAP-Key-Name && &reply:EAP-Session-Id) {
(0) if (EAP-Key-Name && &reply:EAP-Session-Id) -> FALSE
(0) } # post-auth = ok
(0) Login OK: [<<redacted>>] (from client it-mil01-4p01-sw01 port 1)
(0) Sent Access-Accept Id 206 from 10.88.18.9:1812 to 10.88.0.4:1645 length 81
(0) Service-Type := Login-User
(0) Cisco-AVPair := "shell:priv-lvl=15"
(0) Idle-Timeout := 900
(0) Session-Timeout := 3600
(0) Finished request
From this output you can see that the request took the correct path; I left a debug_all
open so you could see the 3 main attribute lists populated (request
, control
and reply
).
And here is the server debug output when processing an EAP-TLS request; it will make different round-trips so I took only the most important exchanges between the NAS and FreeRADIUS.
(1) Received Access-Request Id 159 from 10.88.0.5:62938 to 10.88.18.9:1812 length 519
(1) User-Name = "host/ITMIL01CD0002.corp.cavanasystems.com"
(1) Service-Type = Framed-User
(1) Cisco-AVPair = "service-type=Framed"
(1) Framed-MTU = 1485
(1) EAP-Message = <<redacted>>
(1) Message-Authenticator = 0xfba0aee308323b328372802b4e3b89b5
(1) Cisco-AVPair = "audit-session-id=0500580A0000017E4FFC0EA8"
(1) Cisco-AVPair = "method=dot1x"
(1) Cisco-AVPair = "client-iif-id=1694504226"
(1) Cisco-AVPair = "vlan-id=112"
(1) NAS-IP-Address = 10.88.0.5
(1) NAS-Port-Type = Wireless-802.11
(1) NAS-Port = 5
(1) Cisco-AVPair = "cisco-wlan-ssid=CavanaSystems-CORP-802.1x"
(1) Cisco-AVPair = "wlan-profile-name=Paullo HQ 802.1x"
(1) Called-Station-Id = "90-e9-5e-87-9e-20:CavanaSystems-CORP-802.1x"
(1) Calling-Station-Id = "8a-43-bc-00-47-d1"
(1) Airespace-Wlan-Id = 3
(1) NAS-Identifier = "IT-MIL01-4P01-WC01"
(1) WLAN-Group-Cipher = 1027076
(1) WLAN-Pairwise-Cipher = 1027076
(1) WLAN-AKM-Suite = 1027077
(1) WLAN-Group-Mgmt-Cipher = 1027078
(1) # Executing section authorize from file /etc/raddb/sites-enabled/cavanasystems-site
(1) authorize {
(1) policy filter_username {
(1) if (&User-Name) {
(1) if (&User-Name) -> TRUE
(1) if (&User-Name) {
(1) if (&User-Name =~ / /) {
(1) if (&User-Name =~ / /) -> FALSE
(1) if (&User-Name =~ /@[^@]*@/ ) {
(1) if (&User-Name =~ /@[^@]*@/ ) -> FALSE
(1) if (&User-Name =~ /\.\./ ) {
(1) if (&User-Name =~ /\.\./ ) -> FALSE
(1) if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) {
(1) if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) -> FALSE
(1) if (&User-Name =~ /\.$/) {
(1) if (&User-Name =~ /\.$/) -> FALSE
(1) if (&User-Name =~ /@\./) {
(1) if (&User-Name =~ /@\./) -> FALSE
(1) } # if (&User-Name) = notfound
(1) } # policy filter_username = notfound
(1) [preprocess] = ok
(1) eap: Peer sent EAP Response (code 2) ID 1 length 46
(1) eap: EAP-Identity reply, returning 'ok' so we can short-circuit the rest of authorize
(1) [eap] = ok
(1) } # authorize = ok
(1) Found Auth-Type = eap
(1) # Executing group from file /etc/raddb/sites-enabled/cavanasystems-site
(1) authenticate {
(1) eap: Peer sent packet with method EAP Identity (1)
(1) eap: Calling submodule eap_tls to process data
(1) eap_tls: (TLS) TLS -Initiating new session
(1) eap_tls: (TLS) TLS - Setting verify mode to require certificate from client
(1) eap: Sending EAP Request (code 1) ID 2 length 10
(1) eap: EAP session adding &reply:State = 0xc4a05ec6c4a253d8
(1) [eap] = handled
(1) } # authenticate = handled
(1) Using Post-Auth-Type Challenge
(1) # Executing group from file /etc/raddb/sites-enabled/cavanasystems-site
(1) Post-Auth-Type Challenge {
(1) policy remove_reply_message_if_eap {
(1) if (&reply:EAP-Message && &reply:Reply-Message) {
(1) if (&reply:EAP-Message && &reply:Reply-Message) -> FALSE
(1) else {
(1) [noop] = noop
(1) } # else = noop
(1) } # policy remove_reply_message_if_eap = noop
(1) attr_filter.access_challenge: EXPAND %{User-Name}
(1) attr_filter.access_challenge: --> host/ITMIL01CD0002.corp.cavanasystems.com
(1) attr_filter.access_challenge: Matched entry DEFAULT at line 12
(1) [attr_filter.access_challenge.post-auth] = updated
(1) } # Post-Auth-Type Challenge = updated
(1) session-state: Saving cached attributes
(1) Framed-MTU = 1014
(1) Sent Access-Challenge Id 159 from 10.88.18.9:1812 to 10.88.0.5:62938 length 68
(1) EAP-Message = 0x0102000a0da000000000
(1) Message-Authenticator = 0x00000000000000000000000000000000
(1) State = 0xc4a05ec6c4a253d85b8d5c785d2ec94b
(1) Finished request
Here something very interesting happened. As soon as the eap
module in the authorize
section was hit, it exited with return
which means “stop processing this section, as I have passed and there is nothing more interesting to be done here” and it goes straight to the authenticate
section where the eap
module send an Access-Challenge
.
The following message reaches the authorize
section where we can finally query our LDAP Directory because the client has presented itself in response to the earlier Access-Challenge
:
(2) Received Access-Request Id 167 from 10.88.0.5:62938 to 10.88.18.9:1812 length 681
(2) User-Name = "host/ITMIL01CD0002.corp.cavanasystems.com"
(2) Service-Type = Framed-User
(2) Cisco-AVPair = "service-type=Framed"
(2) Framed-MTU = 1485
(2) EAP-Message = <<redacted>>
(2) Message-Authenticator = 0x86fc839a5e5c554ca7c5fe86c1bdbea2
(2) Cisco-AVPair = "audit-session-id=0500580A0000017E4FFC0EA8"
(2) Cisco-AVPair = "method=dot1x"
(2) Cisco-AVPair = "client-iif-id=1694504226"
(2) Cisco-AVPair = "vlan-id=112"
(2) NAS-IP-Address = 10.88.0.5
(2) NAS-Port-Type = Wireless-802.11
(2) NAS-Port = 5
(2) State = 0xc4a05ec6c4a253d85b8d5c785d2ec94b
(2) Cisco-AVPair = "cisco-wlan-ssid=CavanaSystems-CORP-802.1x"
(2) Cisco-AVPair = "wlan-profile-name=Paullo HQ 802.1x"
(2) Called-Station-Id = "90-e9-5e-87-9e-20:CavanaSystems-CORP-802.1x"
(2) Calling-Station-Id = "8a-43-bc-00-47-d1"
(2) Airespace-Wlan-Id = 3
(2) NAS-Identifier = "IT-MIL01-4P01-WC01"
(2) WLAN-Group-Cipher = 1027076
(2) WLAN-Pairwise-Cipher = 1027076
(2) WLAN-AKM-Suite = 1027077
(2) WLAN-Group-Mgmt-Cipher = 1027078
(2) Restoring &session-state
(2) &session-state:Framed-MTU = 1014
(2) # Executing section authorize from file /etc/raddb/sites-enabled/cavanasystems-site
(2) authorize {
(2) policy filter_username {
(2) if (&User-Name) {
(2) if (&User-Name) -> TRUE
(2) if (&User-Name) {
(2) if (&User-Name =~ / /) {
(2) if (&User-Name =~ / /) -> FALSE
(2) if (&User-Name =~ /@[^@]*@/ ) {
(2) if (&User-Name =~ /@[^@]*@/ ) -> FALSE
(2) if (&User-Name =~ /\.\./ ) {
(2) if (&User-Name =~ /\.\./ ) -> FALSE
(2) if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) {
(2) if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) -> FALSE
(2) if (&User-Name =~ /\.$/) {
(2) if (&User-Name =~ /\.$/) -> FALSE
(2) if (&User-Name =~ /@\./) {
(2) if (&User-Name =~ /@\./) -> FALSE
(2) } # if (&User-Name) = notfound
(2) } # policy filter_username = notfound
(2) [preprocess] = ok
(2) eap: Peer sent EAP Response (code 2) ID 2 length 190
(2) eap: No EAP Start, assuming it's an on-going EAP conversation
(2) [eap] = updated
(2) if (User-Password) {
(2) if (User-Password) -> FALSE
(2) if (EAP-Message) {
(2) if (EAP-Message) -> TRUE
(2) if (EAP-Message) {
(2) if (EAP-Type == EAP-TLS) {
(2) if (EAP-Type == EAP-TLS) -> TRUE
(2) if (EAP-Type == EAP-TLS) {
(2) if (&User-Name =~ /^host\/([A-Z0-9]{13})/) {
(2) if (&User-Name =~ /^host\/([A-Z0-9]{13})/) -> TRUE
(2) if (&User-Name =~ /^host\/([A-Z0-9]{13})/) {
(2) update request {
(2) EXPAND %{1}$
(2) --> ITMIL01CD0002$
(2) Stripped-User-Name := ITMIL01CD0002$
(2) } # update request = noop
rlm_ldap (ldap_computers): Reserved connection (0)
(2) ldap_computers: EXPAND (sAMAccountName=%{%{Stripped-User-Name}:-%{User-Name}})
(2) ldap_computers: --> (sAMAccountName=ITMIL01CD0002$)
(2) ldap_computers: Performing search in "<<redacted>>" with filter "(sAMAccountName=ITMIL01CD0002$)", scope "sub"
(2) ldap_computers: Waiting for search result...
rlm_ldap (ldap_computers): Reconnecting (0)
rlm_ldap (ldap_computers): Connecting to ldaps://<<redacted>>:636 ldaps://<<redacted>>:636
rlm_ldap (ldap_computers): Waiting for bind result...
rlm_ldap (ldap_computers): Bind successful
(2) ldap_computers: WARNING: Search failed: Can't contact LDAP server. Got new socket, retrying...
(2) ldap_computers: Waiting for search result...
(2) ldap_computers: User object found at DN "CN=ITMIL01CD0002,<<redacted>>"
rlm_ldap (ldap_computers): Released connection (0)
Need more connections to reach 10 spares
rlm_ldap (ldap_computers): Opening additional connection (5), 1 of 27 pending slots used
rlm_ldap (ldap_computers): Connecting to ldaps://<<redacted>>:636 ldaps://<<redacted>>:636
rlm_ldap (ldap_computers): Waiting for bind result...
rlm_ldap (ldap_computers): Bind successful
rlm_ldap (ldap_computers): Closing expired connection (4) - Hit idle_timeout limit
rlm_ldap (ldap_computers): Closing expired connection (3) - Hit idle_timeout limit
rlm_ldap (ldap_computers): Closing expired connection (2) - Hit idle_timeout limit
rlm_ldap (ldap_computers): You probably need to lower "min"
rlm_ldap (ldap_computers): Closing expired connection (1) - Hit idle_timeout limit
(2) [ldap_computers] = ok
(2) } # if (&User-Name =~ /^host\/([A-Z0-9]{13})/) = ok
(2) } # if (EAP-Type == EAP-TLS) = ok
(2) } # if (EAP-Message) = ok
(2) if ((ok || updated) && User-Password && !control:Auth-Type) {
(2) if ((ok || updated) && User-Password && !control:Auth-Type) -> FALSE
(2) [expiration] = noop
(2) [logintime] = noop
(2) } # authorize = updated
(2) Found Auth-Type = eap
(2) # Executing group from file /etc/raddb/sites-enabled/cavanasystems-site
(2) authenticate {
(2) eap: Removing EAP session with state 0xc4a05ec6c4a253d8
(2) eap: Previous EAP request found for state 0xc4a05ec6c4a253d8, released from the list
(2) eap: Peer sent packet with method EAP TLS (13)
(2) eap: Calling submodule eap_tls to process data
(2) eap_tls: (TLS) EAP Got final fragment (184 bytes)
(2) eap_tls: WARNING: (TLS) EAP Total received record fragments (184 bytes), does not equal expected expected data length (0 bytes)
(2) eap_tls: (TLS) EAP Done initial handshake
(2) eap_tls: (TLS) TLS - Handshake state - before SSL initialization
(2) eap_tls: (TLS) TLS - Handshake state - Server before SSL initialization
(2) eap_tls: (TLS) TLS - Handshake state - Server before SSL initialization
(2) eap_tls: (TLS) TLS - recv TLS 1.3 Handshake, ClientHello
(2) eap_tls: (TLS) TLS - Handshake state - Server SSLv3/TLS read client hello
(2) eap_tls: (TLS) TLS - send TLS 1.2 Handshake, ServerHello
(2) eap_tls: (TLS) TLS - Handshake state - Server SSLv3/TLS write server hello
(2) eap_tls: (TLS) TLS - send TLS 1.2 Handshake, Certificate
(2) eap_tls: (TLS) TLS - Handshake state - Server SSLv3/TLS write certificate
(2) eap_tls: (TLS) TLS - send TLS 1.2 Handshake, ServerKeyExchange
(2) eap_tls: (TLS) TLS - Handshake state - Server SSLv3/TLS write key exchange
(2) eap_tls: (TLS) TLS - send TLS 1.2 Handshake, CertificateRequest
(2) eap_tls: (TLS) TLS - Handshake state - Server SSLv3/TLS write certificate request
(2) eap_tls: (TLS) TLS - send TLS 1.2 Handshake, ServerHelloDone
(2) eap_tls: (TLS) TLS - Handshake state - Server SSLv3/TLS write server done
(2) eap_tls: (TLS) TLS - Server : Need to read more data: SSLv3/TLS write server done
(2) eap_tls: (TLS) TLS - In Handshake Phase
(2) eap: Sending EAP Request (code 1) ID 3 length 1020
(2) eap: EAP session adding &reply:State = 0xc4a05ec6c5a353d8
(2) [eap] = handled
(2) } # authenticate = handled
(2) Using Post-Auth-Type Challenge
(2) # Executing group from file /etc/raddb/sites-enabled/cavanasystems-site
(2) Post-Auth-Type Challenge {
(2) policy remove_reply_message_if_eap {
(2) if (&reply:EAP-Message && &reply:Reply-Message) {
(2) if (&reply:EAP-Message && &reply:Reply-Message) -> FALSE
(2) else {
(2) [noop] = noop
(2) } # else = noop
(2) } # policy remove_reply_message_if_eap = noop
(2) attr_filter.access_challenge: EXPAND %{User-Name}
(2) attr_filter.access_challenge: --> host/ITMIL01CD0002.corp.cavanasystems.com
(2) attr_filter.access_challenge: Matched entry DEFAULT at line 12
(2) [attr_filter.access_challenge.post-auth] = updated
(2) } # Post-Auth-Type Challenge = updated
(2) session-state: Saving cached attributes
(2) Framed-MTU = 1014
(2) TLS-Session-Information = "(TLS) TLS - recv TLS 1.3 Handshake, ClientHello"
(2) TLS-Session-Information = "(TLS) TLS - send TLS 1.2 Handshake, ServerHello"
(2) TLS-Session-Information = "(TLS) TLS - send TLS 1.2 Handshake, Certificate"
(2) TLS-Session-Information = "(TLS) TLS - send TLS 1.2 Handshake, ServerKeyExchange"
(2) TLS-Session-Information = "(TLS) TLS - send TLS 1.2 Handshake, CertificateRequest"
(2) TLS-Session-Information = "(TLS) TLS - send TLS 1.2 Handshake, ServerHelloDone"
(2) Sent Access-Challenge Id 167 from 10.88.18.9:1812 to 10.88.0.5:62938 length 1086
(2) EAP-Message = <<redacted>>
(2) Message-Authenticator = 0x00000000000000000000000000000000
(2) State = <<redacted>>
(2) Finished request
In this exchange we queried the LDAP directory and obtained the groups the PC is a member of.
There are a few more exchanges where the client negotiates the EAP method and a few others which we omit. Finally, the EAP-TLS submodule is called to perform the validation of the client certificate:
(8) eap: Peer sent packet with method EAP TLS (13)
(8) eap: Calling submodule eap_tls to process data
(8) eap_tls: (TLS) EAP Done initial handshake
(8) eap_tls: (TLS) TLS - Handshake state - Server SSLv3/TLS write server done
(8) eap_tls: (TLS) TLS - recv TLS 1.2 Handshake, Certificate
(8) eap_tls: (TLS) TLS - Creating attributes from 2 certificate in chain
(8) eap_tls: TLS-Cert-Serial := "3700000007f60f540e57f341e0000000000007"
(8) eap_tls: TLS-Cert-Expiration := "340810150157Z"
(8) eap_tls: TLS-Cert-Valid-Since := "240810145157Z"
(8) eap_tls: TLS-Cert-Subject := "/DC=com/DC=cavanasystems/DC=corp/CN=CavanaSystems-Issuing-CA01"
(8) eap_tls: TLS-Cert-Issuer := "/CN=CavanaSystems-Root-CA"
(8) eap_tls: TLS-Cert-Common-Name := "CavanaSystems-Issuing-CA01"
(8) eap_tls: (TLS) TLS - Creating attributes from 1 certificate in chain
(8) eap_tls: TLS-Client-Cert-Serial := "15000000d68fb8981eda9aeae20001000000d6"
(8) eap_tls: TLS-Client-Cert-Expiration := "260201154206Z"
(8) eap_tls: TLS-Client-Cert-Valid-Since := "250201154206Z"
(8) eap_tls: TLS-Client-Cert-Subject := "/C=IT/ST=Milano/L=Paullo/O=CavanaSystems/OU=IT Services/CN=itmil01cd0002.corp.cavanasystems.com"
(8) eap_tls: TLS-Client-Cert-Issuer := "/DC=com/DC=cavanasystems/DC=corp/CN=CavanaSystems-Issuing-CA01"
(8) eap_tls: TLS-Client-Cert-Common-Name := "itmil01cd0002.corp.cavanasystems.com"
(8) eap_tls: TLS-Client-Cert-Subject-Alt-Name-Dns := "itmil01cd0002.corp.cavanasystems.com"
(8) eap_tls: TLS-Client-Cert-Subject-Alt-Name-Dns := "corp.cavanasystems.com"
(8) eap_tls: TLS-Client-Cert-X509v3-Extended-Key-Usage += "TLS Web Client Authentication"
(8) eap_tls: TLS-Client-Cert-X509v3-Subject-Key-Identifier += "56:DE:BF:9B:15:32:10:E1:5A:AD:D2:F9:9B:3D:EB:B4:63:4E:AD:4D"
(8) eap_tls: TLS-Client-Cert-X509v3-Authority-Key-Identifier += "92:36:D2:05:AC:1F:99:1E:01:FD:8A:8D:31:66:10:2C:A1:B3:80:C4"
(8) eap_tls: TLS-Client-Cert-X509v3-Extended-Key-Usage-OID += "1.3.6.1.5.5.7.3.2"
Certificate chain - 1 intermediate CA cert(s) untrusted
To forbid these certificates see 'reject_unknown_intermediate_ca'
(TLS) untrusted certificate with depth [0] subject name /C=IT/ST=Milano/L=Paullo/O=CavanaSystems/OU=IT Services/CN=itmil01cd0002.corp.cavanasystems.com
(8) eap_tls: Starting OCSP Request
(8) eap_tls: ocsp: Using responder URL "http://pki.cavanasystems.com:80/ocsp"
This Update: Feb 8 14:53:49 2025 GMT
Next Update: Feb 9 17:13:49 2025 GMT
(8) eap_tls: ocsp: Cert status: good
(8) eap_tls: ocsp: Certificate is valid
(8) eap_tls: (TLS) TLS - Handshake state - Server SSLv3/TLS read client certificate
(8) eap_tls: (TLS) TLS - recv TLS 1.2 Handshake, ClientKeyExchange
(8) eap_tls: (TLS) TLS - Handshake state - Server SSLv3/TLS read client key exchange
(8) eap_tls: (TLS) TLS - recv TLS 1.2 Handshake, CertificateVerify
(8) eap_tls: (TLS) TLS - Handshake state - Server SSLv3/TLS read certificate verify
(8) eap_tls: (TLS) TLS - Handshake state - Server SSLv3/TLS read change cipher spec
(8) eap_tls: (TLS) TLS - recv TLS 1.2 Handshake, Finished
(8) eap_tls: (TLS) TLS - Handshake state - Server SSLv3/TLS read finished
(8) eap_tls: (TLS) TLS - send TLS 1.2 ChangeCipherSpec
(8) eap_tls: (TLS) TLS - Handshake state - Server SSLv3/TLS write change cipher spec
(8) eap_tls: (TLS) TLS - send TLS 1.2 Handshake, Finished
(8) eap_tls: (TLS) TLS - Handshake state - Server SSLv3/TLS write finished
(8) eap_tls: Serialising session fde36035740edc60485099fcc5f57ee04d006c914ba7ecdb409fa91ff5872d6f, and storing in cache
(8) eap_tls: WARNING: (TLS) TLS - Wrote session fde36035740edc60485099fcc5f57ee04d006c914ba7ecdb409fa91ff5872d6f to /var/log/radius/tlscache/fde36035740edc60485099fcc5f57ee04d006c914ba7ecdb409fa91ff5872d6f.asn1 (1853 bytes)
(8) eap_tls: (TLS) TLS - Handshake state - SSL negotiation finished successfully
(8) eap_tls: (TLS) TLS - Connection Established
(8) eap_tls: TLS-Session-Cipher-Suite = "ECDHE-RSA-AES256-GCM-SHA384"
(8) eap_tls: TLS-Session-Version = "TLS 1.2"
(8) eap: Sending EAP Request (code 1) ID 9 length 61
(8) eap: EAP session adding &reply:State = 0xc4a05ec6c3a953d8
(8) [eap] = handled
(8) } # authenticate = handled
The certificate is validated and the authenticate
section is handled. The only missing step is post-auth
were we process the policies defined earlier:
(9) Received Access-Request Id 87 from 10.88.0.5:62938 to 10.88.18.9:1812 length 497
(9) User-Name = "host/ITMIL01CD0002.corp.cavanasystems.com"
(9) Service-Type = Framed-User
(9) Cisco-AVPair = "service-type=Framed"
(9) Framed-MTU = 1485
(9) EAP-Message = 0x020900060d00
(9) Message-Authenticator = 0x84d81ee4d70d45022793d8f55d5fa5d5
(9) Cisco-AVPair = "audit-session-id=0500580A000001F0634AE3AA"
(9) Cisco-AVPair = "method=dot1x"
(9) Cisco-AVPair = "client-iif-id=3154121829"
(9) Cisco-AVPair = "vlan-id=112"
(9) NAS-IP-Address = 10.88.0.5
(9) NAS-Port-Type = Wireless-802.11
(9) NAS-Port = 5
(9) State = 0x085077840f597ae872d4750374ac81b1
(9) Cisco-AVPair = "cisco-wlan-ssid=CavanaSystems-CORP-802.1x"
(9) Cisco-AVPair = "wlan-profile-name=Paullo HQ 802.1x"
(9) Called-Station-Id = "90-e9-5e-87-9e-20:CavanaSystems-CORP-802.1x"
(9) Calling-Station-Id = "8a-43-bc-00-47-d1"
(9) Airespace-Wlan-Id = 3
(9) NAS-Identifier = "IT-MIL01-4P01-WC01"
(9) WLAN-Group-Cipher = 1027076
(9) WLAN-Pairwise-Cipher = 1027076
(9) WLAN-AKM-Suite = 1027077
(9) WLAN-Group-Mgmt-Cipher = 1027078
(9) Restoring &session-state
(9) &session-state:Framed-MTU = 1014
(9) &session-state:TLS-Session-Information = "(TLS) TLS - recv TLS 1.3 Handshake, ClientHello"
(9) &session-state:TLS-Session-Information = "(TLS) TLS - send TLS 1.2 Handshake, ServerHello"
(9) &session-state:TLS-Session-Information = "(TLS) TLS - send TLS 1.2 Handshake, Certificate"
(9) &session-state:TLS-Session-Information = "(TLS) TLS - send TLS 1.2 Handshake, ServerKeyExchange"
(9) &session-state:TLS-Session-Information = "(TLS) TLS - send TLS 1.2 Handshake, CertificateRequest"
(9) &session-state:TLS-Session-Information = "(TLS) TLS - send TLS 1.2 Handshake, ServerHelloDone"
(9) &session-state:TLS-Session-Information = "(TLS) TLS - recv TLS 1.2 Handshake, Certificate"
(9) &session-state:TLS-Session-Information = "(TLS) TLS - recv TLS 1.2 Handshake, ClientKeyExchange"
(9) &session-state:TLS-Session-Information = "(TLS) TLS - recv TLS 1.2 Handshake, CertificateVerify"
(9) &session-state:TLS-Session-Information = "(TLS) TLS - recv TLS 1.2 Handshake, Finished"
(9) &session-state:TLS-Session-Information = "(TLS) TLS - send TLS 1.2 ChangeCipherSpec"
(9) &session-state:TLS-Session-Information = "(TLS) TLS - send TLS 1.2 Handshake, Finished"
(9) &session-state:TLS-Cache-Filename = "/var/log/radius/tlscache/eed6075122b0771a90d75ce0b6e292529cc728d6db54916b0db834042e840187.asn1"
(9) &session-state:TLS-Session-Cipher-Suite = "ECDHE-RSA-AES256-GCM-SHA384"
(9) &session-state:TLS-Session-Version = "TLS 1.2"
(9) # Executing section authorize from file /etc/raddb/sites-enabled/cavanasystems-site
(9) authorize {
(9) policy filter_username {
(9) if (&User-Name) {
(9) if (&User-Name) -> TRUE
(9) if (&User-Name) {
(9) if (&User-Name =~ / /) {
(9) if (&User-Name =~ / /) -> FALSE
(9) if (&User-Name =~ /@[^@]*@/ ) {
(9) if (&User-Name =~ /@[^@]*@/ ) -> FALSE
(9) if (&User-Name =~ /\.\./ ) {
(9) if (&User-Name =~ /\.\./ ) -> FALSE
(9) if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) {
(9) if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) -> FALSE
(9) if (&User-Name =~ /\.$/) {
(9) if (&User-Name =~ /\.$/) -> FALSE
(9) if (&User-Name =~ /@\./) {
(9) if (&User-Name =~ /@\./) -> FALSE
(9) } # if (&User-Name) = notfound
(9) } # policy filter_username = notfound
(9) [preprocess] = ok
(9) eap: Peer sent EAP Response (code 2) ID 9 length 6
(9) eap: No EAP Start, assuming it's an on-going EAP conversation
(9) [eap] = updated
(9) if (&request:User-Password) {
(9) if (&request:User-Password) -> FALSE
(9) if (&request:EAP-Message) {
(9) if (&request:EAP-Message) -> TRUE
(9) if (&request:EAP-Message) {
(9) if (&request:EAP-Type == EAP-TLS) {
(9) if (&request:EAP-Type == EAP-TLS) -> TRUE
(9) if (&request:EAP-Type == EAP-TLS) {
(9) if (&request:User-Name =~ /^.*([A-Z0-9]{13}).corp\.cavanasystems\.com/i) {
(9) if (&request:User-Name =~ /^.*([A-Z0-9]{13}).corp\.cavanasystems\.com/i) -> TRUE
(9) if (&request:User-Name =~ /^.*([A-Z0-9]{13}).corp\.cavanasystems\.com/i) {
(9) update request {
(9) EXPAND %{1}$
(9) --> ITMIL01CD0002$
(9) &Stripped-User-Name := ITMIL01CD0002$
(9) } # update request = noop
rlm_ldap (ldap_computers): Reserved connection (1)
(9) ldap_computers: EXPAND (sAMAccountName=%{%{Stripped-User-Name}:-%{User-Name}})
(9) ldap_computers: --> (sAMAccountName=ITMIL01CD0002$)
(9) ldap_computers: Performing search in "<<redacted>>" with filter "(sAMAccountName=ITMIL01CD0002$)", scope "sub"
(9) ldap_computers: Waiting for search result...
(9) ldap_computers: User object found at DN "<<redacted>>"
rlm_ldap (ldap_computers): Released connection (1)
(9) [ldap_computers] = ok
(9) } # if (&request:User-Name =~ /^.*([A-Z0-9]{13}).corp\.cavanasystems\.com/i) = ok
(9) } # if (&request:EAP-Type == EAP-TLS) = ok
(9) } # if (&request:EAP-Message) = ok
(9) if ((ok || updated) && User-Password && !control:Auth-Type) {
(9) if ((ok || updated) && User-Password && !control:Auth-Type) -> FALSE
(9) [expiration] = noop
(9) [logintime] = noop
(9) } # authorize = updated
(9) Found Auth-Type = eap
(9) # Executing group from file /etc/raddb/sites-enabled/cavanasystems-site
(9) authenticate {
(9) eap: Removing EAP session with state 0x085077840f597ae8
(9) eap: Previous EAP request found for state 0x085077840f597ae8, released from the list
(9) eap: Peer sent packet with method EAP TLS (13)
(9) eap: Calling submodule eap_tls to process data
(9) eap_tls: (TLS) Peer ACKed our handshake fragment. handshake is finished
(9) eap_tls: (TLS) cache - Setting up attributes for session resumption
(9) eap_tls: caching Stripped-User-Name := "ITMIL01CD0002$"
(9) eap_tls: caching EAP-Type = TLS
(9) eap_tls: caching TLS-Cert-Serial := "3700000002004becd5660d5250000000000002"
(9) eap_tls: caching TLS-Cert-Expiration := "340211095712Z"
(9) eap_tls: caching TLS-Cert-Valid-Since := "240211094712Z"
(9) eap_tls: caching TLS-Cert-Subject := "/DC=com/DC=cavanasystems/DC=corp/CN=CavanaSystems-Issuing-CA01"
(9) eap_tls: caching TLS-Cert-Issuer := "/CN=CavanaSystems-Root-CA"
(9) eap_tls: caching TLS-Cert-Common-Name := "CavanaSystems-Issuing-CA01"
(9) eap_tls: caching TLS-Client-Cert-Serial := "15000000d68fb8981eda9aeae20001000000d6"
(9) eap_tls: caching TLS-Client-Cert-Expiration := "260201154206Z"
(9) eap_tls: caching TLS-Client-Cert-Valid-Since := "250201154206Z"
(9) eap_tls: caching TLS-Client-Cert-Subject := "/C=IT/ST=Milano/L=Paullo/O=CavanaSystems/OU=IT Services/CN=itmil01cd0002.corp.cavanasystems.com"
(9) eap_tls: caching TLS-Client-Cert-Issuer := "/DC=com/DC=cavanasystems/DC=corp/CN=CavanaSystems-Issuing-CA01"
(9) eap_tls: caching TLS-Client-Cert-Common-Name := "itmil01cd0002.corp.cavanasystems.com"
(9) eap_tls: caching TLS-Client-Cert-Subject-Alt-Name-Dns := "itmil01cd0002.corp.cavanasystems.com"
(9) eap_tls: caching TLS-Client-Cert-Subject-Alt-Name-Dns := "corp.cavanasystems.com"
(9) eap_tls: caching TLS-Client-Cert-X509v3-Extended-Key-Usage += "TLS Web Client Authentication"
(9) eap_tls: caching TLS-Client-Cert-X509v3-Subject-Key-Identifier += "56:DE:BF:9B:15:32:10:E1:5A:AD:D2:F9:9B:3D:EB:B4:63:4E:AD:4D"
(9) eap_tls: caching TLS-Client-Cert-X509v3-Authority-Key-Identifier += "92:36:D2:05:AC:1F:99:1E:01:FD:8A:8D:31:66:10:2C:A1:B3:80:C4"
(9) eap_tls: caching TLS-Client-Cert-X509v3-Extended-Key-Usage-OID += "1.3.6.1.5.5.7.3.2"
(9) eap_tls: Saving session eed6075122b0771a90d75ce0b6e292529cc728d6db54916b0db834042e840187 in the disk cache
(9) eap: Sending EAP Success (code 3) ID 9 length 4
(9) eap: Freeing handler
(9) [eap] = ok
(9) } # authenticate = ok
(9) # Executing section post-auth from file /etc/raddb/sites-enabled/cavanasystems-site
(9) post-auth {
(9) if (&control:Auth-Type == PAM) {
(9) if (&control:Auth-Type == PAM) -> FALSE
(9) if (&control:Auth-Type == EAP) {
(9) if (&control:Auth-Type == EAP) -> TRUE
(9) if (&control:Auth-Type == EAP) {
(9) policy cavanasystems_vlan_mappings_wired {
(9) if ((&request:EAP-Type == "TLS") && (&request:NAS-Port-Type == "Ethernet") && (&request:ldap_computers-LDAP-Group == <<redacted>>)) {
(9) if ((&request:EAP-Type == "TLS") && (&request:NAS-Port-Type == "Ethernet") && (&request:ldap_computers-LDAP-Group == <<redacted>>)) -> FALSE
(9) } # policy cavanasystems_vlan_mappings_wired = noop
(9) policy cavanasystems_vlan_mappings_wireless {
(9) if ((&request:EAP-Type == "TLS") && (&request:NAS-Port-Type == "Wireless-802.11") && (&request:Called-Station-ID =~ /^[0-9a-f\-]{17}\:CavanaSystems-CORP-802\.1x$/) && (&request:ldap_computers-LDAP-Group == <<redacted>>)) {
(9) Searching for user in group <<redacted>>
rlm_ldap (ldap_computers): Reserved connection (6)
(9) Using user DN from request <<redacted>>
(9) Checking user object's memberOf attributes
(9) Performing unfiltered search in <<redacted>>, scope "base"
(9) Waiting for search result...
(9) Processing memberOf value <<redacted>> as a DN
(9) User found in group DN <<redacted>>. Comparison between membership: dn, check: dn
rlm_ldap (ldap_computers): Released connection (6)
(9) if ((&request:EAP-Type == "TLS") && (&request:NAS-Port-Type == "Wireless-802.11") && (&request:Called-Station-ID =~ /^[0-9a-f\-]{17}\:CavanaSystems-CORP-802\.1x$/) && (&request:ldap_computers-LDAP-Group == <<redacted>>)) -> TRUE
(9) if ((&request:EAP-Type == "TLS") && (&request:NAS-Port-Type == "Wireless-802.11") && (&request:Called-Station-ID =~ /^[0-9a-f\-]{17}\:CavanaSystems-CORP-802\.1x$/) && (&request:ldap_computers-LDAP-Group == <<redacted>>)) {
(9) update reply {
(9) &Tunnel-Type := VLAN
(9) &Tunnel-Medium-Type := IEEE-802
(9) &Tunnel-Private-Group-Id := "112"
(9) } # update reply = noop
(9) [ok] = ok
(9) } # if ((&request:EAP-Type == "TLS") && (&request:NAS-Port-Type == "Wireless-802.11") && (&request:Called-Station-ID =~ /^[0-9a-f\-]{17}\:CavanaSystems-CORP-802\.1x$/) && (&request:ldap_computers-LDAP-Group == <<redacted>>)) = ok
(9) } # policy cavanasystems_vlan_mappings_wireless = ok
(9) policy cavanasystems_verify_tls_client_common_name {
(9) if (&User-Name =~ /^host\/([A-Z0-9]{13}.corp\.cavanasystems\.com)/) {
(9) if (&User-Name =~ /^host\/([A-Z0-9]{13}.corp\.cavanasystems\.com)/) -> TRUE
(9) if (&User-Name =~ /^host\/([A-Z0-9]{13}.corp\.cavanasystems\.com)/) {
(9) update request {
(9) EXPAND %{1}
(9) --> ITMIL01CD0002.corp.cavanasystems.com
(9) &Sanitized-Host-Name := ITMIL01CD0002.corp.cavanasystems.com
(9) } # update request = noop
(9) } # if (&User-Name =~ /^host\/([A-Z0-9]{13}.corp\.cavanasystems\.com)/) = noop
(9) if ((&User-Name !~ /^@/) && &TLS-Client-Cert-Common-Name && ("%{tolower:%{TLS-Client-Cert-Common-Name}}" != "%{tolower:%{%{&Sanitized-Host-Name}:-%{&User-Name}}}")) {
(9) EXPAND %{tolower:%{TLS-Client-Cert-Common-Name}}
(9) --> itmil01cd0002.corp.cavanasystems.com
(9) EXPAND %{tolower:%{%{&Sanitized-Host-Name}:-%{&User-Name}}}
(9) --> itmil01cd0002.corp.cavanasystems.com
(9) if ((&User-Name !~ /^@/) && &TLS-Client-Cert-Common-Name && ("%{tolower:%{TLS-Client-Cert-Common-Name}}" != "%{tolower:%{%{&Sanitized-Host-Name}:-%{&User-Name}}}")) -> FALSE
(9) } # policy cavanasystems_verify_tls_client_common_name = noop
(9) } # if (&control:Auth-Type == EAP) = ok
(9) [exec] = noop
(9) policy remove_reply_message_if_eap {
(9) if (&reply:EAP-Message && &reply:Reply-Message) {
(9) if (&reply:EAP-Message && &reply:Reply-Message) -> FALSE
(9) else {
(9) [noop] = noop
(9) } # else = noop
(9) } # policy remove_reply_message_if_eap = noop
(9) if (EAP-Key-Name && &reply:EAP-Session-Id) {
(9) if (EAP-Key-Name && &reply:EAP-Session-Id) -> FALSE
(9) } # post-auth = ok
(9) Login OK: [host/ITMIL01CD0002.corp.cavanasystems.com] (from client it-mil01-4p01-wc01 port 5 cli 8a-43-bc-00-47-d1)
(9) Sent Access-Accept Id 87 from 10.88.18.9:1812 to 10.88.0.5:62938 length 220
(9) MS-MPPE-Recv-Key = <<redacted>>
(9) MS-MPPE-Send-Key = <<redacted>>
(9) EAP-Message = 0x03090004
(9) Message-Authenticator = 0x00000000000000000000000000000000
(9) User-Name = "host/ITMIL01CD0002.corp.cavanasystems.com"
(9) Tunnel-Type := VLAN
(9) Tunnel-Medium-Type := IEEE-802
(9) Tunnel-Private-Group-Id := "112"
(9) Finished request
I left the debugging on so you could see what was the state of the AVPs the server used to make its decisions.
Changes into radiusd.conf
Don’t forget to disable proxying and other components you are not using.
Enable auth
logging.
That’s it. The only two very minor things I couldn’t wrap my head around were why radiusd
sees the client certificate as an untrusted intermediate CA when it actually is the client certificate itself and why it fails (crashes during startup) the StartTLS
method for binding via LDAP.
I worked around the LDAP situation by just using explicit LDAPS on port 636, and decided to leave the certificate warning alone (after testing it is actually validating the certificate by testing a mixture of untrusted CAs and trusted ones but with wrong key usage).