Nejčastěji nevyzradí přístupové údaje útok. Je to repozitář, který byl soukromý, když se do něj klíč dostal, a veřejný ve chvíli, kdy ho někdo forknul. To už je klíč v historii, ne v souboru, a smazání řádku nepomůže vůbec nic. Na druhém místě je řádek v logu, který při startu vypsal celý objekt s konfigurací. Ani jedno není nijak důmyslné a oběma zabrání stejný návyk: hodnota žije na serveru, ne v tom, co pushuješ.
Za tím návykem stojí mechanismus a ten má ostré hrany. Proměnné prostředí nejsou trezor - čte je proces, všechno, co spustí, a každý, kdo má přístup na záložku, kde se nastavují. Tento článek popisuje, co to doopravdy je, jak je nastavit a číst bez hádání, co přesně dělat, když už se jedna dostala do commitu, a pět míst, kudy unikají, i když jsi všechno ostatní udělal správně.
Co proměnná prostředí doopravdy je#
Každý proces v Unixu dostane při startu od toho, kdo ho spustil, dvě věci: argumenty a prostředí. Prostředí je prostý seznam dvojic NAME=value. Tvůj program ho čte standardní funkcí a každý potomek, kterého spustí, dostane jeho kopii.
Z toho plynou čtyři vlastnosti a na všech záleží:
- Jsou to řetězce. Žádná čísla, booleany ani null.
DEBUG=falseje sedmiznakový řetězecfalse, který je ve většině jazyků pravdivý, pokud ho testuješ přímo. Parsuj záměrně. - Nastavují se před startem procesu. Změna na záložce Startup nic neudělá, dokud server nerestartuješ. To překvapí lidi, kteří čekají znovunačtení konfigurace.
- Dědí se. Shellový skript, který spouští tvoji aplikaci, je předá dál. Stejně tak podproces, který spustíš kvůli převodu obrázku. Pokud nějaká závislost volá externí program, ten program vidí heslo k tvé databázi.
- Nejsou šifrované. Leží v paměti procesu a na Linuxu v
/procpro cokoli, co běží pod stejným uživatelem. Jejich smysl je v tom, že zůstanou mimo zdrojový strom a mimo logy, ne že jsou zamčené v trezoru.
Alternativa, po které lidé sahají nejčastěji, je konfigurační soubor v repozitáři, který selže přesně tak, jak je popsáno výše, nebo konfigurační soubor mimo repozitář, což je v pořádku a zhruba to je soubor .env. Proměnné prostředí soubor z rovnice prostě vyřadí.
Nastavení: záložka Startup a soubory .env#
Na hostingu s panelem nese proměnné serveru záložka Startup. Zadáš název a hodnotu, restartuješ a aplikace ji přečte tak, jak to už dělá. Do tvého checkoutu se nic nezapíše, takže ji další Git pull nepřepíše a další push neodhalí.
Druhá cesta je soubor .env nahraný přes SFTP a nikdy necommitovaný. Obě jsou legitimní; liší se tím, kdo je vidí a co se stane při novém deployi.
| Kde | Kdo to čte | Přežije Git pull | K čemu se hodí |
|---|---|---|---|
| Záložka Startup | Každý s oprávněním k panelu | Ano | Hlavní místo pro tajné klíče |
.env na serveru | Každý s přístupem k souborům nebo SFTP | Ano, pokud je v gitignore | Dlouhé nebo víceřádkové hodnoty |
.env v repozitáři | Všichni, navždy | Ano | K ničemu |
Když používáš .env, záznam do .gitignore patří jako první, ještě než soubor vznikne:
.env.env.*!.env.examplenode_modules/Commitni .env.example se všemi klíči a bez hodnot, aby další člověk - včetně tebe za osm měsíců - věděl, co aplikace potřebuje. Node čte .env nativně od verze 20.6 přes node --env-file=.env server.js, takže balíček dotenv je na aktuálním runtime volitelný; projekty v Pythonu a Rustu obvykle používají python-dotenv a dotenvy.
Dvě pravidla formátování, která způsobují skutečné chyby. Hodnoty obsahující mezery, # nebo uvozovky je třeba uzavřít do uvozovek a různé parsery se v escapování neshodnou, takže hodnoty drž co nejjednodušší. Víceřádkové hodnoty - soukromý klíč, JSON servisního účtu - do proměnné prostředí nepatří vůbec; zakóduj je do base64 na jeden řádek, nebo nahraj soubor a do proměnné dej jeho cestu.
Čtení bez hádání#
Každý jazyk má jeden obvyklý způsob přístupu a každý má past na chybějící hodnoty.
const url = process.env.DATABASE_URL; // undefined when unsetconst port = Number(process.env.PORT ?? 3000); // always parse numbersconst debug = process.env.DEBUG === "true"; // never trust the stringimport osurl = os.environ["DATABASE_URL"] # raises KeyError when unset - goodport = int(os.getenv("PORT", "8000")) # explicit defaultdebug = os.getenv("DEBUG", "").lower() in {"1", "true", "yes"}let url = std::env::var("DATABASE_URL").expect("DATABASE_URL is not set");let port: u16 = std::env::var("PORT").unwrap_or_else(|_| "8080".into()) .parse().expect("PORT must be a number");V PHP se používá getenv() a hodnoty se podle konfigurace runtime objevují i v $_ENV a $_SERVER, což je spolehlivý zdroj zmatků na webových tarifech - preferuj getenv() a vyzkoušej ho.
Ať používáš jakýkoli jazyk, povinné proměnné zkontroluj při startu a skonči s jasnou zprávou, místo abys na problém přišel při prvním požadavku ve dvě ráno:
const required = ["DATABASE_URL", "SESSION_SECRET", "DISCORD_TOKEN"];const missing = required.filter((name) => !process.env[name]);if (missing.length) { console.error(`missing environment variables: ${missing.join(", ")}`); process.exit(1);}Proces, který odmítne nastartovat, je dobrý výsledek. Proces, který nastartuje s nedefinovanou DATABASE_URL a tři dny tiše zapisuje do lokálního souboru SQLite, dobrý není.
Co patří do prostředí#
Všechno, co k něčemu dává přístup, a všechno, co se liší mezi tvým notebookem a serverem.
- Přístupové údaje k databázi. Na tomto panelu si databázový slot generuje vlastní host, uživatele a heslo, takže úkolem je zkopírovat connection string do proměnné, ne vymýšlet ho. Nikdy ho nepoužívej pro druhou aplikaci.
- API klíče a tokeny pro všechno, co voláš - platební brány, odesílače pošty, objektové úložiště, API nějaké hry.
- URL webhooků. URL webhooku na Discordu nebo Slacku je přístupový údaj: kdo ji drží, může psát jako ty do onoho kanálu, navždy. Vkládají se do veřejných issues častěji než jakýkoli jiný tajný údaj.
- Tokeny botů. Nejčastěji uniklý tajný údaj v oboru. Token Discordu je celý účet; nemá žádný rozsah oprávnění.
- Podpisové a session secrety. Klíč, kterým tvůj framework podepisuje cookies nebo JWT. Když unikne, může někdo vystavit platnou session za libovolného uživatele a jeho výměna odhlásí všechny, což je přesně ta výměna, kterou chceš mít k dispozici.
- Hesla k RCON a administraci herních serverů. Tady se generují pro každý server zvlášť a zaslouží si stejnou vážnost jako klíč k SSH - RCON bezpečně vysvětluje, proč je otevřený port RCON horší, než vypadá.
- Cokoli, co bys nevyvěsil na billboard, což je test, který pokryje případy, na které seznam výše zapomněl.
Co tam nepatří: velké soubory, cokoli, co potřebuješ měnit za běhu bez restartu, a veřejná konfigurace, například feature flag, který v repozitáři nevadí. Také nedávej tajné údaje do argumentů příkazové řádky ve svém start příkazu - argumenty jsou ve výpisech procesů vidět tak, jak prostředí o něco méně, a končí v lozích.
Pokud už se dostal do commitu#
Vyměň ho. To je celá odpověď a všechno ostatní je úklid.
Smazání řádku a nový push nepomůže. Starý commit hodnotu pořád obsahuje a v mnoha hostingových nastaveních zůstává dosažitelný přes svůj hash i po force-pushi. Každý, kdo repozitář naklonoval, má celou historii na disku. Automatické scrapery sledují veřejné repozitáře a nové commity nepřetržitě a reagují v řádu sekund, ne hodin.
Postup, v tomto pořadí:
- Vystav nový přístupový údaj u poskytovatele. Udělej to dřív, než zneplatníš starý, pokud poskytovatel dovolí existenci obou, takže nevznikne žádná mezera.
- Dej novou hodnotu na záložku Startup a restartuj aplikaci.
- Zneplatni starý přístupový údaj. Tohle je krok, který lidé vynechávají, protože aplikace zase funguje. Stará hodnota je platná, dokud to neuděláš.
- Zjisti, čeho se starý dotkl. Dashboardy poskytovatelů ukazují poslední využití podle klíče. Hledej volání, která jsi neudělal, a volání z adres, které nejsou tvoje.
- Teprve potom případně vyčisti historii pomocí
git filter-reponebo BFG. To je kosmetika. Nic nevrátí zpět, rozbije každý existující klon a krok 3 nenahrazuje.
Tajný údaj, který byl pět minut ve veřejném repozitáři, je veřejný tajný údaj. Scrapery jsou rychlejší než ty.
GitHub skenuje veřejné repozitáře kvůli známým formátům přístupových údajů a upozorňuje poskytovatele, proto se uniklý cloudový klíč někdy deaktivuje dřív, než si toho všimneš. Nespoléhej na to: pokrývá rozpoznatelné vzory od zúčastněných poskytovatelů a tvůj vlastní podpisový secret mezi ně nepatří. Pokud najdeš důkaz, že byl údaj použit, širší postup je v článku co dělat, když ti hacknou server.
Výměna bez výpadku#
Rotace by měla být tak nudná, že ji děláš podle plánu, a ne jen po incidentu. Postup závisí na tom, jestli údaj může existovat dvakrát.
Dva klíče najednou - většina poskytovatelů API a snadný případ. Vytvoř druhý klíč, nasaď ho, v dashboardu poskytovatele ověř provoz na novém klíči a první smaž. Žádný výpadek, žádná koordinace.
Jen jedna hodnota - hesla k databázi, session secrety. Tady nastane okamžik změny, takže ho naplánuj: změň heslo v databázi, uprav proměnnou, restartuj. U malé aplikace jde o pár sekund chyb připojení, nejlépe ve chvíli, kdy se nikdo nedívá. U session secretu počítej s tím, že se všichni odhlásí, a pokud na tom záleží, řekni to předem.
Sdílený mezi službami - nejhorší případ, protože výměna jednoho rozbije druhého. Řešení je strukturální: jeden přístupový údaj na každého konzumenta, takže se nic nesdílí a rotace je vždy lokální. Databázoví uživatelé jsou levní. Vytvoř jednoho pro každou aplikaci jen s oprávněními, která potřebuje - to je standardní rada z kontrolního seznamu zabezpečení databáze a důvod, proč se v connection poolech a limitech lépe uvažuje, když má každá aplikace vlastního uživatele.
Po každé rotaci, která se databáze týkala, udělej její zálohu před ní i po ní. Zálohy a obnovy databáze popisuje, jak to zvládnout bez servisního okna.
Cesty, kudy unikají i tak#
Tajný údaj jsi dal do prostředí a přesto skončil na veřejnosti. Pět cest, v pořadí, v jakém se to skutečně děje:
Build ho vložil do kódu. Frontendové nástroje záměrně zapékají některé proměnné prostředí do JavaScriptového balíčku, který posílají prohlížečům. Cokoli s názvem NEXT_PUBLIC_, VITE_ nebo REACT_APP_ je veřejné z principu, bez ohledu na obsah. Serverový API klíč, kterému někdo dal takový prefix, aby „fungoval v komponentě“, je klíč vytištěný v prohlížeči každého návštěvníka. Před nasazením v bundlu hodnotu vyhledej: pokud ji najdeš vyhledáváním v prohlížeči, najde ji kdokoli.
Něco vypsalo konfiguraci do logu. console.log(process.env) při ladění, error handler, který serializuje celý kontext požadavku, stack trace na výchozí chybové stránce frameworku v produkci. V produkci vypni ladicí výstup a pokud tvoje logovací knihovna umí redakci, vyjmenuj v ní své tajné klíče.
Dostal se do screenshotu nebo ticketu. Konverzace s podporou, hlášení chyb, streamy. Ticket v panelu tady vidí všichni zaměstnanci a přílohy jsou soukromé, což je správný kanál pro přístupový údaj, který musíš sdílet - ale cokoli, co někdy bylo v chatu, ber jako už vyměněné.
Zdědil ho podproces a někomu ho prozradil. Nástroje pro hlášení pádů a SDK pro sledování chyb v některých konfiguracích sbírají data o prostředí ve výchozím stavu. Než ho zapneš, přečti si, co tvůj reporter posílá.
Přečetl ho někdo s přístupem do panelu. To není ani tak únik, jako oprávnění, které jsi udělil; další část je o tom, jak ho zúžit.
Kdo je může číst v panelu#
Prostředí chrání hranice účtu, takže práce je na účtu.
- Dvoufázové ověření na účtu vlastníka, s obnovovacími kódy uloženými jinde než ve správci hesel, který drží heslo. Účet, u kterého se ztratí obojí, nejde obnovit - viz dvoufázové ověření na účtu v panelu.
- Subuživatelé s oprávněním, které potřebují, a ne větším. Role jsou tady dost jemné na to, aby šla dát konzole bez souborů nebo soubory bez fakturace, a přístup lze časově omezit a přidělit přes tým místo jednotlivců. Subuživatelé a nejmenší oprávnění popisuje model; stručně: moderátor, který restartuje server, nepotřebuje záložku, na které je vidět tvůj klíč ke Stripe.
- Log aktivity jednotlivého serveru, tedy záznam, kdo co změnil, a první věc ke čtení, když hodnota není taková, jakou jsi nastavil.
- API klíče omezené adresou, seznam relací, ze kterých se lze odhlásit, a limity na počet přihlášení. Používej všechny tři; jsou zdarma.
- Jednorázové přihlášení k databázovému slotu. Otevření phpMyAdminu z panelu používá token, který vyprší za 60 sekund, takže přístupový údaj nezůstane v historii prohlížeče ani v záložce.
Když někdo projekt opouští, pořadí je: odeber subuživatele, vyměň přihlašovací údaje k SFTP u každého serveru, kterého se dotkl, vyměň všechno, co mohl číst. Udělat jen první krok je nejčastější půlřešení. Úplný seznam zabezpečení účtu najdeš na stránce o zabezpečení a pohled na stejný problém ze strany nasazení je v článku nasazení aplikace v Node.js z GitHubu.
FAQ#
Jsou proměnné prostředí opravdu bezpečné?
Jsou bezpečnější než soubor v repozitáři a zhruba stejně bezpečné jako soubor na serveru. Nejsou šifrované v klidu a čte je proces, všechno, co spustí, a každý, kdo má v panelu oprávnění zobrazit záložku Startup. Pro téměř každé malé nasazení to stačí. Pokud potřebuješ auditované, šifrované doručování tajných údajů pro každý požadavek, potřebuješ správce tajných údajů a jinou architekturu, a poznáš to, protože to bude někdo mít napsané v compliance dokumentu.
Je soubor .env lepší, nebo horší než panel?
Spíš jiný než lepší. .env zvládne dlouhé nebo nezvyklé hodnoty a drží všechno na jednom místě, které tvůj kód už čte. Záložka Startup drží hodnotu úplně mimo souborový systém, takže ji přístup k souborům neprozradí, a přežije omylem spuštěné rm ve špatném adresáři. Hodně lidí používá panel pro tajné údaje a commitnutý .env.example jako dokumentaci, což je dobré výchozí řešení.
Pushnul jsem token do veřejného repozitáře a smazal ho. Je to v pořádku?
Ne. Hodnota je v historii commitů a v každém klonu, který už existuje, a veřejné repozitáře se scrapují nepřetržitě. Vystav nový token, nasaď ho, starý zneplatni a pak v logu využití u poskytovatele zkontroluj volání, která jsi neudělal. Přepsání historie je potom upravené, ale na vystavení nic nemění.
Proč je moje proměnná po nastavení undefined?
Proměnné prostředí se čtou při startu procesu, takže hodnota nastavená po startu není vidět, dokud nerestartuješ. Další kandidáti: překlep v názvu, který rozlišuje velká a malá písmena; .env, který se načítá až po kódu, jenž ho čte; nebo proměnná z doby buildu, kterou hledáš za běhu, což je v zabaleném frontendovém kódu úplně jiný mechanismus.
Můžu použít stejné heslo k databázi pro dvě aplikace?
Můžeš a každá budoucí rotace pak bude koordinovaný výpadek. Raději vytvoř databázového uživatele pro každou aplikaci jen s oprávněními, která potřebuje. Nestojí to nic, omezuje to, na co se dostane jedna napadená aplikace, a výměna přístupového údaje pak zasáhne jediný proces.
Kam v tom všem patří hesla k herním serverům?
Na stejné místo. Hesla k RCON, hesla správce a API klíče herního serveru se nastavují na záložce Startup a tady se při instalaci generují pro každý server zvlášť, takže jsou ve výchozím stavu jedinečná. Jediný rozdíl je, že některé hry zapisují heslo do konfiguračního souboru, který mohou číst mody, takže každý server, na který instaluješ cizí kód, ber jako server, jehož tajné údaje tento kód zná - užitečný návyk popisuje článek udržování modovaného serveru v čistotě.




Komentáře
Zcela anonymně: žádný účet, žádný e-mail, žádné cookie. Ukládáme jen jméno, které napíšeš, text a čas - nic dalšího. Počet odkazů je omezený a značkování se nevykresluje.