Hoe wij bij ZeroTech Apple Safari en clientcertificaten met websockets verbonden

Het artikel zal nuttig zijn voor degenen die:

  • weet wat Client Cert is en begrijpt waarom het websockets nodig heeft op mobiele Safari;
  • Ik wil webservices publiceren voor een beperkte kring van mensen of alleen voor mezelf;
  • denkt dat alles al door iemand is gedaan en wil de wereld graag een beetje handiger en veiliger maken.

De geschiedenis van websockets begon ongeveer 8 jaar geleden. Voorheen werden methoden gebruikt in de vorm van lange http-verzoeken (eigenlijk reacties): de browser van de gebruiker stuurde een verzoek naar de server en wachtte tot deze iets antwoordde, na het antwoord maakte hij opnieuw verbinding en wachtte. Maar toen verschenen er websockets.

Hoe wij bij ZeroTech Apple Safari en clientcertificaten met websockets verbonden

Een paar jaar geleden hebben we onze eigen implementatie in pure PHP ontwikkeld, die geen https-verzoeken kan gebruiken, omdat dit de linklaag is. Nog niet zo lang geleden leerden bijna alle webservers verzoeken via https te proxyen en verbinding te ondersteunen: upgrade.

Toen dit gebeurde, werden websockets bijna de standaardservice voor SPA-toepassingen, want hoe handig is het om inhoud aan de gebruiker te leveren op initiatief van de server (een bericht van een andere gebruiker verzenden of een nieuwe versie downloaden van een afbeelding, document, presentatie die iemand anders momenteel aan het bewerken is).

Hoewel Client Certificate al geruime tijd bestaat, wordt het nog steeds slecht ondersteund, omdat het veel problemen veroorzaakt bij het omzeilen ervan. En (mogelijk :slightly_smiling_face: ) daarom willen IOS-browsers (allemaal behalve Safari) het niet gebruiken en vragen het aan bij het lokale certificaatarchief. Certificaten hebben veel voordelen ten opzichte van login/pass- of ssh-sleutels of het afsluiten van de benodigde poorten via een firewall. Maar dat is niet waar het hier om gaat.

Op iOS is de procedure voor het installeren van een certificaat vrij eenvoudig (niet zonder bijzonderheden), maar over het algemeen gebeurt dit volgens instructies, waarvan er veel op internet staan ​​en die alleen beschikbaar zijn voor de Safari-browser. Helaas weet Safari niet hoe hij Client Сert voor websockets moet gebruiken, maar er zijn veel instructies op internet te vinden over hoe je zo'n certificaat kunt maken, maar in de praktijk is dit onhaalbaar.

Hoe wij bij ZeroTech Apple Safari en clientcertificaten met websockets verbonden

Om websockets te begrijpen, hebben we het volgende plan gebruikt: probleem/hypothese/oplossing.

probleem: er is geen ondersteuning voor websockets bij het proxyen van verzoeken naar bronnen die worden beschermd door een clientcertificaat in de mobiele Safari-browser voor IOS en andere applicaties die certificaatondersteuning hebben ingeschakeld.

hypothesen:

  1. Het is mogelijk om een ​​dergelijke uitzondering te configureren om certificaten te gebruiken (wetende dat die er niet zullen zijn) voor websockets van interne/externe proxybronnen.
  2. Voor websockets kunt u een unieke, veilige en verdedigbare verbinding maken met behulp van tijdelijke sessies die worden gegenereerd tijdens een normaal (niet-websocket) browserverzoek.
  3. Tijdelijke sessies kunnen worden geïmplementeerd met behulp van één proxy-webserver (alleen ingebouwde modules en functies).
  4. Tijdelijke sessietokens zijn al geïmplementeerd als kant-en-klare Apache-modules.
  5. Tijdelijke sessietokens kunnen worden geïmplementeerd door de interactiestructuur logisch te ontwerpen.

Zichtbare staat na implementatie.

Doelstelling: het beheer van diensten en infrastructuur moet toegankelijk zijn vanaf een mobiele telefoon op IOS zonder extra programma's (zoals VPN), uniform en veilig.

Bijkomend doel: het besparen van tijd en middelen/telefoonverkeer (sommige diensten zonder websockets genereren onnodige verzoeken) met snellere levering van inhoud op mobiel internet.

Hoe controleren?

1. Openingspagina's:

— например, 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. Of in de ontwikkelaarsconsole:

Hoe wij bij ZeroTech Apple Safari en clientcertificaten met websockets verbonden

Hypothese testen:

1. Het is mogelijk om een ​​dergelijke uitzondering te configureren om certificaten te gebruiken (wetende dat die er niet zullen zijn) voor websockets van interne/externe proxybronnen.

Hier zijn 2 oplossingen gevonden:

a) Op niveau

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

toegangsniveau wijzigen.

Deze methode heeft de volgende nuances:

  • Certificaatverificatie vindt plaats na een verzoek aan de proxybron, dat wil zeggen na een handshake na het verzoek. Dit betekent dat de proxy eerst het verzoek aan de beschermde service zal laden en vervolgens zal afsnijden. Dit is slecht, maar niet cruciaal;
  • In het http2-protocol. Het is nog in concept en browserfabrikanten weten niet hoe ze het moeten implementeren #info over tls1.3 http2 post handshake (werkt nu niet) Implementeer RFC 8740 "Gebruik TLS 1.3 met HTTP/2";
  • Het is niet duidelijk hoe deze verwerking moet worden geünificeerd.

b) Sta op een basisniveau ssl toe zonder certificaat.

SSLVerifyClient vereist => SSLVerifyClient optioneel, maar dit verlaagt het beveiligingsniveau van de proxyserver, aangezien een dergelijke verbinding zonder certificaat wordt verwerkt. U kunt de toegang tot proxydiensten echter verder weigeren met de volgende richtlijn:

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"

Meer gedetailleerde informatie vindt u in het artikel over ssl: Apache Server Client Certificaatverificatie

Beide opties werden getest, optie “b” werd gekozen vanwege zijn veelzijdigheid en compatibiliteit met het http2-protocol.

Om de verificatie van deze hypothese te voltooien, waren er veel experimenten met de configuratie nodig; de volgende ontwerpen werden getest:

if = vereisen = herschrijven

Het resultaat is het volgende basisontwerp:

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>

Rekening houdend met de bestaande autorisatie door de certificaateigenaar, maar met een ontbrekend certificaat, moest ik een niet-bestaande certificaateigenaar toevoegen in de vorm van een van de beschikbare variabelen SSl_PROTOCOL (in plaats van SSL_CLIENT_S_DN_CN), meer details in de documentatie:

Apache-module mod_ssl

Hoe wij bij ZeroTech Apple Safari en clientcertificaten met websockets verbonden

2. Voor websockets kunt u een unieke, veilige en beschermde verbinding maken met behulp van tijdelijke sessies die worden gegenereerd tijdens een normaal (niet-websocket) browserverzoek.

Op basis van eerdere ervaringen moet u een extra sectie aan de configuratie toevoegen om tijdelijke tokens voor websocketverbindingen voor te bereiden tijdens een regulier (niet-websocket) verzoek.

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

Uit testen bleek dat het werkt. Het is mogelijk om cookies naar uzelf over te dragen via de browser van de gebruiker.

3. Tijdelijke sessies kunnen worden geïmplementeerd met behulp van één proxy-webserver (alleen ingebouwde modules en functies).

Zoals we eerder ontdekten, heeft Apache behoorlijk wat kernfunctionaliteit waarmee je voorwaardelijke constructies kunt maken. We hebben echter middelen nodig om onze informatie te beschermen terwijl deze zich in de browser van de gebruiker bevindt, dus bepalen we wat we moeten opslaan en waarom, en welke ingebouwde functies we zullen gebruiken:

  • We hebben een token nodig dat niet gemakkelijk kan worden gedecodeerd.
  • We hebben een token nodig waarin veroudering is ingebouwd en de mogelijkheid om veroudering op de server te controleren.
  • We hebben een token nodig dat wordt gekoppeld aan de eigenaar van het certificaat.

Dit vereist een hashfunctie, een zout en een datum om het token te verouderen. Gebaseerd op de documentatie Expressies in Apache HTTP-server we hebben het allemaal kant-en-klaar sha1 en %{TIME}.

Het resultaat was dit ontwerp:

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

Het doel is bereikt, maar er zijn problemen met serververoudering (je kunt een Cookie van een jaar oud gebruiken), waardoor de tokens, hoewel veilig voor intern gebruik, onveilig zijn voor industrieel (massa)gebruik.

Hoe wij bij ZeroTech Apple Safari en clientcertificaten met websockets verbonden

4. Tijdelijke sessietokens zijn al geïmplementeerd als kant-en-klare Apache-modules.

Er bleef één belangrijk probleem over van de vorige iteratie: het onvermogen om de veroudering van tokens onder controle te houden.

We zoeken een kant-en-klare module die dit doet, met de woorden: apache token json two factor auth

Ja, er zijn kant-en-klare modules, maar die zijn allemaal gebonden aan specifieke acties en hebben artefacten in de vorm van het starten van een sessie en extra Cookies. Dat wil zeggen, niet voor een tijdje.
Het kostte ons vijf uur zoeken, wat geen concreet resultaat opleverde.

5. Tijdelijke sessietokens kunnen worden geïmplementeerd door de structuur van interacties logisch te ontwerpen.

Kant-en-klare modules zijn te complex, omdat we maar een paar functies nodig hebben.

Dat gezegd zijnde is het probleem met de datum dat de ingebouwde functies van Apache het niet mogelijk maken een datum uit de toekomst te genereren, en dat er geen wiskundige optelling/aftrekking in de ingebouwde functies zit bij het controleren op veroudering.

Dat wil zeggen, je kunt niet schrijven:

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

Je kunt maar twee getallen vergelijken.

Terwijl ik zocht naar een oplossing voor het Safari-probleem, vond ik een interessant artikel: HomeAssistant beveiligen met clientcertificaten (werkt met Safari/iOS)
Het beschrijft een voorbeeld van code in Lua voor Nginx, en die, zoals later bleek, de logica herhaalt van dat deel van de configuratie dat we al hebben geïmplementeerd, met uitzondering van het gebruik van de hmac salting-methode voor hashing ( dit werd niet gevonden in Apache).

Het werd duidelijk dat Lua een taal is met duidelijke logica, en dat het mogelijk is om iets eenvoudigs voor Apache te doen:

Na het verschil met Nginx en Apache te hebben bestudeerd:

En beschikbare functies van de Lua-taalfabrikant:
22.1 – Datum en tijd

We hebben een manier gevonden om env-variabelen in een klein Lua-bestand in te stellen om een ​​datum uit de toekomst in te stellen om te vergelijken met de huidige.

Zo ziet een eenvoudig Lua-script eruit:

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

En zo werkt het allemaal in totaal, met optimalisatie van het aantal Cookies en vervanging van het token wanneer de helft van de tijd verstrijkt voordat het oude Cookie (token) vervalt:

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 

Omdat LuaHookAccessChecker pas wordt geactiveerd na toegangscontroles op basis van deze informatie uit Nginx.

Hoe wij bij ZeroTech Apple Safari en clientcertificaten met websockets verbonden

Link naar bron Afbeelding.

Een ander punt.

Over het algemeen maakt het niet uit in welke volgorde de richtlijnen zijn geschreven in de Apache (waarschijnlijk ook Nginx) configuratie, omdat uiteindelijk alles wordt gesorteerd op basis van de volgorde van het verzoek van de gebruiker, wat overeenkomt met het verwerkingsschema Lua-scripts.

Voltooiing:

Zichtbare staat na implementatie (doel):
beheer van diensten en infrastructuur is mogelijk vanaf een mobiele telefoon op IOS zonder extra programma's (VPN), uniform en veilig.

Het doel is bereikt, websockets werken en hebben een beveiligingsniveau van maar liefst een certificaat.

Hoe wij bij ZeroTech Apple Safari en clientcertificaten met websockets verbonden

Bron: www.habr.com

Voeg een reactie