Freeradius + Google Authenticator + LDAP + Fortigate

What to do if you want two-factor authentication but have no money for hardware tokens, and are instead advised to just hang in there with a good mood.

This solution isn’t particularly original; rather, it’s a mix of various solutions found online.

So, given that

Domain Active Directory.

Domain users working via VPN, as many do nowadays.

The VPN gateway is represented by Fortigate.

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

The policy Fortinet regarding its own tokens is less than greedy—there are 10 free tokens, with the rest at a very steep price. I have not considered RSASecureID, Duo, or similar, as I prefer open-source solutions.

Preliminary requirements: host *nix with freeradius, sssd — once added to the domain, domain users can authenticate without issue.

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

In my example, it’s CentOS 7.8.

The logic of operation is as follows: when connecting to the VPN, the user should enter their domain login and OTP instead of a password.

Service configuration

In /etc/raddb/radiusd.conf only the user and group under which it starts are changed freeradius, as the service radiusd must be able to read files in all subdirectories /home/.

user = root
group = root

To be able to use groups in the settings Fortigate, you need to pass Vendor Specific Attribute. For 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 installing freeradius-ldap in the directory raddb/mods-available a file is created ldap.

You need to create a symbolic link in the directory raddb/mods-enabled.

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

I adjust its content to look like this:

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 and raddb/sites-enabled/inner-tunnel in the section authorize I add the name of the policy that will be used — group_authorization. An important point — the name of the policy is defined not by the file name in the directory policy.d, but by the directive inside the file before the curly braces.
In the section authenticate in these same files, you need to uncomment the line pam.

In the file clients.conf we specify 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 configuration options for the bundle freeradius with google authenticator assume user input of credentials in the format: username/password+OTP.

Considering the amount of curses that will befall if the default bundle is used freeradius with Google Authenticator, it was decided to use module configuration pam to validate only the token Google Authenticator.

When a user connects, the following occurs:

  • Freeradius checks whether the user exists in the domain and in a specific group, and if successful, the OTP token is verified.

Everything seemed quite successful until I thought, "How do I register OTP for 300+ users?"

The user must log in to the server with freeradius and under their account and run the application Google authenticator, which will generate a QR code for the user. Here, assistance comes from shellinabox in combination with .bash_profile.

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

The configuration file of the daemon is located in /etc/sysconfig/shellinabox.
I specify port 443 there and can indicate my own certificate.

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

The user just needs to follow the link, enter their domain credentials, and obtain a QR code for the application.

The algorithm is as follows:

  • The user logs into the machine through a browser.
  • It checks if the user is a domain user. If not, no further actions are taken.
  • If the user is domain-based, their membership in the administrator group is checked.
  • If they are not an admin, it checks if Google Authenticator is configured. If not, a QR code is generated, and the user is logged out.
  • If not an admin and Google Authenticator is configured, the user is simply logged out.
  • If the user is an admin, the Google Authenticator check occurs again. If not configured, a QR code is generated.

All logic is executed 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 configuration:

  • Creating Radius-server

    Freeradius + Google Authenticator + LDAP + Fortigate

  • Create necessary groups, if access segregation by groups is required. The group name in Fortigate must correspond to the group passed in Vendor Specific Attribute Fortinet-Group-Name.

    Freeradius + Google Authenticator + LDAP + Fortigate

  • Edit the necessary SSL-portals.

    Freeradius + Google Authenticator + LDAP + Fortigate

  • Add groups to policies.

    Freeradius + Google Authenticator + LDAP + Fortigate

Advantages of this solution:

  • There is an option for OTP authentication on Fortigate an open-source solution.
  • The user is exempt from entering the domain password when connecting via VPN, simplifying the connection process. It's easier to enter a 6-digit password than the one set by security policy. Consequently, the number of tickets with the subject: 'Can't connect to VPN' decreases.

P.S. There are plans to enhance this solution to a full two-factor authorization with challenge-response.

Update:

As promised, I indeed enhanced it to the variant with challenge-response.
So:
In the file /etc/raddb/sites-enabled/default section authorize looks 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 has the following form:

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 their domain credentials in the VPN client.
  • Freeradius checks the validity of the account and password.
  • If the password is correct, a token request is sent.
  • The token is then verified.
  • Profit).

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers 🔥 Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster