Skip to content

Instantly share code, notes, and snippets.

@angely-dev
Created October 14, 2021 12:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save angely-dev/3d3de098c4ae81807ad565358ed63ffa to your computer and use it in GitHub Desktop.
Save angely-dev/3d3de098c4ae81807ad565358ed63ffa to your computer and use it in GitHub Desktop.
Force accept a RADIUS user, using FreeRADIUS and a custom module (UNLANG).
#
# /etc/freeradius/policy.d/force_accept
#
# Force accept a user.
#
# Two scenarios:
# - the user does NOT exist in the database => trigger FORCE-ACCEPT-USERNAME-NOT-FOUND
# - the user does exist but password is incorrect => trigger FORCE-ACCEPT-PASSWORD-INCORRECT
#
# This can be implemented with two modules, called at different sections in the FreeRADIUS sequence.
# (This may also be implemented with a single module for refactoring concern.)
#
force_accept_username_not_found {
#
# MANDATORY: set Auth-Type to Accept.
# Optional: set Tmp-String-0 for logging purpose (used in radiusd.conf:msg_goodpass).
#
update control {
&Auth-Type := Accept
&Tmp-String-0 := "FORCE-ACCEPT-USERNAME-NOT-FOUND"
}
#
# Optional: add some attributes for the NAS.
#
update reply {
&Cisco-AVPair += "ip:vrf-id=FORCE-ACCEPT-USERNAME-NOT-FOUND-VRF"
&Cisco-AVPair += "ip:addr-pool=FORCE-ACCEPT-USERNAME-NOT-FOUND-POOL"
&Cisco-AVPair += "ip:ip-unnumbered=Loopback0"
}
#
# Optional: tell we updated some attributes (good practice).
#
updated
}
force_accept_password_incorrect {
#
# Optional: set Auth-Type to Accept (for refactoring concern if you prefer a single module).
# Optional: set Tmp-String-0 for logging purpose (used in radiusd.conf:msg_goodpass).
#
update control {
# &Auth-Type := Accept
&Tmp-String-0 := "FORCE-ACCEPT-PASSWORD-INCORRECT"
}
#
# RADIUS attributes from radreply are fetched BEFORE the authentication checking,
# so it may be needed to remove them and (optionally) to add some attributes for the NAS.
#
update reply {
# Remove
&Framed-IP-Address !* ANY
&Framed-IP-Netmask !* ANY
&Cisco-AVPair !* ANY
# Add
&Cisco-AVPair += "ip:vrf-id=FORCE-ACCEPT-PASSWORD-INCORRECT-VRF"
&Cisco-AVPair += "ip:addr-pool=FORCE-ACCEPT-PASSWORD-INCORRECT-POOL"
&Cisco-AVPair += "ip:ip-unnumbered=Loopback1"
}
#
# MANDATORY: "pap" and "chap" modules need "ok" status to be returned.
#
ok
}
@angely-dev
Copy link
Author

FreeRADIUS server configuration:

#
# /etc/freeradius/radiusd.conf
#
log {
	#
	auth = yes
   	msg_goodpass = "%{control:Tmp-String-0}" # tell that the user has been forcibly accepted (see force_accept module)
	#
}
#

FreeRADIUS server sequence:

#
# /etc/freeradius/sites-enabled/my_radius
#
server my_radius {
        #
        # Authentication server
        #
        listen {
                type = auth
                #
        }

        #
        # Accounting server
        #
        listen {
                type = acct
                #
        }

        #
        # Handle an Access-Request
        #
        authorize {
                filter_username
                preprocess
                chap
                suffix
                sql
                if (notfound) {
                    # User not found in radcheck table, force accept him
                    force_accept_username_not_found
                }
                # some other things…
                expiration
                logintime
                pap
        }
        authenticate {
                Auth-Type PAP {
                        pap {
                            # Rewrite return code to handle it below (aka soft failure)
                            reject = 1
                        }

                        if (reject) {
                            # PAP authentication failed for the user, force accept him
                            force_accept_password_incorrect
                        }
                }
                Auth-Type CHAP {
                        chap {
                            # Rewrite return code to handle it below (aka soft failure)
                            reject = 1
                        }

                        if (reject) {
                            # CHAP authentication failed for the user, force accept him
                            force_accept_password_incorrect
                        }
                }
        }
        post-auth {
                #
        }

        #
        # Handle an Accounting-Request
        #
        preacct {
                #
        }
        accounting {
                #
        }
}

@angely-dev
Copy link
Author

Below an example of the module execution. Tested on FreeRADIUS version 3.0.21.

> SELECT username, attribute, op, value FROM radreply WHERE username = 'my-user';

+----------+-------------------+----+------------------+
| username | attribute         | op | value            |
+----------+-------------------+----+------------------+
| my-user  | Framed-IP-Address | := | 10.0.0.1         |
| my-user  | Framed-IP-Netmask | := | 255.255.255.255  |
| my-user  | Cisco-AVPair      | += | ip:vrf-id=MY-VRF |
+----------+-------------------+----+------------------+

> SELECT username, attribute, op, value FROM radcheck WHERE username = 'my-user';

+----------+--------------------+----+---------+
| username | attribute          | op | value   |
+----------+--------------------+----+---------+
| my-user  | Cleartext-Password | := | my-pass |
+----------+--------------------+----+---------+
# Bad username
$ radtest bad-user my-pass localhost 0 testing123
Sent Access-Request Id 193 from 0.0.0.0:38285 to 127.0.0.1:1812 length 78
	User-Name = "bad-user"
	User-Password = "my-pass"
	NAS-IP-Address = 127.0.1.1
	NAS-Port = 0
	Message-Authenticator = 0x00
	Cleartext-Password = "my-pass"
Received Access-Accept Id 193 from 127.0.0.1:1812 to 127.0.0.1:38285 length 164
	Cisco-AVPair = "ip:vrf-id=FORCE-ACCEPT-USERNAME-NOT-FOUND-VRF"
	Cisco-AVPair = "ip:addr-pool=FORCE-ACCEPT-USERNAME-NOT-FOUND-POOL"
	Cisco-AVPair = "ip:ip-unnumbered=Loopback0"
# Bad password
$ radtest my-user bad-pass localhost 0 testing123
Sent Access-Request Id 43 from 0.0.0.0:35374 to 127.0.0.1:1812 length 77
	User-Name = "my-user"
	User-Password = "bad-pass"
	NAS-IP-Address = 127.0.1.1
	NAS-Port = 0
	Message-Authenticator = 0x00
	Cleartext-Password = "bad-pass"
Received Access-Accept Id 43 from 127.0.0.1:1812 to 127.0.0.1:35374 length 164
	Cisco-AVPair = "ip:vrf-id=FORCE-ACCEPT-PASSWORD-INCORRECT-VRF"
	Cisco-AVPair = "ip:addr-pool=FORCE-ACCEPT-PASSWORD-INCORRECT-POOL"
	Cisco-AVPair = "ip:ip-unnumbered=Loopback1"
# Correct username and password
$ radtest my-user my-pass localhost 0 testing123
Sent Access-Request Id 194 from 0.0.0.0:48550 to 127.0.0.1:1812 length 77
	User-Name = "my-user"
	User-Password = "my-pass"
	NAS-IP-Address = 127.0.1.1
	NAS-Port = 0
	Message-Authenticator = 0x00
	Cleartext-Password = "my-pass"
Received Access-Accept Id 194 from 127.0.0.1:1812 to 127.0.0.1:48550 length 56
	Framed-IP-Address = 10.0.0.1
	Framed-IP-Netmask = 255.255.255.255
	Cisco-AVPair = "ip:vrf-id=MY-VRF"

FreeRADIUS server log:

==================================================
Bad username
==================================================
(…)
(0) sql: WARNING: User not found in radcheck table.
(…)
(0)     [sql] = notfound
(0)     if (notfound) {
(0)     if (notfound)  -> TRUE
(0)     if (notfound)  {
(0)       policy force_accept_username_not_found {
(0)         update control {
(0)           &Auth-Type := Accept
(0)           &Tmp-String-0 := "FORCE-ACCEPT-USERNAME-NOT-FOUND"
(0)         } # update control = noop
(0)         update reply {
(0)           &Cisco-AVPair += "ip:vrf-id=FORCE-ACCEPT-USERNAME-NOT-FOUND-VRF"
(0)           &Cisco-AVPair += "ip:addr-pool=FORCE-ACCEPT-USERNAME-NOT-FOUND-POOL"
(0)           &Cisco-AVPair += "ip:ip-unnumbered=Loopback0"
(0)         } # update reply = noop
(0)         [updated] = updated
(0)       } # policy force_accept_username_not_found = updated
(0)     } # if (notfound)  = updated
(…)
(0) pap: WARNING: Auth-Type already set.  Not setting to PAP
(0)     [pap] = noop
(0)   } # authorize = updated
(0) Found Auth-Type = Accept
(0) Auth-Type = Accept, accepting the user
(…)
(0) EXPAND %{control:Tmp-String-0}
(0)    --> FORCE-ACCEPT-USERNAME-NOT-FOUND
(0) Login OK: [bad-user/my-pass] (from client localhost port 0) FORCE-ACCEPT-USERNAME-NOT-FOUND
(0) Sent Access-Accept Id 193 from 127.0.0.1:1812 to 127.0.0.1:38285 length 0
(0)   Cisco-AVPair += "ip:vrf-id=FORCE-ACCEPT-USERNAME-NOT-FOUND-VRF"
(0)   Cisco-AVPair += "ip:addr-pool=FORCE-ACCEPT-USERNAME-NOT-FOUND-POOL"
(0)   Cisco-AVPair += "ip:ip-unnumbered=Loopback0"
(0) Finished request
==================================================
Bad password
==================================================
(…)
(1) sql: User found in radreply table, merging reply items
(1) sql:   Framed-IP-Address := 10.0.0.1
(1) sql:   Framed-IP-Netmask := 255.255.255.255
(1) sql:   Cisco-AVPair += "ip:vrf-id=MY-VRF"
(…)
(1)     [sql] = ok
(1)     if (notfound) {
(1)     if (notfound)  -> FALSE
(…)
(1) Found Auth-Type = PAP
(1) # Executing group from file /etc/freeradius/sites-enabled/radius
(1)   Auth-Type PAP {
(1) pap: Login attempt with password
(1) pap: Comparing with "known good" Cleartext-Password
(1) pap: ERROR: Cleartext password does not match "known good" password
(1) pap: Passwords don't match
(1)     [pap] = reject
(1)     if (reject) {
(1)     if (reject)  -> TRUE
(1)     if (reject)  {
(1)       policy force_accept_password_incorrect {
(1)         update control {
(1)           &Tmp-String-0 := "FORCE-ACCEPT-PASSWORD-INCORRECT"
(1)         } # update control = noop
(1)         update reply {
(1)           &Framed-IP-Address !* ANY
(1)           &Framed-IP-Netmask !* ANY
(1)           &Cisco-AVPair !* ANY
(1)           &Cisco-AVPair += "ip:vrf-id=FORCE-ACCEPT-PASSWORD-INCORRECT-VRF"
(1)           &Cisco-AVPair += "ip:addr-pool=FORCE-ACCEPT-PASSWORD-INCORRECT-POOL"
(1)           &Cisco-AVPair += "ip:ip-unnumbered=Loopback1"
(1)         } # update reply = noop
(1)         [ok] = ok
(1)       } # policy force_accept_password_incorrect = ok
(1)     } # if (reject)  = ok
(1)   } # Auth-Type PAP = ok
(…)
(1) EXPAND %{control:Tmp-String-0}
(1)    --> FORCE-ACCEPT-PASSWORD-INCORRECT
(1) Login OK: [my-user/bad-pass] (from client localhost port 0) FORCE-ACCEPT-PASSWORD-INCORRECT
(1) Sent Access-Accept Id 43 from 127.0.0.1:1812 to 127.0.0.1:35374 length 0
(1)   Cisco-AVPair += "ip:vrf-id=FORCE-ACCEPT-PASSWORD-INCORRECT-VRF"
(1)   Cisco-AVPair += "ip:addr-pool=FORCE-ACCEPT-PASSWORD-INCORRECT-POOL"
(1)   Cisco-AVPair += "ip:ip-unnumbered=Loopback1"
(1) Finished request
==================================================
Correct username and password
==================================================
(…)
(2) sql: User found in radreply table, merging reply items
(2) sql:   Framed-IP-Address := 10.0.0.1
(2) sql:   Framed-IP-Netmask := 255.255.255.255
(2) sql:   Cisco-AVPair += "ip:vrf-id=MY-VRF"
(…)
(2)     [sql] = ok
(2)     if (notfound) {
(2)     if (notfound)  -> FALSE
(…)
(2) Found Auth-Type = PAP
(2) # Executing group from file /etc/freeradius/sites-enabled/radius
(2)   Auth-Type PAP {
(2) pap: Login attempt with password
(2) pap: Comparing with "known good" Cleartext-Password
(2) pap: User authenticated successfully
(2)     [pap] = ok
(2)     if (reject) {
(2)     if (reject)  -> FALSE
(2)   } # Auth-Type PAP = ok
(…)
(2) EXPAND %{control:Tmp-String-0}
(2)    --> 
(2) Login OK: [my-user/my-pass] (from client localhost port 0) 
(2) Sent Access-Accept Id 194 from 127.0.0.1:1812 to 127.0.0.1:48550 length 0
(2)   Framed-IP-Address = 10.0.0.1
(2)   Framed-IP-Netmask = 255.255.255.255
(2)   Cisco-AVPair = "ip:vrf-id=MY-VRF"
(2) Finished request

@angely-dev
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment