Kuidas me ZeroTechis Apple Safari ja kliendi sertifikaadid veebipesadega ühendasime

Artikkel on kasulik neile, kes:

  • teab, mis on Client Cert ja mõistab, miks see vajab mobiilses Safaris veebipesasid;
  • Soovin avaldada veebiteenuseid piiratud ringile inimestele või ainult endale;
  • arvab, et kõik on kellegi poolt juba tehtud, ja tahaks maailma natuke mugavamaks ja turvalisemaks muuta.

Websockettide ajalugu algas umbes 8 aastat tagasi. Varem kasutati meetodeid pikkade http-päringute (tegelikult vastuste) kujul: kasutaja brauser saatis serverile päringu ja ootas, kuni see midagi vastab, pärast vastust ühendus uuesti ja ootas. Siis aga ilmusid veebipesad.

Kuidas me ZeroTechis Apple Safari ja kliendi sertifikaadid veebipesadega ühendasime

Mõned aastad tagasi töötasime välja oma juurutuse puhtas PHP-s, mis ei saa kasutada https-päringuid, kuna see on lingikiht. Mitte kaua aega tagasi õppisid peaaegu kõik veebiserverid puhverserveri taotlusi läbi https ja toetama ühendus:uuendust.

Kui see juhtus, muutusid websocketid SPA-rakenduste jaoks peaaegu vaiketeenuseks, sest kui mugav on serveri initsiatiivil kasutajale sisu pakkuda (teise kasutaja sõnumi edastamine või pildi, dokumendi, esitluse uue versiooni allalaadimine mida keegi teine ​​parasjagu toimetab) .

Kuigi Client Certificate on olnud kasutusel juba mõnda aega, on see endiselt halvasti toetatud, kuna see tekitab palju probleeme, kui proovite sellest mööda minna. Ja (võib-olla :slightly_smiling_face: ) sellepärast ei taha IOS-i brauserid (kõik peale Safari) seda kasutada ja taotlevad seda kohalikust sertifikaadihoidlast. Sertifikaatidel on palju eeliseid võrreldes sisselogimise/passi või ssh-võtmetega või vajalike portide sulgemisega läbi tulemüüri. Kuid see pole see, millest see jutt käib.

IOS-is on sertifikaadi installimise protseduur üsna lihtne (mitte ilma spetsiifikata), kuid üldiselt toimub see juhiste järgi, mida Internetis on palju ja mis on saadaval ainult Safari brauseri jaoks. Kahjuks ei oska Safari Client Сert veebipesade jaoks kasutada, kuid Internetis on palju juhiseid, kuidas sellist sertifikaati luua, kuid praktikas on see kättesaamatu.

Kuidas me ZeroTechis Apple Safari ja kliendi sertifikaadid veebipesadega ühendasime

Veebipistikupesade mõistmiseks kasutasime järgmist plaani: probleem/hüpotees/lahendus.

Probleem: IOS-i ja muude serditoe lubanud rakenduste jaoks Safari mobiilibrauseris kliendisertifikaadiga kaitstud ressurssidele päringute puhverserveri puhul veebipesade tugi puudub.

Hüpoteesid:

  1. Sellist erandit on võimalik konfigureerida kasutama sertifikaate (teades, et neid ei tule) sisemiste/väliste puhverserveri ressursside veebisocketite jaoks.
  2. Veebipistikupesade jaoks saate luua unikaalse, turvalise ja kaitstud ühenduse, kasutades ajutisi seansse, mis genereeritakse tavalise (mitte-websocket) brauseri päringu ajal.
  3. Ajutisi seansse saab realiseerida ühe puhverserveri abil (ainult sisseehitatud moodulid ja funktsioonid).
  4. Ajutised seansimärgid on juba kasutusele võetud valmis Apache moodulitena.
  5. Ajutisi seansimärke saab rakendada interaktsioonistruktuuri loogilise kujundamise teel.

Nähtav olek pärast rakendamist.

Eesmärk: teenuste ja infrastruktuuri haldamine peaks olema IOS-i mobiiltelefonilt juurdepääsetav ilma lisaprogrammideta (nt VPN), ühtne ja turvaline.

Lisaeesmärk: säästa aega ja ressursse/telefoniliiklust (mõned ilma veebipistikupesadeta teenused tekitavad tarbetuid päringuid) sisu kiirema edastamisega mobiilses Internetis.

Kuidas kontrollida?

1. Avalehed:

— например, 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. Või arendajakonsoolis:

Kuidas me ZeroTechis Apple Safari ja kliendi sertifikaadid veebipesadega ühendasime

Hüpoteesi testimine:

1. Sellist erandit on võimalik seadistada sertifikaatide kasutamiseks (teades, et neid ei tule) sisemiste/väliste puhverserveri ressursside veebipesadele.

Siit leiti 2 lahendust:

a) tasemel

<Location sock*> SSLVerifyClient optional </Location>
<Location /> SSLVerifyClient require </Location>

muutke juurdepääsutaset.

Sellel meetodil on järgmised nüansid:

  • Sertifikaadi kinnitamine toimub pärast päringu esitamist puhverserverile, st pärast päringujärgset käepigistust. See tähendab, et puhverserver laadib esmalt ja katkestab seejärel kaitstud teenuse päringu. See on halb, kuid mitte kriitiline;
  • http2 protokollis. See on endiselt mustand ja brauseritootjad ei tea, kuidas seda rakendada #info tls1.3 kohta http2 postita käepigistuse (praegu ei tööta) Rakendage RFC 8740 "TLS 1.3 kasutamine HTTP/2-ga";
  • Ei ole selge, kuidas seda töötlemist ühtlustada.

b) Põhitasemel lubage ssl ilma sertifikaadita.

SSLVerifyClient nõuavad => SSLVerifyClient valikuline, kuid see vähendab puhverserveri turvataset, kuna sellist ühendust töödeldakse ilma sertifikaadita. Juurdepääsu puhverserveri teenustele saate siiski keelata järgmise direktiiviga:

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"

Täpsemat teavet leiate ssl-i käsitlevast artiklist: Apache serveri kliendi sertifikaadi autentimine

Mõlemat võimalust testiti, valik “b” valiti selle mitmekülgsuse ja http2-protokolliga ühilduvuse tõttu.

Selle hüpoteesi kontrollimiseks kulus konfiguratsiooniga palju katseid; testiti järgmisi kujundusi:

kui = nõua = kirjuta ümber

Tulemuseks on järgmine põhikujundus:

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>

Võttes arvesse sertifikaadi omaniku olemasolevat volitust, kuid puuduva sertifikaadiga, pidin lisama olematu sertifikaadi omaniku ühe saadaoleva muutuja SSl_PROTOCOL kujul (SSL_CLIENT_S_DN_CN asemel), täpsemalt dokumentatsioonis:

Apache moodul mod_ssl

Kuidas me ZeroTechis Apple Safari ja kliendi sertifikaadid veebipesadega ühendasime

2. Veebipistikupesade puhul saate luua unikaalse, turvalise ja kaitstud ühenduse, kasutades ajutisi seansse, mis genereeritakse tavalise (mitte-websocket) brauseri päringu ajal.

Varasema kogemuse põhjal tuleb konfiguratsiooni lisada täiendav sektsioon, et tavalise (mitteveebipesa) päringu ajal veebipesa ühenduste jaoks ette valmistada ajutised märgid.

#подготовка передача себе С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>

Testimine näitas, et see töötab. Kasutaja brauseri kaudu on võimalik küpsiseid endale üle kanda.

3. Ajutisi seansse saab realiseerida ühe puhverserveri abil (ainult sisseehitatud moodulid ja funktsioonid).

Nagu varem teada saime, on Apache'il üsna palju põhifunktsioone, mis võimaldavad teil luua tingimuslikke konstruktsioone. Siiski vajame vahendeid oma teabe kaitsmiseks, kui see on kasutaja brauseris, nii et me määrame, mida ja miks salvestada ning milliseid sisseehitatud funktsioone kasutame:

  • Vajame märgi, mida ei saa kergesti dekodeerida.
  • Vajame luba, millel on sisseehitatud vananemine ja mis võimaldab serveris vananemist kontrollida.
  • Vajame sertifikaati, mis seotakse sertifikaadi omanikuga.

Selleks on vaja märgi vanandamiseks räsifunktsiooni, soola ja kuupäeva. Dokumentatsiooni põhjal Avaldised Apache HTTP serveris meil on see kõik karbist väljas sha1 ja %{TIME}.

Tulemuseks oli selline disain:

#нет сертификата, и обращение к 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>

Eesmärk on saavutatud, kuid probleeme on serveri vananemisega (võite kasutada aasta vanust küpsist), mis tähendab, et tokenid on küll sisekasutuseks ohutud, kuid tööstuslikuks (massiks) kasutamiseks ohtlikud.

Kuidas me ZeroTechis Apple Safari ja kliendi sertifikaadid veebipesadega ühendasime

4. Ajutised seansimärgid on juba rakendatud valmis Apache moodulitena.

Eelmisest iteratsioonist jäi alles üks oluline probleem – võimetus kontrollida märgi vananemist.

Otsime valmis moodulit, mis teeb seda sõnadega: apache token json kahefaktoriline auth

Jah, on olemas valmismoodulid, kuid need kõik on seotud konkreetsete toimingutega ja sisaldavad artefakte seansi alustamise ja täiendavate küpsiste kujul. See tähendab, et mitte mõnda aega.
Otsimiseks kulus meil viis tundi, mis aga konkreetset tulemust ei andnud.

5. Ajutisi seansimärke saab realiseerida interaktsioonide struktuuri loogiliselt kujundades.

Valmis moodulid on liiga keerulised, sest vajame vaid paari funktsiooni.

Sellegipoolest on kuupäeva probleemiks see, et Apache sisseehitatud funktsioonid ei võimalda tulevikust kuupäeva genereerida ja sisseehitatud funktsioonides pole vananemise kontrollimisel matemaatilist liitmist/lahutamist.

See tähendab, et te ei saa kirjutada:

(%{env:zt-cert-date} + 30) > %{DATE}

Võrrelda saab ainult kahte numbrit.

Safari probleemile lahendust otsides leidsin huvitava artikli: HomeAssistanti kaitsmine kliendi sertifikaatidega (töötab Safari/iOS-iga)
See kirjeldab Nginxi jaoks mõeldud Lua koodi näidet ja mis, nagu selgus, kordab suurel määral konfiguratsiooni selle osa loogikat, mille oleme juba rakendanud, välja arvatud hmac soolamismeetodi kasutamine räsimiseks ( seda Apache'is ei leitud).

Selgus, et Lua on selge loogikaga keel ja Apache jaoks on võimalik teha midagi lihtsat:

Olles uurinud erinevust Nginxi ja Apache'iga:

Ja saadaolevad funktsioonid Lua keele tootjalt:
22.1 – Kuupäev ja kellaaeg

Leidsime viisi, kuidas määrata väikeses Lua failis env muutujad, et määrata kuupäev tulevikust, mida praegusega võrrelda.

Lihtne Lua skript näeb välja selline:

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

Ja see kõik kokku käib nii, optimeerides küpsiste arvu ja asendades märgi, kui pool aega enne vana küpsise (luba) aegumist saabub:

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 

Kuna LuaHookAccessChecker aktiveeritakse alles pärast juurdepääsu kontrollimist, mis põhineb sellel Nginxi teabel.

Kuidas me ZeroTechis Apple Safari ja kliendi sertifikaadid veebipesadega ühendasime

Link allikale Pilt.

Teine asi.

Üldiselt pole vahet, millises järjekorras Apache (ilmselt ka Nginxi) konfiguratsioonis juhised on kirjutatud, kuna lõpuks sorteeritakse kõik vastavalt kasutaja päringu järjekorrale, mis vastab töötlemise skeemile. Lua skriptid.

Lõpetamine:

Nähtav olek pärast rakendamist (eesmärk):
teenuste ja infrastruktuuri haldamine on IOS-i mobiiltelefonilt saadaval ilma lisaprogrammideta (VPN), ühtne ja turvaline.

Eesmärk on saavutatud, veebipistikupesad töötavad ja nende turvalisuse tase ei ole madalam kui sertifikaat.

Kuidas me ZeroTechis Apple Safari ja kliendi sertifikaadid veebipesadega ühendasime

Allikas: www.habr.com

Lisa kommentaar