NGINX: Nieodpowiednie użycie dyrektywy deny
21 May 2016
allow best-practices deny http nginx
Dyrektywy allow
oraz deny
dostarczane są z modułem ngx_http_access_module i umożliwiają zezwolenie na dostęp lub jego ograniczenie do wybranych adresów klientów. Obie nadają się do budowania list kontroli dostępu.
Moim zdaniem najlepszym sposobem budowania list ACL jest zacząć zawsze od odmowy, a następnie przyznawać dostęp adresom IP tylko do wybranych lokalizacji. NGINX dostarcza podobny mechanizm nadawania dostępu, jednak reguły są przetwarzane w kolejności, od góry do dołu: jeśli pierwszą dyrektywą w sekwencji będzie deny all
, wówczas wszystkie dalsze dyrektywy allow
nie przyniosą żadnego efektu. Dlatego regułę blokującą zawsze ustawiamy po dyrektywach zezwalających:
location /login {
allow 192.168.252.10;
allow 192.168.252.11;
allow 192.168.252.12;
deny all;
}
Pamiętaj, że dyrektywa
deny
zawsze zwróci kod błędu 403 Forbidden, odnoszący się do klienta uzyskującego dostęp, który nie jest upoważniony do wykonania danego żądania. Został on zdefiniowany w RFC 7231 [IETF] i dokładnie oznacza, że serwer, który przyjął żądanie, w pełni je rozumie jednak odmawia autoryzacji.
Czy stosowanie powyższych dyrektyw może rodzić jakieś negatywne konsekwencje? Zdecydowanie tak: obie dyrektywy mogą działać wbrew oczekiwaniom, zwłaszcza, łącząc je z mechanizmem przepisywania dostarczanym przez serwer NGINX. Taka konfiguracja może być jednak dosyć specyficzna i możliwe, że nigdy z niej nie skorzystasz, chyba że zamiast odpowiedzi bezpośrednio do klienta przekierujesz ruch w inne miejsce. Więcej na ten temat poczytasz na blogu OpenResty w rozdziale Nginx directive execution order (03), w którym niezwykle dokładnie opisano cały przypadek.
Wracając do problemu, spójrz na poniższy przykład:
server {
server_name example.com;
deny all;
location = /test {
return 200 "it's all okay";
more_set_headers 'Content-Type: text/plain';
}
}
Następnie, wykonując poniższe żądanie:
curl -i https://example.com/test
HTTP/2 200
date: Wed, 11 Nov 2018 10:02:45 GMT
content-length: 13
server: Unknown
content-type: text/plain
it's all okay
Widzisz, że dostaliśmy odpowiedź o treści “it’s all okay” z kodem 200. Dlaczego, skoro jawnie zablokowaliśmy dostęp do całego zasobu /test
za pomocą dyrektywy deny
i która jest jakby nad kontekstem location = /test
w konfiguracji (tj. jej zakres rozchodzi się na cały blok server
)?
Jest to prawidłowe zachowanie i ma związek z całym mechanizmem przetwarzania żądań. Każdy request, jak już dotrze do serwera NGINX, zostaje przetwarzany w tzw. fazach. Tych faz jest dokładnie jedenaście:
- NGX_HTTP_POST_READ_PHASE - pierwsza faza, w której czytany jest nagłówek żądania
- przykładowe moduły: ngx_http_realip_module
- NGX_HTTP_SERVER_REWRITE_PHASE - implementacja dyrektyw przepisywania zdefiniowanych w bloku serwera; w tej fazie m.in. zmieniany jest identyfikator URI żądania za pomocą wyrażeń regularnych (PCRE)
- przykładowe moduły: ngx_http_rewrite_module
-
NGX_HTTP_FIND_CONFIG_PHASE - zamieniana jest lokalizacja zgodnie z URI (wyszukiwanie lokalizacji)
- NGX_HTTP_REWRITE_PHASE - modyfikacja URI na poziomie lokalizacji
- przykładowe moduły: ngx_http_rewrite_module
- NGX_HTTP_POST_REWRITE_PHASE - przetwarzanie końcowe URI (żądanie zostaje przekierowane do nowej lokalizacji)
- przykładowe moduły: ngx_http_rewrite_module
- NGX_HTTP_PREACCESS_PHASE - wstępne przetwarzanie uwierzytelniania; sprawdzane są m.in. limity żądań oraz limity połączeń (ograniczenie dostępu)
- przykładowe moduły: ngx_http_limit_req_module, ngx_http_limit_conn_module, ngx_http_realip_module
- NGX_HTTP_ACCESS_PHASE - weryfikacja klienta (proces uwierzytelnienia, ograniczenie dostępu)
- przykładowe moduły: ngx_http_access_module, ngx_http_auth_basic_module
- NGX_HTTP_POST_ACCESS_PHASE - faza przetwarzania końcowego związana z ograniczaniem dostępu
- przykładowe moduły: ngx_http_access_module, ngx_http_auth_basic_module
- NGX_HTTP_PRECONTENT_PHASE - generowanie treści (odpowiedzi)
- przykładowe moduły: ngx_http_try_files_module
- NGX_HTTP_CONTENT_PHASE - przetwarzanie treści (odpowiedzi)
- przykładowe moduły: ngx_http_index_module, ngx_http_autoindex_module, ngx_http_gzip_module
- NGX_HTTP_LOG_PHASE - mechanizm logowania, tj. zapisywanie informacji do pliku z logami
- przykładowe moduły: ngx_http_log_module
Przygotowałem również proste wyjaśnienie, które pomoże ci zrozumieć, jakie moduły oraz dyrektywy są używane na każdym etapie:
Dodatkowo każda z faz ma listę powiązanych z nią procedur obsługi. Co więcej, na każdej fazie można zarejestrować dowolną liczbę handlerów.
Polecam zapoznać się ze świetnym wyjaśnieniem dotyczącym faz przetwarzania żądań. Dodatkowo, w tym oficjalnym przewodniku także dość dokładnie opisano cały proces przejścia żądania przez każdą z faz.
Wróćmy teraz do naszego problemu i „dziwnego” zachowania dyrektywy deny
w połączeniu z wykorzystaniem dyrektywy return
— co w konsekwencji prowadzi do natychmiastowego przesłania odpowiedzi do klienta, a nie zablokowania dostępu do danego zasobu.
Jak już wspomniałem, wynika to z faktu, że przetwarzanie żądania odbywa się w fazach, a faza przepisywania (do której należy dyrektywa return
) wykonywana jest przed fazą dostępu (w której działa dyrektywa deny
). Niestety NGINX nie zgłasza nic niepokojącego (bo i po co) podczas przeładowania, więc odpowiedzialność poprawnego budowania reguł filtrujących wraz z pozostałymi mechanizmami spada na administratora.
Jednym z rozwiązań jest użycie instrukcji if
w połączeniu z modułami geo
lub map
. Na przykład:
server_name example.com;
location / {
if ($whitelist.acl) {
set $pass 1;
}
if ($pass = 1) {
return 200 "it's all okay";
# lub:
# proxy_pass http://bk_web01;
more_set_headers 'Content-Type: text/plain';
}
if ($pass != 1) {
return 403;
}
}
Zgodnie z dokumentacją, nie zaleca się używania instrukcji
if
(zwłaszcza w kontekście lokalizacji), chociaż moim zdaniem, użycie takiej konstrukcji może być nieco bardziej elastyczne oraz bezpieczniejsze dzięki wykorzystaniu ww. modułów. Po drugie, sama dokumentacja wskazuje przypadki użycia, w których po prostu nie można uniknąć użycia tego warunku, na przykład jeśli trzeba przetestować zmienną, która nie ma równoważnej dyrektywy.
Planując budowanie list kontroli dostępu, rozważ kilka opcji, z których możesz skorzystać. NGINX dostarcza moduły ngx_http_access_module, ngx_http_geo_module, ngx_http_map_module lub ngx_http_auth_basic_module, które pozwalają na nadawanie dostępów i zabezpieczanie miejsc w aplikacji.
Zawsze powinieneś przetestować swoje reguły przed ich ostatecznym wdrożeniem:
-
sprawdź, na jakich fazach działają wykorzystywane dyrektywy
-
wykonaj kilka testowych żądań w celu potwierdzenia poprawnego działania mechanizmów zezwalających lub blokujących dostęp do chronionych zasobów Twojej aplikacji
-
wykonaj kilka testowych żądań w celu sprawdzenia i weryfikacji kodów odpowiedzi HTTP dla chronionych zasobów Twojej aplikacji
-
należy zminimalizować dostęp każdego użytkownika do krytycznych zasobów tylko do wymaganych adresów IP po uprzednim potwierdzeniu klienta (zwłaszcza dla IP spoza sieci klienta)
-
przed dodaniem adresu IP klienta zweryfikuj czy jest on faktycznym właścicielem adresu w bazie danych whois
-
regularnie poddawaj weryfikacji swoje reguły kontroli dostępu, adresy IP oraz dane do logowania, aby upewnić się, że są aktualne i nie mają słabych punktów