Freeradius + Google Authenticator + LDAP + Fortigate

What if two-factor authentication is both desirable and prickly, but there is no money for hardware tokens and in general they offer to stay in a good mood.

This solution is not something super original, but rather a mix of different solutions found on the Internet.

So given

Domain Name Active Directory.

Domain users working through a VPN, like many today.

Acts as a VPN gateway Fortigate.

Saving the password for the VPN client is prohibited by security policy.

Politics Fortinet in relation to your own tokens, you can’t call it less than a zhlob - there are as many as 10 free tokens, the rest - at a very non-kosher price. I did not consider RSASecureID, Duo and the like, because I want open source.

Prerequisites: host * nix with installed freeradius, ssd - entered into the domain, domain users can easily authenticate on it.

Additional packages: shellina box, figlett, freeradius-ldap, font rebel.tlf from the repository https://github.com/xero/figlet-fonts.

In my example - CentOS 7.8.

The logic of work is supposed to be as follows: when connecting to a VPN, the user must enter a domain login and OTP instead of a password.

Services setup

В /etc/raddb/radiusd.conf only the user and group on behalf of which starts freeradius, since the service radiusd should be able to read files in all subdirectories / home /.

user = root
group = root

To be able to use groups in settings Fortigate, must be transmitted Vendor Specific Attribute. To do this, in the directory raddb/policy.d I create a file with the following content:

group_authorization {
    if (&LDAP-Group[*] == "CN=vpn_admins,OU=vpn-groups,DC=domain,DC=local") {
            update reply {
                &Fortinet-Group-Name = "vpn_admins" }
            update control {
                &Auth-Type := PAM
                &Reply-Message := "Welcome Admin"
                }
        }
    else {
        update reply {
        &Reply-Message := "Not authorized for vpn"
            }
        reject
        }
}

After installation, freeradius-ldap in the directory raddb/mods-available file is created ldap.

Need to create a symbolic link to the directory raddb/mods-enabled.

ln -s /etc/raddb/mods-available/ldap /etc/raddb/mods-enabled/ldap

I bring its contents to this form:

ldap {
        server = 'domain.local'
        identity = 'CN=freerad_user,OU=users,DC=domain,DC=local'
        password = "SupeSecretP@ssword"
        base_dn = 'dc=domain,dc=local'
        sasl {
        }
        user {
                base_dn = "${..base_dn}"
                filter = "(sAMAccountname=%{%{Stripped-User-Name}:-%{User-Name}})"
                sasl {
                }
                scope = 'sub'
        }
        group {
                base_dn = "${..base_dn}"
                filter = '(objectClass=Group)'
                scope = 'sub'
                name_attribute = cn
                membership_filter = "(|(member=%{control:Ldap-UserDn})(memberUid=%{%{Stripped-User-Name}:-%{User-Name}}))"
                membership_attribute = 'memberOf'
        }
}

In files raddb/sites-enabled/default и raddb/sites-enabled/inner-tunnel in the section authorize I add the name of the policy to be used - group_authorization. An important point - the name of the policy is not determined by the name of the file in the directory policy.d, but by a directive inside the file before the curly braces.
In section authenticate in the same files you need to uncomment the line pam.

In file clients.conf prescribe the parameters with which it will connect Fortigate:

client fortigate {
    ipaddr = 192.168.1.200
    secret = testing123
    require_message_authenticator = no
    nas_type = other
}

Module configuration pam.d/radiusd:

#%PAM-1.0
auth       sufficient   pam_google_authenticator.so
auth       include      password-auth
account    required     pam_nologin.so
account    include      password-auth
password   include      password-auth
session    include      password-auth

Default bundle implementation options freeradius с google authenticator require the user to enter credentials in the format: username/password+OTP.

By imagining the number of curses that will fall on the head, in the case of using the default bundle freeradius с Google Authenticator, it was decided to use the module configuration pam so that only the token can be checked Google Authenticator.

When a user connects, the following happens:

  • Freeradius checks if the user is in the domain and in a certain group and, if successful, checks the OTP token.

Everything looked good enough until the moment when I thought “How can I register OTP for 300+ users?”

The user must login to the server with freeradius and from under your account and run the application Google authenticator, which will generate a QR code for the application for the user. This is where help comes in. shellina box in combination with .bash_profile.

[root@freeradius ~]# yum install -y shellinabox

The daemon config file is located at /etc/sysconfig/shellinabox.
I specify port 443 there and you can specify your certificate.

[root@freeradius ~]#systemctl enable --now shellinaboxd

The user only needs to follow the link, enter domain credits and receive a QR code for the application.

The algorithm is as follows:

  • The user logs in to the machine through a browser.
  • Whether the domain user is checked. If not, then no action is taken.
  • If the user is a domain user, membership in the Administrators group is checked.
  • If not an admin, it checks if Google Authenticator is configured. If not, then a QR code and user logout is generated.
  • If not an admin and Google Authenticator is configured, then just logout.
  • If admin, then check Google Authenticator again. If not configured, a QR code is generated.

All logic is done using /etc/skel/.bash_profile.

cat /etc/skel/.bash_profile

# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
        . ~/.bashrc
fi

# User specific environment and startup programs
# Make several commands available from user shell

if [[ -z $(id $USER | grep "admins") || -z $(cat /etc/passwd | grep $USER) ]]
  then
    [[ ! -d $HOME/bin ]] && mkdir $HOME/bin
    [[ ! -f $HOME/bin/id ]] && ln -s /usr/bin/id $HOME/bin/id
    [[ ! -f $HOME/bin/google-auth ]] && ln -s /usr/bin/google-authenticator $HOME/bin/google-auth
    [[ ! -f $HOME/bin/grep ]] && ln -s /usr/bin/grep $HOME/bin/grep
    [[ ! -f $HOME/bin/figlet ]] && ln -s /usr/bin/figlet $HOME/bin/figlet
    [[ ! -f $HOME/bin/rebel.tlf ]] && ln -s /usr/share/figlet/rebel.tlf $HOME/bin/rebel.tlf
    [[ ! -f $HOME/bin/sleep ]] && ln -s /usr/bin/sleep $HOME/bin/sleep
  # Set PATH env to <home user directory>/bin
    PATH=$HOME/bin
    export PATH
  else
    PATH=PATH=$PATH:$HOME/.local/bin:$HOME/bin
    export PATH
fi


if [[ -n $(id $USER | grep "domain users") ]]
  then
    if [[ ! -e $HOME/.google_authenticator ]]
      then
        if [[ -n $(id $USER | grep "admins") ]]
          then
            figlet -t -f $HOME/bin/rebel.tlf "Welcome to Company GAuth setup portal"
            sleep 1.5
            echo "Please, run any of these software on your device, where you would like to setup OTP:
Google Autheticator:
AppStore - https://apps.apple.com/us/app/google-authenticator/id388497605
Play Market - https://play.google.com/stor/apps/details?id=com.google.android.apps.authenticator2&hl=en
FreeOTP:
AppStore - https://apps.apple.com/us/app/freeotp-authenticator/id872559395
Play Market - https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp&hl=en

And prepare to scan QR code.

"
            sleep 5
            google-auth -f -t -w 3 -r 3 -R 30 -d -e 1
            echo "Congratulations, now you can use an OTP token from application as a password connecting to VPN."
          else
            figlet -t -f $HOME/bin/rebel.tlf "Welcome to Company GAuth setup portal"
            sleep 1.5
            echo "Please, run any of these software on your device, where you would like to setup OTP:
Google Autheticator:
AppStore - https://apps.apple.com/us/app/google-authenticator/id388497605
Play Market - https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en
FreeOTP:
AppStore - https://apps.apple.com/us/app/freeotp-authenticator/id872559395
Play Market - https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp&hl=en

And prepare to scan QR code.

"
            sleep 5
            google-auth -f -t -w 3 -r 3 -R 30 -d -e 1
            echo "Congratulations, now you can use an OTP token from application as a password to VPN."
            logout
        fi
      else
        echo "You have already setup a Google Authenticator"
        if [[ -z $(id $USER | grep "admins") ]]
          then
          logout
        fi
    fi
  else
    echo "You don't need to set up a Google Authenticator"
fi

Fortigate setup:

  • Create Radius-server

    Freeradius + Google Authenticator + LDAP + Fortigate

  • We create the necessary groups, if necessary, access control by groups. Group name on Fortigate must match the group that is passed in Vendor Specific Attribute Fortinet-Group-Name.

    Freeradius + Google Authenticator + LDAP + Fortigate

  • Editing the necessary SSL-portals.

    Freeradius + Google Authenticator + LDAP + Fortigate

  • Adding groups to policies.

    Freeradius + Google Authenticator + LDAP + Fortigate

The advantages of this solution:

  • It is possible to authenticate by OTP on Fortigate open source solution.
  • The user does not enter a domain password when connecting via VPN, which somewhat simplifies the connection process. The 6-digit password is easier to enter than the one provided by the security policy. As a result, the number of tickets with the subject: “I can’t connect to the VPN” decreases.

PS We plan to upgrade this solution to a full-fledged two-factor authentication with challenge-response.

Update:

As promised, I tweaked it to the challenge-response option.
So:
In file /etc/raddb/sites-enabled/default section authorize as follows:

authorize {
    filter_username
    preprocess
    auth_log
    chap
    mschap
    suffix
    eap {
        ok = return
    }
    files
    -sql
    #-ldap
    expiration
    logintime
    if (!State) {
        if (&User-Password) {
            # If !State and User-Password (PAP), then force LDAP:
            update control {
                Ldap-UserDN := "%{User-Name}"
                Auth-Type := LDAP
            }
        }
        else {
            reject
        }
    }
    else {
        # If State, then proxy request:
        group_authorization
    }
pap
}

Section authenticate now looks like this:

authenticate {
        Auth-Type PAP {
                pap
        }
        Auth-Type CHAP {
                chap
        }
        Auth-Type MS-CHAP {
                mschap
        }
        mschap
        digest
        # Attempt authentication with a direct LDAP bind:
        Auth-Type LDAP {
        ldap
        if (ok) {
            update reply {
                # Create a random State attribute:
                State := "%{randstr:aaaaaaaaaaaaaaaa}"
                Reply-Message := "Please enter OTP"
                }
            # Return Access-Challenge:
            challenge
            }
        }
        pam
        eap
}

Now user verification occurs according to the following algorithm:

  • The user enters domain credits in the VPN client.
  • Freeradius checks the validity of the account and password
  • If the password is correct, then a request for a token is sent.
  • The token is being verified.
  • profit).

Source: habr.com

Add a comment