Nitrooos

Myśli programisty

Mechanizm uwierzytelniania na serwerach przedprodukcyjnych

nitrooos

Rozpoczynając nowy projekt często pojawia się potrzeba zorganizowania osobnych instancji naszej aplikacji, dostępnych wyłącznie dla testerów bądź klientów końcowych. Z założenia mają one służyć testowaniu i sprawdzaniu wypocin efektów naszej wielogodzinnej i oddanej pracy. Właśnie wtedy przyda się nam wiedza w jaki sposób ograniczyć dostęp do takich przed-produkcyjnych serwerów w prosty i skuteczny sposób. Potrzebny nam jest mechanizm uwierzytelniania użytkowników w dostępie do strony.

"Basic Access Authentication"

Czyli, prościej mówiąc, wyskakujące okienko z pytaniem o nazwę użytkownika i hasło, gdy tylko próbujemy wejść pod dany adres. Wydaje się zbyt proste i piękne, aby było prawdziwe? Cóż, rzeczywiście ten mechanizm uwierzytelniania posiada pewien haczyk. Mianowicie dane (tj. nazwa użytkownika i hasło) przesyłane są przez przeglądarkę w formie zakodowanej w base64. I nic więcej. Oznacza to, że ten sposób wymaga włączenia przez nas obsługi protokołu HTTPS i każdorazowe przekierowywanie żądań HTTP na HTTPS. Dlaczego? Ponieważ dane przesyłane poprzez HTTP nie są w żaden sposób chronione, a samo kodowanie base64 jest banalne do odwrócenia.

Istnieje wiele stron, na których możemy samodzielnie zakodować oraz odkodować dane w tym formacie. Z zalet z kolei możemy jednak wymienić to, że, dla naszej wygody, przeglądarka zapamiętuje wpisane dane. Ma to znaczenie, ponieważ, jeśli były poprawne, wysyła je automatycznie przy kolejnych żądaniach uwierzytelnienia. Dzięki temu nie będziemy musieli wpisywać ich za każdą próbą dostępu do serwera. Dodatkowo, możemy zdefiniować wiele par <nazwa użytkownika, hasło> i nadawać im dostępy np. tylko do niektórych elementów strony.

Stworzenie pliku z listą użytkowników i ich haseł

Zacząć należy od stworzenia odpowiedniego pliku (nazywanego zwyczajowo .htpasswd), zawierającego definicje par <nazwa użytkownika, hasło>, którym zezwalamy na dostęp do strony. Najprościej wygenerować go za pomocą narzędzia htpasswd, dostępnego na Ubuntu w pakiecie apache2-utils:

    sudo apt-get install apache2-utils

Teraz możemy stworzyć odpowiedni plik wraz z pierwszym użytkownikiem:

    htpasswd -c .htpasswd nitrooos

Zostaniemy poproszeni o wpisanie hasła dla nowego użytkownika i jego powtórzenie. Po zatwierdzeniu wpis dla użytkownika nitrooos będzie gotowy :) Kolejnych użytkowników można doadć poprzez ponowne wykonanie powyższej komendy, ale bez przełącznika -c. Odpowiada on bowiem za utworzenie nowego pliku o podanej nazwie. Przykładowa zawartość pliku .htpasswd wygląda następująco:

    nitrooos:$apr1$4gKNcA2h$rhk7RtslHU4m.6EAs6T7j.
    admin:$apr1$pLzt4H94$R6wTAUQphdWjvavOphViX.

Włączamy mechanizm uwierzytelniania w konfiguracji serwera sieciowego

Kolejnym krokiem jest włączenie mechanizmu w konfiguracji serwera HTTP. Dla każdego z nich robi się to inaczej, niemniej jednak pokażę przykładowe opcje konfiguracje dla Apache oraz nginx. W przypadku serwera Apache należy w pliku konfiguracji odpowiedniego wirtualnego hosta dodać sekcję <Directory> ze wskazaniem ścieżki, pod którą rezyduje plik .htpasswd (/etc/apache2/.htpasswd w tym przykładzie):

<VirtualHost *:80>
    DocumentRoot /var/www/html

    <Directory "/var/www/html">
        AuthType Basic
        AuthName "Restricted Content"
        AuthUserFile /etc/apache2/.htpasswd
        Require valid-user
    </Directory>
</VirtualHost>

W przypadku serwera nginx należy do odpowiedniej sekcji location dodać wpisy auth_basic oraz auth_basic_user_file. Plik .htpasswd znajduje się w tym przypadku pod ścieżką /etc/nginx/conf.d/.htpasswd:

location / {
    auth_basic "Restricted content";
    auth_basic_user_file /etc/nginx/conf.d/.htpasswd;
}

Po dokonaniu tych zmian serwer należy oczywiście zrestartować:

    sudo service apache2 restart

lub też:

    sudo service nginx restart

Jeśli wszystko poszło po naszej myśli, w tym momencie dostęp do naszej witryny będzie chroniony przez mechanizm uwierzytelniania Basic Access Authentication.

Przekierowanie ruchu HTTP na HTTPS

Jak napisałem na początku, metoda ta wymaga obsługi ruchu poprzez protokół HTTPS. Oznacza to konieczność zdobycia odpowiedniego certyfikatu oraz włączenie go w konfiguracji serwera sieciowego. Nie jest to tematem tego wpisu, należy jednak pamiętać o stworzeniu przekierowania ruchu HTTP na HTTPS (przykład dla serwera nginx):

server {
    listen [::]:80;
    listen 80;
    rewrite ^ https://$host$request_uri? permanent;
}

Bonus: ciastko sesyjne jako podstawowy mechanizm uwierzytelniania

Alternatywą dla powyższej metody może być ustawianie w przeglądarce użytkownika specjalnego ciastka sesyjnego. Taki plik (ang. cookie) o ustalonej wartości, wymagany będzie aby uzyskać dostęp do serwisu. Bezpieczniejsza wersja zakłada, że użytkownik samemu ustawia sobie takie ciastko, np. w kosoli JavaScript przeglądarki, wersja prostsza to po prostu udostępnienie pod specjalnym adresem strony ustawiającej ciastko za nas. Oczywiście ta metoda nie wyklucza stosowania poprzedniej. Na potrzeby prezentacji załóżmy, że ciastko uwierzytelniające, wymagane przez serwer posiada nazwę "TestServerAuthCookie" i wartość "test_server_granted".

Ustawianie ciastka sesyjnego przez użytkownika w konsoli JavaScript

W tym scenariuszu użytkownik, aby uzyskać dostęp do strony, musi po wejściu pod jej adres uruchomić narzędzia deweloperskie przeglądarki (np. DevTools w Google Chrome), przejść do zakładki "Console" i wkleić prosty kawałek kodu:

document.cookie = "TestServerAuthCookie=test_server_granted";

Oczywiście w rzeczywistej implementacji nazwa i wartość ciastka nie powinny być tak przewidywalne i łatwe do odgadnięcia. Do tego dochodzi jeszcze konieczność odpowiedniego skonfigurowania serwera sieciowego tak, aby sprawdzał czy wymagane ciastko jest obecne w parametrach żądania HTTP. W przypadku serwera Apache odpowednie reguły wyglądają tak:

RewriteCond %{HTTP_COOKIE} !TestServerAuthCookie=test_server_granted
RewriteRule .* - [NC,L,F]

Natomiast dla nginx'a są następujące:

location / {
    if ($http_cookie !~ 'TestServerAuthCookie=test_server_granted') {
        return 403;
    }
}

Oznacza to, że próbując uzyskać dostęp do naszego serwisu otrzymamy status błędu 403 Forbidden za każdym razem, gdy w żądaniu nie znajdzie się ciastko o pożądanej nazwie i wartości.

Specjalny link uwierzytelniający

Zwróćmy uwagę, że podany powyżej sposób może być dość niewygodny. Wymaga on otwierania konsoli przeglądarki za każdym razem, gdy potrzebujemy się uwierzytelnić na serwerze. Może być to uciążliwe np. dla osób testujących naszą aplikację, ponieważ często korzystają oni z trybu incognito, który to stanowi nową, osobną sesję. W związku z tym ustawianie ciastka sesyjnego wymagane jest po każdorazowej inicjalizacji trybu incognito. Aby nieco ułatwić życie takim maruderom osobom, możemy stworzyć osobny URL ustawiający wymagane ciastko w przeglądarce automatycznie. Rozwiązanie to jest o tyle wygodniejsze, że link taki można zapamiętać i ustawić np. na pasku zakładek przeglądarki. Dzięki temu proces kontroli dostępu sprowadzi się do jednego kliknięcia w przycisk. Prościej już się nie da :)

Stwórzmy więc osobną ścieżkę na serwerze i zwróćmy z niej dokument HTML, zawierający skrypt ustawiający wymaganą wartość ciastka. Zacząć należy od utworzenia pliku site_login.html, zawierającego prosty skrypt JavaScript, ustawiający ciastko sesyjne. Plik ten powinien wyglądać np. tak:

<html>
    <body>
        <script>
            document.cookie = 'TestServerAuthCookie=test_server_granted';
            window.location.replace('http://test.our.gretest.web.service');
        </script>
    <body>
<html>

Następnie należy zdefiniować w konfiguracji serwera regułę, która w przypadku wejścia pod specjalny adres (link uwierzytelniający) dokona przekierowania pod /site_login.html. W przypadku serwera Apache konfiguracja wygląda tak:

RewriteCond "%{REQUEST_URI}" "!=/authentication_link_QRSvD44xNedyEeqmyGWtevidLbmeUG1NGaeVeEtJ.html"
RewriteCond %{HTTP_COOKIE} !TestServerAuthCookie=test_server_granted
RewriteRule .* /site_login.html [NC,L,F]

Natomiast dla nginx'a prezentuje się następująco:

location /authentication_link_QRSvD44xNedyEeqmyGWtevidLbmeUG1NGaeVeEtJ.html {
    add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive";
    return 200 /site_login.html;
}

Jeśli wejdziemy pod specjalny adres (link uwierzytelniający, tutaj /authentication_link_QRSvD44xNedyEeqmyGWtevidLbmeUG1NGaeVeEtJ.html), serwer zwróci prosty dokument HTML. Skrypt w nim zawarty automatycznie ustawi ciastko sesyjne na pożądaną wartość, po czym dokona przekierowania (window.location.replace) na stronę główną naszego serwisu. Proste i wygodne. Dodatkowo ustawiamy nagłówek odpowiedzi X-Robots-Tag na wartość "noindex, nofollow, nosnippet, noarchive". Sprawi to, że roboty indeksujące, nawet jeśli zbłądzą i odnajdą nasz link, nie zaindeksują go w żaden sposób.

Podsumowanie

Pokazałem kilka pomysłów, jak może być zorganizowany mechanizm uwierzytelniania użytkowników w dostępie do serwerów, na których serwujemy wersje deweloperskie/testowe/przedprodukcyjne naszych aplikacji. Są to oczywiście rady dość proste, które jednak mogą stanowić dobry punkt wyjścia w temacie uwierzytelniania użytkowników serwerów testowych. Dla osób chcących zachować większe bezpieczeństwo bądź kontrolę nad tym, kto dokładnie, kiedy może uzyskać dostęp czy też chcących logować zdarzenia dostępu do strony, mogą okazać się niewystarczające. Czasami jednak, gdy pracuje się pod presją czasu, lepsze jest takie rozwiązanie niż żadne. Natomiast dla niektórych projektów może okazać się wręcz wystarczające. Cieszę się, że mogłem podzielić się z Wami swoją wiedzą! Jendocześnie zapraszam do śledzenia bloga i czekania na kolejne wpisy, bo one już niedługo :)