ProHoster > Blog > Adminisztráció > Mi, a ZeroTech, hogyan kapcsoltuk össze az Apple Safarit és az ügyféltanúsítványokat websocketekkel
Mi, a ZeroTech, hogyan kapcsoltuk össze az Apple Safarit és az ügyféltanúsítványokat websocketekkel
A cikk hasznos lesz azoknak, akik:
tudja, mi az a Client Cert, és megérti, miért van szüksége websocketekre a mobil Safariban;
Webes szolgáltatásokat szeretnék közzétenni korlátozott körnek vagy csak magamnak;
úgy gondolja, hogy valaki már mindent megtett, és szeretné egy kicsit kényelmesebbé és biztonságosabbá tenni a világot.
A websocket története körülbelül 8 évvel ezelőtt kezdődött. Korábban a módszereket hosszú http kérések (valójában válaszok) formájában alkalmazták: a felhasználó böngészője kérést küldött a szervernek, és várta, hogy válaszoljon valamire, a válasz után újra csatlakozott és várt. De aztán megjelentek a websocket-ek.
Néhány éve kifejlesztettük a saját implementációnkat tiszta PHP-ben, amely nem tud https kéréseket használni, mivel ez a link réteg. Nem sokkal ezelőtt szinte az összes webszerver megtanulta a https-en keresztüli proxy kéréseket, és támogatja a connect:upgrade funkciót.
Amikor ez megtörtént, a websocket szinte az alapértelmezett szolgáltatása lett a SPA alkalmazásoknak, mert mennyire kényelmes a szerver kezdeményezésére tartalmat szolgáltatni a felhasználónak (üzenet továbbítása másik felhasználótól, vagy kép, dokumentum, prezentáció új verziójának letöltése) hogy valaki más éppen szerkeszt) .
Bár az ügyféltanúsítvány már jó ideje létezik, továbbra is gyengén támogatott, mivel sok problémát okoz, amikor megpróbálja megkerülni. És (esetleg :slightly_smiling_face: ) ezért nem akarják az IOS böngészők (a Safari kivételével) használni, és a helyi tanúsítványtárolóból kérik. A tanúsítványok számos előnnyel rendelkeznek a bejelentkezési/átlépési vagy ssh-kulcsokhoz, illetve a szükséges portok tűzfalon keresztüli bezárásához képest. De nem erről van szó.
Az iOS rendszeren a tanúsítvány telepítése meglehetősen egyszerű (nem nélkülözhetetlen), de általában az utasítások szerint történik, amelyekből sok van az interneten, és amelyek csak a Safari böngészőhöz érhetők el. Sajnos a Safari nem tudja, hogyan kell a Client Сert websocketekhez használni, de az interneten számos utasítás található egy ilyen tanúsítvány létrehozására, de a gyakorlatban ez elérhetetlen.
A websocket megértéséhez a következő tervet használtuk: probléma/hipotézis/megoldás.
probléma: nem támogatja a web socketeket, amikor proxy kéréseket küld olyan erőforrásokhoz, amelyeket ügyféltanúsítvány véd a Safari mobilböngészőben az IOS-hez és más olyan alkalmazásokhoz, amelyek engedélyezték a tanúsítványtámogatást.
Hipotézisek:
Egy ilyen kivétel beállítható úgy, hogy tanúsítványokat használjon (tudván, hogy nem lesz) a belső/külső proxy erőforrások websocketjeihez.
A websocketeknél egyedi, biztonságos és védhető kapcsolatot hozhat létre olyan ideiglenes munkamenetekkel, amelyek normál (nem websocket) böngészőkérés során jönnek létre.
Az ideiglenes munkamenetek egyetlen proxy webszerverrel valósíthatók meg (csak beépített modulok és funkciók).
Az ideiglenes munkamenet-tokeneket már kész Apache modulként implementálták.
Az ideiglenes munkamenet tokenek az interakciós struktúra logikus megtervezésével valósíthatók meg.
Látható állapot megvalósítás után.
A munka célja: a szolgáltatások és az infrastruktúra kezelésének elérhetőnek kell lennie egy mobiltelefonról IOS rendszeren további programok (például VPN) nélkül, egységesen és biztonságosan.
További cél: időt és erőforrásokat/telefonforgalmat takaríthat meg (egyes webaljzatok nélküli szolgáltatások szükségtelen kéréseket generálnak) a tartalom gyorsabb kézbesítésével a mobil interneten.
Hogyan ellenőrizzük?
1. Nyitó oldalak:
— например, 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. Vagy a fejlesztői konzolon:
Hipotézis tesztelés:
1. Egy ilyen kivétel beállítható úgy, hogy tanúsítványokat használjon (tudván, hogy nem lesz) a belső/külső proxy erőforrások websocketjeihez.
A tanúsítvány ellenőrzésére a proxy erőforráshoz intézett kérés, azaz a kérés utáni kézfogás után kerül sor. Ez azt jelenti, hogy a proxy először betölti, majd leállítja a védett szolgáltatás kérését. Ez rossz, de nem kritikus;
Nem világos, hogyan lehet egységesíteni ezt a feldolgozást.
b) Alapszinten engedélyezze az ssl-t tanúsítvány nélkül.
Az SSLVerifyClient követelmény => SSLVerifyClient opcionális, de ez csökkenti a proxyszerver biztonsági szintjét, mivel az ilyen kapcsolat feldolgozása tanúsítvány nélkül történik. Továbbra is megtagadhatja azonban a proxyszolgáltatásokhoz való hozzáférést a következő direktívával:
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>
Figyelembe véve a tanúsítvány tulajdonosának meglévő jogosultságát, de hiányzó tanúsítvánnyal, egy nem létező tanúsítványtulajdonost kellett hozzáadnom az egyik elérhető SSl_PROTOCOL változó formájában (az SSL_CLIENT_S_DN_CN helyett), további részletek a dokumentációban:
2. Websocket esetén egyedi, biztonságos és védett kapcsolatot hozhat létre olyan ideiglenes munkamenetek segítségével, amelyek normál (nem websocket) böngészőkérés során jönnek létre.
A korábbi tapasztalatok alapján egy további szakaszt kell hozzáadnia a konfigurációhoz, hogy ideiglenes tokeneket készítsen a webaljzat-kapcsolatokhoz egy normál (nem webes socket) kérés során.
#подготовка передача себе С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>
A tesztelés azt mutatta, hogy működik. Lehetőség van cookie-k átvitelére saját magának a felhasználó böngészőjén keresztül.
3. Ideiglenes munkamenetek egy proxy webszerverrel valósíthatók meg (csak beépített modulok és funkciók).
Amint azt korábban megtudtuk, az Apache meglehetősen sok alapvető funkcióval rendelkezik, amelyek lehetővé teszik feltételes konstrukciók létrehozását. Szükségünk van azonban arra, hogy megvédjük adatainkat, amíg azok a felhasználó böngészőjében vannak, ezért meghatározzuk, hogy mit és miért tároljunk, és milyen beépített funkciókat fogunk használni:
Olyan tokenre van szükségünk, amelyet nem lehet könnyen dekódolni.
Szükségünk van egy tokenre, amelybe az elavulás be van építve, és amely képes ellenőrizni az elavulást a szerveren.
Szükségünk van egy tokenre, amely a tanúsítvány tulajdonosához lesz társítva.
Ehhez egy hash-függvényre, egy sóra és egy dátumra van szükség a token öregítéséhez. A dokumentáció alapján Kifejezések az Apache HTTP szerveren mindet a dobozból megkaptuk sha1 és %{TIME}.
Az eredmény a következő design lett:
#нет сертификата, и обращение к 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>
A célt sikerült elérni, de problémák vannak a szerver elavulásával (egy éves Cookie-t használhatsz), ami azt jelenti, hogy a tokenek bár belső használatra biztonságosak, ipari (tömeges) felhasználásra nem biztonságosak.
4. Az ideiglenes munkamenet-tokeneket már kész Apache modulként implementálták.
Egy jelentős probléma maradt az előző iterációból – a token elöregedésének szabályozásának képtelensége.
Olyan kész modult keresünk, ami ezt a következő szavakkal teszi: apache token json two factor auth
Igen, vannak kész modulok, de mindegyik konkrét műveletekhez van kötve, és tartalmaznak műtermékeket munkamenet indítása és további cookie-k formájában. Vagyis egy darabig nem.
Öt órát vett igénybe a keresés, ami nem adott konkrét eredményt.
5. Az ideiglenes munkamenet tokenek az interakciók szerkezetének logikus megtervezésével valósíthatók meg.
A kész modulok túl bonyolultak, mert csak néhány funkcióra van szükségünk.
Ennek ellenére a dátummal az a probléma, hogy az Apache beépített függvényei nem teszik lehetővé a dátum generálását a jövőből, és a beépített függvényekben nincs matematikai összeadás/kivonás az elavulás ellenőrzésekor.
Vagyis nem írhatod:
(%{env:zt-cert-date} + 30) > %{DATE}
Csak két számot lehet összehasonlítani.
Miközben a Safari probléma megoldását kerestem, találtam egy érdekes cikket: A HomeAssistant biztosítása ügyféltanúsítványokkal (Safari/iOS rendszerrel működik)
Leír egy példát a Lua for Nginx kódjára, amely, mint kiderült, nagyon megismétli a konfiguráció azon részének logikáját, amelyet már megvalósítottunk, kivéve a hmac sózási módszer használatát a kivonatoláshoz ( ez nem található az Apache-ban).
Világossá vált, hogy a Lua egy tiszta logikával rendelkező nyelv, és az Apache számára is lehet valami egyszerűt tenni:
Találtunk egy módot az env változók beállítására egy kis Lua fájlban, hogy beállíthassunk egy jövőbeli dátumot a jelenlegivel való összehasonlításhoz.
Így néz ki egy egyszerű Lua szkript:
require 'apache2'
function handler(r)
local fmt = '%Y%m%d%H%M%S'
local timeout = 3600 -- 1 hour
r.notes['zt-cert-timeout'] = timeout
r.notes['zt-cert-date-next'] = os.date(fmt,os.time()+timeout)
r.notes['zt-cert-date-halfnext'] = os.date(fmt,os.time()+ (timeout/2))
r.notes['zt-cert-date-now'] = os.date(fmt,os.time())
return apache2.OK
end
És ez így működik összességében, a Cookie-k számának optimalizálásával és a token cseréjével, amikor a régi Cookie (token) lejárta előtti idő fele eltelik:
SSLVerifyClient optional
#LuaScope thread
#generate event variables zt-cert-date-next
LuaHookAccessChecker /usr/local/etc/apache24/sslincludes/websocket_token.lua handler early
#запрещаем без сертификата что-то ещё, кроме webscoket
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 certauth
<If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'">
<If "%{HTTP:Upgrade} = 'websocket'">
SetEnvIf Cookie "zt-cert=([^,;]+),([^,;]+),[^,;]+,([^,;]+)" zt-cert-sha1=$1 zt-cert-date=$2 zt-cert-uid=$3
<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}$/
Require expr %{env:zt-cert-date} -ge %{env:zt-cert-date-now}
</RequireAll>
#замещаем авторизацию по владельцу сертификата на авторизацию по номеру протокола
SSLUserName SSl_PROTOCOL
SSLOptions -FakeBasicAuth
</If>
</If>
<If "%{SSL:SSL_CLIENT_VERIFY} = 'SUCCESS'">
<If "%{HTTP:Upgrade} != 'websocket'">
SetEnvIf Cookie "zt-cert=([^,;]+),[^,;]+,([^,;]+)" HAVE_zt-cert-sha1=$1 HAVE_zt-cert-date-halfnow=$2
SetEnvIfExpr "env('HAVE_zt-cert-date-halfnow') -ge %{TIME} && env('HAVE_zt-cert-sha1')=~/.{40}/" HAVE_zt-cert-sha1-found=1
Define zt-cert "path=/;Max-Age=%{env:zt-cert-timeout};HttpOnly;Secure;SameSite=Strict"
Define dates_user "%{env:zt-cert-date-next},%{env:zt-cert-date-halfnext},%{SSL_CLIENT_S_DN_CN}"
Header set Set-Cookie "expr=zt-cert=%{sha1:salt1%{env:zt-cert-date-next}sal3%{SSL_CLIENT_S_DN_CN}salt2},${dates_user};${zt-cert}" env=!HAVE_zt-cert-sha1-found
</If>
</If>
SetEnvIfExpr "env('HAVE_zt-cert-date-halfnow') -ge %{TIME} && env('HAVE_zt-cert-sha1')=~/.{40}/" HAVE_zt-cert-sha1-found=1
работает,
а так работать не будет
SetEnvIfExpr "env('HAVE_zt-cert-date-halfnow') -ge env('zt-cert-date-now') && env('HAVE_zt-cert-sha1')=~/.{40}/" HAVE_zt-cert-sha1-found=1
Mivel a LuaHookAccessChecker csak az Nginx információi alapján történő hozzáférés-ellenőrzések után aktiválódik.
Általában nem mindegy, hogy az Apache (valószínűleg az Nginx) konfigurációban milyen sorrendben vannak megírva a direktívák, mivel végül minden a felhasználótól érkező kérés sorrendje alapján lesz rendezve, ami megfelel a feldolgozási sémának. Lua szkriptek.
Befejezés:
Látható állapot megvalósítás után (cél):
a szolgáltatások és az infrastruktúra kezelése IOS-en lévő mobiltelefonról további programok (VPN) nélkül, egységesen és biztonságosan elérhető.
A célt elértük, a web socket-ek működnek, és legalább egy tanúsítvánnyal rendelkeznek.