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 .
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/ldapPrzygotowuję 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 shellinaboxdUż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

- 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.

- Edytujemy niezbędne SSL-portale.

- Dodajemy grupy do polityk.

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




