SSH-policy és jóváhagyás

Amit az átjáró egy operátornak a célgépen enged, ha már tudja, ki ő: egy parancs-szabály alakja és amire illeszkedik, amit a policy nem tud elolvasni, mi van az átjáró által aláírt tanúsítványokban, és a kapuk, amiken egy connect átmegy. Hogy ki az operátor, és melyik csoport mit kapuz, az identitások, csoportok és zárolás lapon áll.

Parancs-policy

Egy szabály alakja

A policy.yaml szabályok listáját tartalmazza. Mindegyik ez:

Kulcs Jelentés
name A szabály azonosítója. Megjelenik az elutasítási üzenetben és az auditbejegyzésben
pattern Egy reguláris kifejezés. A teljes sorra és a benne lévő minden parancsra illesztődik — lásd Mire illeszkednek a szabályok
category Egy karbantartott minta név alatt (script_execution, interactive_pty) nyers regex helyett. A pattern-nel kölcsönösen kizáró
action deny vagy require_approval. Ha hiányzik, az deny. Bármi más betöltési hiba
reason Az operátornak megmutatott és az auditnaplóba írt indoklás
timeout_seconds Meddig vár egy jóváhagyási kérés. require_approval szabálynál alapból 300
min_level A szabály inaktív az ennél alacsonyabb lockdown szintű hosztokon
roles A szabály csak ezekre a szerepkörökre vonatkozik. Az üres minden szerepkört jelent

Két akció van, nem három. Nincs allow, és nem is kell: az a parancs, amelyik egyetlen szabályra sem illeszkedik, lefut. Tehát minden parancs a három osztály egyikébe esik — egy illeszkedő szabály megtagadta, egy illeszkedő szabály egy második személyhez küldte, vagy azzal engedélyeződött, hogy semmire nem illeszkedett.

Egy szövegdarabon a kiértékelés első-találat három blokkon, rögzített sorrendben: előbb a beépített tiltó szabályok, aztán a policy.yaml szabályai, végül a beépített jóváhagyási szabályok. Ettől a sorrendtől viselkedik jól az unió. A tiltások jönnek elsőként, tehát semmilyen fájl nem gyengítheti őket; a jóváhagyási alapértelmezések jönnek utoljára, tehát egy saját, szűkebb szabály beelőzhet egyet és felülírhatja.

A beépített tiltó szabályok minden policy részei, beleértve egy olyan átjáró policyját is, amelynek egyáltalán nincs policy.yaml-ja:

Szabály Mit tilt
block_interactive_root_shell sudo -i, sudo -s, és sudo <shell> -c nélkül, a célprogram alapnevével összevetve
block_sudo_shell_wrapper sudo <shell> -c …, ugyanaz az alapnév-összevetés — egy shell-burok elrejti a valódi parancsot a policy-motor elől
block_destructive_root_rm rm -rf /
block_passwd_changes passwd, sudo-val vagy anélkül — a jelszóváltoztatás a címtáron keresztül megy
block_sudo_not_allowlisted Csak akkor van jelen, ha a policy.yaml beállítja a sudo_allowed_commands-ot: minden olyan sudo-cél, amelynek az alapneve nincs a listán

A két sudo-szabály úgy illeszt, hogy a sudo saját flagjeit értelmezi és a célprogram alapnevét hasonlítja össze, nem úgy, hogy a sudo szó utáni szövegre illeszt — a sudo /bin/bash és a sudo bash tehát ugyanaz a tény a számukra. Az engedélylista opt-in, de ha egyszer bekapcsolták, ugyanabból a fájlból nem írható felül: a beépített tiltó blokkon belül ül.

A két beépített jóváhagyási szabály a 4-es lockdown szinttől felfelé érvényes: approve_script_execution_on_prod (egy szkript futtatása megkerüli a parancsonkénti policyt) és approve_interactive_pty_on_prod (interaktív terminálprogram).

Egy pillantásra: mi fut, mi vár, mi tiltott

Minden sor, amit egy operátor beír, a három kimenet egyikével végződik: lefut, egy második emberre vár, vagy tiltott. Ez a táblázat a teljes döntés egy helyen — az utána következő szakaszok a részletek.

Mit tartalmaz a sor Kimenet Miért Hol állítható
Parancs, amire egyetlen szabály sem illeszkedik Lefut Nincs allow akció: amit semmi nem kifogásol, az engedett Egy szabály a policy.yaml-ba (deny vagy require_approval, szűkíthető roles-szal és min_level-lel)
Parancs, amire egy saját szabályod illeszkedik A szabály akciója A te szabályod, a te döntésed policy.yaml
Beépített tiltás: root shell sudo-val, sudo shell-burkoló, rm -rf /, passwd Tiltott Ez a padló, ami minden policyben megmarad — fájl nem gyengítheti Nem állítható. A sudo_allowed_commands csak bővítheti
Beépített jóváhagyás: script futtatása, interaktív terminál-program, keményített hoszton Jóváhagyásra vár Egy script vagy teljes képernyős program elrejti a policy elől a benne futó parancsokat Egy elé álló, szűkebb saját szabály (más időkorlát, más roles)
Több parancs egy sorban (;, &&, &, vagy-lista, cső, ( ), { }, ciklus- vagy feltétel-törzs) Minden parancs értékelődik; a legszigorúbb kimenet nyer Egy második parancs nem utazhat az első döntésén Nem állítható — így működik az illesztés
Előtag mögötti parancs (sudo, env, timeout, …), vagy idézőjellel, escape-pel írt (up\time) A ténylegesen futó parancsként értékelődik Egy burkoló vagy egy escape nem rejtheti el a programot a szabályok elől Nem állítható
Csak futáskor eldőlő szerkezet: $(...), backtick, eval, bash -c, heredoc, függvény-definíció, változóból jövő parancsnév Alapból tiltott Előre egyetlen parse sem tudja megmondani, mi fog futni A policy.yaml dynamic_construct_action kulcsa: deny (alap), require_approval, vagy allow (laborbeállítás)
Sor, amit a bash nem tud parse-olni Tiltott Senki nem tudja megmondani, mit jelent Nem állítható (block_unparseable_line)
exit más parancsokkal egy sorban Tiltott Egy soron belül nem zárná a munkamenetet, vagyis nem azt tenné, aminek látszik Nem állítható (block_exit_in_compound_line) — az exit külön sorba kerüljön
Ami egy engedélyezett vi, less vagy python belsejében íródik Nem értékelődik, rögzül Az átjáró a programot megnyitó sort látja, nem azt, ami benne történik A célgépen: a fiók jogai és a sudo beállítása

Egy sor kimenete még a futtatása előtt megnézhető: isg policy check.

A webUI jóváhagyás- és munkamenet-nézete: nincs függő jóváhagyás, a jóváhagyási előzmények, és a lezárt munkamenetek a célgéppel, a felhasználóval, a szerepkörrel, az indulás idejével, az engedélyezett és az elutasított parancsok számával, a parancsnaplóval, a terminál-felvétellel és a tickettel

Képernyőkép: referencia-környezet (env1), 2026-10-17.

Mire illeszkednek a szabályok

Az operátor egy SORT gépel be, és egy sor egynél több parancsot is futtathat. Az átjáró úgy értelmezi, ahogy a bash értelmezi, és minden parancsot kiértékel, ami abból a sorból lefuthat — nem csak a sor elejét:

Az idézőjelezés és az escape-elés előbb feloldódik, tehát a re\boot a reboot parancs — ez az, amit a bash is futtatna.

A legszigorúbb döntés nyer: a deny üt a require_approval-on, az pedig a nincs-találaton. Egy saját szabály tehát nem tudja enyhíteni egy beépített tiltást azzal, hogy ugyanabban a sorban valami másra illeszkedik — nincs is allow akció, amivel ezt le lehetne írni. Ha a döntés egy részre esett, nem a teljes sorra, az üzenet megmondja, melyikre:

DENIED by policy: block_passwd_changes (password changes must go through the IdP) — part 2 of 2: "passwd -S peter"

Sorok, amiket a policy nem tud elolvasni

Van olyan szerkezet, ami csak futás közben dől el, és semmilyen elemzés nem tudja előre megmondani: a parancs-behelyettesítés ($(...) vagy backtick), a folyamat-behelyettesítés (<(...)), a here-document, a shell-függvény-definíció, az eval, a source, a ., az exec, az alias, egy shell vagy interpreter, aminek a programját sztringként adták át (bash -c, python3 -c, perl -e), és minden olyan parancsnév, ami csak behelyettesítés után áll elő ($X, vagy egy glob magában a programnévben).

Azt, hogy egy ilyen sorral mi történjen, a policy.yaml dynamic_construct_action kulcsa dönti el. Értéke deny (az alap, és a hiányzó kulcs is ezt jelenti), require_approval vagy allow; bármi más betöltési hiba. Az első kettőnél a döntés a block_dynamic_shell_construct, illetve az approve_dynamic_shell_construct szabály néven jelenik meg. Az allow kiértékelés nélkül futtatja a sort, és laborbeállítás — a policy betöltésekor figyelmeztetést naplóz.

Két sor-szintű szabály egyáltalán nem állítható:

Szabály Mikor szól
block_unparseable_line A sor nem olyan shell-sor, amit a bash értelmezni tud — lezáratlan idézőjel, befejezetlen here-document. Nincs olyan olvasata a „futtasd le mégis"-nek egy olyan sornál, aminek a jelentését senki nem tudja megmondani
block_exit_in_compound_line Az exit, a quit vagy a logout olyan sorban szerepel, amiben más parancsok is vannak. Ezek a szavak csak önálló sorként zárják a munkamenetet; máshol a célgépnek küldött parancsok, amik a munkamenettel nem csinálnak semmit, és ezt a különbséget jobb elutasítani, mint csendben a másikat csinálni

Hogy mindezt egy saját soron is lásd, futtatás nélkül: az isg policy check kiírja a felbontást, minden jelölt-szöveget, és a döntő szabályt:

isg policy check -- 'id; passwd -S peter'

A kilépési kódja maga a döntés: 0 engedélyezve, 1 tiltva, 2 jóváhagyás szükséges.

Hol áll meg a policy

Az átjáró policyja nem kemény határ, és nem is annak áruljuk. Azt szűkíti, ami a célgépig eljut, és rögzíti, mit próbáltak. Három dolog eleve kívül esik rajta:

A kemény határ a célgépen van: egy minimális jogú fiók, és egy olyan sudo beállítás, ami megnevezi, mire emelkedhet az a fiók. Az átjáró az előtte álló kapu és az arról szóló nyilvántartás, hogy ki ment át rajta — a kettő együtt, nem külön-külön. A fiók alakját lásd a Célgép lapon.

Mit tartalmaz egy tanúsítvány

Az átjáró négyféle tanúsítványt ír alá, és a különbségek abban vannak, hogy melyik mit nevez meg, és meddig él.

Emberi munkamenet Szolgáltatásfiók Megújítható identitás Hoszt
Principalok Az egyetlen feloldott --as OS-felhasználó A ca.yaml service_accounts[].principal egyetlen principalja — (X.509 kliens-tanúsítvány) Minden hosztnév, amin az átjáró tárcsázza azt a gépet
Key ID isg-human:<principal> A fiók neve — isg-host:<first principal>
Kiterjesztések permit-pty, és semmi más permit-pty — nincs
Kritikus opciók nincs nincs — nincs
Élettartam 10 perc, a kiadás köré centrálva ttl_seconds, alapból 1 óra renewable_ttl_days, alapból 30 nap host_cert_ttl, alapból 1 év
A kulcs eredete A daemon memóriájában generálva, sosem íródik lemezre A kérelmező kulcsa A szolgáltatás gépén generálva A célgép meglévő hosztkulcsa, amit a CA csak aláír

A principal az --as kérdésre adott válasz, a feloldás után — az az OS-felhasználó, akivé a munkamenet válik a célgépen. Semmi más nincs benne: egy principal, egy tanúsítvány, egy munkamenet.

A permit-pty és semmi más döntés, nem kihagyás. Nincs permit-port-forwarding, nincs permit-agent-forwarding, nincs permit-X11-forwarding, nincs permit-user-rc. Egy továbbított port pontosan az a csatorna, amit a policy-motor nem lát, ezért maga a hitelesítő adat képtelen megnyitni egyet.

A csoporttagság sosem íródik bele egy tanúsítványba. A csoportok azt kapuzzák, hogy kiadódik-e egy tanúsítvány; nem a tanúsítvány hordozza őket. A szerepkör sem, és annak a kiléte sem, aki a munkamenetet más nevében kérte — ezek az auditnaplóban élnek, ahová egy rájuk vonatkozó kérdés tartozik.

A tíz perces emberi ablak az átjáró és a célgép közti órajel-eltérésre van méretezve a kézfogás alatt, és semmi másra. Az SSH csak a kapcsolat felépítése közben nézi meg a tanúsítványt, tehát az a munkamenet, amelyik az ablakán belül kapcsolódott, utána is tovább működik; a valakit elvágó kar a következő kiadás megtagadása, és pont ezt teszik az alábbi hozzáférési kapuk.

Milyen kapukon megy át egy connect

Mielőtt bármilyen hitelesítő adat létezne, egy kérés rögzített sorrendben dől el, és minden nem-interaktív dolog történik elsőként, hogy egy olyan kérés, amit soha nem engedtek volna át, ne kerüljön az operátornak egy többfaktoros kihívásába.

A ca.yaml human_access szekciója engedélylista, első-találat-nyer szemantikával. Minden szabály megnevezi a principals-t (azokat az --as értékeket, amikre kiterjed; itt a * megengedett), és aztán vagy a hosts-ot (szó szerinti nevek — itt szándékosan nincs *), vagy a host_labels-t (CMDB kulcs/érték párok halmaza, amelyeknek mind illeszkedniük kell). Egy szabály ezenfelül követelhet csoportot (require_group), és megnevezheti, hogy ki kérhet munkamenetet az adott principal nevében (requested_by; ha a kulcs hiányzik, csak maga a principal). Tiltó szabály nincs: az a kérés, amelyik semmire nem illeszkedik, no_matching_rule okkal utasítódik el.

Ennek a listának két tulajdonságát érdemes kimondani. Egy host_labels szabály nem tud elavult vagy elérhetetlen CMDB-címkékre illeszkedni — labels_stale-t vagy labels_unavailable-t jelent ahelyett, hogy találgatna. És hogy a szekció kikényszerített-e, az a saját kapcsolója, a human_access.enforce: ha ki van kapcsolva, minden döntés attól még kiértékelődik és auditálódik, csak egyik sem blokkol. A requested_by_enforce bekapcsolása enforce nélkül ellentmondásként utasítódik el.

Egy harmadik tulajdonság arról szól, honnan jönnek a címkék, és ez az, amiért a daemon szigorú ebben. Egy host_labels szabály csak annyira valódi, amennyire a mögötte álló CMDB: egy adat nélküli resolver minden hosztra üres címke-halmazt ad, tehát minden ilyen szabály egyszerre abbahagyja az illeszkedést — csendben, úgy, hogy a kimenetben semmi nem mondja ki, hogy a címkék eltűntek. Ezért egy olyan cmdb.yaml, ami megnevez egy nem létező mock_file-t, megtagadja az indulást ahelyett, hogy címkék nélkül jönne fel, és az isg system check cmdb.yaml sora ugyanazzal a szöveggel pirosat ad. A kulcs teljes kihagyása továbbra is megengedett — az a kimondott módja annak, hogy címkék nélkül fussunk —, és a sor ilyenkor sárga, megnevezve a következményt.

Honnan jönnek ezek a szabályok egy telepített gateway-en. Ha a vezérlő nem szállít ca.yaml-t, az Ansible-role generál egy minimálisat, és abba két allow-szabályt ír, nem egy széles listát. Az első az operátoroké (isg_gateway_operators): ugyanazok az emberek, akik a gateway gépén helyi fiókot és isg-operators-tagságot is kapnak. A második csak akkor jelenik meg, ha az isg_gateway_human_access_users megnevez valakit, és azokra a nevesített emberekre szól, ugyanazokra a hosztokra. Ez a második csoport a gateway-en helyi fiókot nem kap, és ott csoport-tagságot sem — az útjuk a célgépre a tanúsítvány, a gateway pedig csak a kiadója. A nevek fióknevek, soha nem *: a role elutasítja az olyan lista renderelését, amelyben van, mert a principals-ban a * nem egy név, hanem mindenki. Az isg_gateway_requested_by_enforce az, ami ugyanabban a generált fájlban bekapcsolja a requested_by_enforce-ot, és mivel egyik szabály sem hordoz requested_by-t, minden principal csak önmaga nevében kérhet munkamenetet.

A hostgroup.yaml a második, független kapu: hosztonként megkövetelt csoportok, a hoszt címkéi alapján kiválasztva. Saját enforce kapcsolója van, és kikapcsolva ugyanígy viselkedik — mérve és auditálva, de nem kikényszerítve.

Aztán a többfaktoros kihívás, amelynek a módját az mfa.yaml mfa_mode-ja adja: email (SMTP-vel kézbesített véletlen kód, vagy a totp_outbox.txt-be írva, ha nincs továbbító konfigurálva), totp (RFC 6238, operátoronként egyszer regisztrálva az isg mfa enroll-lal), vagy totp-with-email-fallback. Az ismételt hibák a felhasználót és a forráscímet is kizárják arra az időre, amit az mfa.yaml beállít.

A totp-with-email-fallback módban — és csak ott — az az operátor, aki e-mailes kóddal lépett be, maga regisztrálhat hitelesítő appot, a webUI /profile lapjáról. A megerősítés két kódot kér, nem egyet: az appét, és egy frissen kiküldöttet arra a faktorra, amivel a fiók már rendelkezik. Azért, mert ebben a módban a TOTP-rekord kiváltja az e-mailes faktort, tehát egy eltérített böngésző-session nem regisztrálhatja be a saját appját. Újraregisztráció ezen az úton nincs — egy elvesztett hitelesítő app az adminisztrátor MFA_RESET-je, nem önkiszolgáló lépés.

Ki rendelkezik a második faktor felett email módban. Az átjáró a kódot arra a mail attribútumra küldi, amit belépéskor a címtár-bejegyzésből olvas ki, az első faktor pedig a címtárbeli jelszó. Aki a címtárban egy felhasználó jelszavát visszaállíthatja és az e-mail mezőjét átírhatja, az mfa_mode: email mellett mindkét faktort uralja — a második faktor ilyenkor nem független a címtártól. totp mellett a felhasználó egyszer regisztrálja a titkot, és az átjáró őrzi (mfa.yaml store: fájl az átjáró saját állapotában, $ISG_HOME/totp, vagy OpenBao store: vault mellett), a címtár hatókörén kívül. A totp-with-email-fallback a kettő között áll: aki már regisztrált hitelesítő appot, az a totp helyzetében van, aki még nem, az az email helyzetében — és egy e-mailben kapott kóddal maga regisztrálhat appot. Ajánlás: ahol a címtárat nem ugyanazok adminisztrálják, mint az átjárót, a totp a választás; az email ott illik, ahol a címtárat mindkét faktor bizalmi horgonyaként elfogadjátok, a totp-with-email-fallback pedig az átállás útja a kettő között.

Az átjárós bejelentkezésnek (ssh isg-login@gateway) van még egy útja: ha az sso.yaml-ban cli: optional áll, a promptnál adott üres jelszó elindítja az RFC 8628 device flow-t az identity providerrel, cli: required mellett pedig egyáltalán nincs jelszó-prompt. A promptnál begépelt felhasználónévnek egyeznie kell azzal a névvel, amit a provider visszaad, és utána ugyanaz a címtár-kapu és ugyanaz a többfaktoros ág következik, mint fentebb. Az isg connect nincs ezen az úton — a hívója a socketből áll össze, nem egy bejelentkezésből.

És csak ezután íródik alá egy tanúsítvány — és minden utána begépelt parancs a saját policy-döntése és a saját auditbejegyzése.

Amit az átjáró nem nyújt SSH-n

Ezek mért hiányok: olyan dolgok, amiket a kód megtagad, itt felsorolva, hogy egy telepítés ne épüljön rá egyikre sem.