Skip to content

Instantly share code, notes, and snippets.

@angely-dev
Last active October 14, 2021 12:49
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/6bfbb37a62e9d6e61b5c7e17892479ab to your computer and use it in GitHub Desktop.
Save angely-dev/6bfbb37a62e9d6e61b5c7e17892479ab to your computer and use it in GitHub Desktop.
Translate RADIUS attributes from one vendor (say, Cisco) to another one (say, Huawei), using FreeRADIUS and a custom module (UNLANG).
#
# /etc/freeradius/policy.d/cisco2huawei
#
# Attributes adaptation from Cisco to Huawei.
#
cisco2huawei {
foreach &reply:Cisco-AVPair {
if ("%{Foreach-Variable-0}" =~ /ip:vrf-id=(.*)/) {
#
# Adapt the VRF attribute.
#
# Example: "Cisco-AVPair += vrf-id=MY-VRF"
# => "Huawei-Vpn-Instance := MY-VRF"
#
update reply {
&Cisco-AVPair -= "%{Foreach-Variable-0}"
&Huawei-Vpn-Instance := "%{1}"
}
updated
}
elsif ("%{Foreach-Variable-0}" =~ /ip:sub-qos-policy-out=(.*)/) {
#
# Adapt the QoS out attribute.
#
# Example: "Cisco-AVPair += ip:sub-qos-policy-out=MY-QOS-OUT"
# => "Huawei-Down-QOS-Profile-Name := MY-QOS-OUT"
#
update reply {
&Cisco-AVPair -= "%{Foreach-Variable-0}"
&Huawei-Down-QOS-Profile-Name := "%{1}"
}
updated
}
elsif ("%{Foreach-Variable-0}" =~ /ip:route=vrf ([a-zA-Z0-9-]+) (.*) (.*) (.*)/) {
#
# Adapt the route attribute.
#
# Example: "Cisco-AVPair += ip:route=vrf MY-VRF 192.168.1.0 255.255.255.0 10.0.0.1"
# => "Framed-Route += 192.168.1.0 255.255.255.0"
#
update reply {
&Cisco-AVPair -= "%{Foreach-Variable-0}"
&Framed-Route += "%{2} %{3}"
}
updated
}
}
}
@angely-dev
Copy link
Author

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
                cisco2huawei # custom module
                # some other things…
                expiration
                logintime
                pap
        }
        authenticate {
                #
        }
        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' ORDER BY attribute;

+----------+-------------------+----+--------------------------------------------------------+
| username | attribute         | op | value                                                  |
+----------+-------------------+----+--------------------------------------------------------+
| my-user  | Cisco-AVPair      | += | ip:vrf-id=MY-VRF                                       |
| my-user  | Cisco-AVPair      | += | ip:sub-qos-policy-out=MY-QOS-OUT                       |
| my-user  | Cisco-AVPair      | += | ip:route=vrf MY-VRF 192.168.1.0 255.255.255.0 10.0.0.1 |
| my-user  | Cisco-AVPair      | += | ip:route=vrf MY-VRF 192.168.2.0 255.255.255.0 10.0.0.1 |
| my-user  | Framed-IP-Address | := | 10.0.0.1                                               |
| my-user  | Framed-IP-Netmask | := | 255.255.255.255                                        |
+----------+-------------------+----+--------------------------------------------------------+

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

+----------+--------------------+----+---------+
| username | attribute          | op | value   |
+----------+--------------------+----+---------+
| my-user  | Cleartext-Password | := | my-pass |
+----------+--------------------+----+---------+
$ radtest my-user my-pass localhost 0 testing123
Sent Access-Request Id 151 from 0.0.0.0:49101 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 151 from 127.0.0.1:1812 to 127.0.0.1:49101 length 118
	Framed-IP-Address = 10.0.0.1
	Framed-IP-Netmask = 255.255.255.255
	Huawei-VPN-Instance = "MY-VRF"
	Huawei-Down-QOS-Profile-Name = "MY-QOS-OUT"
	Framed-Route = "192.168.1.0 255.255.255.0"
	Framed-Route = "192.168.2.0 255.255.255.0"

FreeRADIUS server log:

(…)
(0)     policy cisco2huawei {
(0)       foreach &reply:Cisco-AVPair 
(0)         if ("%{Foreach-Variable-0}" =~ /ip:vrf-id=(.*)/) {
(0)         EXPAND Foreach-Variable-0
(0)            --> ip:vrf-id=MY-VRF
(0)         EXPAND %{Foreach-Variable-0}
(0)            --> ip:vrf-id=MY-VRF
(0)         if ("%{Foreach-Variable-0}" =~ /ip:vrf-id=(.*)/)  -> TRUE
(0)         if ("%{Foreach-Variable-0}" =~ /ip:vrf-id=(.*)/)  {
(0)           update reply {
(0)             EXPAND Foreach-Variable-0
(0)                --> ip:vrf-id=MY-VRF
(0)             EXPAND %{Foreach-Variable-0}
(0)                --> ip:vrf-id=MY-VRF
(0)             &Cisco-AVPair -= ip:vrf-id=MY-VRF
(0)             EXPAND %{1}
(0)                --> MY-VRF
(0)             &Huawei-Vpn-Instance := MY-VRF
(0)           } # update reply = noop
(0)           [updated] = updated
(0)         } # if ("%{Foreach-Variable-0}" =~ /ip:vrf-id=(.*)/)  = updated
(0)         ... skipping elsif: Preceding "if" was taken
(0)         ... skipping elsif: Preceding "if" was taken
(0)         if ("%{Foreach-Variable-0}" =~ /ip:vrf-id=(.*)/) {
(0)         EXPAND Foreach-Variable-0
(0)            --> ip:sub-qos-policy-out=MY-QOS-OUT
(0)         EXPAND %{Foreach-Variable-0}
(0)            --> ip:sub-qos-policy-out=MY-QOS-OUT
(0)         if ("%{Foreach-Variable-0}" =~ /ip:vrf-id=(.*)/)  -> FALSE
(0)         elsif ("%{Foreach-Variable-0}" =~ /ip:sub-qos-policy-out=(.*)/) {
(0)         EXPAND Foreach-Variable-0
(0)            --> ip:sub-qos-policy-out=MY-QOS-OUT
(0)         EXPAND %{Foreach-Variable-0}
(0)            --> ip:sub-qos-policy-out=MY-QOS-OUT
(0)         elsif ("%{Foreach-Variable-0}" =~ /ip:sub-qos-policy-out=(.*)/)  -> TRUE
(0)         elsif ("%{Foreach-Variable-0}" =~ /ip:sub-qos-policy-out=(.*)/)  {
(0)           update reply {
(0)             EXPAND Foreach-Variable-0
(0)                --> ip:sub-qos-policy-out=MY-QOS-OUT
(0)             EXPAND %{Foreach-Variable-0}
(0)                --> ip:sub-qos-policy-out=MY-QOS-OUT
(0)             &Cisco-AVPair -= ip:sub-qos-policy-out=MY-QOS-OUT
(0)             EXPAND %{1}
(0)                --> MY-QOS-OUT
(0)             &Huawei-Down-QOS-Profile-Name := MY-QOS-OUT
(0)           } # update reply = noop
(0)           [updated] = updated
(0)         } # elsif ("%{Foreach-Variable-0}" =~ /ip:sub-qos-policy-out=(.*)/)  = updated
(0)         ... skipping elsif: Preceding "if" was taken
(0)         if ("%{Foreach-Variable-0}" =~ /ip:vrf-id=(.*)/) {
(0)         EXPAND Foreach-Variable-0
(0)            --> ip:route=vrf MY-VRF 192.168.1.0 255.255.255.0 10.0.0.1
(0)         EXPAND %{Foreach-Variable-0}
(0)            --> ip:route=vrf MY-VRF 192.168.1.0 255.255.255.0 10.0.0.1
(0)         if ("%{Foreach-Variable-0}" =~ /ip:vrf-id=(.*)/)  -> FALSE
(0)         elsif ("%{Foreach-Variable-0}" =~ /ip:sub-qos-policy-out=(.*)/) {
(0)         EXPAND Foreach-Variable-0
(0)            --> ip:route=vrf MY-VRF 192.168.1.0 255.255.255.0 10.0.0.1
(0)         EXPAND %{Foreach-Variable-0}
(0)            --> ip:route=vrf MY-VRF 192.168.1.0 255.255.255.0 10.0.0.1
(0)         elsif ("%{Foreach-Variable-0}" =~ /ip:sub-qos-policy-out=(.*)/)  -> FALSE
(0)         elsif ("%{Foreach-Variable-0}" =~ /ip:route=vrf ([a-zA-Z0-9-]+) (.*) (.*) (.*)/) {
(0)         EXPAND Foreach-Variable-0
(0)            --> ip:route=vrf MY-VRF 192.168.1.0 255.255.255.0 10.0.0.1
(0)         EXPAND %{Foreach-Variable-0}
(0)            --> ip:route=vrf MY-VRF 192.168.1.0 255.255.255.0 10.0.0.1
(0)         elsif ("%{Foreach-Variable-0}" =~ /ip:route=vrf ([a-zA-Z0-9-]+) (.*) (.*) (.*)/)  -> TRUE
(0)         elsif ("%{Foreach-Variable-0}" =~ /ip:route=vrf ([a-zA-Z0-9-]+) (.*) (.*) (.*)/)  {
(0)           update reply {
(0)             EXPAND Foreach-Variable-0
(0)                --> ip:route=vrf MY-VRF 192.168.1.0 255.255.255.0 10.0.0.1
(0)             EXPAND %{Foreach-Variable-0}
(0)                --> ip:route=vrf MY-VRF 192.168.1.0 255.255.255.0 10.0.0.1
(0)             &Cisco-AVPair -= ip:route=vrf MY-VRF 192.168.1.0 255.255.255.0 10.0.0.1
(0)             EXPAND %{2} %{3}
(0)                --> 192.168.1.0 255.255.255.0
(0)             &Framed-Route += 192.168.1.0 255.255.255.0
(0)           } # update reply = noop
(0)           [updated] = updated
(0)         } # elsif ("%{Foreach-Variable-0}" =~ /ip:route=vrf ([a-zA-Z0-9-]+) (.*) (.*) (.*)/)  = updated
(0)         if ("%{Foreach-Variable-0}" =~ /ip:vrf-id=(.*)/) {
(0)         EXPAND Foreach-Variable-0
(0)            --> ip:route=vrf MY-VRF 192.168.2.0 255.255.255.0 10.0.0.1
(0)         EXPAND %{Foreach-Variable-0}
(0)            --> ip:route=vrf MY-VRF 192.168.2.0 255.255.255.0 10.0.0.1
(0)         if ("%{Foreach-Variable-0}" =~ /ip:vrf-id=(.*)/)  -> FALSE
(0)         elsif ("%{Foreach-Variable-0}" =~ /ip:sub-qos-policy-out=(.*)/) {
(0)         EXPAND Foreach-Variable-0
(0)            --> ip:route=vrf MY-VRF 192.168.2.0 255.255.255.0 10.0.0.1
(0)         EXPAND %{Foreach-Variable-0}
(0)            --> ip:route=vrf MY-VRF 192.168.2.0 255.255.255.0 10.0.0.1
(0)         elsif ("%{Foreach-Variable-0}" =~ /ip:sub-qos-policy-out=(.*)/)  -> FALSE
(0)         elsif ("%{Foreach-Variable-0}" =~ /ip:route=vrf ([a-zA-Z0-9-]+) (.*) (.*) (.*)/) {
(0)         EXPAND Foreach-Variable-0
(0)            --> ip:route=vrf MY-VRF 192.168.2.0 255.255.255.0 10.0.0.1
(0)         EXPAND %{Foreach-Variable-0}
(0)            --> ip:route=vrf MY-VRF 192.168.2.0 255.255.255.0 10.0.0.1
(0)         elsif ("%{Foreach-Variable-0}" =~ /ip:route=vrf ([a-zA-Z0-9-]+) (.*) (.*) (.*)/)  -> TRUE
(0)         elsif ("%{Foreach-Variable-0}" =~ /ip:route=vrf ([a-zA-Z0-9-]+) (.*) (.*) (.*)/)  {
(0)           update reply {
(0)             EXPAND Foreach-Variable-0
(0)                --> ip:route=vrf MY-VRF 192.168.2.0 255.255.255.0 10.0.0.1
(0)             EXPAND %{Foreach-Variable-0}
(0)                --> ip:route=vrf MY-VRF 192.168.2.0 255.255.255.0 10.0.0.1
(0)             &Cisco-AVPair -= ip:route=vrf MY-VRF 192.168.2.0 255.255.255.0 10.0.0.1
(0)             EXPAND %{2} %{3}
(0)                --> 192.168.2.0 255.255.255.0
(0)             &Framed-Route += 192.168.2.0 255.255.255.0
(0)           } # update reply = noop
(0)           [updated] = updated
(0)         } # elsif ("%{Foreach-Variable-0}" =~ /ip:route=vrf ([a-zA-Z0-9-]+) (.*) (.*) (.*)/)  = updated
(0)       } # foreach &reply:Cisco-AVPair = updated
(0)     } # policy cisco2huawei = updated
(…)

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