FreeRADIUS with Two Factor Authentication (Google Authenticator)

Goal: Setup FreeRADIUS server that uses Google two factor authentication + LDAP (CentOS 7 based)

My specific use case was to setup a Cisco AnyConnect VPN and authenticate against a RADIUS server. I needed to have strong two factor authentication and easy group administration of users belonging to specific VPN group profiles. It is easy enough to point a Cisco ASA to a RADIUS server, and tying in Google Authenticator via PAM is straightforward, but things quickly become more complicated if you need to manage more than one VPN profile that is backed by different LDAP groups. 

Consider the following situation:
1. You need two VPN profiles, one for Sales and one for Engineering
2. You need to verify that users logging in belong to the correct LDAP group

Let's examine how FreeRADIUS integrates with PAM, and how PAM in turn interacts with LDAP:

  • Install the google authenticator PAM module
  • 
    
    cd ~
    git clone https://code.google.com/p/google-authenticator/
    cd google-authenticator/libpam/
    make
    make install
  • Install FreeRADIUS
    
    
    yum install freeradius
    yum install freeradius-utils
  • Configure FreeRADIUS to use PAM
    • Open /etc/raddb/users and add:
    
    
    DEFAULT        Auth-Type := PAM
  • Configure the specific PAM config file to use when PAM is being used for authentication within FreeRADIUS.
    • This is done by setting the pam_auth directive in /etc/raddb/mods-enabled/pam, i.e. pam_auth = radiusd (this assumes /etc/pam.d/radiusd exists)
In the above process, you'll notice that you can only point FreeRADIUS to a single PAM config file for ALL authentication requests that come in. What we need is a way to specify multiple PAM config files and route different requests (for different VPN profiles). We'll be configuring the different PAM config files to validate that the authenticating user is a member of ('ingroup' in PAM verbiage) the correct group.

FreeRADIUS lets you define sites, similar to Apache on Debian based systems, and these sites have one or more servers which tell FreeRADIUS how to handle authentication, authorization, etc. You can also define which addresses and ports FreeRADIUS will bind to, which turns out to be critically important to achieving what we are trying to accomplish.

Since there isn't a way to disambiguate which PAM file to use for LDAP group validation from two different authorization requests coming from the same source (Cisco ASA), we'll need to configure the ASA with multiple AAA servers. I will leave the ASA configuration items, as well as the AnyConnect setup, as an exercise for the reader. We only have one FreeRADIUS server, so we'll have to configure each AAA server entry to use a different port number for authentication requests.

First, add an entry to /etc/raddb/clients.conf to direct requests from the ASA to the default site, /etc/raddb/sites-enabled/default:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
client vpn {     
       ipaddr = ASA IP ADDRESS
       proto = *
       secret = CLIENT SECRET
       require_message_authenticator = no
       nas_type         = other
       limit {
               max_connections = 16
               lifetime = 0
               idle_timeout = 30
       }
}

We'll have two 'listen' stanzas, each one listening on a specific port and forwarding all request types to its own server configuration. A site can have multiple server stanzas, and these stanzas are where you can define what port the authentication request should be sent to, and optionally which PAM file to use when PAM is enabled as the authentication method. Here's an example of a site configured with multiple listen stanzas, each pointing to a different server stanza:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
listen {
 type = auth
 ipaddr = 192.168.1.100
 port = 2012
 virtual_server = vpn-group-sales
 limit {
       max_connections = 16
       lifetime = 0
       idle_timeout = 30
 }
}

listen {
 ipaddr = 192.168.1.100
 port = 2013
 type = acct
}

listen {
 type = auth
 ipaddr = 192.168.1.100
 port = 2112
 virtual_server = vpn-group-support
 limit {
       max_connections = 16
       lifetime = 0
       idle_timeout = 30
 }
}

listen {
 ipaddr = 192.168.1.100
 port = 2113
 type = acct
}

server vpn-group-sales {
  <SNIP>...
  authorize {
         ...
    pap
   update control {
           Pam-Auth = "radiusd-vpn-group-sales"  # or whatever...
   }
  }
  authenticate {
   Auth-Type PAP {
    pap
   }
   ...
   pam
  }
  ...</SNIP>
}

server vpn-group-support {
  <SNIP>...
  authorize {
         ...
    pap
   update control {
           Pam-Auth = "radiusd-vpn-group-support"  # or whatever...
   }
  }
  authenticate {
   Auth-Type PAP {
    pap
   }
   ...
   pam
  }
  ...</SNIP>
}

Take note of the 'update control' section in each server stanza. The Pam Auth directive defines which file under /etc/pam.d will be used for the incoming authorization request. Here's an example of a pam file which uses google auth and ties into LDAP using Winbind (there are several other ways to use LDAP with PAM which aren't covered in this post):


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
auth        required      pam_env.so
auth        requisite     pam_succeed_if.so uid >= 1000 quiet_success
auth        requisite     pam_google_authenticator.so forward_pass
auth        requisite     pam_succeed_if.so user ingroup LDAP_GROUP_NAME debug
auth        sufficient    pam_krb5.so use_first_pass
auth        sufficient    pam_winbind.so use_first_pass
auth        required      pam_deny.so

account     required      pam_access.so
account     required      pam_unix.so broken_shadow
account     sufficient    pam_localuser.so
account     sufficient    pam_succeed_if.so uid < 1000 quiet
account     [default=bad success=ok user_unknown=ignore] pam_krb5.so
account     [default=bad success=ok user_unknown=ignore] pam_winbind.so
account     required      pam_permit.so

password    requisite     pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
password    sufficient    pam_unix.so sha512 shadow nullok try_first_pass use_authtok
password    sufficient    pam_krb5.so use_authtok
password    sufficient    pam_winbind.so use_authtok
password    required      pam_deny.so

session     optional      pam_keyinit.so revoke
session     required      pam_limits.so
-session     optional      pam_systemd.so
session     optional      pam_oddjob_mkhomedir.so umask=0077
session     [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session     required      pam_unix.so
session     optional      pam_krb5.so
session     optional      pam_winbind.so

To summarize the process:
  • Install Google Authenticator
  • Install FreeRADIUS
  • ASA - Setup AAA servers
    • Point each AAA server at the FreeRADIUS server
      • Each server needs its own authentication port
  • Add default rule to use PAM libraries for authentication
  • Add client entry to clients.conf
  • Add listen directives for each authentication port
    • configure each listen directive to point to a unique server stanza
  • Configure each server stanza with the PAM file you want to use for authentication

Comments

Popular Posts