Samodzielny menedżer sekretów: porównanie rozwiązań
Wybierz najlepszy menedżer sekretów dla pojedynczego VPS. Porównujemy OpenBao, Infisical, SOPS z age oraz systemd credentials pod kątem kosztów utrzymania i niezawodności.
Czym różni się samodzielnie hostowany menedżer sekretów od menedżera haseł
Samodzielnie hostowany menedżer sekretów przekazuje dane uwierzytelniające procesom. Menedżer haseł przekazuje je ludziom. Wszystkie pozostałe różnice wynikają z tego jednego faktu. Menedżer haseł jest odblokowywany przez człowieka, który jest obecny i zwraca uwagę na proces. Menedżer sekretów musi dostarczyć aplikacji hasło do bazy danych o godzinie 03:00, gdy nikt nie czuwa.
Tryby awarii różnią się w istotny sposób. Zablokowany menedżer haseł to niedogodność: wystarczy ponownie wpisać hasło główne. Zablokowany (sealed) menedżer sekretów oznacza awarię: każda usługa, która zrestartuje się w tym czasie, uruchomi się bez danych uwierzytelniających i pozostanie niedostępna. Uruchomienie Vaultwarden jako własnego menedżera haseł dobrze rozwiązuje problem użytkownika. Nie rozwiązuje jednak problemu maszynowego i nigdy nie było do tego projektowane.
Realistyczne opcje dla pojedynczego serwera dzielą się na dwie grupy. OpenBao oraz Infisical to usługi: API, baza danych, TLS (transport layer security), etap logowania oraz proces, który trzeba utrzymywać w działaniu. SOPS z age, systemd credentials oraz Docker secrets to pliki: zaszyfrowane w spoczynku, odszyfrowywane przez coś, co już działa, bez konieczności monitorowania dodatkowych elementów.
Oto szczera odpowiedź na wstępie. Dla pojedynczej maszyny obsługiwanej przez jedną lub dwie osoby, opcje oparte na plikach są zazwyczaj właściwym wyborem. OpenBao, którego nikt nie odblokowuje poprawnie i w którym nikt nie rotuje kluczy, jest gorszy niż plik env z uprawnieniami 600, ponieważ dodaje ruchomy element i kopię zapasową, którą można błędnie skonfigurować, nie oferując w zamian żadnej rotacji, której nie można wykonać ręcznie.
Czy tryb 600 dla pliku env jest wystarczający?
Często tak. Chroni on przed odczytaniem hasła do bazy danych przez innego użytkownika w systemie. Uprawnienia plików w systemie Unix realizują to zadanie, zanim sieć zostanie uruchomiona.
sudo install -d -m 750 -o root -g myapp /etc/myapp
sudo install -m 640 -o root -g myapp /dev/null /etc/myapp/env
sudoedit /etc/myapp/envNależy sprawdzić to z obu stron:
sudo -u myapp cat /etc/myapp/env
sudo -u nobody cat /etc/myapp/envPierwsze polecenie wyświetla zawartość pliku. Drugie zwraca cat: /etc/myapp/env: Permission denied, ponieważ nobody nie należy do grupy myapp, a plik nie posiada uprawnień dla innych użytkowników. To cały model bezpieczeństwa i jest on skuteczny.
Wyciek następuje w kolejnym kroku. Jednostka systemd z EnvironmentFile= kopiuje te wartości do środowiska procesu, a środowisko procesu jest czytelne.
[Service]
User=myapp
EnvironmentFile=/etc/myapp/env
ExecStart=/usr/local/bin/myappsudo cat /proc/$(pgrep -n myapp)/environ | tr '\0' '\n'To polecenie wyświetla sekrety w postaci jawnej, ponieważ /proc/<pid>/environ jest czytelny dla użytkownika root oraz użytkownika, na którego koncie działa proces. Narzędzie do raportowania awarii, które dołącza środowisko do raportu, widzi to samo. Podobnie każde narzędzie działające na tym samym koncie, dlatego usuwanie sekretów z agentów AI zaczyna się od usunięcia ich ze środowiska. Należy połączyć ten plik z dedykowanym użytkownikiem serwisowym o niskich uprawnieniach, aby "użytkownik, na którego koncie działa proces" nie był użytkownikiem root.
SOPS z age: szyfrowane sekrety w repozytorium git
SOPS (secrets operations) szyfruje wartości w plikach YAML lub JSON, pozostawiając klucze w postaci jawnej. age to niewielkie narzędzie szyfrujące, które zapewnia jedną parę kluczy bez konieczności korzystania z serwera kluczy. Połączenie tych narzędzi pozwala na przechowywanie secrets.enc.yaml obok kodu, a git diff nadal wskazuje, które ustawienie uległo zmianie, nie ujawniając przy tym jego nowej wartości.
Narzędzie age znajduje się w pakietach Ubuntu 24.04. SOPS nie jest dostępne w repozytoriach, dlatego należy pobrać .deb ze strony wydań. Wersja 3.13.3 była aktualna w sierpniu 2026 roku.
sudo apt update && sudo apt install -y age
curl -LO https://github.com/getsops/sops/releases/download/v3.13.3/sops_3.13.3_amd64.deb
sudo apt install -y ./sops_3.13.3_amd64.deb
sops --versionWygeneruj parę kluczy. age-keygen zapisuje klucz prywatny do pliku i wyświetla klucz publiczny, dzięki czemu zobaczysz linię zaczynającą się od Public key: age1....
mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
chmod 600 ~/.config/sops/age/keys.txt
age-keygen -y ~/.config/sops/age/keys.txtUmieść klucz publiczny w .sops.yaml w katalogu głównym repozytorium, aby nie trzeba było pamiętać o podawaniu odbiorcy w wierszu poleceń.
creation_rules:
- age: age1s3cqcks5genc6ru8chl0hkkd04zmxvczsvdxq99ekffe4gmvjpzsedk23csops encrypt secrets.yaml > secrets.enc.yaml
sops decrypt secrets.enc.yamlReguła bez path_regex pasuje do wszystkiego, co jest pożądanym zachowaniem na początku. Jeśli później dodasz regułę, skonfiguruj ją tak, aby pasowała do pliku przekazywanego do sops, ponieważ reguły są sprawdzane względem ścieżki wejściowej, a nie pliku, do którego przekierowujesz wyjście.
Podczas wykonywania przekaż wartości do jednego procesu i żadnego innego:
sops exec-env secrets.enc.yaml './myapp'sops exec-env dokonuje deszyfracji w pamięci i ustawia wartości w środowisku procesu potomnego, dzięki czemu tekst jawny nie jest zapisywany na dysku. Ostrzeżenie dotyczące środowiska z poprzedniej sekcji nadal dotyczy tego procesu potomnego.
Dwie kwestie sprawiają użytkownikom najwięcej trudności. Błąd Failed to get the data key required to decrypt the SOPS file w systemd prawie zawsze oznacza, że SOPS szukał w niewłaściwym katalogu domowym, ponieważ jednostka nie dziedziczy zmiennej HOME. Ustaw ścieżkę jawnie za pomocą Environment=SOPS_AGE_KEY_FILE=/etc/sops/age.txt w jednostce. Ponadto edycja .sops.yaml nie powoduje ponownego szyfrowania istniejących danych: dodanie klucza publicznego współpracownika wpływa tylko na nowe pliki, dlatego należy uruchomić sops updatekeys secrets.enc.yaml dla każdego istniejącego pliku. Jeśli konfiguracja jest już obsługiwana przez Ansible, szyfrowanie tych samych wartości za pomocą Ansible Vault pozwala osiągnąć ten sam cel bez wprowadzania dodatkowego narzędzia.
Poświadczenia systemd: sekrety, które nie trafiają do środowiska
Ubuntu 24.04 zawiera systemd 255, więc instalacja nie jest wymagana. systemd-creds szyfruje sekret na hoście, a systemd odszyfrowuje go do prywatnego katalogu, który może odczytać tylko jedna usługa.
sudo systemd-creds setup
sudo install -d -m 700 /etc/myapp
echo -n 'hunter2' | sudo systemd-creds encrypt --name=db_password - /etc/myapp/db_password.cred[Service]
User=myapp
LoadCredentialEncrypted=db_password:/etc/myapp/db_password.cred
ExecStart=/usr/local/bin/myappUsługa odczytuje wartość z pliku o nazwie db_password wewnątrz katalogu wskazanego przez $CREDENTIALS_DIRECTORY. Wartość nie znajduje się w zmiennych środowiskowych, więc /proc/<pid>/environ nie wyświetla żadnych przydatnych informacji, a tekst jawny nigdy nie trafia do głównego systemu plików.
Przed wskazaniem jednostki na plik należy zweryfikować, czy ulega on poprawnemu odszyfrowaniu:
sudo systemd-creds decrypt /etc/myapp/db_password.cred -Należy wiedzieć, który klucz został użyty do szyfrowania, ponieważ decyduje to o użyteczności kopii zapasowej. Domyślne --with-key=auto wykorzystuje układ TPM2 (trusted platform module version 2), jeśli jest on obecny i dostępny, a w przeciwnym razie klucz hosta. Większość instancji VPS nie posiada układu TPM2.
systemd-analyze has-tpm2no oznacza, że użyto klucza hosta, który znajduje się w /var/lib/systemd/credential.secret i jest dostępny tylko dla użytkownika root. Przywrócenie db_password.cred na nowy serwer VPS bez tego pliku uniemożliwi odszyfrowanie danych. Należy skopiować credential.secret do tej samej kopii zapasowej lub przechowywać tekst jawny w dostępnym miejscu.
Docker secrets: pliki w /run/secrets
Compose odczytuje plik z hosta i montuje go w kontenerze w /run/secrets/<name>.
services:
app:
image: myapp:latest
environment:
DB_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./db_password.txtdocker compose exec app cat /run/secrets/db_password
docker compose exec app env | grep -i passwordPierwsze polecenie wyświetla sekret. Drugie wyświetla tylko DB_PASSWORD_FILE=/run/secrets/db_password, co jest kluczowe: wartość nigdy nie trafia do zmiennych środowiskowych kontenera, więc nie pojawia się w danych wyjściowych docker inspect. Wiele oficjalnych obrazów jest już przygotowanych na ten schemat, a obraz Postgres odczytuje POSTGRES_PASSWORD_FILE dokładnie w ten sposób.
Należy mieć świadomość charakteru tego rozwiązania. Poza trybem Swarm nie występuje szyfrowanie na żadnej warstwie: ./db_password.txt to plik tekstowy na hoście, a jego jedyną ochroną są uprawnienia i właściciel. Należy ustawić je samodzielnie, ponieważ Compose bez ostrzeżenia zamontuje plik dostępny dla wszystkich. Szersze zestawienie wad i zalet w porównaniu do prostego skrótu env_file znajduje się w przewodniku po plikach env i sekretach w Compose.
Rzeczywiste koszty utrzymania OpenBao i Vault
OpenBao to fork HashiCorp Vault rozwijany przez Linux Foundation, powstały po zmianie licencji Vault na Business Source License w 2023 roku. OpenBao pozostaje na licencji MPL 2.0 (Mozilla Public License). Wersja 2.6.2 była aktualna w sierpniu 2026 roku. Prawie wszystkie poniższe informacje dotyczą również Vault, ponieważ fork zachował ten sam zestaw poleceń.
docker pull docker.io/openbao/openbaoPakiety dla Debian i Ubuntu znajdują się na stronie pobierania OpenBao, jeśli preferowane jest zarządzanie aktualizacjami przez apt. Serwer wymaga pliku konfiguracyjnego zawierającego definicję listener oraz backendu pamięci masowej:
listener "tcp" {
address = "127.0.0.1:8200"
tls_cert_file = "/path/to/full-chain.pem"
tls_key_file = "/path/to/private-key.pem"
}
storage "raft" {
path = "/path/to/raft/data"
node_id = "raft_node_1"
}Następnie należy uruchomić usługę:
bao operator initDomyślnie proces ten dzieli klucz główny (root key) na 5 udziałów i wymaga podania 3 z nich do odblokowania (unseal), co określają flagi -key-shares oraz -key-threshold. Udziały oraz początkowy token root są wyświetlane tylko raz i nie są powtarzane.
Teraz część, którą pomija większość porównań. Zrestartowany serwer to zablokowany serwer. OpenBao przechowuje klucz główny wyłącznie w pamięci operacyjnej, więc po restarcie nie może odszyfrować własnej pamięci masowej, dopóki ktoś nie dostarczy wymaganego progu udziałów. Aktualizacja jądra lub zakończenie procesu przez OOM Killer kończy się zatem zablokowaniem serwera i brakiem możliwości logowania dla aplikacji.
Na jednoosobowym VPS podział Shamira nie zapewnia dodatkowej ochrony, ponieważ wszystkie pięć udziałów trafia do tego samego menedżera haseł należącego do tej samej osoby. Funkcja auto unseal przenosi klucz do zaufanego urządzenia lub usługi, co w dużej chmurze oznacza zarządzaną usługę kluczy, a na VPS zazwyczaj oznacza plik klucza znajdujący się na tym samym dysku co chronione dane. Jest to realne obniżenie poziomu bezpieczeństwa w zamian za serwer, który uruchamia się automatycznie po restarcie. Należy dokonać tego wyboru świadomie i odnotować przyjętą strategię.
Infisical: interfejs użytkownika, baza danych i główny klucz, który pozostaje w Twoich rękach
Infisical to platforma do zarządzania sekretami z interfejsem webowym, obsługą projektów, środowisk oraz kontrolą dostępu dla poszczególnych użytkowników. Samodzielna instalacja przy użyciu Compose jest krótka:
curl -o docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
curl -o .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
docker compose -f docker-compose.prod.yml up -dPrzed wykonaniem ostatniego polecenia należy edytować .env. Dwie wartości muszą być unikalne, a jedna z nich nie może ulec zmianie po uruchomieniu:
openssl rand -hex 16
openssl rand -base64 32Pierwszą z nich jest ENCRYPTION_KEY, 16-bajtowy ciąg szesnastkowy. Jest to klucz, którym szyfrowane są sekrety wewnątrz PostgreSQL, więc jego utrata sprawi, że poprawna kopia zapasowa bazy danych stanie się bezużytecznym zbiorem szyfrogramów, a zmiana na działającej instancji uniemożliwi odszyfrowanie istniejących sekretów. Drugą wartością jest AUTH_SECRET, 32-bajtowy ciąg zakodowany w base64, używany do obsługi sesji. SITE_URL musi być bezwzględnym adresem URL, pod którym usługa będzie faktycznie dostępna, wraz z protokołem, w przeciwnym razie przekierowania po logowaniu przestaną działać.
Infisical sprawdza się lepiej niż OpenBao w sytuacjach, gdy priorytetem jest praca zespołowa: interfejs webowy dla małego zespołu oraz separacja środowisk, zamiast automatycznie wygasających poświadczeń do baz danych. Kosztem jest konieczność utrzymywania PostgreSQL, Redis oraz certyfikatu TLS, które od teraz musisz samodzielnie aktualizować i archiwizować.
Co dzieje się, gdy usługa sekretów nie działa, a aplikacja restartuje się
To pytanie rozstrzyga, czy usługa sekretów powinna znajdować się na pojedynczej maszynie. Pliki są możliwe do odczytania przed uruchomieniem sieci. Usługa nie jest.
Zrestartuj maszynę, a aplikacja oraz OpenBao uruchomią się w tym samym momencie. Aplikacja żąda hasła do bazy danych, OpenBao jest wciąż zablokowane (sealed), żądanie kończy się niepowodzeniem, a systemd restartuje aplikację w pętli, dopóki człowiek nie wprowadzi udziałów odblokowujących (unseal shares). Nic nie jest uszkodzone. Nic też nie działa.
Istnieją dwa uczciwe sposoby radzenia sobie z tym problemem. Uporządkuj jednostki i pozwól aplikacji na ponowienie próby: After= usługę sekretów, dodaj Restart=on-failure oraz RestartSec= wystarczająco długi, aby nie przeciążać API. Alternatywnie pobierz sekret w czasie wdrażania zamiast podczas startu systemu: zapisz sekret w pliku o uprawnieniach 600 lub jako systemd credential, dzięki czemu działający system zależy od pliku, a nie od API.
Wygasanie tokenów to ten sam problem w zwolnionym tempie. Tokeny i dzierżawy OpenBao mają określony czas życia (TTL), więc długo działający proces, który nigdy nie odnawia dostępu, traci go w momencie niezwiązanym z żadnym wdrożeniem. Ta awaria jest myląca właśnie dlatego, że danego dnia nic się nie zmieniło.
Tworzenie kopii zapasowej samego magazynu
Każda z wymienionych opcji posiada klucz, a kopia zapasowa bez tego klucza jest bezużyteczna. Należy zapisać lokalizację, w której przechowywany jest dany klucz.
W przypadku pliku env, to sam plik stanowi sekret, dlatego kopia zapasowa musi być zaszyfrowana. Dla SOPS zaszyfrowany plik może znajdować się w dowolnym publicznym miejscu, natomiast klucz prywatny age w ~/.config/sops/age/keys.txt jest elementem, którego nie wolno utracić. W przypadku poświadczeń systemd należy wykonać kopię zapasową /var/lib/systemd/credential.secret wraz z plikami .cred. Dla Infisical należy wykonać zrzut bazy PostgreSQL i przechowywać ENCRYPTION_KEY w oddzielnym miejscu.
OpenBao z silnikiem pamięci masowej raft wykonuje własne migawki:
bao operator raft snapshot save backup.snap
bao operator raft snapshot restore backup.snapMigawka zawiera zaszyfrowane dane, więc przywrócenie jej na nowym serwerze nadal wymaga udziałów odblokowujących (unseal shares) z bao operator init. Zadanie uruchamiane co noc, które kopiuje migawki do pamięci obiektowej, podczas gdy udziały nie są nigdzie przechowywane, nie stanowi żadnej kopii zapasowej. Przed poleganiem na kopii zapasowej należy przetestować proces przywracania na tymczasowym serwerze VPS.
Rejestrowanie audytowe: kto odczytał dany sekret
Pliki nie zapewniają ścieżki audytu. Tryb dostępu i właściciel określają jedynie, kto mógł odczytać sekret. Nigdy nie informują o tym, kto faktycznie to zrobił. auditd z obserwacją ścieżki jest najbliższym zamiennikiem, jednak raportuje on jedynie otwarcie pliku, a nie to, która wartość została użyta.
OpenBao rejestruje każde żądanie w urządzeniu audytowym, które zostanie jawnie włączone:
bao audit enable file file_path=/var/log/openbao_audit.logDwa fakty dotyczące tego dziennika zmieniają sposób zarządzania serwerem. Większość ciągów znaków w żądaniach i odpowiedziach jest haszowana przy użyciu HMAC-SHA256 wraz z solą, dzięki czemu można dopasować znaną wartość do wpisu w dzienniku bez przechowywania w nim tekstu jawnego. Liczby całkowite oraz wartości logiczne są zapisywane w postaci jawnej, więc sekret liczbowy nie jest chroniony przez to haszowanie.
Kolejną kwestią operacyjną jest pułapka: OpenBao nie odpowiada na żądania, jeśli żadne włączone urządzenie audytowe nie może ich zarejestrować, a urządzenie, które ulegnie awarii w sposób blokujący, powoduje zawieszenie żądań do czasu usunięcia usterki. Pełny dysk w /var/log powoduje celowe wyłączenie API sekretów. Należy zapewnić dziennikowi audytu własną przestrzeń oraz regułę logrotate już pierwszego dnia, a nie dopiero po pierwszej awarii.
Który menedżer sekretów typu self-hosted wybrać?
Podlicz maszyny oraz użytkowników, a następnie dokonaj wyboru.
- Jedna maszyna, jeden użytkownik: plik env z uprawnieniami 600, należący do root i odczytywany przez użytkownika usługi. Użyj systemd credentials, jeśli wartość ma być wykluczona ze środowiska procesu.
- Jedna maszyna, od dwóch do pięciu użytkowników, konfiguracja już w git: SOPS z age. Każda osoba otrzymuje parę kluczy, a
.sops.yamlzawiera listę wszystkich kluczy publicznych uprawnionych do deszyfracji. - Kilka maszyn, jedno repozytorium konfiguracji, brak potrzeby stosowania poświadczeń z datą ważności: nadal SOPS z age, z jednym kluczem odbiorcy na hosta, dzięki czemu skradziony klucz hosta deszyfruje tylko pliki tego konkretnego hosta.
- Kilka maszyn i kilka zespołów, które faktycznie potrzebują poświadczeń bazodanowych z określonym czasem życia oraz ścieżki audytu: OpenBao; należy uwzględnić w budżecie godzinę pracy operatora miesięcznie na procedury odblokowywania (unsealing) i testy przywracania danych.
Zasada leżąca u podstaw wszystkich czterech punktów jest taka sama. Uruchamiaj najprostsze rozwiązanie, które spełnia jasno określony wymóg, ponieważ niedziałający menedżer sekretów jest nieodróżnialny od pustego menedżera sekretów.
FAQ
Czy warto wdrażać własny menedżer sekretów na pojedynczym VPS?
Zazwyczaj nie, jeśli mowa o usługach takich jak OpenBao czy Infisical. Na jednej maszynie obsługiwanej przez jedną lub dwie osoby, plik env z uprawnieniami 600 lub zaszyfrowane poświadczenia systemd zapewniają taką samą ochronę przed innym użytkownikiem lokalnym, nie wymagając przy tym procesu odblokowywania (unseal) ani utrzymywania dodatkowej usługi do aktualizacji. Usługa zarządzania sekretami staje się opłacalna dopiero przy większej liczbie maszyn i użytkowników lub w przypadku realnej potrzeby stosowania poświadczeń wygasających automatycznie bez ręcznej rotacji.
Jaka jest różnica między menedżerem haseł a menedżerem sekretów?
Menedżer haseł przechowuje poświadczenia wpisywane przez człowieka, który odblokowuje je w swojej obecności. Menedżer sekretów dostarcza poświadczenia procesom, więc musi działać o godzinie 03:00 bez nadzoru. Konsekwencją tego jest kluczowa różnica: zablokowany menedżer haseł wymaga podania hasła głównego, natomiast zablokowany (sealed) menedżer sekretów powoduje zatrzymanie każdej usługi, która próbuje się zrestartować w tym stanie.
Co stanie się z moimi aplikacjami, jeśli OpenBao pozostanie zablokowany po restarcie?
Aplikacje nie będą mogły pobrać swoich sekretów, co spowoduje błędy uruchomienia, a systemd będzie je restartował w pętli, dopóki ktoś nie dostarczy wymaganego progu odblokowania (domyślnie 3 z 5 udziałów). OpenBao przechowuje klucz główny wyłącznie w pamięci RAM, więc każdy restart ponownie go blokuje. Należy albo włączyć funkcję auto unseal, akceptując fakt, że na pojedynczym VPS klucz odblokowujący znajduje się na tym samym dysku co dane, albo renderować sekrety do pliku w momencie wdrażania, aby proces uruchamiania systemu nie zależał od API.
Czy mogę umieszczać pliki zaszyfrowane przez SOPS w publicznym repozytorium?
Wartości są zaszyfrowane, więc są bezpieczne dla każdego, kto nie posiada klucza prywatnego age. Klucze nie są szyfrowane: czytelnik może zobaczyć, że posiadasz STRIPE_SECRET_KEY oraz SMTP_PASSWORD, a także jak często każdy z nich ulega zmianie. Te metadane są akceptowalne dla większości projektów, ale nie dla wszystkich. Przechowuj klucz prywatny age poza repozytorium i uruchamiaj sops updatekeys dla każdego istniejącego pliku za każdym razem, gdy dodajesz lub usuwasz odbiorcę.