Freeradius + Google Autenticator + LDAP + Fortigate

Co zrobić, jeśli chciałoby się mieć dwuskładnikową autoryzację, ale brakuje funduszy na tokeny sprzętowe, a w ogóle proponują trzymać się dobrego humoru.

To rozwiązanie nie jest czymś superoryginalnym, raczej jest to miks różnych rozwiązań znalezionych w internecie.

A więc mamy

Domena Active Directory.

Użytkownicy domeny pracujący przez VPN, jak to obecnie bywa.

Jako bramka VPN działa Fortigate.

Zapisanie hasła dla klienta VPN jest zabronione przez politykę bezpieczeństwa.

Polityka Fortinet w sprawie własnych tokenów nie można nazwać inną niż skąpą — darmowe tokeny są aż 10 jednostek, reszta w bardzo nieprzyzwoitej cenie. RSASecureID, Duo i podobne nie były brane pod uwagę, ponieważ nie chcę zamkniętego oprogramowania.

Wymagania wstępne: host *nix z zainstalowanym freeradius, sssd — wprowadzony do domeny, użytkownicy domenowi mogą swobodnie się na nim autoryzować.

Dodatkowe pakiety: shellinabox, figlet, freeeradius-ldap, czcionka rebel.tlf z repozytorium https://github.com/xero/figlet-fonts.

W moim przykładzie — CentOS 7.8.

Logika działania jest taka: przy podłączeniu do VPN użytkownik musi wprowadzić login domenowy i OTP zamiast hasła.

Konfiguracja usług

W /etc/raddb/radiusd.conf zmienia się tylko użytkownik i grupa, w imieniu których uruchamia się freeradius, ponieważ usługa radiusd musi umieć czytać pliki we wszystkich podkatalogach /home/.

user = root
group = root

Aby można było używać grup w ustawieniach Fortigate, należy przekazać Vendor Specific Attribute. W tym celu w katalogu raddb/policy.d tworzę plik z następującą zawartością:

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 := "Witaj Adminie"
                }
        }
    else {
        update reply {
        &Reply-Message := "Nieautoryzowany dostęp do vpn"
            }
        reject
        }
}

Po zainstalowaniu freeradius-ldap w katalogu raddb/mods-available tworzony jest plik ldap.

Należy stworzyć link symboliczny w katalogu raddb/mods-enabled.

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

Przygotowuję jego zawartość w następujący sposób:

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'
        }
}

W plikach raddb/sites-enabled/default i raddb/sites-enabled/inner-tunnel w sekcji authorize dodaję nazwę polityki, która będzie używana — group_authorization. Kluczowa sprawa — nazwa polityki nie jest określona przez nazwę pliku w katalogu policy.d, lecz przez dyrektywę w obrębie pliku przed klamrami.
W sekcji authenticate w tych samych plikach należy odkomentować linię pam.

W pliku clients.conf określamy parametry, z którymi będzie się łączyć Fortigate:

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

Konfiguracja modułu 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

Domyślne opcje implementacji zestawu freeradius z google authenticator zakładają wprowadzenie przez użytkownika danych w formacie: nazwa_użytkownika/hasło+OTP.

Biorąc pod uwagę ilość przekleństw, które na mnie spadną, w przypadku użycia domyślnego zestawu freeradius z Google Authenticator, podjęto decyzję o wykorzystaniu konfiguracji modułu pam w taki sposób, aby sprawdzać tylko token Google Authenticator.

Podczas łączenia użytkownika dzieje się następujące:

  • Freeradius weryfikuje istnienie użytkownika w domenie i w określonej grupie, a w przypadku sukcesu następuje weryfikacja tokena OTP.

Wszystko wyglądało całkiem obiecująco do momentu, gdy pomyślałem: „Jak zarejestrować OTP dla 300+ użytkowników?”

Użytkownik musi zalogować się na serwerze z freeradius i z własnego konta oraz uruchomić aplikację Google authenticator, która wygeneruje dla użytkownika kod QR do aplikacji. I tu z pomocą przychodzi shellinabox w połączeniu z .bash_profile.

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

Plik konfiguracyjny demona znajduje się w /etc/sysconfig/shellinabox.
Wskazuję tam port 443 i można wskazać własny certyfikat.

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

Użytkownik musi jedynie wejść pod link, wpisać dane domenowe i otrzymać kod QR do aplikacji.

Algorytm jest następujący:

  • Użytkownik loguje się na maszynie przez przeglądarkę.
  • Sprawdza się, czy użytkownik jest z domeny. Jeśli nie, nie podejmuje się żadnych działań.
  • Jeśli użytkownik jest domenowy, sprawdzana jest przynależność do grupy administratorów.
  • Jeśli nie jest administratorem, sprawdzane jest, czy skonfigurowano Google Authenticator. Jeśli nie, generowany jest kod QR i następuje wylogowanie użytkownika.
  • Jeśli nie jest administratorem i Google Authenticator jest skonfigurowany, następuje po prostu wylogowanie.
  • Jeśli jest administratorem, ponownie sprawdzane jest ustawienie Google Authenticator. Jeśli nie jest skonfigurowany, generowany jest kod QR.

Cała logika jest realizowana z wykorzystaniem /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

Konfiguracja Fortigate:

  • Tworzymy Radius-serwer

    Freeradius + Google Autenticator + LDAP + Fortigate

  • Tworzymy niezbędne grupy, w razie potrzeby dzieląc dostęp według grup. Nazwa grupy na Fortigate musi odpowiadać grupie, która jest przesyłana w Vendor Specific Attribute Fortinet-Group-Name.

    Freeradius + Google Autenticator + LDAP + Fortigate

  • Edytujemy niezbędne SSL-portale.

    Freeradius + Google Autenticator + LDAP + Fortigate

  • Dodajemy grupy do polityk.

    Freeradius + Google Autenticator + LDAP + Fortigate

Zalety tego rozwiązania:

  • Istnieje możliwość uwierzytelniania za pomocą OTP na Fortigate rozwiązaniu open source.
  • Wykluczono wprowadzenie hasła domenowego przez użytkownika przy łączeniu przez VPN, co nieco upraszcza proces łączenia. Wprowadzenie 6-cyfrowego hasła jest prostsze niż tego, które przewiduje polityka bezpieczeństwa. W konsekwencji, zmniejsza to liczbę zgłoszeń o temacie: „Nie mogę połączyć się z VPN”.

P.S. W planach jest rozwinąć to rozwiązanie do pełnoprawnej autoryzacji dwuskładnikowej z wyzwaniem-odpowiedzią.

Aktualizacja:

Jak obiecałem, rozwinąłem to do wersji z wyzwaniem-odpowiedzią.
A zatem:
W pliku /etc/raddb/sites-enabled/default sekcja authorize wygląda następująco:

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

Sekcja authenticate teraz ma następującą postać:

authenticate {
        Auth-Type PAP {
                pap
        }
        Auth-Type CHAP {
                chap
        }
        Auth-Type MS-CHAP {
                mschap
        }
        mschap
        digest
        # Próba uwierzytelnienia za pomocą bezpośredniego połączenia z LDAP:
        Auth-Type LDAP {
        ldap
        if (ok) {
            update reply {
                # Tworzenie losowego atrybutu State:
                State := "%{randstr:aaaaaaaaaaaaaaaa}"
                Reply-Message := "Proszę wprowadzić OTP"
                }
            # Zwróć Access-Challenge:
            challenge
            }
        }
        pam
        eap
}

Teraz sprawdzanie użytkownika odbywa się według następującego algorytmu:

  • Użytkownik wprowadza dane logowania domeny w kliencie VPN.
  • Freeradius sprawdza ważność konta i hasła.
  • Jeśli hasło jest poprawne, wysyłane jest żądanie tokena.
  • Następuje sprawdzenie tokena.
  • Zysk).

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster