ProHoster > Blog > uprava > Kako smo mi u ZeroTechu povezali Apple Safari i klijentske certifikate s websocketima
Kako smo mi u ZeroTechu povezali Apple Safari i klijentske certifikate s websocketima
Članak će biti koristan onima koji:
zna što je Client Cert i razumije zašto su mu potrebni websocketi na mobilnom Safariju;
Želio bih objaviti web usluge ograničenom krugu ljudi ili samo sebi;
misli da je sve već netko napravio i htio bi svijet učiniti malo ugodnijim i sigurnijim.
Povijest websocketa započela je prije otprilike 8 godina. Ranije su se metode koristile u obliku dugih http zahtjeva (zapravo odgovora): korisnikov preglednik šalje zahtjev poslužitelju i čeka da on nešto odgovori, nakon odgovora se ponovno spaja i čeka. Ali onda su se pojavili websocketi.
Prije nekoliko godina razvili smo vlastitu implementaciju u čistom PHP-u, koji ne može koristiti https zahtjeve, budući da je ovo sloj veze. Nedavno su gotovo svi web-poslužitelji naučili proxy zahtjeve preko https-a i podržavali connection:upgrade.
Kada se to dogodilo, websockets je postao gotovo zadana usluga za SPA aplikacije, jer kako je zgodno pružiti sadržaj korisniku na inicijativu poslužitelja (prenijeti poruku drugog korisnika ili preuzeti novu verziju slike, dokumenta, prezentacije) koju netko drugi trenutačno uređuje) .
Iako Client Certificate postoji već duže vrijeme, još uvijek je slabo podržan jer stvara mnogo problema kada ga se pokušava zaobići. I (možda :slightly_smiling_face: ) zato ga IOS preglednici (svi osim Safarija) ne žele koristiti i traže ga od lokalnog spremišta certifikata. Certifikati imaju mnoge prednosti u usporedbi s login/pass ili ssh ključevima ili zatvaranjem potrebnih portova kroz vatrozid. Ali nije riječ o tome.
Na iOS-u je postupak instaliranja certifikata prilično jednostavan (nije bez specifičnosti), ali općenito se radi prema uputama kojih ima jako puno na internetu i koje su dostupne samo za preglednik Safari. Nažalost, Safari ne zna koristiti Client Sert za web sockete, no na internetu postoji mnogo uputa kako napraviti takav certifikat, no u praksi je to nedostižno.
Da bismo razumjeli websockets, koristili smo sljedeći plan: problem/hipoteza/rješenje.
Problem: ne postoji podrška za web utičnice kada proxy zahtjevi prema resursima koji su zaštićeni certifikatom klijenta na mobilnom pregledniku Safari za IOS i drugim aplikacijama koje imaju omogućenu podršku za certifikate.
Hipoteze:
Moguće je konfigurirati takvu iznimku za korištenje certifikata (znajući da ih neće biti) za websockete unutarnjih/vanjskih proxy resursa.
Za websockete možete uspostaviti jedinstvenu, sigurnu i branjivu vezu koristeći privremene sesije koje se generiraju tijekom uobičajenog (ne-websocket) zahtjeva preglednika.
Privremene sesije mogu se implementirati pomoću jednog proxy web poslužitelja (samo ugrađeni moduli i funkcije).
Privremeni tokeni sesije već su implementirani kao gotovi Apache moduli.
Privremeni tokeni sesije mogu se implementirati logičkim projektiranjem strukture interakcije.
Vidljivo stanje nakon implementacije.
Cilj: upravljanje uslugama i infrastrukturom treba biti dostupno s mobilnog telefona na iOS-u bez dodatnih programa (kao što je VPN), unificirano i sigurno.
Dodatni cilj: ušteda vremena i resursa/telefonskog prometa (neke usluge bez web socketa generiraju nepotrebne zahtjeve) uz bržu isporuku sadržaja na mobilnom internetu.
Kako provjeriti?
1. Otvaranje stranica:
— например, https://teamcity.yourdomain.com в мобильном браузере Safari (доступен также в десктопной версии) — вызывает успешное подключение к веб-сокетам.
— например, https://teamcity.yourdomain.com/admin/admin.html?item=diagnostics&tab=webS…— показывает ping/pong.
— например, https://rancher.yourdomain.com/p/c-84bnv:p-vkszd/workload/deployment:danidb:ph…-> viewlogs — показывает логи контейнера.
2. Ili u konzoli za razvojne programere:
Testiranje hipoteze:
1. Moguće je konfigurirati takvu iznimku za korištenje certifikata (znajući da ih neće biti) za web utičnice unutarnjih/vanjskih proxy resursa.
Provjera certifikata događa se nakon zahtjeva za proxy resurs, to jest, rukovanje nakon zahtjeva. To znači da će proxy prvo učitati, a zatim prekinuti zahtjev prema zaštićenoj usluzi. Ovo je loše, ali nije kritično;
b) Na osnovnoj razini dopustite ssl bez certifikata.
SSLVerifyClient zahtijeva => SSLVerifyClient izborno, ali to smanjuje razinu sigurnosti proxy poslužitelja, jer će takva veza biti obrađena bez certifikata. Međutim, možete dodatno zabraniti pristup proxy uslugama sljedećom direktivom:
RewriteEngine on
RewriteCond %{SSL:SSL_CLIENT_VERIFY} !=SUCCESS
RewriteRule .? - [F]
ErrorDocument 403 "You need a client side certificate issued by CAcert to access this site"
SSLVerifyClient optional
RewriteEngine on
RewriteCond %{SSL:SSL_CLIENT_VERIFY} !=SUCCESS
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule .? - [F]
#ErrorDocument 403 "You need a client side certificate issued by CAcert to access this site"
#websocket for safari without cert auth
<If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'">
<If "%{HTTP:Upgrade} = 'websocket'">
...
#замещаем авторизацию по владельцу сертификата на авторизацию по номеру протокола
SSLUserName SSl_PROTOCOL
</If>
</If>
Uzimajući u obzir postojeću autorizaciju od strane vlasnika certifikata, ali uz certifikat koji nedostaje, morao sam dodati nepostojećeg vlasnika certifikata u obliku jedne od dostupnih varijabli SSl_PROTOCOL (umjesto SSL_CLIENT_S_DN_CN), više detalja u dokumentaciji:
2. Za websockete možete uspostaviti jedinstvenu, sigurnu i zaštićenu vezu koristeći privremene sesije koje se generiraju tijekom uobičajenog (ne-websocket) zahtjeva preglednika.
Na temelju prethodnog iskustva, trebate dodati dodatni odjeljak u konfiguraciju kako biste pripremili privremene tokene za web socket veze tijekom redovnog zahtjeva (ne-web socket).
#подготовка передача себе Сookie через пользовательский браузер
<If "%{SSL:SSL_CLIENT_VERIFY} = 'SUCCESS'">
<If "%{HTTP:Upgrade} != 'websocket'">
Header set Set-Cookie "websocket-allowed=true; path=/; Max-Age=100"
</If>
</If>
#проверка Cookie для установления веб-сокет соединения
<source lang="javascript">
<If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'">
<If "%{HTTP:Upgrade} = 'websocket'">
#check for exists cookie
#get and check
SetEnvIf Cookie "websocket-allowed=(.*)" env-var-name=$1
#or rewrite rule
RewriteCond %{HTTP_COOKIE} !^.*mycookie.*$
#or if
<If "%{HTTP_COOKIE} =~ /(^|; )cookie-names*=s*some-val(;|$)/ >
</If
</If>
</If>
Testiranje je pokazalo da radi. Kolačiće je moguće prenijeti sebi putem preglednika korisnika.
3. Privremene sesije mogu se implementirati pomoću jednog proxy web poslužitelja (samo ugrađeni moduli i funkcije).
Kao što smo ranije saznali, Apache ima dosta osnovnih funkcija koje vam omogućuju stvaranje uvjetnih konstrukcija. Međutim, potrebna su nam sredstva za zaštitu naših informacija dok su u pregledniku korisnika, tako da određujemo što ćemo pohraniti i zašto te koje ćemo ugrađene funkcije koristiti:
Trebamo token koji se ne može lako dekodirati.
Trebamo token koji ima ugrađenu zastarjelost i mogućnost provjere zastarjelosti na poslužitelju.
Trebamo token koji će biti povezan s vlasnikom certifikata.
To zahtijeva funkciju raspršivanja, sol i datum za starenje tokena. Na temelju dokumentacije Izrazi u Apache HTTP poslužitelju imamo sve izvan kutije sha1 i %{TIME}.
Rezultat je bio ovaj dizajn:
#нет сертификата, и обращение к websocket
<If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'">
<If "%{HTTP:Upgrade} = 'websocket'">
SetEnvIf Cookie "zt-cert-sha1=([^;]+)" zt-cert-sha1=$1
SetEnvIf Cookie "zt-cert-uid=([^;]+)" zt-cert-uid=$1
SetEnvIf Cookie "zt-cert-date=([^;]+)" zt-cert-date=$1
#только так можно работать с переменными, полученными в env-ах в этот момент времени, более они нигде не доступны для функции хеширования (по отдельности можно, но не вместе, да и ещё с хешированием)
<RequireAll>
Require expr %{sha1:salt1%{env:zt-cert-date}salt3%{env:zt-cert-uid}salt2} == %{env:zt-cert-sha1}
Require expr %{env:zt-cert-sha1} =~ /^.{40}$/
</RequireAll>
</If>
</If>
#есть сертификат, запрашивается не websocket
<If "%{SSL:SSL_CLIENT_VERIFY} = 'SUCCESS'">
<If "%{HTTP:Upgrade} != 'websocket'">
SetEnvIf Cookie "zt-cert-sha1=([^;]+)" HAVE_zt-cert-sha1=$1
SetEnv zt_cert "path=/; HttpOnly;Secure;SameSite=Strict"
#Новые куки ставятся, если старых нет
Header add Set-Cookie "expr=zt-cert-sha1=%{sha1:salt1%{TIME}salt3%{SSL_CLIENT_S_DN_CN}salt2};%{env:zt_cert}" env=!HAVE_zt-cert-sha1
Header add Set-Cookie "expr=zt-cert-uid=%{SSL_CLIENT_S_DN_CN};%{env:zt_cert}" env=!HAVE_zt-cert-sha1
Header add Set-Cookie "expr=zt-cert-date=%{TIME};%{env:zt_cert}" env=!HAVE_zt-cert-sha1
</If>
</If>
Cilj je postignut, ali postoje problemi sa zastarjelošću poslužitelja (možete koristiti kolačić star godinu dana), što znači da tokeni, iako sigurni za internu upotrebu, nisu sigurni za industrijsku (masovnu) upotrebu.
4. Privremeni tokeni sesije već su implementirani kao gotovi Apache moduli.
Jedan značajan problem ostao je iz prethodne iteracije - nemogućnost kontrole starenja tokena.
Tražimo gotov modul koji to radi, prema riječima: apache token json two factor auth
Da, postoje gotovi moduli, ali svi su vezani za određene akcije i imaju artefakte u obliku pokretanja sesije i dodatnih kolačića. Odnosno, ne za neko vrijeme.
Tragala nam je pet sati, koja nije dala konkretan rezultat.
5. Privremeni tokeni sesije mogu se implementirati logičkim projektiranjem strukture interakcija.
Gotovi moduli su presloženi, jer nam treba samo nekoliko funkcija.
Uz to, problem s datumom je taj što Apacheove ugrađene funkcije ne dopuštaju generiranje datuma iz budućnosti, a nema matematičkog zbrajanja/oduzimanja u ugrađenim funkcijama prilikom provjere zastarjelosti.
Odnosno, ne možete napisati:
(%{env:zt-cert-date} + 30) > %{DATE}
Možete usporediti samo dva broja.
Dok sam tražio rješenje za Safari problem, pronašao sam zanimljiv članak: Osiguravanje HomeAssistant s klijentskim certifikatima (radi sa Safari/iOS)
Opisuje primjer koda u Lua za Nginx, a koji, kako se pokazalo, uvelike ponavlja logiku onog dijela konfiguracije koji smo već implementirali, s iznimkom korištenja metode hmac salting za raspršivanje ( ovo nije pronađeno u Apacheu).
Postalo je jasno da je Lua jezik s jasnom logikom i da je moguće učiniti nešto jednostavno za Apache:
Općenito, nije važno kojim su redoslijedom direktive napisane u Apache (vjerojatno i Nginx) konfiguraciji, budući da će na kraju sve biti poredano na temelju redoslijeda zahtjeva korisnika, koji odgovara shemi za obradu Lua skripte.
Završetak:
Vidljivo stanje nakon implementacije (cilj):
upravljanje uslugama i infrastrukturom dostupno je s mobilnog telefona na iOS-u bez dodatnih programa (VPN), objedinjeno i sigurno.
Cilj je postignut, web utičnice rade i imaju razinu sigurnosti ništa manju od certifikata.