Dynamic VLAN assignment depending on LDAP user group and MAC address

Fabrizio Vecchi fabrizio.vecchi at mindcandy.com
Fri Oct 11 18:41:07 CEST 2013


Hi everyone.

First of all, sorry if my email is very long, I am just trying not to leave
any important details out. :)

In my Company, I'd like to setup a freeradius based wifi authentication
following the same principle:
First check if a user is using the Company's laptop (or phone) by checking
a list of MAC addresses. If the device is in the list, let the user
authenticate through LDAP and get a VLAN depending on the user's group; if
it's not present, authenticate the user against ldap, but assign the user
to a "public" VLAN, which cannot reach our internal servers.
This is basically to take care of users who connect to our network with
their own devices, on which we don't have control and that could spread all
sorts of malware in the internal network.

So far, I managed to do the dynamic VLAN assignment, but cannot seem to get
it to work together with the MAC checking.
I can get an auth to be refused if the MAC is not listed in the
authorized_macs file, but can't quite put the two things together. Perhaps
I am a bit confused with regards to where to put the MAC check. For now, I
just managed to get the check to work only on the authorization phase in
sites-enabled/default, but then the VLAN assignment, which is done in the
internal-tunnel, seems to overwrite my changes.
So I tried to put the MAC check in the post-auth section in the default
file, but the MAC check doesn't seem to ever work.

Here are the relevant config files:

Radius version:
2.1.10+dfsg-2+squeeze1 (running on Debian)

--- policy.conf
policy {
    forbid_eap {
        if (EAP-Message) {
            reject
        }
    }
    permit_only_eap {
        if (!EAP-Message) {
            if (!"%{outer.request:EAP-Message}") {
                reject
            }
        }
    }
    deny_realms {
        if (User-Name =~ /@|\\/) {
            reject
        }
    }
    do_not_respond {
        update control {
            Response-Packet-Type := Do-Not-Respond
        }
        handled
    }
    cui_authorize {
        update request {
            Chargeable-User-Identity:='\\000'
        }
    }
    cui_postauth {
        if (FreeRadius-Proxied-To == 127.0.0.1) {
            if (outer.request:Chargeable-User-Identity) {
                update outer.reply {

Chargeable-User-Identity:="%{md5:%{config:cui_hash_key}%{User-Name}}"
                }
            }
        }
        else {
            if (Chargeable-User-Identity) {
                update reply {

Chargeable-User-Identity="%{md5:%{config:cui_hash_key}%{User-Name}}"
                }
            }
        }
    }
    cui_updatedb {
        if (reply:Chargeable-User-Identity) {
            cui
        }
    }
    cui_accounting {
        if (!Chargeable-User-Identity) {
            update control {
                Chargable-User-Identity := "%{cui: SELECT cui FROM cui
WHERE clientipaddress = '%{Client-IP-Address}' AND callingstationid =
'%{Calling-Station-Id}' AND username = '%{User-Name}'}"
            }
        }
        if (Chargeable-User-Identity && (Chargeable-User-Identity != "")) {
            cui
        }
    }
    rewrite_calling_station_id {
            if (Calling-Station-Id =~
/([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})/i){
                    update request {
                        Calling-Station-Id :=
"%{1}-%{2}-%{3}-%{4}-%{5}-%{6}"
                    }
            }
            else {
                    noop
            }
    }

}


--- modules/files:
files {
    usersfile = ${confdir}/users
    acctusersfile = ${confdir}/acct_users
    preproxy_usersfile = ${confdir}/preproxy_users
    compat = no
}
files second_files {
    usersfile = ${confdir}/second_users
    acctusersfile = ${confdir}/second_acct_users
    preproxy_usersfile = ${confdir}/second_preproxy_users
}
files authorized_macs {
        key = "%{tolower:%{Calling-Station-ID}}"
        usersfile = ${confdir}/authorized_macs
        compat = no
}

---authorized_macs
e8-99-c4-a2-39-36
  Reply-Message = "Device with MAC Address %{Calling-Station-Id} authorized
for network access"

--- sites-available/default
authorize {
    preprocess
    auth_log
    suffix
    eap {
        ok = return
    }
    expiration
    logintime
    pap
}
authenticate {
    Auth-Type PAP {
        pap
    }
    eap
}
preacct {
    preprocess
    acct_unique
    suffix
}
accounting {
    sql {
        fail = 1
    }
}
session {
    radutmp
    sql {
        fail = 1
    }
}
post-auth {
        rewrite_calling_station_id
        authorized_macs
        if (!ok) {
        update reply {
        Tunnel-Type = VLAN
            Tunnel-Medium-Type = IEEE-802
            Tunnel-Private-Group-Id = 36
            }
        }
    sql {
        fail = 1
    }
    exec
    Post-Auth-Type REJECT {
        attr_filter.access_reject
    }
}
pre-proxy {
}
post-proxy {
    eap
}

--- sites-available/inner-tunnel
authorize {
    preprocess
    auth_log
    suffix
    eap {
        ok = return
    }
    expiration
    logintime
    pap
}
authenticate {
    Auth-Type PAP {
        pap
    }
    eap
}
preacct {
    preprocess
    acct_unique
    suffix
}
accounting {
    sql {
        fail = 1
    }
}
session {
    radutmp
    sql {
        fail = 1
    }
}
post-auth {
        rewrite_calling_station_id
        authorized_macs
        if (!ok) {
        update reply {
        Tunnel-Type = VLAN
            Tunnel-Medium-Type = IEEE-802
            Tunnel-Private-Group-Id = 36
            }
        }
    sql {
        fail = 1
    }
    exec
    Post-Auth-Type REJECT {
        attr_filter.access_reject
    }
}
pre-proxy {
}
post-proxy {
    eap
}
root at ops-radius01:/srv/etc/freeradius# cat sites-available/inner-tunnel |
grep -v '#' | sed '/^$/d'
server inner-tunnel {
listen {
       ipaddr = 127.0.0.1
       port = 18120
       type = auth
}
authorize {
    update control {
           Proxy-To-Realm := LOCAL
    }
    eap {
        ok = return
    }
    files
    ldap

    pap
}
authenticate {
    Auth-Type PAP {
        pap
    }
    Auth-Type CHAP {
        chap
    }
    Auth-Type MS-CHAP {
        mschap
    }
    Auth-Type LDAP {
        ldap
    }
    eap
}
session {
    radutmp
}
post-auth {
    sql {
        fail = 1
    }
    ldap
    Post-Auth-Type REJECT {
        attr_filter.access_reject
    }
     if (LDAP-Group ==
"cn=dept_tech_corporate_it,ou=Groups,c=gb,dc=mindcandy,dc=com") {
        update reply {
            Tunnel-Type = VLAN
                    Tunnel-Medium-Type = IEEE-802
                    Tunnel-Private-Group-Id = 40
        }
        }
     elsif (LDAP-Group ==
"cn=dept_tech_infrastructure,ou=Groups,c=gb,dc=mindcandy,dc=com") {
        update reply {
            Tunnel-Type = VLAN
                    Tunnel-Medium-Type = IEEE-802
                    Tunnel-Private-Group-Id = 40
        }
        }
     elsif (LDAP-Group ==
"cn=dept_tech_bi,ou=Groups,c=gb,dc=mindcandy,dc=com") {
        update reply {
            Tunnel-Type = VLAN
                    Tunnel-Medium-Type = IEEE-802
                    Tunnel-Private-Group-Id = 41
        }
        }
     elsif (LDAP-Group ==
"cn=dept_tech_development,ou=Groups,c=gb,dc=mindcandy,dc=com") {
        update reply {
            Tunnel-Type = VLAN
                    Tunnel-Medium-Type = IEEE-802
                    Tunnel-Private-Group-Id = 42
        }
        }
     elsif (LDAP-Group ==
"cn=dept_finance,ou=Groups,c=gb,dc=mindcandy,dc=com") {
        update reply {
            Tunnel-Type = VLAN
                    Tunnel-Medium-Type = IEEE-802
                    Tunnel-Private-Group-Id = 44
        }
        }
        else {
        update reply {
            Tunnel-Type = VLAN
                    Tunnel-Medium-Type = IEEE-802
                    Tunnel-Private-Group-Id = 34
        }
        }
}
pre-proxy {
}
post-proxy {
    eap
}

And here is an authentication example, with a device not listed in
authorized_macs:
(...)
rad_recv: Access-Request packet from host 192.168.59.202 port 32769,
id=129, length=345
    User-Name = "fabrizio.vecchi"
    Calling-Station-Id = "60-fa-cd-47-1a-44"
    Called-Station-Id = "24-01-c7-28-aa-d0:MindCandyAuth"
    NAS-Port = 1
    Cisco-AVPair = "audit-session-id=ca3ba8c0000000dede1c5852"
    NAS-IP-Address = 192.168.59.202
    NAS-Identifier = "Cisco_6e:1f:4f"
    Airespace-Wlan-Id = 5
    Service-Type = Framed-User
    Framed-MTU = 1300
    NAS-Port-Type = Wireless-802.11
    Tunnel-Type:0 = VLAN
    Tunnel-Medium-Type:0 = IEEE-802
    Tunnel-Private-Group-Id:0 = "36"
    EAP-Message =
0x0206005f15800000005517030100506509e5008fb8b33c992bdddc007472c4f5d210aa8d535f74724cccc1bc99c4cb8785066c7ef4f262c470986626e1d31efc71f0d3b42b80663afc9fdc68715d1ee49c02af509c6b12de0bca5bf5501cba
    State = 0xf1f3e6cbf5f5f3adc22ef694ca5dfcba
    Message-Authenticator = 0xeff670953d883040f13b8dfc42d39849
# Executing section authorize from file
/etc/freeradius/sites-enabled/default
+- entering group authorize {...}
++[preprocess] returns ok
[auth_log]     expand:
/var/log/freeradius/radacct/%{Client-IP-Address}/auth-detail-%Y%m%d ->
/var/log/freeradius/radacct/192.168.59.202/auth-detail-20131011
[auth_log]
/var/log/freeradius/radacct/%{Client-IP-Address}/auth-detail-%Y%m%d expands
to /var/log/freeradius/radacct/192.168.59.202/auth-detail-20131011
[auth_log]     expand: %t -> Fri Oct 11 17:12:54 2013
++[auth_log] returns ok
[suffix] No '@' in User-Name = "fabrizio.vecchi", looking up realm NULL
[suffix] No such realm "NULL"
++[suffix] returns noop
[eap] EAP packet type response id 6 length 95
[eap] Continuing tunnel setup.
++[eap] returns ok
Found Auth-Type = EAP
# Executing group from file /etc/freeradius/sites-enabled/default
+- entering group authenticate {...}
[eap] Request found, released from the list
[eap] EAP/ttls
[eap] processing type ttls
[ttls] Authenticate
[ttls] processing EAP-TLS
  TLS Length 85
[ttls] Length Included
[ttls] eaptls_verify returned 11
[ttls] eaptls_process returned 7
[ttls] Session established.  Proceeding to decode tunneled attributes.
[ttls] Got tunneled request
    User-Name = "fabrizio.vecchi"
    User-Password = <password>
    FreeRADIUS-Proxied-To = 127.0.0.1
[ttls] Sending tunneled request
    User-Name = "fabrizio.vecchi"
    User-Password = <password>
    FreeRADIUS-Proxied-To = 127.0.0.1
    Calling-Station-Id = "60-fa-cd-47-1a-44"
    Called-Station-Id = "24-01-c7-28-aa-d0:MindCandyAuth"
    NAS-Port = 1
    Cisco-AVPair = "audit-session-id=ca3ba8c0000000dede1c5852"
    NAS-IP-Address = 192.168.59.202
    NAS-Identifier = "Cisco_6e:1f:4f"
    Airespace-Wlan-Id = 5
    Service-Type = Framed-User
    Framed-MTU = 1300
    NAS-Port-Type = Wireless-802.11
    Tunnel-Type:0 = VLAN
    Tunnel-Medium-Type:0 = IEEE-802
    Tunnel-Private-Group-Id:0 = "36"
server inner-tunnel {
# Executing section authorize from file
/etc/freeradius/sites-enabled/inner-tunnel
+- entering group authorize {...}
++[control] returns notfound
[eap] No EAP-Message, not doing EAP
++[eap] returns noop
++[files] returns noop
[ldap] performing user authorization for fabrizio.vecchi
[ldap]     expand: %{Stripped-User-Name} ->
[ldap]     ... expanding second conditional
[ldap]     expand: %{User-Name} -> fabrizio.vecchi
[ldap]     expand: (uid=%{%{Stripped-User-Name}:-%{User-Name}}) ->
(uid=fabrizio.vecchi)
[ldap]     expand: c=gb,dc=mindcandy,dc=com -> c=gb,dc=mindcandy,dc=com
  [ldap] ldap_get_conn: Checking Id: 0
  [ldap] ldap_get_conn: Got Id: 0
  [ldap] attempting LDAP reconnection
  [ldap] (re)connect to 192.168.50.41:389, authentication 0
  [ldap] bind as cn=admin,dc=mindcandy,dc=com/4kaZi638uSFurX to
192.168.50.41:389
  [ldap] waiting for bind result ...
  [ldap] Bind was successful
  [ldap] performing search in c=gb,dc=mindcandy,dc=com, with filter
(uid=fabrizio.vecchi)
[ldap] Added User-Password = {SSHA}mhuhx35skdNyJ7BrJuviLnMt2iDI3lFs in
check items
[ldap] No default NMAS login sequence
[ldap] looking for check items in directory...
  [ldap] userPassword -> Password-With-Header ==
"{SSHA}mhuhx35skdNyJ7BrJuviLnMt2iDI3lFs"
  [ldap] sambaNtPassword -> NT-Password ==
0x3730424545463943433843443839414435374133463731413541354446333742
[ldap] looking for reply items in directory...
[ldap] user fabrizio.vecchi authorized to use remote access
  [ldap] ldap_release_conn: Release Id: 0
++[ldap] returns ok
[pap] Normalizing NT-Password from hex encoding
[pap] Normalizing SSHA1-Password from base64 encoding
[pap] Normalizing SSHA1-Password from base64 encoding
++[pap] returns updated
Found Auth-Type = PAP
# Executing group from file /etc/freeradius/sites-enabled/inner-tunnel
+- entering group PAP {...}
[pap] login attempt with password <password>
[pap] Using NT encryption.
[pap]     expand: %{User-Password} -> <password>
[pap] NT-Hash of <password> = 70beef9cc8cd89ad57a3f71a5a5df37b
[pap]     expand: %{mschap:NT-Hash %{User-Password}} ->
70beef9cc8cd89ad57a3f71a5a5df37b
[pap] User authenticated successfully
++[pap] returns ok
# Executing section post-auth from file
/etc/freeradius/sites-enabled/inner-tunnel
+- entering group post-auth {...}
[sql]     expand: %{User-Name} -> fabrizio.vecchi
[sql] sql_set_user escaped user --> 'fabrizio.vecchi'
[sql]     expand: %{User-Password} -> <password>
[sql]     expand: INSERT INTO radpostauth
(username, pass, reply, authdate)                           VALUES
(                           '%{User-Name}',
'%{%{User-Password}:-%{Chap-Password}}',
'%{reply:Packet-Type}', '%S') -> INSERT INTO
radpostauth                           (username, pass, reply,
authdate)                           VALUES (
'fabrizio.vecchi',
'<password>',                           'Access-Accept', '2013-10-11
17:12:54')
rlm_sql (sql) in sql_postauth: query is INSERT INTO
radpostauth                           (username, pass, reply,
authdate)                           VALUES (
'fabrizio.vecchi',
'<password>',                           'Access-Accept', '2013-10-11
17:12:54')
rlm_sql (sql): Reserving sql socket id: 3
rlm_sql (sql): Released sql socket id: 3
++[sql] returns ok
++[ldap] returns noop
++? if (LDAP-Group ==
"cn=dept_tech_corporate_it,ou=Groups,c=gb,dc=mindcandy,dc=com")
  [ldap] Entering ldap_groupcmp()
    expand: c=gb,dc=mindcandy,dc=com -> c=gb,dc=mindcandy,dc=com
    expand:
(|(&(objectClass=GroupOfNames)(member=%{control:Ldap-UserDn}))(&(objectClass=GroupOfUniqueNames)(uniquemember=%{control:Ldap-UserDn})))
->
(|(&(objectClass=GroupOfNames)(member=uid\3dfabrizio.vecchi\2cou\3dPeople\2cc\3dgb\2cdc\3dmindcandy\2cdc\3dcom))(&(objectClass=GroupOfUniqueNames)(uniquemember=uid\3dfabrizio.vecchi\2cou\3dPeople\2cc\3dgb\2cdc\3dmindcandy\2cdc\3dcom)))
  [ldap] ldap_get_conn: Checking Id: 0
  [ldap] ldap_get_conn: Got Id: 0
  [ldap] performing search in
cn=dept_tech_corporate_it,ou=Groups,c=gb,dc=mindcandy,dc=com, with filter
(|(&(objectClass=GroupOfNames)(member=uid\3dfabrizio.vecchi\2cou\3dPeople\2cc\3dgb\2cdc\3dmindcandy\2cdc\3dcom))(&(objectClass=GroupOfUniqueNames)(uniquemember=uid\3dfabrizio.vecchi\2cou\3dPeople\2cc\3dgb\2cdc\3dmindcandy\2cdc\3dcom)))
rlm_ldap::ldap_groupcmp: User found in group
cn=dept_tech_corporate_it,ou=Groups,c=gb,dc=mindcandy,dc=com
  [ldap] ldap_release_conn: Release Id: 0
? Evaluating (LDAP-Group ==
"cn=dept_tech_corporate_it,ou=Groups,c=gb,dc=mindcandy,dc=com") -> TRUE
++? if (LDAP-Group ==
"cn=dept_tech_corporate_it,ou=Groups,c=gb,dc=mindcandy,dc=com") -> TRUE
++- entering if (LDAP-Group ==
"cn=dept_tech_corporate_it,ou=Groups,c=gb,dc=mindcandy,dc=com") {...}
+++[reply] returns noop
++- if (LDAP-Group ==
"cn=dept_tech_corporate_it,ou=Groups,c=gb,dc=mindcandy,dc=com") returns noop
++ ... skipping elsif for request 5: Preceding "if" was taken
++ ... skipping elsif for request 5: Preceding "if" was taken
++ ... skipping elsif for request 5: Preceding "if" was taken
++ ... skipping elsif for request 5: Preceding "if" was taken
++ ... skipping else for request 5: Preceding "if" was taken
} # server inner-tunnel
[ttls] Got tunneled reply code 2
    Tunnel-Type:0 = VLAN
    Tunnel-Medium-Type:0 = IEEE-802
    Tunnel-Private-Group-Id:0 = "40"
[ttls] Got tunneled Access-Accept
[eap] Freeing handler
++[eap] returns ok
# Executing section post-auth from file
/etc/freeradius/sites-enabled/default
+- entering group post-auth {...}
++- entering policy rewrite_calling_station_id {...}
+++? if (Calling-Station-Id =~
/([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})/i)
? Evaluating (Calling-Station-Id =~
/([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})/i)
-> TRUE
+++? if (Calling-Station-Id =~
/([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})/i)
-> TRUE
+++- entering if (Calling-Station-Id =~
/([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})/i)
{...}
    expand: %{1}-%{2}-%{3}-%{4}-%{5}-%{6} -> 60-fa-cd-47-1a-44
++++[request] returns noop
+++- if (Calling-Station-Id =~
/([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})/i)
returns noop
+++ ... skipping else for request 5: Preceding "if" was taken
++- policy rewrite_calling_station_id returns noop
[authorized_macs]     expand: %{Calling-Station-ID} -> 60-fa-cd-47-1a-44
[authorized_macs]     expand: %{tolower:%{Calling-Station-ID}} ->
60-fa-cd-47-1a-44
++[authorized_macs] returns noop
++? if (!ok)
? Evaluating !(ok) -> TRUE
++? if (!ok) -> TRUE
++- entering if (!ok) {...}
+++[reply] returns noop
++- if (!ok) returns noop
[sql]     expand: %{User-Name} -> fabrizio.vecchi
[sql] sql_set_user escaped user --> 'fabrizio.vecchi'
[sql]     expand: %{User-Password} ->
[sql]     ... expanding second conditional
[sql]     expand: %{Chap-Password} ->
[sql]     expand: INSERT INTO radpostauth
(username, pass, reply, authdate)                           VALUES
(                           '%{User-Name}',
'%{%{User-Password}:-%{Chap-Password}}',
'%{reply:Packet-Type}', '%S') -> INSERT INTO
radpostauth                           (username, pass, reply,
authdate)                           VALUES (
'fabrizio.vecchi',                           '',
'Access-Accept', '2013-10-11 17:12:54')
rlm_sql (sql) in sql_postauth: query is INSERT INTO
radpostauth                           (username, pass, reply,
authdate)                           VALUES (
'fabrizio.vecchi',                           '',
'Access-Accept', '2013-10-11 17:12:54')
rlm_sql (sql): Reserving sql socket id: 2
rlm_sql (sql): Released sql socket id: 2
++[sql] returns ok
++[exec] returns noop
Sending Access-Accept of id 129 to 192.168.59.202 port 32769
    Tunnel-Type:0 = VLAN
    Tunnel-Medium-Type:0 = IEEE-802
    Tunnel-Private-Group-Id:0 = "40"
    MS-MPPE-Recv-Key =
0x0b1d759946c02f9063b69141cdebfd8a229d7d996fdea460aa1fb4e11182270a
    MS-MPPE-Send-Key =
0x62814f3d40bc9b08d2661cbc70a93c836f1e8f453e22ee77d3ad3f78dd2432f0
    EAP-Message = 0x03060004
    Message-Authenticator = 0x00000000000000000000000000000000
    User-Name = "fabrizio.vecchi"
Finished request 5.
Going to the next request
Waking up in 4.8 seconds.

As you can see, the device wasn't listed in the file, the authentication
went fine, saying that the tunnel that I should get has ID 40, but that
wasn't overwritten by the authorized_macs check...

Any pointers would be greatly appreciated.

Thankyou so much for your help.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.freeradius.org/pipermail/freeradius-users/attachments/20131011/3fe0379c/attachment-0001.html>


More information about the Freeradius-Users mailing list