Samodzielny menedżer sekretów: porównanie rozwiązań
Porównanie OpenBao, Infisical, SOPS z age oraz systemd credentials. Analiza kosztów utrzymania i wymagań dla pojedynczego VPS. Wybierz optymalne narzędzie do zarządzania sekretami.
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 inne 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 ludzki. Nie rozwiązuje jednak problemu maszynowego i nigdy nie było do tego projektowane. Jeśli używasz go równolegle, elementami wymagającymi zabezpieczenia są token administratora oraz plik kopii zapasowej, a nie zawartość skarbca, którą klient szyfruje samodzielnie. Wzmocnienie zabezpieczeń Vaultwarden omawia oba te aspekty.
Realistyczne opcje dla pojedynczego serwera dzielą się na dwie grupy. OpenBao oraz Infisical to usługi: wymagają API, bazy danych, TLS (transport layer security), etapu logowania oraz procesu, który należy utrzymywać w stanie aktywnym. SOPS z użyciem age, systemd credentials oraz Docker secrets to pliki: szyfrowane w spoczynku, odszyfrowywane przez działający już proces, niewymagające dodatkowego monitorowania.
Oto szczera odpowiedź na wstępie. W przypadku 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 gorszym rozwiązaniem niż plik env z uprawnieniami 600. Dodaje on ruchomy element i konieczność tworzenia kopii zapasowej, co łatwo zepsuć, a nie zapewnia żadnej automatyzacji 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 jeszcze 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 (world bits). To cały model bezpieczeństwa i jest on w pełni skuteczny.
Wyciek następuje w kolejnym kroku. Jednostka systemd z dyrektywą EnvironmentFile= kopiuje te wartości do środowiska procesu, a środowisko procesu jest możliwe do odczytania.
[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ż plik /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 inne narzędzie działające na tym samym koncie, dlatego utrzymywanie sekretów poza agentami AI zaczyna się od usunięcia ich ze środowiska. Należy połączyć ten plik z dedykowanym użytkownikiem usługi o niskich uprawnieniach, aby "użytkownik, na którego koncie działa proces" nie był kontem 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, dzięki czemu 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ępny w repozytoriach, dlatego należy pobrać .deb ze strony wydań. W sierpniu 2026 roku aktualną wersją była 3.13.3.
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. Polecenie 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 pliku .sops.yaml w głównym katalogu repozytorium, aby nie musieć każdorazowo podawać 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 pracy. Jeśli w przyszłości 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 przekierowywane jest wyjście.
Podczas uruchamiania 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.
Dwa problemy 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. Należy jawnie ustawić ścieżkę za pomocą Environment=SOPS_AGE_KEY_FILE=/etc/sops/age.txt w pliku jednostki. Ponadto edycja pliku .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
System Ubuntu 24.04 zawiera systemd 255, więc instalacja nie jest wymagana. Narzędzie systemd-creds szyfruje sekret na hoście, a systemd odszyfrowuje go do prywatnego katalogu, który może odczytać tylko jedna konkretna 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ść ta nie znajduje się w zmiennych środowiskowych, więc /proc/<pid>/environ nie wyświetla żadnych przydatnych informacji, a tekst jawny nigdy nie zapisuje się w głównym systemie plików.
Przed wskazaniem jednostki na plik należy zweryfikować, czy poprawnie się on odszyfrowuje:
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 ustawienie --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-tpm2Oznaczenie no 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 spowoduje, że odszyfrowanie stanie się niemożliwe. 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 obsługuje już ten format, 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 jest plikiem tekstowym na hoście, a jego jedyną ochronę stanowią uprawnienia i właściciel. Należy skonfigurować je samodzielnie, ponieważ Compose bez ostrzeżenia zamontuje plik dostępny dla wszystkich użytkowników. Szersze zestawienie wad i zalet w porównaniu z prostym skrótem 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 interfejs 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 listener oraz backend 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ć go jednorazowo:
bao operator initDomyślnie dzieli to klucz główny na 5 udziałów i wymaga 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 nigdy więcej.
Teraz część, którą pomija większość porównań. Zrestartowany serwer to zablokowany serwer. OpenBao przechowuje klucz główny wyłącznie w pamięci RAM, 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 zablokowanym serwerem i aplikacjami, które nie mogą się zalogować.
Na jednoosobowym VPS podział Shamira nie chroni przed niczym, 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, wymienione na serwer, który uruchamia się samoczynnie po restarcie. Należy dokonać tego wyboru świadomie i odnotować przyjętą metodę.
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 WWW, 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 edytuj .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 sekrety są szyfrowane wewnątrz PostgreSQL, więc jego utrata sprawia, że poprawna kopia zapasowa bazy danych staje się bezużytecznym zbiorem szyfrogramów, a zmiana na działającej instancji uniemożliwia odszyfrowanie istniejących sekretów. Drugą wartością jest AUTH_SECRET, 32-bajtowy ciąg w formacie 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, gdy rzeczywistą potrzebą jest praca zespołowa: interfejs WWW dla małego zespołu oraz separacja środowisk, zamiast poświadczeń bazy danych wygasających automatycznie. Kosztem jest konieczność utrzymywania PostgreSQL, Redis oraz certyfikatu TLS, które od teraz musisz samodzielnie aktualizować i archiwizować.
Co się dzieje, 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.
Po zrestartowaniu maszyny aplikacja oraz OpenBao startują w tym samym momencie. Aplikacja żąda hasła do bazy danych, OpenBao jest nadal 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 rozwiązania tego problemu. Należy uporządkować jednostki i pozwolić aplikacji na ponowienie próby: After= usługa sekretów, plus Restart=on-failure oraz RestartSec= wystarczająco długi, aby nie przeciążać API. Alternatywnie, należy pobrać sekret w czasie wdrażania zamiast podczas startu: wygenerować sekret do pliku o uprawnieniach 600 lub użyć systemd credential, dzięki czemu działający system zależy od pliku, a nie od API.
Wygaśnięcie tokena to ten sam problem w wolniejszym 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 magazynu
Każda z poniższych opcji wymaga klucza; kopia zapasowa bez niego jest bezużyteczna. Należy zanotować lokalizację posiadanego klucza.
W przypadku pliku env, sam plik stanowi sekret, więc 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 systemd credentials 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 oddzielnej lokalizacji.
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 typu nightly, które kopiuje migawki do pamięci obiektowej, podczas gdy udziały nie są nigdzie przechowywane, nie stanowi kopii zapasowej. Przed poleganiem na tym rozwiązaniu należy przetestować proces przywracania na tymczasowym serwerze VPS.
Logowanie 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 monitorowaniem ścieżki jest najbliższym zamiennikiem, jednak raportuje jedynie fakt otwarcia pliku, a nie to, która wartość została użyta.
OpenBao loguje każde żądanie do urządzenia audytowego, które zostanie jawnie włączone:
bao audit enable file file_path=/var/log/openbao_audit.logDwa fakty dotyczące tego logu wpływają na sposób uruchamiania serwera. Większość ciągów znaków w żądaniach i odpowiedziach jest haszowana przy użyciu HMAC-SHA256 oraz soli, dzięki czemu można dopasować znaną wartość do logu bez przechowywania w nim tekstu jawnego. Liczby całkowite oraz wartości logiczne są zapisywane w postaci jawnej, więc numeryczny sekret 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. Zapełnienie dysku w /var/log powoduje wyłączenie API sekretów zgodnie z założeniami projektowymi. Należy zapewnić logom audytowym oddzielną przestrzeń oraz regułę logrotate już pierwszego dnia, a nie po pierwszej awarii.
Który menedżer sekretów typu self-hosted wybrać?
Podlicz maszyny i użytkowników, a następnie dokonaj wyboru.
- Jedna maszyna, jeden użytkownik: plik env z uprawnieniami 600, którego właścicielem jest root, a odczytuje go użytkownik usługi. Użyj systemd credentials, jeśli chcesz wykluczyć wartość 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 deszyfrowania. - 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ń bazy danych z określonym czasem życia oraz ścieżki audytu, którą ktoś przegląda: OpenBao; uwzględnij 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 korzystać z własnego menedżera 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 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ń, które wygasają bez konieczności 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 go w swojej obecności. Menedżer sekretów dostarcza poświadczenia procesom, więc musi działać o godzinie 03:00 bez nadzoru. Konsekwencja tego faktu stanowi główną różnicę: zablokowany menedżer haseł wymaga ponownego wpisania hasła głównego, natomiast zablokowany (sealed) menedżer sekretów zatrzymuje każdą usługę, która próbuje się zrestartować w tym stanie.
Co stanie się z moimi aplikacjami, jeśli OpenBao zostanie zablokowane po restarcie?
Aplikacje nie będą mogły pobrać swoich sekretów, więc nie uruchomią się, 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ę przesyłać pliki zaszyfrowane za pomocą SOPS do publicznego 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 na każdym istniejącym pliku za każdym razem, gdy dodajesz lub usuwasz odbiorcę.