Sziasztok kollégák,
múlt héten kicsit elszaladt velem a paci és újraírtam egy projektet amit régesrégen már egyszer megcsináltam: egy olyan SSH szerver ami Docker containereket vagy Kubernetes podokat indít.
A cél az hogy a user SSH-zva közvetlenül a konténerben kössön ki mindenféle shellout és egyéb huncutságok nélkül. Ezt aztán lehet használni pl. webhostinghoz ahol a userhez tartozó site-ok a containerbe vannak mountolva a permission mátrixnak megfelelően, honeypotnak vagy egyéb érdekes és izgalmas dolgokra.
Hogy dinamikusan confolható legyen az egész cucc az autentikációt valamint a container környezet konfigurálását egy külső szolgáltatáson keresztül végzi.
Most hozzátok a kérésem: üssétek, nyomkodjátok, találjatok benne bugokat.
- Forráskód: https://github.com/janoszen/containerssh
- Quick start Docker környezetre: https://github.com/janoszen/containerssh/tree/master/example
- Cikk hogy hogyan működik: https://pasztor.at/blog/ssh-direct-to-docker
Köszönöm a segítségeteket!
Update: megjelent a 0.1.0-s verzió
Hozzászólások
Ket kerdes:
Blog | @hron84
via @snq-
Jelenleg nem, bár gondolkoztam egy "dockerexec" illetve "kubeexec" backenden ami pont ezt valósítja meg. A webhosting környezetnél ez annyira nem szempont. (A ContainerSSH egy elődje pont webhostingot szolgált ki és soha nem volt erre panasz.) A nehézség itt az hogy Kubernetes környezetben a pod adott esetben átkerül máshová amitől a state nem marad meg.
Félig. Jelenleg ha megszakad a kapcsolat akkor eltakarítja a containert azonnal. Ha a ContainerSSH maga pusztul meg akkor erre sajnos még nem képes, és még nem is egészen tudom hogy hogyan oldom meg mivel a config szerver dinamikusan adhat át configot, tehát a ContainerSSH eleinte adott esetben nem is tudja hogy hova fog csatlakozni.
Ez egy nagyszerű dolog! Synology NAS-ra megpróbálom felrakni.
Elvben működnie kell. Jelenleg csak x86-ra van fent a Docker hubon de semmi nem szól az ellen hogy lefordítsd másra ha esetleg kisebb Synologyd van.
x86 elég, mert arra van official docker, míg kisebb synologykra nincs sajna.
Kuberneteses use case-t hogy képzeljem el, hol futna ez a szolgáltatás? Külön valami frontend szerveren, vagy k8s PODként? Utóbbi esetben a service exposure kicsit problémás lehet (NodePort)
Az embert 2 éven át arra tanítják hogyan álljon meg a 2 lábán, és hogyan beszéljen... Aztán azt mondják neki: -"Ülj le és kuss legyen!"..
Mindkettő működik. Vagy NodePort-tal expose-olod, vagy pedig load balancer erőforrás mögé rakod. Ha a host key-t secretben tárolod akkor semmi nem szól az ellen hogy akár több példányt futtass belőle.
Megpróbálok belőle összerakni valami Kubernetes YAML filet ami példaszerűen deployolja, hátha úgy egyszerűbb lesz.
Szia!
Nagyon király! Ment a csillag.
Nekem csak a Docker nem tetszik benne. Sokkal nyugodtabb vagyok, amikor nem kell egy root joggal futó szupercsillagharcos daemon-hoz kötnöm magam.
Valamelyik Red Hat alternatíván, pl. Podman-en gondolkoztál már? Nekem nagyon bevált.
(A docker debug kurzusodból pár éve nagyon sokat tanultam, hatalmas köszi érte!!!)
"If I had six hours to chop down a tree, I'd spend the first four hours sharpening the axe."
Nem kötelező Dockert használni, használhatsz Kubernetest is. A k3s pl. könnyebben települ mint a Docker. Kubernetessel tudsz pod- és network security policyt tenni rá.
A Podmannel az a baj hogy nincs HTTP API-ja (mivel ugye nincs daemon), így ha beépítem a Podman támogatást az csak és kizárólag Linuxon fog működni. Lehetne build flagekkel trükközni de ez jelenleg elég fájdalmas megoldásnak tűnik amíg nincs értelmes plugin rendszer.Rájöttem hogy marhaságot beszélek, az API doksi úgy néz ki mintha lehetne legalább socketen keresztül beszélgetni vele. Valamiért úgy volt meg, hogy a Podman daemonless. Légyszi nyiss egy feature requestet Githubon a leírással és ha kap 1-2 upvote-ot átküzdöm magam rajta.
A Dockert is lehet rootless módon indítani.
Na közben picit részletesebben elolvastam a Podman doksit.
Egyrészt ez akasztott meg:
Másrészt pedig úgy tűnik, hogy ahhoz hogy az API elérhető legyen sajnos kötelezően futnia kell a Podman service-nek, azaz semmit nem nyersz vele a Dockerrel szemben. A konténer kezelést magát pedig nem szívesen gyógyítanám bele az SSH szerverbe hacsak nincs valami nyomós indok rá.
containerd?
Megoldhatónak tűnik első ránézésre. Használnád is?
Problémamentesen használjuk PROD környezetben, igaz csak néhány száz konténerrel.
Ja, nem az, hanem hogy az SSH szervert használnád-e. A containerd használhatóságával kapcsolatban nincsenek aggályaim.
Kijött az új Podman és van benne Docker-kompatibilis REST API. Megnéztem az API leírást és mindent támogat ami kell a ContainerSSH-hoz, így ezzel is tudod használni.
Ha jól értem, az a lényege, hogy egy ssh szerveren keresztül lövöd be a konténereket másik szerveren?
Ha igen, akkor nem lenne elég simán a context-et használni?
Igen, ez a lényege, plusz csinál egy "attach"-et a konténerre és annak a ki/bemenetét összeköti az SSH-val.
Context alatt mit értünk itt?
és erre még nem volt out-of-the-box megoldás?
Mert az ötlet egyébként baromi jó :)
Én nem találtam.
csak fogalom nelkul pofazok, de systemd-fele tcp socket altal inditott service altal inditott container megoldas nem mukodhet? mondom, nem tudom a konrketumokat, de systemd van eleg nagy rak ahhoz, hogy ilyent IS tudjon mar.
Én sem vagyok Systemd szakértő, de ha jól értettem az úgy működne, hogy jön egy kapcsolat a 22-es portra, mire ő indít egy konténert amiben benne fut az SSH szerver ami fogadja a kapcsolatot. Azt nem oldja meg, hogy usertől függően pl. más könyvtárakat mountoljon. Azt persze megteheted, hogy rendszer szinten confolod a usereket és bekorlátozod arra hogy csak docker run-t vagy kubectl run-t futtathat, de őszintén, mennyi az esélyed úgy beconfolni hogy ne legyen benne kibúvó? Akkor már inkább megbízom a Go SSH libjében.
Most gyorsan összeraktam egy authorized_keys command-os megoldást, és valami működést azért sikerült belőle kicsiholnom
szóval ssh-keygen után egy új user .ssh/authorized_keys-ébe:
command="docker run -it --rm ubuntu:18.04",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-rsa <ssh kulcs>
Még annó gitolite-nál láttam ezt így használva, sok ssh kulccsal is működik (ezért kell git clone-nál a git user-rel "belépni"), aztán lehet scriptelni, hogy ki milyen command-ot futat.
Nem tudom ez mennyire biztonságos, és mik a hátülűtői, aki jobban ért hozzá megmondaná, hogy ez miben tud kevesebbet?
(Közben megtaláltam a commentet lentebb, azért itt hagyom, hátha valaki nem ismerte)
Én nem ismertem, köszi!
Igen, ez lenne az alternatíva. Sajnos én sem tudom, hogy ez mennyire biztonságos, de az biztos, hogy meg kell oldanod a user managementet, illetve a Docker paraméterezését. ContainerSSH-val ez egy pár soros HTTP szerver.
Edit: még be kellene illesztened azt, hogy $SSH_ORIGINAL_COMMAND. Ott viszont izgalmas kérdés lesz, hogy nem lehet-e kijátszani és a Dockernek plusz paramétereket átadni.
és mik a hátülűtői
Pl. ki és hogyan fogja feltakarítani a leszakadó kapcsolatok után bentragadt konténereket? (ez amúgy Jani kódjánál is megoldandó feladat, csak az ő kódjával ellentétben itt nem nagyon látom a triviális megoldás lehetőségét, amihez nem kell pl. az sshd-be mélyen beletúrni).
A másik, hogy így a gw gépre a usereknek identitást kell adni, míg ez elkerülhető a Jani verziójában - ez nagy könnyebbség, ha pl. MSSP-t akarsz játszani, és tenantonként mindenkinek saját user adatbázis lehetőséget kell adnod (= otthon, a saját kis LDAP-jában/AD-jában szeretné a saját usereit menedzselni).
A leszakadó kapcsolatok utáni eltakarítás már megoldott, a ContainerSSH lerohadása után hátramaradó konténerek jelentenek még problémát.
Szerintem valami sqlite-ba logolod oket container id-vel, az elmeletileg egyedi a hoston.
Blog | @hron84
via @snq-
Ok, és mizu a redundanciával? Illetve akkor letárolom az összes látott Docker / Kubernetes backend hozzáférési adatait? Sajnos nem ennyire egyszerű.
Nem akarsz ilyen stateful szarságot.
Inkább rakni a konténerekre/podokra cimkéket, és vagy a létrehozó processzt újraindítani, és újraindítás után a cimkék alapján ismerje fel a korábbi futásának "ereményeit", vagy valami tőle független nagyon egyszerű takarítót futtatni (esetleg per Docker host / K8s cluster), ami valami heartbeatszerű jelzés hiánya esetén takarít. A K8s esetén a heartbeat lehet egy rendszeresen frissített státuszmező, az kb. semmibe nem kerül. A Docker nem tudom, hogy tud-e bármi hasonlót...
Erre gondolsz?
https://docs.docker.com/engine/reference/builder/#healthcheck
Nem. Ez csak arra jó, hogy a Docker újraindítson saját magán egy megállt komponenst.
Mi azt szeretnénk, hogy ha bármi okból a konténereket indító és vezérlő komponens abba a helyzetbe kerül, hogy nem tudja vezérelni a konténereket, akkor helyette valaki más takarítson. Mivel ők vagy ugyanazon a gépen futnak, vagy nem, így az is hibalehetőség, hogy mindenki működik, csak éppen a hálózat nem. Emiatt az nem segít senkin, ha ilyenkor újraindítom akármelyik komponenst, az segít, ha a konténer futtató környezeten belül van ez a takarító processz, mert nála csak azzal a hibával kell számolni, hogy rosszul írtam meg, és leáll/végtelen ciklusba kerül.
Sima dockerben is lehet csinálni random labeleket, aztan a docker socketen keresztül futtatni valamit morickat ami takarit, ez gondolom lehet az is, amit janosszen irt (még nem neztem,, de todo).
Valóban, de a ContainerSSHnál az a problematika, hogy a backendeket teljesen dinamikusan tudod konfigurálni. Vagyis a ContainerSSH nem tudja hogy milyen Kubernetes vagy Docker backendjeid vannak, csak a kapcsolat létrejöttekor eszmél rá. Erre valamilyen fájdalommentes megoldást kell találni.
De ez önmagában miért baj? Gondolom a végén csak belebeszélsz a docker socketbe (legalábbis az example composeod beteszi volumeként). Ha van egy containerssh instance specifikus prefixed, akkor azzal tudsz labelezni, és egyrészt induláskor ki tudod takarítani a fenébe az előző futás után maradt szemetet, másrészt egyébként is tudod használni pl az event apit, ami egyébként az rm problémára is megoldást adhat, mert az event streamen visszajön a konténer exit codeja, nem feltétlen kell neked direkt superviseolni, ahogy gondolom teszed. pl:
A probléma abban rejlik hogy induláskor a ContainerSSH-nak nem feltétlenül vannak meg a hozzáférési adatok a backendhez mivel azokat a config szerver is visszaadhatja usertől függően. Tehát pl. tudsz olyat csinálni hogy minden usert a saját Docker hostjára vagy Kubernetes clusterére küldesz be és ezeket csak a config szerver ismeri (amit Te írsz), a ContainerSSH nem.
Az rm egy érdekes ötlet de sajnos csak a Docker backenddel működne. A Kubernetes API nem támogatja a podok törlését futás után.
Na jó, valamikor igyekszem elolvasni, hogy pontosan hogy is működik, aztán jövök ötletelni :) (Mert azzal elsőre mélyen egyetértek, hogy nem kéne valami single point of state adatbázis, de látom, hogy van itt azért architektúra, amit fel kéne fogni)
Olvasd el a readme-t, az elmagyarázza az alapötletet. :)
Ezt terveztem :-)
Na, megnézegettem. Mikor csak átfutottam, akkor még féltem tőle, hogy kódot is kell túrni, mert a hogyan indulunk környékén lesz valami varázslat, de a readmet olvasva sajnos az van, hogy annyira frankó dinamikus architektúrát rajzoltál, hogy azzal kissé lábon is lőtted magad.
Mivel még a backendek listája sem ismert, ezért esélyed sincs saját hatáskörben stateless megoldani a dolgot. Kb a következő lehetőségeid vannak, ahogy én látom:
Illetve ha te takarítasz, akkor az tulajdonképp lehet opcionális, ha valakinek nem kell, akkor goto 1.
Én inkább arra gondoltam, hogy bevezetek plusz egy endpointot a config szerveren ami visszaad egy backend-listát takarítás céljából induláskor. Illetve természetesen ugyanezt a konfigurációban is megadhatod. Ha nem adsz meg semmit akkor nem takarít.
Persze, lehet tenni ezt a config szerverbe is, de tartani neki is kell valahol, bár abban igazad van, hogy ő jó eséllyel egyébként is rendelkezik ezzel az infóval :)
ilyenkor a futo docker nem kerul stop allapotba? egyszeruen csak torolni kell a stoppedeket. nyilvan valahogy tarolni hogy melyik tartozik ehhez. pl nevben valami prefix.
A vegtelen ciklus is vegeter egyszer, csak kelloen eros hardver kell hozza!
Igazából lehet, hogy egy --rm a docker runnak pont elég
A ContainerSSH nem használja a --rm flaget mert akkor nem tudja lekérdezni a konténer exit kódját és visszaküldeni az SSH csatornán keresztül.
Igazából csak dockerből indultam ki, ott van egy úgynevezett context amivel be tudsz állítani egy szerver elérést egy azonosítóval (ssh, kulcsot pedig .ssh/config).
Utána ki tudsz adn bármilyen parancsot amit egy adott szerveren fog futtatni a docker.
pl.:
#A valami-szerver alatt futó docker konténereket listázza
docker --context valami-szerver ps
#Ez pedig a valami-masik szerveren
docker --context valami-masik ps
Nagy hiányosság volt, hogy docker-compose alatt ez nem működött, csak dev verziókban volt benne, az is bugos volt tapasztalataim szerint.
Legutóbb már úgy láttam, hogy stable verzióban is bekerült.
Ja értem. (Itt hangsúlyosan nem arról van szó hogy nekem kell containerekbe SSH-znom, hanem arról, hogy nagy számú usernek akarsz SSH szolgáltatást nyújtani.)
Persze, meg tudod csinálni, hogy minden usernek van egy context-je és az OpenSSH-t rákényszeríted hogy minden usernek fixen a "docker" parancsokat hajtsa végre (force-command ha jól emlékszem). Nekem ezzel az a bajom, hogy 1. minden usernek léteznie kell a gazdagépen 2. a konténeren kívül kerül végrehajtásra egy külső parancs, tehát ha abban van bármivéle escape baki, el van csettintve az OpenSSH config ezer tekerentyűje közül egy, stb. akkor a user a konténeren kívül köt ki és mindent lát.
Arról lehet vitatkozni, hogy az OpenSSH elconfolása-e a nagyobb veszély vagy a Go SSH libje, de nekem személy szerint az OpenSSH-s megoldás eléggé bántja a szememet.
Köszi a választ. Lényegében a hoszting szoftvert akartad akkor megoldani, értem. Így valóban biztonságosabb mint a context.
Szuper! Ügyes vagy, köszönjük! Én már nyomkodom.
De ez nem is javaban van irva, mi tortent?!?! :P
Efpe barátom, látom nem olvastad el a cikket. :D (Lebuktál.) A Javas verziót megírtam 2017-ben de sajnos az SSH library (Apache Mina) és a Docker library nem egészen működött együtt úgy ahogyan kellene.
mi a meglepo? sosem olvasok el semmit, csak esz nelkul kommentelek :'(
Ikszdé
Amikor Javas SSH megoldást kerestem, a Mina nem jött szembe. Köszi, így van nég egy kód, amiből kiindulhatok. Javas kód lesz, ha egyszer elkészült.
Ha érdekel, megosztom a kódot ami nálam a lemezen van, de nem “alkalmas a publikációra”. Arra figyelj, hogy a Mina default cipher setje már nem aktuális, tekergetni kell rajta.
OpenSSH kód alapján kezdtem nulláról, merrt nem találtam olyat, ami csak az encryptiont kezeli, és a többit vagyis a plain text csomagokat már szabadon kezelhetem. A titkosítást megírni meg elég bonyolult. Host key, sokféle algo. Én kitettem a kódomat githubra, de még semmire sem jó.
Köszi, bármi segítséget szívesen fogadok. Az nagyon hasznos, ha látom, hogyan kell használni ezt a libet.
Irj rám privátban, adok egy kicsontozott változatot abból az SSHD-ból amit anno írtam. (Megszereztem rá a jogokat de nem éppen szép a kódja.)
subscribe
A kollégák találtak benne egy bugot, ha használjátok frissítsetek 0.2.0-ra. Ezen felül kapott egy weboldalt és az auth-config szerver egy normális API dokumentációt.
Időközben megjelent a 0.3.0 szép új Prometheus metrikákkal. Kollégák, ismerősök jelentik, hogy a 0.2.2-t Augusztus eleje óta használják és stabilan működik.