Przechowywanie haseł i sekretów w repozytorium? Pewnie, ale tylko z Ansible Vault

avlab.pl 3 tygodni temu
Zdjęcie: Przechowywanie haseł i sekretów w repozytorium? Pewnie, ale tylko z Ansible Vault


Ansible - krok po kroku

Pewne dobre praktyki dotyczące bezpieczeństwa jasno wskazują, iż w repozytorium naszych projektów nie powinny znajdować się hasła, klucze API do różnych usług czy wreszcie konkretne ustawienia pod dane środowisko. Jest to oczywiście słuszne podejście, bo w sytuacji, gdy atakujący uzyska w wybrany sposób dostęp do zawartości repozytorium, będzie mógł rozszerzyć skalę ataku. To zagrożenie jest zdecydowanie bardziej realne w przypadku projektów open-source z publicznie dostępnymi repozytoriami Git (hostowanymi dla przykładu w serwisach GitHub czy GitLab.com), natomiast przedstawione podejście powinno być stosowane również w wewnętrznych projektach.

Z drugiej strony doświadczenie pokazuje, iż jednak dostęp do sekretów czy haseł bezpośrednio w repozytorium jest przydatny. W typowym scenariuszu opracowujemy aplikację, która działa już na trzech osobnych środowiskach – standardowo develop, test/staging i produkcyjnym (pomijamy lokalne środowiska programistów). Okazuje się, iż nowa funkcjonalność wymaga dodatkowej zmiennej pobieranej właśnie z konfiguracji w pliku. Aby uniknąć problemów z działaniem spowodowanych brakiem dodania wspomnianej zmiennej do plików, programiści mogliby samodzielnie edytować pliki konfiguracyjne, a te z wykorzystaniem CI/CD byłyby przesyłane na konkretne środowisko podczas zautomatyzowanego wdrożenia.

Zresztą edycja plików z konfiguracjami jest prosta, gdy aplikacja działa na jednym serwerze. jeżeli maszyn jest więcej, a konfiguracja nie została umieszczona w zasobie współdzielonym (NFS czy bardziej zaawansowany GlusterFS), to konieczna okaże się edycja na każdym jednym serwerze, co tylko zwiększy ryzyko pomyłki. Dzięki CI/CD będzie możliwość przesłania odpowiednich plików z jednego miejsca. Dodatkowo konfiguracja przechowywana w repozytorium powinna stanowić „źródło prawdy” – zmiany wprowadzamy wyłącznie w repozytorium, bo te dodane na serwerach zostaną nadpisane w trakcie wdrożenia (przyjęła się tutaj nazwa „deploy”).

Sprawny dostęp do plików konfiguracyjnych będzie pomocny też w sytuacjach „awaryjnych”. Programiści nie będą zmuszeni do połączenia z serwerem aplikacyjnym i manualnego sprawdzenia konfiguracji, aby chociażby odnaleźć dostęp do bazy danych. O ile poświadczenia do usług najlepiej udostępniać poprzez narzędzia takie jak passbolt, to mimo wszystko możliwość znalezienia ich w repozytorium będzie wartościowa.

Ansible Vault

Jawne przechowywanie konfiguracji z sekretami, kluczami API, dostępami do usług itd. w repozytorium nie jest zalecane. Rozwiązaniem tego problemu jest wykorzystanie Ansible Vault. To dedykowane narzędzie działające z poziomu CLI, które szyfruje zawartość plików. Może działać w pełni niezależnie od playbooków Ansible, więc równie dobrze sprawdzi się do szyfrowania różnych prywatnych zasobów. Użycie jest wręcz trywialne i nie powinno nikomu sprawiać trudności. Jako algorytm szyfrowania stosuje bardzo mocny AES256 – więc od strony bezpieczeństwa wszystkie kwestie są zaadresowane, a od użytkownika wymaga się jedynie zastosowania w miarę złożonego hasła i zadbanie o jego bezpieczne przechowywanie (oczywiście nie w repozytorium).

Ansible Vault jest częścią Ansible, więc do jego użycia potrzebujemy zainstalować pakiet ansible – w naszym systemie operacyjnym czy też w WSL jeżeli pracujemy w systemie Windows. Z kolei do praktycznego wykorzystania potrzebne będą jedynie cztery polecenia:

ansible-vault encrypt plik ansible-vault edit plik ansible-vault view plik ansible-vault decrypt plik

Służą one odpowiednio do zaszyfrowania pliku, jego edycji, podglądu i wreszcie odszyfrowania. Jak widać nie jest to szczególnie skomplikowane rozwiązanie.

Bazowe CI/CD do wdrożeń i konfiguracja aplikacji

Praktyczne użycie w CI/CD również nie wymaga złożonych przygotowań, choć oczywiście wszystko zależy od obecnego stanu naszych „potoków”. jeżeli jednak od początku stawialiśmy na prostotę i przejrzystość, to dodanie obsługi Ansible Vault nie będzie problematyczne. Niezależnie od konkretnego narzędzia (GitLab CI/CD, Jenkins, GitHub Actions czy inne) kroki będą identyczne – różnica dotyczy wyłącznie obsługi zmiennej z hasłem/nazwą pliku.

Dla przykładu zaprezentujemy job GitLab CI/CD, który odpowiada za wdrożenie zbudowanego we wcześniejszym stage obrazu Docker zawierającego system CMS Drupal na serwer aplikacyjny.

. deploy: &deploy image: alpine before_script: - apk --update add openssh-client - eval $(ssh-agent -s) - ssh-add ~/.ssh/config script: - ssh $SSH_HOSTNAME "mkdir -p $SSH_PATH" - scp .gitlab/docker-compose.yml $SSH_HOSTNAME:$SSH_PATH - scp .env $SSH_HOSTNAME:$SSH_PATH - ssh $SSH_HOSTNAME "docker network create panel || true" - ssh $SSH_HOSTNAME "docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY" - ssh $SSH_HOSTNAME "cd $SSH_PATH && docker compose pull --policy missing && docker compose up -d --force-recreate" - ssh $SSH_HOSTNAME "docker system prune -a -f"

Używamy więc obrazu Docker alpine, w którym instalujemy klienta SSH, ładujemy klucz prywatny do połączenia z serwerem i wyłączamy weryfikację kluczy serwera na podstawie known_hosts – znane każdemu pytanie Are you sure you want to continue connecting (yes/no/[fingerprint])? przy pierwszym połączeniu z daną maszyną. W trakcie wykonywania CI/CD nie możemy wpisać yes, a dodanie reguły StrictHostKeyChecking no stanowi obejście tego problemu.

Dalej tworzymy ścieżkę dla plików aplikacji (tutaj akurat jest to katalog przeznaczony na docker-compose.yml, plik .env z wersją obrazu i katalog data, który w docker-compose.yml mapujemy na /opt/drupal/web/sites/default w kontenerze), wysyłamy wymagane pliki, próbujemy utworzyć Docker network dla naszego kontenera, autoryzujemy się w GitLab container registry i ostatecznie uruchamiamy nową wersję kontenera, po czym usuwamy „śmieci” (głównie poprzednia wersja obrazu).

Polecam zainteresować się tematem CI/CD, a szczególnie wdrożeniami wykorzystującymi konteneryzację, bo współcześnie jest to jeden z wiodących trendów. Coraz rzadziej spotyka się, aby aplikacja była uruchomiona „natywnie”, zamiast z użyciem Docker czy innych platform.

Pod względami wydajności sugerowałbym jednak korzystać ze standardowych serwisów dla serwerów baz danych, brokerów wiadomości czy kolejek. Warto wiedzieć, jak uruchomić bazę PostgreSQL w kontenerze, ale dla środowisk produkcyjnych wciąż lepiej sprawdza się podejście bardziej tradycyjne.

Na ten moment konfiguracja aplikacji, co dla Drupal oznacza plik web/sites/default/settings.php, jest przechowywana na serwerze. W podstawowej wersji ogranicza się ona do zawartości analogicznej do tej przedstawionej poniżej.

<?php $databases = []; $settings['hash_salt'] = 'qiax36xQHjKowW5jMEhdKOH9HkOAFPwdjg4-xEKrultxIKnvFl7ogzDQxEaEs-jCClfEUoM0OQ'; $settings['update_free_access'] = FALSE; $settings['container_yamls'][] = $app_root . '/' . $site_path . '/services.yml'; $settings['file_scan_ignore_directories'] = [ 'node_modules', 'bower_components', ]; $settings['entity_update_batch_size'] = 50; $settings['entity_update_backup'] = TRUE; $settings['migrate_node_migrate_type_classic'] = FALSE; $databases['default']['default'] = array ( 'database' => 'panel_avlab', 'username' => 'avlab', 'password' => 'avlab12', 'prefix' => '', 'host' => 'host.docker.internal', 'port' => '5432', 'driver' => 'pgsql', 'namespace' => 'Drupal\\pgsql\\Driver\\Database\\pgsql', 'autoload' => 'core/modules/pgsql/src/Driver/Database/pgsql/', ); $settings['config_sync_directory'] = 'sites/default/files/config_ZjrSfkAvOdwJYZDVtjTHpQf2yoBaVmpxyJKJdQDNsom-44emwIO-noFq4EK9H3Z4VxiYHfxS9Q/sync';

Warto zwrócić uwagę na host.docker.internal jako host bazy danych. Ze względów bezpieczeństwa nie powinno się bez istotnego powodu wystawiać portów „wrażliwych” usług. Z tego powodu w naszym przypadku serwer bazy PostgreSQL nasłuchuje na adresach localhost i interfejsu docker0 (domyślnie 172.17.0.1), więc możliwy jest wyłącznie dostęp lokalny i z kontenerów (z wiadomych powodów 127.0.0.1 dla kontenera jest adresem localhost tego kontenera, a nie naszego hosta). jeżeli baza danych działa wyłącznie w zakresie sieci wewnętrznej, to oczywiście nie jest to aż tak istotne, aczkolwiek raczej powinno się minimalizować możliwości dostępu.

Korzystanie z Ansible Vault

Zakładamy, iż to konfiguracja przeznaczona dla środowiska produkcyjnego. W celu zachowania porządku tworzymy więc osobny katalog przeznaczony dla plików tego środowiska. W moim przypadku definicje stage’y CI/CD znajdują się w katalogu .gitlab, więc tworzę katalog .gitlab/produkcja i przenoszę do niego pokazany wyżej plik settings.php.

Oryginalna zawartość pliku settings.php

Struktura została przygotowana, więc możemy zaszyfrować plik. Dwukrotnie zostaniemy poproszeni o podanie hasła.

Szyfrowanie pliku dzięki Ansible Vault

Możemy wyświetlić zaszyfrowany przed chwilą plik, aby potwierdzić, iż jego treść stała się niejawna.

Zaszyfrowana zawartość

Integracja z CI/CD

Zmiany możemy przesłać do zdalnego repozytorium (w nim powinna znajdować się wyłącznie wersja zaszyfrowana). Oprócz tego dodamy obsługę do CI/CD – odszyfrowanie konkretnego pliku w zależności od środowiska i jego przesłanie na docelowy host. W przypadku GitLab CI/CD jest to wyjątkowo proste.

Dodanie obsługi Ansible Vault do GitLab CI/CD

Zmiany polegają na dodaniu instalacji paczki ansible, zapisaniu do pliku .vault_pass hasła użytego do zaszyfrowania danych, odszyfrowania wskazanego pliku konfiguracyjnego i ostatecznie jego przesłania na docelowy serwer do poprawnej lokalizacji. Jak widać, hasło musimy zapisać w zmiennej PROD_VAULT_PASS – tutaj dla environment nazwanego production. W GitLab zmienne można jawnie definiować w sekcji variables wybranych jobów (wtedy są widoczne dla wszystkich członków repozytorium) albo też poprzez Settings -> CI/CD -> Variables, gdzie możemy je „ukryć”, czyli oznaczyć jako Masked and hidden, dzięki czemu próba ich odczytania w jobie zakończy się wyświetleniem [MASKED], a na stronie Variables nie będzie możliwości ich podglądu. To skuteczne zabezpieczenie przed nieautoryzowanym pobraniem hasła do Ansible Vault.

Dodane zmienne w GitLab

Rzecz jasna jest to przykładowe podejście do obsługi Ansible Vault w CI/CD i niekoniecznie sprawdzi się w każdej sytuacji. Niemniej jednak zmiany zostały zaimplementowane w sposób czytelny i zrozumiały dla wszystkich – nie będzie potrzeby przygotowania dziesiątek opisów w dokumentacji.

Wszystko działa zgodnie z oczekiwaniami.

Wykonanie joba z deploy'em

Na serwer aplikacyjny skutecznie została przesłana odszyfrowana konfiguracja.

Katalog aplikacyjny z jawną treścią konfiguracji

Istotna uwaga dotyczy executora dla gitlab-runner. W naszym ustawieniu ma on opcję docker, więc wszystkie joby wykonują się w kontenerach, które są natychmiastowo usuwane po ich zakończeniu (w zdecydowanej większości scenariuszy jest to najlepsze rozwiązanie). Nie ma więc realnej możliwości, aby odszyfrowane pliki stały się dostępne dla innych, nieautoryzowanych osób (wykluczając jednak niepoprawne konfiguracje CI/CD, takie jak niepotrzebny cache zdefiniowany dla joba zawierającego instrukcje wdrożenia i obsługę Ansible Vault). jeżeli jednak polecenia wykonywane są bezpośrednio w powłoce na danym hoście, to koniecznie należy zadbać o usunięcie pozostawionych artefaktów.

Jeśli pojawi się potrzeba modyfikacji zmiennych, to najszybszym sposobem będzie skorzystanie z ansible-vault edit. Po podaniu hasła zostanie otworzony domyślny edytor z odszyfrowaną, tymczasową zawartością. Zapisane zmiany znajdą się w zaszyfrowanym pliku. Niestety śledzenie modyfikacji przy szyfrowanych danych jest dość utrudnione – jak widać poniżej dodanie jednej linii do settings.php spowodowało zmiany całej zaszyfrowanej wersji.

Podgląd różnic pomiędzy commitami w repozytorium

Z drugiej strony wygoda związana z możliwością bezpiecznego przechowywania konfiguracji powinno rekompensować tę niedogodność.

Podsumowanie

Ansible Vault jest prostym narzędziem, dzięki którego niewielkim nakładem pracy możemy sensownie zabezpieczyć dane hostowane i współdzielone w repozytoriach naszych projektów. Wyjątkowa łatwość użycia (również w CI/CD) sprawia, iż to rozwiązanie jest dostępne dla wszystkich i na pewno nie wymaga przysłowiowego „doktoryzowania się” z obsługi. Przedstawione narzędzie powinno stać się integralną częścią każdego większego środowiska projektowego, bo stanowi zwyczajne wsparcie w w tej chwili stosowanych strategiach wdrożeń. Ansible Vault z powodzeniem może być wykorzystywane także dla zastosowań prywatnych, np. w celu przesyłania danych do tzw. dysków chmurowych.

Idź do oryginalnego materiału