DEA.FI

Moonforge, osa 1

Moi taas.

Olen tässä monesti jo lupaillut, että kohta päästää koodin ääreen. Seuraavalla kertaa se lopulta tapahtuu!

Käytin tässä useamman kuukauden – mm. aivan kaiken kesäloman – siihen, että opettelin Vulkania ja säteenseurantaa nollasta asti. Uskollisimmat katsojat ovatkin ehkä nähneet tästä muutamia valikoituja kuvankaappauksia Twitterissä. Ihan mahdoton urakka tavoitella täydellisyyttä, vaikka aika pitkälle olenkin jo päässyt.

Mutta ei puhutakaan tällä kertaa siitä. Sen sijaan palataan toiseen rakkaaseen aiheeseen, eli backend-maailmaan. Suureksi yllätyksekseni töissä tulikin taukoa projektien välille, ja oli siten aikaa opetella Kubernetesta. Se on aika kiva. Tai ainakin tosi paljon parempi kuin tehdä samoja juttuja pelkästään Dockerin voimin.

Aiheesta voisi puhua paljonkin, mutta tämänkertainen aihe liittyy Kuberneteksessa hostattujen palveluiden selainkäytön turvaamiseen. Tässä ensimmäisessä osassa tarinoin lähinnä tästä matkasta ja niistä rajoitteista ja ongelmista, mihin olen törmännyt. Seuraavassa osassa tarjoan erään ratkaisun.

Mutta siis – tällä hetkellä noita turvattavia palveluja ei ole kovinkaan montaa, lähinnä Traefikin dashboard ja Grafana. Ja Prometheus. Joista jälkimmäisin on se ongelmatapaus, mistä kaikki lähti liikkeelle – joskin asiaan liittyy vahvasti myös foliohattuilu. Kohta siitä lisää :>

Traefik on siis Kuberneteksen kanssa käytettävä Ingress-kontrolleri, eli käytännössä layer 7 kuormantasain omalla säännöstöllään. Eli kun siellä Kuberneteksessa on vaikka tuo nättejä tilastoja näyttävä Grafana, niin sitä varten on tehty Traefik-sääntö, joka ohjaa liikennettä sille, kun HTTP-pyynnöstä löytyy tietty Host-headerin arvo. Ihan perus juttuja.

Koska en halua koko maailman pääsevän noihin vain minulle tarkoitettuihin palveluihin käsiksi, päädyin aluksi ottamaan käyttöön Authelia-kirjautumispalvelun, ja sitten määrittämään Traefikkiin forward auth middlewaren noille rajatuille palveluille. Käytännössä tällöin jokainen HTTP-pyyntö välitetään ensin Authelialle, joka sitten katsoo, onko sallittu käyttäjä kirjautunut sisään, ja vain siinä tapauksessa Traefik päästä pyynnön läpi sille alkuperäiselle palvelulle, sekä halutessa sisällyttää muutaman käyttäjän identiteetin sisältävän headerin pyyntöihin mukaan.

Ja tämä toimii hienosti. Samalla mahdollistuu kerralla SSO kaikkiin tuettuihin palveluihin. Esim. Grafanan saa konffattua siten, että sisäänkirjautuminen korvautuu noilla em. headereilla. Ja sitten taas jos on jokin vielä yksinkertaisempi sovellus, niin sen ei edes tarvitse olla tietoinen koko asiasta, sillä pyynnöt eivät vain tule perille ennen kuin se validi kirjautumissessio on olemassa. Ja kun on, niin sitten se pyyntö vasta tulee läpi kaikessa kokonaisuudessaan.

Mutta tässä on myös se ongelma. Siinä kaikessa kokonaisuudessaan. Jotta tuo kirjautumissessio saadaan mukaan, niin käyttäjän selaimessa on oltava sen sisältävä keksi koko ylätason domainille. Authelian tapauksessa se on aina se sama kirjautumissession avaimen sisältävä keksi, ja sen kautta Authelia osaa sitten selvittää käyttäjän ja käyttäjän oikeutukset. Se varsinainen foliohattuongelma on siis se, että se sama keksi käy kaikkialle. Jos teen pyynnön esimerkiksi Grafanaan, niin se keksi menee ensin Authelialle Traefikin forward authin kautta, ja sen jälkeen kaikkien muidenkin alkuperäisten HTTP-headereiden mukana itse Grafanalle. Joka ei sillä keksillä mitään tee, mutta siellä se kulkee mukana.

Mutta entäs jos tekeekin? :O Jos kyseessä olisikin jonkinlaisen tietoturvamurron kohteeksi joutunut palvelu, niin se voi sitten helposti välittää tuon keksin hyökkääjälle, joka voi sitten sen kautta saada pääsyn kaikkiin muihin palveluihin, mihin käyttäjällä on pääsy.

Mutta onneksi Traefik on monipuolinen työjuhta, johon tuon forward authin lisäksi saa monia muitakin middlewareja muokkaamaan HTTP-pyyntöjä, jotka kulkevat sen kautta. Paitsi että keksejä niillä ei kyllä voi poistaa. Itse voisi toki koodata oman lisäosan, mutta se vaatii sitten maksullisen Enterprise-version.

No vittu.

Ehkä homman voisi ratkaista jollain muulla tavalla, mutta sitten se ei ainakaan luultavasti olisi enää läheskään niin helppo integroitu kokonaisuus kuin jonkin sovelluksen reitin kylkeen lisätty lisäsääntö.

Ja lisäksi jos palataan siihen ongelman alkuun Prometheukseen, niin kaipasin jonkinlaista palveluiden välistä luvitusta, joka kuitenkin sisältää käyttäjän identiteetin. Minulla voisi sitten olla vaikkapa selainlaajennos, joka tarjoaa mukautetun uuden tyhjän tabin näkymän, ja hakee sinne sitten käyttäjän oikeuksilla tilastoja Prometheuksesta oman backendinsä kautta. Ja tähän backendiin käyttäjä voisi sitten kirjautua, ja samalla antaa palvelulle luvan tehdä käyttäjänä pyyntöjä Prometheukseen, mutta ei mihinkään muualle.

Tämähän kuulostaa ihan OpenID Connectilta ja JWT:ltä, mihin on leivottu sisään kenelle ja mitä varten se on. Tuo Authelian yksi läpinäkymätön keksi ei sitä ole, mutta vakiomallinen JWT olisi erinomainen asia defense in depth -mielessäkin, kun pelkän forward auth -middlewaren lisäksi se itse palvelu voisi vielä tarkastaa, että kaikki todellakin on kunnossa. Nyt kaikki on vain sen yhden pienen asetusrivin varassa.

Autheliaa voisi toki käyttää OIDC-providerina, mutta se on oma ongelmansa, kun kaikki palvelut eivät sitä tue, ja vaatisivat sitten jonkunlaisen proxyn sitä varten, ja se puolestaan vaatii sitten oman konffit ja tilan ylläpitämiset ja kaikkea. Sitten oltaisiin taas kovin kovin kaukana siitä yhdestä yksinkertaisesta Traefik-säännöstä, millä se liikenne alun perin saadaan palveluun virtaamaan.

Mutta jos kuitenkin edes yrittäisin. Noin ne asiat varmaan kuitenkin kuuluisi tehdä. Siihen uuteen hienompaan identiteettipalveluun voisi sitten varmasti sisällyttää integroidummin käyttöoikeudetkin siten, että minä saan tehdä mitä lystään, ja kaverit pääsevät vain muutamiin tarkkaan valittuihin palveluihin.

Ja minähän yritin.

Tutkailin asiaa, ja Keycloak vaikutti hyvältä seuraavalta askeleelta. Red Hatin ylläpitämä pitkäikäinen ja monipuolinen IDP, millä saisi varmastikin tehtyä kaiken tarvittavan. Ja tukee myös kaksivaiheista tunnistautumista, sekä Yubikeytä. Paitsi että se on vähän liiankin monipuolinen, ja niitä asioita mitä halusin tehdä ei voinut saada aikaan kuin vain monimutkaisemmalla konfiguraatiolla.

Esim. vaikka tuo palveluiden rajaus ei ole suoraan tuettu käyttötapaus. Oikein tapa olisi Keycloakin erillinen auktorisointipalvelu, mutta se vaatisi kaiken säädön lisäksi vielä erikseen tukea jokaiselta palvelulta. Keycloak nimittäin generoi JWT:t täysillä oikeuksilla, mutta sitten se auktorisointipalvelu kertoisi, että ovatko oikeudet päteviä. Jos vääriä tokeneita ei haluaisi alun perinkään saada maailmalle, niin toinen ratkaisuksi ehdotettu tapa olisi korvata kunkin palvelun osalta kaikki eri kirjautumis-flow’t omilla versoilla, jotka ovat kopioita vakioista, mutta sillä erotuksella, että ne tekevät halutut käyttöoikeustarkastukset oman koodin avulla. Mutta:

a) Uploadatut koodit on deprekoitu, ja mikään opas ei ole kunnolla päivitetty uuteen aikaan.

b) Ne mitkä olevinaan ovat, nii ei. Bundlatut JavaScript-tiedostot ei vaan toimi, vaikka pitäisi.

c) Muut tavat ratkaista asia kaatuivat hallintapaneelin bugiin, joka on nyttemmin tosin korjattu. Mutta ei silloin kun asian kanssa taistelin.

d) Ja siltikin kaikkien flow’ien muuttaminen jokaiselle eri palvelulle on ihan kauhea duuni, ja…

e) … minulla ei ole niin vankka osaaminen, että voisin varmuudella sanoa, että kaikki nuo eri flow’t on muutettu oikealla tavalla, ja

f) Varmuus siitä, että esim. jonkun päivityksen tai muun asetusmuutoksen jälkeen ei aktivoidukaan joku sellainen flow, mistä en olekaan tietoinen, ja sieltä ne tarkastukset sitten puuttuvat. Ei näin perustavanlaatuista osaa tietoturvasta voi rakentaa näin hauraalle pohjalle.

Sanotaanko, että hommasta jäi aika paha maku. Mutta ehkä oman IDP:n ylläpitäminen ei edes voikaan olla kovin helppoa. Vaikka siinä sitten se self-hosted foliohattu vähän lennähtääkin jo päästä pois, niin vaihtoehdoksi minulle ehdotettiin hostattua Auth0-palvelua. Sen pitäisi sisältää vakiona haluamani käyttöoikeusasiat, ja siten siis generoida vain sellaisia JWT-tokeneita, mihin käyttäjällä onkin oikeasti oikeus. Sen jälkeen ongelmana olisi ”vain” luoda se OIDC-proxy niille sitä tukemattomille palveluille – mielellään jotenkin siten, että jokaista palvelua ei kuitenkaan tarvitsisi erikseen rekisteröidä erikseen. Pitäisi sitten tehdä joku oma Kuberneteksen metadataa lukeva työläinen, joka tarpeen mukaan ylläpitää sovellusrekisteriä ja ja ja… Kuulostaa työläältä.

Vaan enpäs edes päässyt sinne asti. Tietoturva oli koko tämän jutun pointti, ja Auth0 halusi, että minä yksittäisenä käyttäjän maksaisin kaksivaiheisesta tunnistautumisesta 240 dollaria + alvit, joka vitun kuukausi. Siihen rahaan saisi jo Elisalta 100/100 laajakaistan. En vitussa maksa. Kolme sataa pelkästä kaksivaiheisesta tunnistautumisesta, ja joka kuukausi…

Joten mitä vaihtoehtoja minulle enää jää? Minulla ei ole osaamista Keycloakin vaatimiin monimutkaisiin asioihin, ja Auth0 on ihan liian kallis. Ja siltikin vaatisi sen oman proxyn niille ”legacy”-palveluille.

No tietenkin se mitä teen parhaiten, eli keksin pyörän uusiksi tekemällä oman version näihin omiin tarpeisiin. Tämä voi kuulostaa aika pahalta, mutta vakuutan, että lopputulos on suhteellisen yksinkertainen. Ja vain näihin omiin tarpeisiin, eli lopputulos on sitten yksinkertainen ja helppo säätää, kun ei ole mitään ylimääräisiä ominaisuuksia mihin kompastua. Se on se ydinasia siinä. Ja vaikkei valmista ehkä koskaan tulisikaan, niin kaikkein tärkeimpänä pointtina se matka, eli oppisin siinä samalla sitten sen, että mitä kaikkea asiaan lopulta liittyykään. Mutta – spoiler spoiler – tällä kertaa tulikin valmista, ja pyörän sijaan minulla onkin nyt rengas. Eihän se ihan samanlainen ole, mutta sopii tähän käyttötarkoitukseen niin paljon paremmin.

Mutta tästä lisää siis ensi kerralla :)

Pysykää siis kanavalla; YouTube-mantrat ja silleen. Moi moi.