markdown
Autentizace-pred-sluzbami.md
markdown
# Autentizace před službami Spousta self-hosted aplikací má přihlašování slabé nebo žádné. Řešením je postavit před ně vrstvu, která ověří uživatele dřív, než ho k aplikaci vůbec pustí. Předpokládá to fungující [reverse proxy](Reverse-proxy). Jestli si chceš jen vybrat a nečíst celou stránku: pro rychlé zakrytí jednoho dashboardu stačí [základní autentizace](#nejjednodušší-základní-http-autentizace), pro všechno ostatní vezmi [Pocket ID s tinyauthem](#doporučeno-pocket-id--tinyauth). ## Kdy to potřebuješ **Aplikace nemá přihlašování vůbec.** Uptime Kuma na začátku, různé dashboardy, Prometheus, přehledy. **Má, ale slabé.** Bez dvoufaktoru, bez omezení pokusů, s výchozím heslem. **Chceš jedno přihlášení pro všechno.** Deset služeb, deset hesel je otrava a vede k opakovaným heslům. Když je služba jen za [VPN](WireGuard), většinou to nepotřebuješ. Tohle je pro věci vystavené do internetu. ## Nejjednodušší: základní HTTP autentizace Jeden řádek konfigurace, žádná další služba. V [Caddy](Caddy): ```tajne.example.cz { basic_auth { martin $2a$14$hash } reverse_proxy 127.0.0.1:9000}``` V [nginxu](Nginx): ```nginxlocation / { auth_basic "Přihlášení"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://127.0.0.1:9000;}``` Omezení jsou reálná: ošklivé okno prohlížeče, žádný dvoufaktor, žádné odhlášení, rozbíjí to mobilní aplikace a API. Pro rychlé zakrytí dashboardu to stačí, pro cokoliv, co používáš denně, ne. ## Doporučeno: Pocket ID + tinyauth Tohle je dnes pro homelab nejrozumnější kombinace a je to varianta, kterou bych volil znovu. Dělí si práci na dvě části, které dělá každá jeden nástroj pořádně: **[Pocket ID](https://pocket-id.org/)** je poskytovatel identity. Spravuje uživatele a skupiny a mluví OIDC. Zvláštní je tím, že **přihlašování řeší výhradně passkeys** — žádná hesla, ani jako záložní možnost. To zní jako omezení, ale je to ta nejlepší vlastnost celé sestavy: co neexistuje, nejde uhodnout, vyzradit ani vyphishovat. Nové zařízení se přidává jednorázovým kódem z administrace. **[tinyauth](https://tinyauth.app/)** je brána. Sedí u [reverse proxy](Reverse-proxy), ptá se jí na každý požadavek a neověřené posílá na přihlášení. Umí obecné OIDC, takže ho na Pocket ID napojíš. Proč ne rovnou něco jednoho: Pocket ID sám o sobě je jen poskytovatel identity, forward auth neumí. A tinyauth sám o sobě umí jen pár uživatelů v konfiguraci. Dohromady dají jednotné přihlášení s passkeys a paměťově se vejdou do desítek megabajtů — [Authentik](#authentik) potřebuje řádově víc. ### Docker Compose ```yamlservices: pocket-id: image: ghcr.io/pocket-id/pocket-id:v2 restart: unless-stopped environment: APP_URL: https://id.example.cz ENCRYPTION_KEY: ${ENCRYPTION_KEY} # povinné, min. 16 znaků TRUST_PROXY: "true" ALLOW_INSECURE_CALLBACK_URLS: "false" volumes: - ./pocket-id:/app/data networks: [web] tinyauth: image: ghcr.io/tinyauthapp/tinyauth:v5 restart: unless-stopped environment: TINYAUTH_APPURL: https://auth.example.cz TINYAUTH_OAUTH_PROVIDERS_POCKETID_NAME: Pocket ID TINYAUTH_OAUTH_PROVIDERS_POCKETID_CLIENTID: ${OIDC_ID} TINYAUTH_OAUTH_PROVIDERS_POCKETID_CLIENTSECRET: ${OIDC_SECRET} TINYAUTH_OAUTH_PROVIDERS_POCKETID_AUTHURL: https://id.example.cz/authorize TINYAUTH_OAUTH_PROVIDERS_POCKETID_TOKENURL: https://id.example.cz/api/oidc/token TINYAUTH_OAUTH_PROVIDERS_POCKETID_USERINFOURL: https://id.example.cz/api/oidc/userinfo TINYAUTH_OAUTH_PROVIDERS_POCKETID_REDIRECTURL: https://auth.example.cz/api/oauth/callback/pocketid TINYAUTH_OAUTH_PROVIDERS_POCKETID_SCOPES: openid email profile groups TINYAUTH_OAUTH_AUTOREDIRECT: pocketid networks: [web]``` **Hlídej si cestu k obrazu tinyauthu.** Původní `ghcr.io/steveiliop56/tinyauth` je zamrzlá na verzi 5.0.7 — projekt se přestěhoval pod organizaci a novější vydání včetně bezpečnostních oprav vycházejí jen jako `ghcr.io/tinyauthapp/tinyauth`. Spousta návodů má ještě starou cestu a `docker compose pull` na ní aktualizaci nenajde, protože tam žádná novější není. Obě služby postav za proxy jako každou jinou, Pocket ID uvnitř poslouchá na portu `1411`. **Pocket ID musí běžet přes HTTPS.** Passkeys stojí na WebAuthn a to funguje jen v zabezpečeném kontextu. Na `http://` se nepřihlásíš ani na zkoušku. Pár proměnných, na kterých se to láme: - `ENCRYPTION_KEY` je od verze 2 **povinný**. Bez něj Pocket ID nenastartuje.- `TINYAUTH_SECRET` do konfigurace **nepatří** — od verze 4 neexistuje a tiše se zahodí. Relace jsou v databázi a cookie nese jen neprůhledné číslo. Ve starších návodech ho najdeš, ignoruj to. Totéž platí pro `TINYAUTH_PORT` (dnes `TINYAUTH_SERVER_PORT`) a `TINYAUTH_USERS` (dnes `TINYAUTH_AUTH_USERS`).- `TINYAUTH_OAUTH_AUTOREDIRECT: pocketid` přeskočí přihlašovací stránku tinyauthu a pošle uživatele rovnou na Pocket ID. Když máš jen jednoho poskytovatele, chceš to. ### Propojení V administraci Pocket ID založ OIDC klienta a jako návratovou adresu (callback) mu dej přesně tu z `REDIRECTURL` výše. Tvar je vždycky `<TINYAUTH_APPURL>/api/oauth/callback/<jméno-poskytovatele>`, tedy `pocketid` musí sedět s tím, co máš v názvech proměnných. Vygenerovaný identifikátor a tajemství doplň do `OIDC_ID` a `OIDC_SECRET`. **Nově založený klient nepustí nikoho.** Tohle je past, na které skončí většina lidí s dojmem, že něco špatně opsali: čerstvý OIDC klient nemá povolenou žádnou skupinu, takže se přes něj nepřihlásí ani administrátor. V detailu klienta buď klikni na **Unrestrict**, nebo mu přiřaď povolené skupiny uživatelů. ### První přihlášení Passkey si musí každý uživatel založit sám, admin to za něj udělat nemůže. Postup je takový, že mu v administraci vygeneruješ **jednorázový kód** (Users → login code, nebo `docker compose exec pocket-id /app/pocket-id one-time-access-token <uživatel>`), on ho zadá a při té příležitosti si passkey zaregistruje. Stejnou cestou se řeší ztracený telefon. **Počítej s tím, že jsi trvale poslední záchrana** — kdo přijde o všechny passkeys, potřebuje tebe. Posílání kódů e-mailem sice jde zapnout, ale dokumentace to sama označuje za nebezpečné, protože pak stačí přístup do schránky. ### Napojení na proxy tinyauth má pro každou proxy vlastní koncový bod: `/api/auth/traefik`, `/api/auth/caddy`, `/api/auth/nginx` a `/api/auth/envoy`. Jiné jméno vrátí `400`. [Caddy](Caddy) — vytvoř si úryvek a použij ho tam, kde chceš: ```(chraneno) { forward_auth tinyauth:3000 { uri /api/auth/caddy copy_headers Remote-User Remote-Name Remote-Email Remote-Groups }} nas.example.cz { import chraneno reverse_proxy 127.0.0.1:5000}``` Nic víc není potřeba. Caddy vrátí klientovi neúspěšnou odpověď tinyauthu i s přesměrováním, takže žádný `handle_response` nepiš — přidáním si to jen rozbiješ. [Traefik](Traefik): ```yamllabels: - traefik.http.middlewares.tinyauth.forwardauth.address=http://tinyauth:3000/api/auth/traefik - traefik.http.middlewares.tinyauth.forwardauth.authResponseHeaders=Remote-User,Remote-Name,Remote-Email,Remote-Groups``` `authResponseHeaders` uveď vždycky, jinak se identita k aplikaci nedostane. `trustForwardHeader` naopak nenastavuj — Traefik si hlavičky `X-Forwarded-*` vyrábí sám. Potřebuješ ho jen tehdy, když je Traefik sám za další proxy. [Nginx](Nginx): ```nginxlocation / { auth_request /tinyauth; auth_request_set $user $upstream_http_remote_user; auth_request_set $cil $upstream_http_x_tinyauth_location; proxy_set_header Remote-User $user; error_page 401 403 = @prihlasit; proxy_pass http://127.0.0.1:5000;} location = /tinyauth { internal; proxy_pass http://tinyauth:3000/api/auth/nginx; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Uri $request_uri; proxy_pass_request_body off; proxy_set_header Content-Length "";} location @prihlasit { return 302 $cil;}``` **Přihlašovací adresu si nesestavuj sám.** tinyauth ji pošle hotovou v hlavičce `x-tinyauth-location` a ty ji jen převezmeš. Návody, které tu adresu skládají ručně z parametrů, se rozbijí při první aktualizaci. ### Kdo smí kam Tady je past, na které to lidem tiše nefunguje, jak si myslí. **Kde se ověřuje, určuje proxy. Kdo smí projít, určuje tinyauth.** To jsou dvě nezávislé věci. Když někam přidáš middleware, zapnul jsi ověření totožnosti — ale ve výchozím nastavení je politika `allow`, takže aplikace bez vlastního pravidla pustí **kohokoliv přihlášeného**. Pro rodinný Jellyfin to je v pořádku, pro rozhraní hypervizoru ne. Omezení na konkrétní lidi nebo skupiny z Pocket ID se nastavuje v tinyauthu per aplikaci a aplikace se páruje podle hlavičky `Host`. Buď jí nastavíš `config.domain`, nebo se jméno aplikace musí rovnat první části doménového jména (`nas` ↔ `nas.example.cz`). Omezení podle IP adresy (`ip.allow`, `ip.block`) navíc **mlčky nedělá nic**, dokud nenastavíš `TINYAUTH_AUTH_TRUSTEDPROXIES`. Bez toho tinyauth vidí jako zdroj adresu proxy, ne klienta. ### Doména cookie Platí tu obecné pravidlo forward auth ze [schématu níž](#jak-forward-auth-funguje), ale tinyauth ho odvozuje po svém a stojí za to vědět jak: **vezme hostname z `TINYAUTH_APPURL` a odřízne první část.** Z `auth.example.cz` tedy vznikne cookie pro `example.cz`, kterou dostanou všechny služby na `*.example.cz`. To chceš. Dvě situace, kdy to nevyjde: - **`auth.sluzby.example.cz`** vyrobí cookie jen pro `sluzby.example.cz`. Služba na `nas.example.cz` ji nikdy nedostane a skončíš v nekonečné smyčce přihlášení → aplikace → přihlášení.- **Doména od dynamického DNS.** Když z `auth.neco.duckdns.org` vyjde `duckdns.org`, tinyauth **vůbec nenastartuje** a napíše, že doména je v seznamu veřejných přípon. Cookie pro `duckdns.org` by totiž dostal každý, kdo tam má subdoménu. Řešení je vlastní doména. Když se ti přihlášení točí dokola, otevři v prohlížeči vývojářské nástroje a podívej se, jestli se cookie posílá i na doménu chráněné služby. Skoro vždycky je to tohle. ### Kdy tuhle kombinaci nebrat Poctivě — nehodí se vždycky: - **Potřebuješ SAML.** Neumí ho ani Pocket ID, ani tinyauth. (Pro pořádek: Authelia taky ne. SAML dnes reálně znamená [Authentik](#authentik) nebo Keycloak.)- **Aplikace se potřebuje připojit k LDAP serveru.** Obě součásti umí LDAP jen číst — Pocket ID z něj synchronizuje uživatele, tinyauth proti němu umí ověřovat. Ani jedna ale není LDAP server, na který by se stará aplikace přihlásila. To je opět Authentik.- **Někteří uživatelé passkeys nezvládnou.** Pocket ID záměrně nemá hesla vůbec. Když víš, že to u tebe neprojde, chceš [Authelii](#authelia).- **Chceš podmíněný přístup** ve stylu „z domova stačí heslo, zvenku i druhý faktor", nebo pořádná role a oprávnění. tinyauth umí jen seznamy povolených a zakázaných; autor sám v dokumentaci píše, že na prostředí vyžadující RBAC nemá cílit. ## Authelia Nejrozšířenější řešení a dlouho výchozí volba. Přihlašovací stránka, dvoufaktor, pravidla podle domény a cesty. Vezmi ji tam, kde potřebuješ hesla nebo pravidla podle sítě. ```yamlservices: authelia: image: authelia/authelia:latest volumes: - ./authelia:/config networks: [web] labels: - traefik.enable=true - traefik.http.routers.authelia.rule=Host(`auth.example.cz`) - traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.example.cz - traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true - traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Email``` Chráněnou službu pak označíš: ```yamllabels: - traefik.http.routers.app.middlewares=authelia``` Pravidla v `configuration.yml`: ```yamlaccess_control: default_policy: deny rules: - domain: "verejne.example.cz" policy: bypass - domain: "nas.example.cz" policy: one_factor networks: [192.168.1.0/24] - domain: "nas.example.cz" policy: two_factor - domain: "*.example.cz" policy: two_factor subject: ["group:admin"]``` Ta dvojice pravidel pro `nas.example.cz` je užitečný vzor: **z domácí sítě stačí heslo, z internetu se vyžaduje i druhý faktor.** Pořadí rozhoduje, první shoda vyhrává. Uživatelé můžou být v souboru (`users_database.yml`) nebo v LDAP. Pro domácnost stačí soubor. ## Authentik Plnohodnotný poskytovatel identity. Umí OAuth2, OIDC, SAML, LDAP a proxy autentizaci. Vezmi ho, když: - máš aplikace, které umí OIDC a chceš skutečné jednotné přihlášení, ne jen bránu- chceš uživatelské rozhraní pro správu uživatelů místo editace YAML- potřebuješ propojit s Google nebo GitHub účty Cenou je podstatně vyšší spotřeba paměti a složitější nastavení. Pro tři služby je to kanón na vrabce. ## Jak forward auth funguje Užitečné vědět, protože se to špatně ladí naslepo. Schéma platí pro všechny brány, jména jsou z kombinace výše: ```mermaidsequenceDiagram participant K as Klient participant P as Proxy participant T as tinyauth participant I as Pocket ID participant S as Služba K->>P: GET nas.example.cz P->>T: mám ho pustit? T->>P: ne, neověřený P->>K: přesměrování na přihlášení K->>I: přihlášení passkeyem I->>T: totožnost a skupiny (OIDC) T->>K: cookie pro example.cz K->>P: GET nas.example.cz (s cookie) P->>T: mám ho pustit? T->>P: ano, je to martin P->>S: GET / + hlavička Remote-User S->>K: obsah``` Z toho plynou dvě věci, na kterých se to nejčastěji láme. **Cookie musí platit pro celou doménu.** Proto musí být brána na subdoméně stejné domény jako chráněné služby. `auth.example.cz` a `nas.example.cz` ano, `auth.jina.cz` a `nas.example.cz` ne. Jak přesně tu doménu odvozuje tinyauth je [výš](#doména-cookie). **Proxy se ptá při každém požadavku.** Když je brána dole, nedostaneš se nikam. Počítej s tím a nedávej za ni věci, které potřebuješ, když se něco pokazí — třeba monitoring nebo rozhraní hypervizoru. ## Předávání identity aplikaci Některé aplikace umí důvěřovat hlavičce `Remote-User` a přihlásit uživatele automaticky. Nextcloud, Grafana a Jellyfin to zvládají. tinyauth posílá dál pětici `Remote-User`, `Remote-Name`, `Remote-Email`, `Remote-Groups` a `Remote-Sub`. Dvě z nich jsou podmíněné: `Remote-Groups` dostaneš jen u přihlášení přes OIDC nebo LDAP, `Remote-Sub` jen u OIDC. Kdo se přihlásí lokálním účtem tinyauthu, přijde k aplikaci **bez skupin** — a aplikace, která podle nich přiděluje oprávnění, ho pak nikam nepustí. Je to pohodlné, ale **nebezpečné, pokud jde aplikace obejít**. Kdyby se někdo dostal na aplikaci mimo proxy a poslal si `Remote-User: admin`, je uvnitř. Proto musí služba poslouchat jen na loopback a být dostupná výhradně přes proxy — viz [Docker a firewall](Docker-a-firewall). ## Co za autentizaci nedávat **Cokoliv, co používá mobilní aplikace nebo API.** Forward auth je postavená na cookies a přesměrování v prohlížeči. Immich, Nextcloud a podobné mají vlastní přihlašování — nech ho a autentizaci před ně nedávej, jinak si rozbiješ synchronizaci. **Věci, které potřebuješ, když se to rozbije.** Monitoring, konzole hypervizoru, IPMI. Ty dej za [VPN](WireGuard). **Webhooky a callbacky.** Zaplacení, notifikace z externích služeb. Musí mít výjimku (`policy: bypass`) na konkrétní cestu. ## Časté chyby | Problém | Příčina ||---|---|| Přihlášení se točí dokola | cookie neplatí pro doménu služby — viz [doména cookie](#doména-cookie) || tinyauth vůbec nenastartuje | odvozená doména je veřejná přípona, typicky dynamické DNS || Přihlásí se kdokoliv, i když jsi nastavil skupiny | výchozí politika je `allow`, aplikace bez pravidla pustí každého || Pocket ID nepustí ani tebe | nový OIDC klient nemá povolenou žádnou skupinu, chybí **Unrestrict** || Passkey nejde zaregistrovat | Pocket ID neběží přes HTTPS || Aplikace nevidí uživatele | u Traefiku chybí `authResponseHeaders` || Omezení podle IP nefunguje | není nastavené `TINYAUTH_AUTH_TRUSTEDPROXIES` || Aktualizace nepřicházejí | starý obraz `steveiliop56/tinyauth`, přejdi na `tinyauthapp/tinyauth` || Rozbila se mobilní aplikace | forward auth nepatří před služby s vlastním klientem |