Plik
.env poznałeś w module wprowadzającym i lokalnie sprawdza się doskonale. Na produkcji jest inaczej - i warto wiedzieć dlaczego, zanim ktoś przekona Cię, że wystarczy dopisać go do .gitignore.Cztery powody naraz. Ryzyko przypadkowego commita - jedno
git add -f albo nowy programista bez skonfigurowanego .gitignore i hasło jest w historii repozytorium na zawsze. Brak rotacji - zmiana hasła do bazy oznacza ręczną edycję pliku na każdym serwerze. Brak audytu - nikt nie wie, kto i kiedy odczytał sekret. I plain text - hasła leżą jawnym tekstem na dysku, dostępne dla każdego procesu z uprawnieniami do odczytu.Zauważ, czego wśród tych powodów nie ma: pliki
.env nie są wolne, nie zajmują pamięci i działają w Node.js bez zarzutu. Problem nie jest techniczny, tylko organizacyjny.Rzym nie wysyłał tajnych depesz zwykłym posłańcem. Trafiały do kancelarii, która wydawała je za pokwitowaniem i wiedziała, kto co odebrał.
Zanim przejdziemy do kancelarii, jedna rzecz o samych plikach.
ConfigModule przyjmuje ich listę:1ConfigModule.forRoot({
2 isGlobal: true,
3 envFilePath: ['.env.local', '.env.development', '.env'],
4});Kolejność rejestracji jest stała:
, ConfigModule
, opcje z .forRoot({
, na końcu isGlobal: true,
.})
A teraz rzecz, która myli najczęściej: wygrywa plik podany jako pierwszy. Gdy ta sama zmienna występuje w kilku plikach, wartość bierzemy z
- nie z .env.local
.env, mimo że to on wygląda na „główny".Logika jest taka: od najbardziej szczegółowego do najbardziej ogólnego.
.env.local to Twoje osobiste nadpisania, których nikt inny nie ma; .env.development to ustawienia całego środowiska; .env to wartości domyślne. Bardziej szczegółowy przesłania ogólniejszy.HashiCorp Vault to narzędzie do centralnego zarządzania sekretami z audytem i rotacją. Nie jest frameworkiem frontendowym, systemem testów ani narzędziem do kompresji obrazów - to skarbiec, który wydaje wartości na żądanie i zapisuje każde wydanie.
Rozwiązuje dokładnie te braki, które wypisaliśmy: sekret nie leży w pliku (nie ma czego zacommitować), można go zmienić w jednym miejscu (rotacja), a każdy odczyt zostaje odnotowany (audyt).
Aplikacja pobiera sekrety przy starcie, zanim cokolwiek ich potrzebuje:
1export async function loadSecrets(): Promise<Record<string, string>> {
2 const vault = new VaultClient({
3 endpoint: process.env.VAULT_ADDR,
4 token: process.env.VAULT_TOKEN,
5 });
6
7 const { data } = await vault.read('secret/data/legion-api');
8
9 return {
10 DATABASE: data.data.DATABASE,
11 JWT_SECRET: data.data.JWT_SECRET,
12 REDIS_PASSWORD: data.data.REDIS_PASSWORD,
13 };
14}Zwróć uwagę na paradoks, który tu widać: żeby dostać się do skarbca, też potrzebujesz klucza - adresu i tokenu, które nadal przychodzą ze zmiennych środowiskowych. Vault nie usuwa tego problemu, tylko sprowadza go do jednego sekretu zamiast dwudziestu. Ten jeden bywa dodatkowo wydawany krótkoterminowo przez mechanizm samej infrastruktury.
W dojrzałej konfiguracji sekrety mogą pochodzić z kilku miejsc naraz. Kolejność od najwyższego priorytetu wygląda tak:
Reguła jest prosta: im wyżej, tym więcej kontroli. Fallback na zmienne środowiskowe zostaje na dole nie dlatego, że jest zły, tylko dlatego, że nie daje ani audytu, ani rotacji - ale za to działa bez żadnej infrastruktury, więc lokalne uruchomienie nie wymaga stawiania Vaulta.
Niezależnie od źródła, w kodzie sięgasz po sekret tak samo:
1const dbUrl = this.configService.get<string>('DATABASE');Kolejność członów jest stała:
, this
, .configService
, .get<string>
. To ta sama metoda, którą poznałeś przy konfiguracji - i o to chodzi. Serwis nie wie, czy wartość przyszła z Vaulta, z chmury, czy ze zmiennej środowiskowej; zmiana źródła nie dotyka ani jednej linii logiki.('DATABASE')
W klastrze pojawia się dodatkowy kłopot: konfigurację opisujemy plikami YAML, które trzymamy w repozytorium - a sekret w takim pliku to znowu hasło w Gicie.
Sealed Secrets umożliwiają bezpieczne przechowywanie zaszyfrowanych sekretów w repozytorium Git. Szyfrujesz wartość kluczem publicznym kontrolera działającego w klastrze; powstały plik możesz spokojnie zacommitować, bo odszyfrować go potrafi wyłącznie ten kontroler, kluczem prywatnym, którego nigdzie nie wysyłasz.
To rozwiązuje sprzeczność między „wszystko w repozytorium" a „sekretów w repozytorium nie trzymamy". Sealed Secrets nie skalują podów, nie kompresują obrazów i nie monitorują zużycia - szyfrują sekrety tak, by mogły podróżować z kodem.
Kancelaria wydaje depesze za pokwitowaniem:
.env na produkcji jest niebezpieczny z czterech powodów naraz: ryzyko przypadkowego commita, brak rotacji, brak audytu, plain text - nie z powodu wydajności czy pamięci,envFilePath wygrywa plik podany jako pierwszy: .env.local przesłania .env.development, a ten .env,ConfigModule, .forRoot({, isGlobal: true,, }),this + .configService + .get<string> + ('DATABASE') - logika nie wie, skąd wartość pochodzi,W następnej lekcji wrócimy do pipeline'u - tym razem z macierzami budowania, artefaktami i cache'em. A na razie zapamiętaj: plik
.env mówi, co jest hasłem; kancelaria mówi dodatkowo, kto je odebrał i kiedy je zmieniono.