~2011

freeradius 2 ldap setup

This is a little recipe for configuring freeradius 2 with a ldap backend and some other tricks. Mostly usable for EAP setups. Since I'm trying this out on an old Debian Lenny server I'm using the backports version 2.1.10. So this is based on the fresh install of that version.

What I usually do is add some version control to the configuration using mercurial. Just issue the following commands in the /etc/freeradius directory to initialise a repository:

hg init .
hg add *
hg commit

Now for every change you do commit it so you can always undo your changes.

Default setup

The default setup suits most needs. Don't edit it too much. For a quick test uncomment the following line in /etc/freeradius/users

steve   Cleartext-Password := "testing"

Now start the radius server:

/etc/init.d/freeradius restart

We can now do a test to see if we can authenticate:

radtest steve testing 127.0.0.1 0 testing123
Sending Access-Request of id 141 to 127.0.0.1 port 1812
    User-Name = "steve"
    User-Password = "testing"
    NAS-IP-Address = 127.0.1.1
    NAS-Port = 0
rad_recv: Access-Accept packet from host 127.0.0.1 port 1812, id=141, length=20

This should work for you as well. If doesn't you have done something wrong and there is no need to continue until you have a successfull Access-Accept! Retrace your steps and search the archives to find what's wrong!

Before we continue comment the steve line we just uncommented. We don't want to forget this, right!

Modules

Freeradius uses modules to do all it's magic. First we'll define a ldap module for use with our ldap backend. In /etc/freeradius/modules/ldap you'll find the base for the ldap module. You can edit this file to your needs however I choose to create a custom module based on the ldap module. I just copy the ldap module and edit it to your needs.

cp ldap my-ldap
nano my-ldap

Change the first line of the config like the following:

ldap my-ldap {

This way the module will be a named ldap module! The rest of the config is up to your needs.

Some remarks about the configuration: * when using ldaps pay attention to the require_cert option * I don't have radius attributes in my ldap server. I just use it for authentication/autorization. This can be accomplished by using the access_attr option which I set to "cn". So if you exists as a user and can authenicate you are authorized for access. * Pay attention to the "set_auth_type" option. It can make the difference between doing an ldap bind authentication or a password compare.

Default Server

To use this new ldap module we will edit the default server in /etc/freeradius/sites-available/default. This is a big file with many options. I'm pasting the bare default config below just to have an overview of how the modules tie together:

authorize {
    preprocess
    chap
    mschap
    digest
    suffix
    eap {
        ok = return
    }
    files
    expiration
    logintime
    pap
}
authenticate {
    Auth-Type PAP {
        pap
    }
    Auth-Type CHAP {
        chap
    }
    Auth-Type MS-CHAP {
        mschap
    }
    digest
    unix
    eap
}
preacct {
    preprocess
    acct_unique
    suffix
    files
}

accounting {
    detail
    unix
    radutmp
    exec
    attr_filter.accounting_response
}
session {
    radutmp
}

post-auth {
    exec
    Post-Auth-Type REJECT {
        attr_filter.access_reject
    }
}

pre-proxy {
}

post-proxy {
    eap
}

We want to do authentication and autorization through our ldap. So we need to add the my-ldap module to corresponding section.

Add the my-ldap module to the authorize section just before expiration. You'll notice some commented LDAP references there. Without comments the authorize section will look like this:

authorize {
    preprocess
    chap
    mschap
    digest
    suffix
    eap {
        ok = return
    }
    files
    my-ldap
    expiration
    logintime
    pap
}

Start the server in server in debug and do another radtest this time with a user in the ldap directory.

freeradius -X &
(lots of output)
....
radtest test testing 127.0.0.1 0 testing123

Sending Access-Request of id 90 to 127.0.0.1 port 1812
    User-Name = "test"
    User-Password = "testing"
    NAS-IP-Address = 127.0.1.1
    NAS-Port = 0
rad_recv: Access-Request packet from host 127.0.0.1 port 59404, id=90, length=55
    User-Name = "test"
    User-Password = "testing"
    NAS-IP-Address = 127.0.1.1
    NAS-Port = 0
# Executing section authorize from file /etc/freeradius/sites-enabled/default
+- entering group authorize {...}
++[preprocess] returns ok
++[chap] returns noop
++[mschap] returns noop
++[digest] returns noop
[suffix] No '@' in User-Name = "test", looking up realm NULL
[suffix] No such realm "NULL"
++[suffix] returns noop
[eap] No EAP-Message, not doing EAP
++[eap] returns noop
++[files] returns noop
[my-ldap] performing user authorization for test
[my-ldap]   expand: %{Stripped-User-Name} ->
[my-ldap]   ... expanding second conditional
[my-ldap]   expand: %{User-Name} -> test
[my-ldap]   expand: (cn=%{%{Stripped-User-Name}:-%{User-Name}}) -> (cn=test)
[my-ldap]   expand: ou=users,o=test -> ou=users,o=test
  [my-ldap] ldap_get_conn: Checking Id: 0
  [my-ldap] ldap_get_conn: Got Id: 0
  [my-ldap] attempting LDAP reconnection
  [my-ldap] (re)connect to ldaps://ldap.test.lan, authentication 0
  [my-ldap] bind as cn=admin-test,o=test/testpass to ldaps://ldap.test.lan
  [my-ldap] waiting for bind result ...
  [my-ldap] Bind was successful
  [my-ldap] performing search in ou=users,o=test, with filter (cn=test)
[my-ldap] checking if remote access for test is allowed by cn
[my-ldap] Added the eDirectory password testing in check items as Cleartext-Password
[my-ldap] No default NMAS login sequence
[my-ldap] looking for check items in directory...
[my-ldap] looking for reply items in directory...
[my-ldap] user test authorized to use remote access
  [my-ldap] ldap_release_conn: Release Id: 0
++[my-ldap] returns ok
++[expiration] returns noop
++[logintime] returns noop
++[pap] returns updated
Found Auth-Type = PAP
# Executing group from file /etc/freeradius/sites-enabled/default
+- entering group PAP {...}
[pap] login attempt with password "testing"
[pap] Using clear text password "testing"
[pap] User authenticated successfully
++[pap] returns ok
# Executing section post-auth from file /etc/freeradius/sites-enabled/default
+- entering group post-auth {...}
++[exec] returns noop
Sending Access-Accept of id 90 to 127.0.0.1 port 59404
Finished request 0.
Going to the next request
Waking up in 4.9 seconds.
rad_recv: Access-Accept packet from host 127.0.0.1 port 1812, id=90, length=20

Adding clients

Edit /etc/freeradius/clients.conf and add your network or device hosts. In my case I just add the whole network:

client 10.0.0.0/24 {
    secret      = testing123-1
    shortname   = primary-network
}

If your run your radius server now it should be capable of authenticating your users.

Adding realms

I want to use realms to authenticate my users to different authentication backends this is accomplished in many ways. I do it by using proxy.conf and freeradius-fu to map realms to authentication backends. The proxy.conf config, as usual in freeradius, has many options. I'm just using a simple 'old-style' configuration with no options so it will stay local.

realm test.lan {
}

If we start the radius server now it will also authenticate users with a test.lan realm appended to the username (user@test.lan). The server will tell you in debug mode the following:

[suffix] Adding Realm = "test.lan"
[suffix] Authentication realm is LOCAL.

So it will do a local authentication. To map realms to a specific backend we need to edit /etc/freeradius/sites-available/default. We will force specific realms to use specific autorize modules. Replace the my-ldap line we added before with the following lines:

        #realm switcher
        switch "%{Realm}" {
                case test.lan {
                        my-ldap
                }
                case test2.lan {
                        my-ldap2
                }
        }

Of course in this example you'll need to create the my-ldap2 module as well as add test2.lan to proxy.conf

Add ntdomain support

Since Microsoft always needs to do it different from the rest. The realms you get from the domains of the Windows supplicant don't contain an @. Instead Microsoft uses DOMAIN\user. To support this just uncomment the ntdomain in the default server authorize section (You're supposed to find that file by now)

        #
        #  If you are using multiple kinds of realms, you probably
        #  want to set "ignore_null = yes" for all of them.
        #  Otherwise, when the first style of realm doesn't match,
        #  the other styles won't be checked.
        #
        suffix
        ntdomain

It gets even worse. If you are using the windows credentials to login to your network you will probably need to enable:

with_ntdomain_hack = yes

Apparently windows send the DOMAIN\user but when doing the mschap challenge only does the user part. So by default the mschap will fail. This hacks around that. In my case it was needed.

You'll need to add the ntdomain realms to the proxy.conf as well.

If you want to test a ntdomain realm with radtest you need to use 4 '\' characters. This has to do with the fact that '\' character is used for escaping characters.

Add EAP support

To do 802.1x on our network we need EAP support. This is done through eap.conf. I won't go into detail about setting up the certificates. Alan DeKok has written a good guide for that: http:*deployingradius.com/documents/configuration/certificates.html.

In my case I'm doing mostly EAP/TTLS and EAP/PEAP. So this setup is a bit tailored for that:

in eap.conf change the following:

default_eap_type = peap
ttls {
 copy_request_to_tunnel = yes
 use_tunneled_reply = yes
}
peap {
 copy_request_to_tunnel = yes
 use_tunneled_reply = yes
}

It's important to understand that the EAP is not handled by the default server. It's actually processed by the inner-tunnel server. The default server proxies the EAP to the inner-tunnel which is listening on a different port on the loopback adapter. The authentication of a user is also handled there. So if you need LDAP authentication for your EAP requests you'll need to add the same 'my-ldap' entry to the authorize section of the inner-tunnel virtual server. Actually if you only need to authenticate EAP requests and not plain radius you can even remove the 'my-ldap' entry from the default server.

Reply attributes

In the old server (v1.1) I used to put the reply attributes in users file. However I found it's easier to put them in the post-auth section of the server config. In this example I add VLAN information to the reply-attributes.

in /etc/freeradius/sites-available/default you'll find some commented examples of update reply. Around those examples add:

    update reply {
        Service-Type = Framed-User
        Tunnel-type = VLAN
        Tunnel-medium-type = IEEE-802
        Tunnel-Private-Group-Id = 132
    }

Now after a succesfull authentication these attributes are added to the reply list.

Quarantaining MAC-addresses

We sometimes find virus or malware traffic originating from our network. We usually can trace that easily to a certain mac-address. So in our case we want certain mac-addresses to be forced in a different VLAN. A poor-men's quarantaining system.

Since Freeradius is extremely powerful and flexible this is easily accomplished. There's even a example module already in the modules directory (mac2vlan). In our case we have to use different attributes and we send VLAN ID's not names! Our network controllers send a Calling-Station-Id containing the users mac-address. We just create the file /etc/freeradius/callingstationid2vlanid:

passwd callingstationid2vlanid {
  filename = ${raddbdir}/mac2vlanid
  format = "*Calling-Station-Id:=Tunnel-Private-Group-Id"
  hashsize = 0
  ignorenislike = no
  allowmultiplekeys = no
}

I've put hashfile = 0 so the file is read from disk every request. This removes the need to reload the server to reread the file.

Now in postauth add the callingstationid2vlanid before the 'update replies'.

Rewrite mac addresses

Every vendor has it's own idea of how a mac address is written so it could be handy to rewrite the mac format. I've found this snippet on the mailinglist to do exactly that:

#
# Rewrite called station id attribute into a standard format using unlang
#
if ( "%{request: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 := "%{tolower:%{1}-%{2}-%{3}-%{4}-%{5}-%{6}}"
        }
}

Add this code just before callingstationid2vlanid in the post-auth section.