Kā mēs ZeroTech savienojām Apple Safari un klientu sertifikātus ar tīmekļa ligzdām

Raksts būs noderīgs tiem, kuri:

  • zina, kas ir Client Cert, un saprot, kāpēc tam ir vajadzīgas tīmekļa ligzdas mobilajā Safari;
  • Vēlos publicēt tīmekļa pakalpojumus ierobežotam cilvēku lokam vai tikai sev;
  • domā, ka visu jau kāds ir izdarījis, un gribētu pasauli padarīt mazliet ērtāku un drošāku.

Tīmekļa kontaktligzdu vēsture sākās apmēram pirms 8 gadiem. Iepriekš metodes tika izmantotas garu http pieprasījumu (faktiski atbilžu) veidā: lietotāja pārlūkprogramma nosūtīja serverim pieprasījumu un gaidīja, kad tas kaut ko atbildēs, pēc atbildes atkal izveidoja savienojumu un gaidīja. Bet tad parādījās tīmekļa kontaktligzdas.

Kā mēs ZeroTech savienojām Apple Safari un klientu sertifikātus ar tīmekļa ligzdām

Pirms dažiem gadiem mēs izstrādājām paši savu implementāciju tīrā PHP, kas nevar izmantot https pieprasījumus, jo tas ir saites slānis. Pirms neilga laika gandrīz visi tīmekļa serveri iemācījās veikt starpniekservera pieprasījumus, izmantojot https, un atbalstīt savienojumu: jaunināšanu.

Kad tas notika, websockets kļuva gandrīz par noklusējuma servisu SPA aplikācijām, jo ​​cik ērti ir nodrošināt lietotājam saturu pēc servera iniciatīvas (pārsūtīt ziņu no cita lietotāja vai lejupielādēt jaunu attēla, dokumenta, prezentācijas versiju ka kāds cits pašlaik rediģē) .

Lai gan klienta sertifikāts pastāv jau labu laiku, tas joprojām ir slikti atbalstīts, jo tas rada daudz problēmu, mēģinot to apiet. Un (iespējams, :slightly_smiling_face: ) tāpēc IOS pārlūkprogrammas (visas, izņemot Safari) nevēlas to izmantot un pieprasīt no vietējā sertifikātu krātuves. Sertifikātiem ir daudz priekšrocību salīdzinājumā ar pieteikšanās/pass vai ssh atslēgām vai nepieciešamo portu aizvēršanu caur ugunsmūri. Bet ne par to ir runa.

Operētājsistēmā iOS sertifikāta instalēšanas procedūra ir diezgan vienkārša (ne bez specifikas), taču kopumā tas tiek darīts pēc instrukcijām, kuru internetā ir daudz un kuras ir pieejamas tikai pārlūkprogrammai Safari. Diemžēl Safari nezina, kā izmantot Client Сert tīmekļa ligzdām, taču internetā ir daudz instrukciju, kā izveidot šādu sertifikātu, taču praksē tas nav sasniedzams.

Kā mēs ZeroTech savienojām Apple Safari un klientu sertifikātus ar tīmekļa ligzdām

Lai izprastu tīmekļa kontaktligzdas, mēs izmantojām šādu plānu: problēma/hipotēze/risinājums.

Problēma: netiek atbalstītas tīmekļa ligzdas, kad tiek nosūtīti starpniekservera pieprasījumi resursiem, kas ir aizsargāti ar klienta sertifikātu Safari mobilajā pārlūkprogrammā IOS un citām lietojumprogrammām, kurās ir iespējots sertifikātu atbalsts.

Hipotēzes:

  1. Ir iespējams konfigurēt šādu izņēmumu, lai izmantotu sertifikātus (zinot, ka tādu nebūs) iekšējo/ārējo starpniekservera resursu tīmekļa ligzdām.
  2. Tīmekļa ligzdām varat izveidot unikālu, drošu un aizsargājamu savienojumu, izmantojot pagaidu sesijas, kas tiek ģenerētas parasta (ne tīmekļa ligzdas) pārlūkprogrammas pieprasījuma laikā.
  3. Pagaidu sesijas var īstenot, izmantojot vienu starpniekserveri (tikai iebūvētie moduļi un funkcijas).
  4. Pagaidu sesijas marķieri jau ir ieviesti kā gatavi Apache moduļi.
  5. Pagaidu sesijas marķierus var ieviest, loģiski veidojot mijiedarbības struktūru.

Redzams stāvoklis pēc ieviešanas.

Mērķis: pakalpojumu un infrastruktūras pārvaldībai jābūt pieejamai no mobilā tālruņa IOS bez papildu programmām (piemēram, VPN), vienotai un drošai.

Papildu mērķis: ietaupot laiku un resursus/tālruņa trafiku (daži pakalpojumi bez tīmekļa ligzdām rada nevajadzīgus pieprasījumus) ar ātrāku satura piegādi mobilajā internetā.

Kā pārbaudīt?

1. Atvēršanas lapas:

— например, 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. Vai izstrādātāja konsolē:

Kā mēs ZeroTech savienojām Apple Safari un klientu sertifikātus ar tīmekļa ligzdām

Hipotēžu pārbaude:

1. Ir iespējams konfigurēt šādu izņēmumu, lai izmantotu sertifikātus (zinot, ka tādu nebūs) iekšējo/ārējo starpniekserveru resursu tīmekļa ligzdām.

Šeit tika atrasti 2 risinājumi:

a) līmenī

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

mainīt piekļuves līmeni.

Šai metodei ir šādas nianses:

  • Sertifikāta pārbaude notiek pēc pieprasījuma starpniekservera resursam, tas ir, pēc pieprasījuma rokasspiediena. Tas nozīmē, ka starpniekserveris vispirms ielādēs un pēc tam pārtrauks pieprasījumu aizsargātajam pakalpojumam. Tas ir slikti, bet ne kritiski;
  • http2 protokolā. Tas joprojām ir melnraksts, un pārlūkprogrammu ražotāji nezina, kā to ieviest # info about tls1.3 http2 post handshake (pašlaik nedarbojas) Ieviest RFC 8740 “TLS 1.3 lietošana ar HTTP/2”;
  • Nav skaidrs, kā šo apstrādi apvienot.

b) Pamatlīmenī atļaujiet ssl bez sertifikāta.

SSLVerifyClient prasa => SSLVerifyClient nav obligāti, taču tas samazina starpniekservera drošības līmeni, jo šāds savienojums tiks apstrādāts bez sertifikāta. Tomēr jūs varat turpināt liegt piekļuvi starpniekservera pakalpojumiem, izmantojot šādu direktīvu:

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"

Sīkāku informāciju var atrast rakstā par ssl: Apache servera klienta sertifikāta autentifikācija

Abas opcijas tika pārbaudītas, opcija “b” tika izvēlēta tās daudzpusības un savietojamības ar http2 protokolu dēļ.

Lai pabeigtu šīs hipotēzes pārbaudi, bija jāveic daudz eksperimentu ar konfigurāciju; tika pārbaudīti šādi modeļi:

ja = prasīt = pārrakstīt

Rezultāts ir šāds pamata dizains:

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>

Ņemot vērā sertifikāta īpašnieka esošo pilnvarojumu, bet ar trūkstošu sertifikātu, man bija jāpievieno neesošs sertifikāta īpašnieks viena no pieejamajiem mainīgajiem SSl_PROTOCOL (nevis SSL_CLIENT_S_DN_CN), sīkāka informācija dokumentācijā:

Apache modulis mod_ssl

Kā mēs ZeroTech savienojām Apple Safari un klientu sertifikātus ar tīmekļa ligzdām

2. Tīmekļa ligzdām varat izveidot unikālu, drošu un aizsargātu savienojumu, izmantojot pagaidu sesijas, kas tiek ģenerētas parastā (bez tīmekļa ligzdas) pārlūkprogrammas pieprasījuma laikā.

Pamatojoties uz iepriekšējo pieredzi, konfigurācijai jāpievieno papildu sadaļa, lai sagatavotu pagaidu marķierus tīmekļa ligzdas savienojumiem parastā (ne tīmekļa ligzdas) pieprasījuma laikā.

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

Pārbaude parādīja, ka tas darbojas. Sīkdatnes ir iespējams pārsūtīt sev, izmantojot lietotāja pārlūkprogrammu.

3. Pagaidu sesijas var realizēt, izmantojot vienu starpniekserveri (tikai iebūvētie moduļi un funkcijas).

Kā mēs noskaidrojām iepriekš, Apache ir diezgan daudz pamata funkcionalitātes, kas ļauj izveidot nosacījumu konstrukcijas. Tomēr mums ir nepieciešami līdzekļi, lai aizsargātu mūsu informāciju, kamēr tā atrodas lietotāja pārlūkprogrammā, tāpēc mēs nosakām, ko un kāpēc uzglabāt, un kādas iebūvētās funkcijas izmantosim:

  • Mums ir nepieciešams marķieris, ko nevar viegli atšifrēt.
  • Mums ir nepieciešams marķieris, kurā ir iebūvēta novecošanās, un iespēja pārbaudīt novecošanos serverī.
  • Mums ir nepieciešams marķieris, kas tiks saistīts ar sertifikāta īpašnieku.

Tam nepieciešama jaukšanas funkcija, sāls un datums marķiera novecošanai. Pamatojoties uz dokumentāciju Izteiksmes Apache HTTP serverī mums tas viss ir no kastes sha1 un %{TIME}.

Rezultāts bija šāds dizains:

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

Mērķis ir sasniegts, taču ir problēmas ar servera novecošanos (var izmantot gadu vecu Cookie), kas nozīmē, ka tokeni, lai arī ir droši iekšējai lietošanai, tomēr nav droši rūpnieciskai (masveida) lietošanai.

Kā mēs ZeroTech savienojām Apple Safari un klientu sertifikātus ar tīmekļa ligzdām

4. Pagaidu sesijas marķieri jau ir ieviesti kā gatavi Apache moduļi.

No iepriekšējās iterācijas palika viena būtiska problēma - nespēja kontrolēt marķiera novecošanos.

Mēs meklējam gatavu moduli, kas to dara saskaņā ar vārdiem: apache token json divu faktoru auth

Jā, ir gatavi moduļi, taču tie visi ir saistīti ar konkrētām darbībām un tiem ir artefakti sesijas sākšanas un papildu sīkfailu veidā. Tas ir, ne uz laiku.
Meklēšana aizņēma piecas stundas, kas nedeva konkrētu rezultātu.

5. Pagaidu sesijas marķieri var tikt realizēti, loģiski veidojot mijiedarbības struktūru.

Gatavie moduļi ir pārāk sarežģīti, jo mums ir vajadzīgas tikai dažas funkcijas.

Tomēr problēma ar datumu ir tāda, ka Apache iebūvētās funkcijas neļauj ģenerēt datumu no nākotnes, un, pārbaudot novecošanos, iebūvētajās funkcijās netiek veikta matemātiska saskaitīšana/atņemšana.

Tas ir, jūs nevarat rakstīt:

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

Jūs varat salīdzināt tikai divus skaitļus.

Meklējot risinājumu Safari problēmai, es atradu interesantu rakstu: Home Assistant nodrošināšana ar klienta sertifikātiem (darbojas ar Safari/iOS)
Tajā ir aprakstīts koda piemērs Lua for Nginx, un tas, kā izrādījās, ļoti atkārto tās konfigurācijas daļas loģiku, kuru mēs jau esam ieviesuši, izņemot hmac sālīšanas metodes izmantošanu jaukšanai ( tas netika atrasts Apache).

Kļuva skaidrs, ka Lua ir valoda ar skaidru loģiku, un Apache var izdarīt kaut ko vienkāršu:

Izpētot atšķirību ar Nginx un Apache:

Un pieejamās funkcijas no Lua valodas ražotāja:
22.1 – datums un laiks

Mēs atradām veidu, kā nelielā Lua failā iestatīt env mainīgos, lai iestatītu datumu no nākotnes, ko salīdzināt ar pašreizējo.

Lūk, kā izskatās vienkāršs Lua skripts:

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

Un tas viss darbojas kopumā, optimizējot sīkfailu skaitu un nomainot marķieri, kad pienāk puse laika pirms vecā sīkfaila (token) derīguma termiņa beigām:

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 

Tā kā LuaHookAccessChecker tiks aktivizēts tikai pēc piekļuves pārbaudēm, pamatojoties uz šo informāciju no Nginx.

Kā mēs ZeroTech savienojām Apple Safari un klientu sertifikātus ar tīmekļa ligzdām

Saite uz avotu Attēls.

Vēl viens punkts.

Kopumā nav svarīgi, kādā secībā Apache (iespējams, arī Nginx) konfigurācijā tiek rakstītas direktīvas, jo galu galā viss tiks sakārtots pēc lietotāja pieprasījuma secības, kas atbilst apstrādes shēmai. Lua skripti.

Pabeigšana:

Redzamais stāvoklis pēc ieviešanas (mērķis):
pakalpojumu un infrastruktūras pārvaldība ir pieejama no mobilā tālruņa IOS bez papildu programmām (VPN), vienota un droša.

Mērķis ir sasniegts, tīmekļa ligzdas darbojas un tām ir ne mazāks drošības līmenis par sertifikātu.

Kā mēs ZeroTech savienojām Apple Safari un klientu sertifikātus ar tīmekļa ligzdām

Avots: www.habr.com

Pievieno komentāru