Kuinka me ZeroTechissä liitimme Apple Safarin ja asiakassertifikaatit verkkoliitäntöihin

Artikkeli on hyödyllinen niille, jotka:

  • tietää, mikä Client Cert on ja ymmärtää, miksi se tarvitsee websocketit mobiilissa Safarissa;
  • Haluan julkaista verkkopalveluita rajoitetulle ihmisjoukolle tai vain itselleni;
  • ajattelee, että joku on jo tehnyt kaiken, ja haluaisi tehdä maailmasta hieman mukavampaa ja turvallisempaa.

Websockettien historia alkoi noin 8 vuotta sitten. Aikaisemmin menetelmiä käytettiin pitkien http-pyyntöjen (oikeastaan ​​vastausten) muodossa: käyttäjän selain lähetti palvelimelle pyynnön ja odotti sen vastaavaa johonkin, vastauksen jälkeen se yhdistyi uudelleen ja odotti. Mutta sitten websocketit ilmestyivät.

Kuinka me ZeroTechissä liitimme Apple Safarin ja asiakassertifikaatit verkkoliitäntöihin

Muutama vuosi sitten kehitimme oman toteutuksen puhtaalla PHP:llä, joka ei voi käyttää https-pyyntöjä, koska tämä on linkkikerros. Ei kauan sitten melkein kaikki verkkopalvelimet oppivat välittämään pyyntöjä https:n kautta ja tukemaan yhteys:päivitystä.

Kun näin tapahtui, websocketeista tuli melkein SPA-sovellusten oletuspalvelu, koska kuinka kätevää on tarjota sisältöä käyttäjälle palvelimen aloitteesta (lähettää viesti toiselta käyttäjältä tai ladata uusi versio kuvasta, asiakirjasta, esityksestä jota joku muu muokkaa parhaillaan).

Vaikka Client Certificate on ollut olemassa jo jonkin aikaa, se on edelleen huonosti tuettu, koska se aiheuttaa paljon ongelmia, kun sitä yritetään ohittaa. Ja (mahdollisesti :slightly_smiling_face: ) siksi IOS-selaimet (kaikki paitsi Safari) eivät halua käyttää sitä ja pyytää sitä paikallisesta varmennevarastosta. Sertifikaateissa on monia etuja verrattuna login/pass- tai ssh-avaimiin tai tarvittavien porttien sulkemiseen palomuurin kautta. Mutta siitä ei tässä ole kyse.

IOS-käyttöjärjestelmässä varmenteen asennus on melko yksinkertainen (ei ilman tarkempia tietoja), mutta yleensä se tehdään ohjeiden mukaan, joita Internetissä on paljon ja jotka ovat saatavilla vain Safari-selaimelle. Valitettavasti Safari ei osaa käyttää Client Сert -sovellusta verkkopistorasioihin, mutta Internetissä on monia ohjeita tällaisen varmenteen luomiseen, mutta käytännössä se on mahdotonta saavuttaa.

Kuinka me ZeroTechissä liitimme Apple Safarin ja asiakassertifikaatit verkkoliitäntöihin

Websockettien ymmärtämiseksi käytimme seuraavaa suunnitelmaa: ongelma/hypoteesi/ratkaisu.

Ongelma: web-socket-tuki ei tue välityspalvelinpyyntöjä resursseihin, jotka on suojattu asiakasvarmenteella Safari-mobiiliselaimessa IOS:lle ja muille sovelluksille, jotka ovat ottaneet käyttöön varmennetuen.

Hypoteesit:

  1. On mahdollista määrittää tällainen poikkeus käyttämään varmenteita (tietäen, että niitä ei tule olemaan) sisäisten/ulkoisten välityspalvelinresurssien verkkopistorasioihin.
  2. Websocketeille voit muodostaa ainutlaatuisen, suojatun ja suojatun yhteyden väliaikaisilla istunnoilla, jotka luodaan normaalin (ei-websocket-) selainpyynnön aikana.
  3. Väliaikaiset istunnot voidaan toteuttaa yhdellä välityspalvelimella (vain sisäänrakennetut moduulit ja toiminnot).
  4. Väliaikaiset istuntotunnukset on jo toteutettu valmiina Apache-moduuleina.
  5. Väliaikaiset istuntotunnukset voidaan toteuttaa suunnittelemalla loogisesti vuorovaikutusrakenne.

Näkyvä tila toteutuksen jälkeen.

Tavoite: Palvelujen ja infrastruktuurin hallinnan tulee olla saavutettavissa IOS-puhelimesta ilman lisäohjelmia (kuten VPN), yhtenäisesti ja turvallisesti.

Lisätavoite: säästää aikaa ja resursseja/puhelinliikennettä (jotkut palvelut, joissa ei ole verkkopistorasiaa, tuottavat tarpeettomia pyyntöjä) ja nopeampi sisällön toimitus mobiilissa Internetissä.

Kuinka tarkistaa?

1. Avaussivut:

— например, 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. Tai kehittäjäkonsolissa:

Kuinka me ZeroTechissä liitimme Apple Safarin ja asiakassertifikaatit verkkoliitäntöihin

Hypoteesin testaus:

1. On mahdollista määrittää tällainen poikkeus käyttämään varmenteita (tietäen, että niitä ei tule olemaan) sisäisten/ulkoisten välityspalvelinresurssien verkkosocketeille.

Täältä löytyi 2 ratkaisua:

a) Tasolla

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

muuttaa käyttöoikeustasoa.

Tällä menetelmällä on seuraavat vivahteet:

  • Varmenteen vahvistus tapahtuu välityspalvelimelle osoitetun pyynnön jälkeen, eli pyynnön jälkeisen kättelyn jälkeen. Tämä tarkoittaa, että välityspalvelin ensin lataa ja katkaisee pyynnön suojattuun palveluun. Tämä on huono, mutta ei kriittinen;
  • http2-protokollassa. Se on vielä luonnoksessa, eivätkä selainvalmistajat osaa ottaa sitä käyttöön # info about tls1.3 http2 post handshake (ei toimi nyt) Ota käyttöön RFC 8740 "TLS 1.3:n käyttö HTTP/2:n kanssa";
  • Ei ole selvää, kuinka tämä käsittely yhtenäistetään.

b) Perustasolla salli ssl ilman varmennetta.

SSLVerifyClient vaatii => SSLVerifyClient valinnainen, mutta tämä heikentää välityspalvelimen suojaustasoa, koska tällainen yhteys käsitellään ilman varmennetta. Voit kuitenkin edelleen estää pääsyn välityspalvelinpalveluihin seuraavalla käskyllä:

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"

Tarkemmat tiedot löytyvät artikkelista ssl:stä: Apache Server Client Certificate Authentication

Molemmat vaihtoehdot testattiin, vaihtoehto "b" valittiin sen monipuolisuuden ja yhteensopivuuden vuoksi http2-protokollan kanssa.

Tämän hypoteesin vahvistaminen vaati paljon kokeita kokoonpanon kanssa; seuraavat mallit testattiin:

jos = vaadi = kirjoita uudelleen

Tuloksena on seuraava perussuunnittelu:

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>

Ottaen huomioon varmenteen omistajan olemassa olevan valtuutuksen, mutta varmenteen puuttuessa, minun piti lisätä olematon varmenteen omistaja johonkin käytettävissä olevista muuttujista SSl_PROTOCOL (SSL_CLIENT_S_DN_CN sijaan), lisätietoja dokumentaatiosta:

Apache-moduuli mod_ssl

Kuinka me ZeroTechissä liitimme Apple Safarin ja asiakassertifikaatit verkkoliitäntöihin

2. Websocketeille voit muodostaa ainutlaatuisen, suojatun ja suojatun yhteyden väliaikaisilla istunnoilla, jotka luodaan normaalin (ei-websocket-) selainpyynnön aikana.

Aiemman kokemuksen perusteella sinun on lisättävä kokoonpanoon ylimääräinen osio, jotta voit valmistella tilapäisiä tunnuksia verkkoliitäntäyhteyksiä varten tavallisen (ei-web-socketin) pyynnön aikana.

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

Testaus osoitti, että se toimii. On mahdollista siirtää evästeitä itsellesi käyttäjän selaimen kautta.

3. Väliaikaiset istunnot voidaan toteuttaa yhdellä välityspalvelimella (vain sisäänrakennetut moduulit ja toiminnot).

Kuten huomasimme aiemmin, Apachessa on melko paljon ydintoimintoja, joiden avulla voit luoda ehdollisia rakenteita. Tarvitsemme kuitenkin keinoja suojata tietomme, kun ne ovat käyttäjän selaimessa, joten määritämme, mitä tallennetaan ja miksi sekä mitä sisäänrakennettuja toimintoja käytämme:

  • Tarvitsemme tunnuksen, jota ei voida helposti purkaa.
  • Tarvitsemme tunnuksen, jossa on sisäänrakennettu vanhentuminen ja mahdollisuus tarkistaa vanhentuminen palvelimella.
  • Tarvitsemme tunnuksen, joka liitetään varmenteen omistajaan.

Tämä vaatii hajautusfunktion, suolan ja päivämäärän merkin vanhentamiseksi. Dokumentaation perusteella Lausekkeet Apache HTTP -palvelimessa meillä on kaikki valmiina sha1 ja %{TIME}.

Tuloksena oli tämä muotoilu:

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

Tavoite on saavutettu, mutta palvelimen vanhentumiseen liittyy ongelmia (voit käyttää vuoden vanhaa evästettä), mikä tarkoittaa, että tokenit, vaikka ne ovat turvallisia sisäiseen käyttöön, eivät ole turvallisia teolliseen (massa) käyttöön.

Kuinka me ZeroTechissä liitimme Apple Safarin ja asiakassertifikaatit verkkoliitäntöihin

4. Väliaikaiset istuntotunnukset on jo toteutettu valmiina Apache-moduuleina.

Edellisestä iteraatiosta jäi yksi merkittävä ongelma - kyvyttömyys hallita merkkien vanhenemista.

Etsimme valmiita moduulia, joka tekee tämän sanojen mukaan: apache token json kaksitekijäinen auth

Kyllä, on olemassa valmiita moduuleja, mutta ne kaikki on sidottu tiettyihin toimiin ja niissä on artefakteja istunnon aloittamisen ja lisäevästeiden muodossa. Eli ei vähään aikaan.
Haku kesti viisi tuntia, mikä ei antanut konkreettista tulosta.

5. Väliaikaiset istuntotunnukset voidaan toteuttaa suunnittelemalla loogisesti vuorovaikutusten rakenne.

Valmiit moduulit ovat liian monimutkaisia, koska tarvitsemme vain pari toimintoa.

Päivämäärän ongelmana on kuitenkin se, että Apachen sisäänrakennetut funktiot eivät salli päivämäärän luomista tulevaisuudesta, eikä sisäänrakennetuissa funktioissa ole matemaattista yhteen-/vähennyslaskua, kun tarkistetaan vanhentuneisuutta.

Eli et voi kirjoittaa:

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

Voit verrata vain kahta numeroa.

Etsiessäni ratkaisua Safari-ongelmaan löysin mielenkiintoisen artikkelin: HomeAssistantin suojaaminen asiakasvarmenteilla (toimii Safarin/iOS:n kanssa)
Se kuvaa esimerkkiä koodista Lua for Nginx, ja joka, kuten kävi ilmi, toistaa hyvin paljon jo toteuttamamme kokoonpanon osan logiikkaa, lukuun ottamatta hmac-suolausmenetelmän käyttöä tiivistykseen ( tätä ei löytynyt Apachesta).

Kävi selväksi, että Lua on kieli, jolla on selkeä logiikka, ja Apachelle on mahdollista tehdä jotain yksinkertaista:

Tutkittuaan eroa Nginxin ja Apachen kanssa:

Ja saatavilla olevat toiminnot Lua-kielen valmistajalta:
22.1 – Päivämäärä ja aika

Löysimme tavan asettaa env-muuttujia pieneen Lua-tiedostoon, jotta voimme asettaa päivämäärän tulevaisuudesta verrataksemme nykyiseen.

Tältä yksinkertainen Lua-skripti näyttää:

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 näin se kaikki toimii kokonaisuudessaan, optimoimalla evästeiden määrää ja korvaamalla tunnuksen, kun puolet ajasta ennen vanhan evästeen (tunnus) vanhenemista on:

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 

Koska LuaHookAccessChecker aktivoituu vasta pääsyntarkistusten jälkeen, jotka perustuvat näihin Nginxin tietoihin.

Kuinka me ZeroTechissä liitimme Apple Safarin ja asiakassertifikaatit verkkoliitäntöihin

Linkki lähteeseen Kuva.

Toinen kohta.

Yleisesti ottaen ei ole väliä missä järjestyksessä käskyt kirjoitetaan Apachen (luultavasti myös Nginx) kokoonpanossa, koska lopulta kaikki lajitellaan käyttäjän pyynnön järjestyksen mukaan, joka vastaa käsittelyjärjestelmää. Lua-skriptit.

Valmistuminen:

Näkyvä tila käyttöönoton jälkeen (tavoite):
Palveluiden ja infrastruktuurin hallinta on saatavilla IOS-puhelimesta ilman lisäohjelmia (VPN), yhtenäinen ja turvallinen.

Tavoite on saavutettu, web-socketit toimivat ja niillä on vähintään sertifikaatin turvallisuustaso.

Kuinka me ZeroTechissä liitimme Apple Safarin ja asiakassertifikaatit verkkoliitäntöihin

Lähde: will.com

Lisää kommentti