Come noi di ZeroTech abbiamo collegato Apple Safari e i certificati client con i websocket

L'articolo sarà utile a coloro che:

  • sa cos'è Client Cert e capisce perché ha bisogno dei websocket su Safari mobile;
  • Vorrei pubblicare servizi web ad una cerchia ristretta di persone o solo a me stesso;
  • pensa che tutto sia già stato fatto da qualcuno, e vorrebbe rendere il mondo un po' più comodo e sicuro.

La storia dei websocket è iniziata circa 8 anni fa. In precedenza, venivano utilizzati metodi sotto forma di lunghe richieste http (in realtà risposte): il browser dell'utente inviava una richiesta al server e aspettava che rispondesse a qualcosa, dopo la risposta si collegava di nuovo e aspettava. Ma poi sono comparsi i websocket.

Come noi di ZeroTech abbiamo collegato Apple Safari e i certificati client con i websocket

Alcuni anni fa abbiamo sviluppato la nostra implementazione in PHP puro, che non può utilizzare richieste https, poiché questo è il livello di collegamento. Non molto tempo fa, quasi tutti i server Web hanno imparato a eseguire il proxy delle richieste su https e a supportare Connection:Upgrade.

Quando ciò è accaduto, i websocket sono diventati quasi il servizio predefinito per le applicazioni SPA, perché è conveniente fornire contenuti all'utente su iniziativa del server (trasmettere un messaggio da un altro utente o scaricare una nuova versione di un'immagine, documento, presentazione che qualcun altro sta attualmente modificando).

Sebbene il certificato client sia in circolazione da un po’ di tempo, rimane ancora scarsamente supportato, poiché crea molti problemi quando si tenta di aggirarlo. E (forse :slightly_smiling_face: ) è per questo che i browser IOS (tutti tranne Safari) non vogliono usarlo e richiederlo dall'archivio certificati locale. I certificati presentano molti vantaggi rispetto alle chiavi login/pass o ssh o alla chiusura delle porte necessarie attraverso un firewall. Ma non si tratta di questo.

Su iOS la procedura per installare un certificato è abbastanza semplice (non priva di specifiche), ma in generale viene eseguita secondo le istruzioni, di cui ce ne sono molte su Internet e che sono disponibili solo per il browser Safari. Sfortunatamente, Safari non sa come utilizzare Client Сert per i socket Web, ma su Internet ci sono molte istruzioni su come creare un tale certificato, ma in pratica questo è irraggiungibile.

Come noi di ZeroTech abbiamo collegato Apple Safari e i certificati client con i websocket

Per comprendere i websocket, abbiamo utilizzato il seguente piano: problema/ipotesi/soluzione.

Problema: non è disponibile alcun supporto per i socket Web durante l'inoltro di richieste a risorse protette da un certificato client sul browser mobile Safari per IOS e altre applicazioni che hanno abilitato il supporto dei certificati.

ipotesi:

  1. È possibile configurare tale eccezione per utilizzare i certificati (sapendo che non ce ne saranno) per i websocket di risorse proxy interne/esterne.
  2. Per i websocket, puoi creare una connessione unica, sicura e difendibile utilizzando sessioni temporanee generate durante una normale richiesta del browser (non websocket).
  3. Le sessioni temporanee possono essere implementate utilizzando un server Web proxy (solo moduli e funzioni integrati).
  4. I token di sessione temporanei sono già stati implementati come moduli Apache già pronti.
  5. I token di sessione temporanei possono essere implementati progettando logicamente la struttura di interazione.

Stato visibile dopo l'implementazione.

Obbiettivo: la gestione dei servizi e delle infrastrutture dovrebbe essere accessibile da cellulare su IOS senza programmi aggiuntivi (come VPN), unificata e sicura.

Obiettivo aggiuntivo: risparmio di tempo e risorse/traffico telefonico (alcuni servizi senza presa web generano richieste non necessarie) con una consegna più rapida dei contenuti su Internet mobile.

Come controllare?

1. Pagine di apertura:

— например, 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. Oppure nella console degli sviluppatori:

Come noi di ZeroTech abbiamo collegato Apple Safari e i certificati client con i websocket

Controllo di un'ipotesi:

1. È possibile configurare tale eccezione per utilizzare i certificati (sapendo che non ce ne saranno) per socket web di risorse proxy interne/esterne.

Qui sono state trovate 2 soluzioni:

a) A livello

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

modificare il livello di accesso.

Questo metodo ha le seguenti sfumature:

  • La verifica del certificato avviene dopo una richiesta alla risorsa proxy, ovvero l'handshake della richiesta post. Ciò significa che il proxy prima caricherà e poi interromperà la richiesta al servizio protetto. Questo è negativo, ma non critico;
  • Nel protocollo http2. È ancora in bozza e i produttori di browser non sanno come implementarlo #info su tls1.3 http2 post handshake (non funziona ora) Implementare RFC 8740 "Utilizzo di TLS 1.3 con HTTP/2";
  • Non è chiaro come unificare questa elaborazione.

b) A livello base, consenti SSL senza certificato.

SSLVerifyClient require => SSLVerifyClient facoltativo, ma ciò riduce il livello di sicurezza del server proxy, poiché tale connessione verrà elaborata senza certificato. Tuttavia, puoi negare ulteriormente l'accesso ai servizi proxy con la seguente direttiva:

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"

Informazioni più dettagliate possono essere trovate nell'articolo su SSL: Autenticazione del certificato client del server Apache

Sono state testate entrambe le opzioni, l'opzione “b” è stata scelta per la sua versatilità e compatibilità con il protocollo http2.

Per completare la verifica di questa ipotesi sono stati necessari numerosi esperimenti di configurazione; sono stati testati i seguenti progetti:

se = richiedere = riscrivere

Il risultato è il seguente progetto di base:

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>

Tenendo conto dell'autorizzazione esistente da parte del proprietario del certificato, ma con un certificato mancante, ho dovuto aggiungere un proprietario del certificato inesistente sotto forma di una delle variabili disponibili SSl_PROTOCOL (invece di SSL_CLIENT_S_DN_CN), maggiori dettagli nella documentazione:

Modulo Apache mod_ssl

Come noi di ZeroTech abbiamo collegato Apple Safari e i certificati client con i websocket

2. Per i websocket, puoi creare una connessione unica, sicura e protetta utilizzando sessioni temporanee generate durante una normale richiesta del browser (non websocket).

In base all'esperienza precedente, è necessario aggiungere una sezione aggiuntiva alla configurazione per preparare token temporanei per le connessioni socket web durante una richiesta regolare (non socket web).

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

I test hanno dimostrato che funziona. È possibile trasferire cookie a se stessi attraverso il browser dell'utente.

3. Le sessioni temporanee possono essere implementate utilizzando un server Web proxy (solo moduli e funzioni integrati).

Come abbiamo scoperto in precedenza, Apache ha molte funzionalità di base che ti consentono di creare costrutti condizionali. Tuttavia, abbiamo bisogno di mezzi per proteggere le nostre informazioni mentre si trovano nel browser dell'utente, quindi stabiliamo cosa archiviare e perché, e quali funzioni integrate utilizzeremo:

  • Abbiamo bisogno di un token che non possa essere facilmente decodificato.
  • Abbiamo bisogno di un token che contenga l'obsolescenza incorporata e la capacità di controllare l'obsolescenza sul server.
  • Abbiamo bisogno di un token che verrà associato al proprietario del certificato.

Ciò richiede una funzione di hashing, un sale e una data per invecchiare il token. Sulla base della documentazione Espressioni nel server HTTP Apache abbiamo tutto pronto per l'uso sha1 e %{TIME}.

Il risultato è stato questo disegno:

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

L'obiettivo è stato raggiunto, ma ci sono problemi di obsolescenza dei server (è possibile utilizzare un cookie vecchio di un anno), il che significa che i token, sebbene sicuri per uso interno, non sono sicuri per l'uso industriale (di massa).

Come noi di ZeroTech abbiamo collegato Apple Safari e i certificati client con i websocket

4. I token di sessione temporanei sono già stati implementati come moduli Apache già pronti.

Dall'iterazione precedente rimaneva un problema significativo: l'incapacità di controllare l'invecchiamento dei token.

Stiamo cercando un modulo già pronto che faccia questo, secondo le parole: apache token json two factor auth

Sì, ci sono moduli già pronti, ma sono tutti legati ad azioni specifiche e presentano artefatti sotto forma di avvio di una sessione e cookie aggiuntivi. Cioè, non per un po'.
Ci sono volute cinque ore per la ricerca, che non ha dato un risultato concreto.

5. I token di sessione temporanei possono essere implementati progettando logicamente la struttura delle interazioni.

I moduli già pronti sono troppo complessi, perché abbiamo bisogno solo di un paio di funzioni.

Detto questo, il problema con la data è che le funzioni integrate di Apache non consentono di generare una data dal futuro e non vi è alcuna addizione/sottrazione matematica nelle funzioni integrate durante il controllo dell'obsolescenza.

Cioè non puoi scrivere:

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

Puoi confrontare solo due numeri.

Durante la ricerca di una soluzione alternativa al problema di Safari, ho trovato un articolo interessante: Protezione di HomeAssistant con certificati client (funziona con Safari/iOS)
Descrive un esempio di codice in Lua per Nginx e che, come si è scoperto, ripete molto la logica di quella parte della configurazione che abbiamo già implementato, ad eccezione dell'uso del metodo hmac salting per l'hashing ( questo non è stato trovato in Apache).

È diventato chiaro che Lua è un linguaggio con una logica chiara ed è possibile fare qualcosa di semplice per Apache:

Dopo aver studiato la differenza con Nginx e Apache:

E funzioni disponibili dal produttore del linguaggio Lua:
22.1 – Data e Ora

Abbiamo trovato un modo per impostare le variabili env in un piccolo file Lua per impostare una data futura da confrontare con quella attuale.

Ecco come appare un semplice script Lua:

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

Ed ecco come funziona nel complesso, con l'ottimizzazione del numero di cookie e la sostituzione del token quando arriva la metà del tempo prima che il vecchio cookie (token) scada:

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 

Perché LuaHookAccessChecker verrà attivato solo dopo i controlli di accesso basati su queste informazioni di Nginx.

Come noi di ZeroTech abbiamo collegato Apple Safari e i certificati client con i websocket

Collegamento alla fonte Immagine.

Un altro punto.

In generale, non importa in quale ordine sono scritte le direttive nella configurazione di Apache (probabilmente anche Nginx), poiché alla fine tutto verrà ordinato in base all'ordine della richiesta dell'utente, che corrisponde allo schema di elaborazione Script Lua.

Completamento:

Stato visibile dopo l'implementazione (obiettivo):
la gestione dei servizi e delle infrastrutture è possibile da cellulare su IOS senza programmi aggiuntivi (VPN), unificata e sicura.

L'obiettivo è stato raggiunto, i web socket funzionano e hanno un livello di sicurezza niente meno che un certificato.

Come noi di ZeroTech abbiamo collegato Apple Safari e i certificati client con i websocket

Fonte: habr.com

Aggiungi un commento