ZeroTech olarak Apple Safari'yi ve istemci sertifikalarını websocket'lerle nasıl bağladık?

Makale aşağıdaki kişiler için yararlı olacaktır:

  • Client Cert'in ne olduğunu biliyor ve mobil Safari'de neden websocket'lere ihtiyaç duyduğunu anlıyor;
  • Web hizmetlerini sınırlı sayıda kişiye veya yalnızca kendime yayınlamak istiyorum;
  • her şeyin zaten birileri tarafından yapıldığını düşünüyor ve dünyayı biraz daha rahat ve güvenli hale getirmek istiyor.

Websocket’lerin tarihi yaklaşık 8 yıl önce başladı. Daha önce yöntemler uzun http istekleri (aslında yanıtlar) şeklinde kullanılıyordu: Kullanıcının tarayıcısı sunucuya bir istek gönderip bir şeye yanıt vermesini bekliyordu, yanıttan sonra tekrar bağlanıp bekledi. Ancak daha sonra websocket'ler ortaya çıktı.

ZeroTech olarak Apple Safari'yi ve istemci sertifikalarını websocket'lerle nasıl bağladık?

Birkaç yıl önce saf PHP'de, bağlantı katmanı olduğundan https isteklerini kullanamayan kendi uygulamamızı geliştirdik. Kısa bir süre önce neredeyse tüm web sunucuları, istekleri https üzerinden proxy olarak kullanmayı ve bağlantı: yükseltmeyi desteklemeyi öğrendi.

Bu gerçekleştiğinde, websockets SPA uygulamaları için neredeyse varsayılan hizmet haline geldi, çünkü sunucunun inisiyatifinde kullanıcıya içerik sağlamak (başka bir kullanıcıdan mesaj iletmek veya bir görselin, belgenin, sunumun yeni bir sürümünü indirmek) ne kadar kolay? başka birisinin şu anda düzenleme yaptığını) .

İstemci Sertifikası bir süredir ortalıkta olmasına rağmen, atlanmaya çalışıldığında birçok sorun yarattığından hâlâ yeterince desteklenmiyor. Ve (muhtemelen :slightly_smiling_face: ) bu nedenle IOS tarayıcıları (Safari hariç tümü) onu kullanmak istemiyor ve yerel sertifika deposundan talep ediyor. Sertifikaların, giriş/şifre veya ssh anahtarlarına veya gerekli bağlantı noktalarını bir güvenlik duvarı aracılığıyla kapatmaya kıyasla birçok avantajı vardır. Ama mesele bu değil.

İOS'ta, bir sertifika yükleme prosedürü oldukça basittir (ayrıntılar olmadan değil), ancak genel olarak internette çok sayıda bulunan ve yalnızca Safari tarayıcısı için mevcut olan talimatlara göre yapılır. Ne yazık ki Safari, Client Сert'in web soketleri için nasıl kullanılacağını bilmiyor, ancak internette böyle bir sertifikanın nasıl oluşturulacağına dair birçok talimat var, ancak pratikte bu ulaşılamaz.

ZeroTech olarak Apple Safari'yi ve istemci sertifikalarını websocket'lerle nasıl bağladık?

Websocket'leri anlamak için aşağıdaki planı kullandık: problem/hipotez/çözüm.

sorun: IOS ve sertifika desteğini etkinleştiren diğer uygulamalar için Safari mobil tarayıcısında bir istemci sertifikasıyla korunan kaynaklara istekler proxy olarak aktarılırken web yuvaları desteği yoktur.

hipotezler:

  1. Böyle bir istisnayı, dahili/harici proxy kaynakların web soketlerine yönelik sertifikaları (hiçbirinin olmayacağını bilerek) kullanacak şekilde yapılandırmak mümkündür.
  2. Websocket'ler için, normal (websocket olmayan) bir tarayıcı isteği sırasında oluşturulan geçici oturumları kullanarak benzersiz, güvenli ve savunulabilir bir bağlantı oluşturabilirsiniz.
  3. Geçici oturumlar tek bir proxy web sunucusu (yalnızca yerleşik modüller ve işlevler) kullanılarak gerçekleştirilebilir.
  4. Geçici oturum belirteçleri halihazırda hazır Apache modülleri olarak uygulanmıştır.
  5. Geçici oturum belirteçleri, etkileşim yapısının mantıksal olarak tasarlanmasıyla uygulanabilir.

Uygulamadan sonraki görünür durum.

Çalışmanın amacı: hizmetlerin ve altyapının yönetimine IOS üzerinde cep telefonundan ek programlara (VPN gibi) ihtiyaç duymadan, birleşik ve güvenli bir şekilde erişilebilmelidir.

Ek hedef: İçeriğin mobil İnternet üzerinde daha hızlı sunulmasıyla zamandan ve kaynaklardan/telefon trafiğinden tasarruf edilir (web soketleri olmayan bazı hizmetler gereksiz istekler oluşturur).

Nasıl kontrol edilir?

1. Açılış sayfaları:

— например, 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. Veya geliştirici konsolunda:

ZeroTech olarak Apple Safari'yi ve istemci sertifikalarını websocket'lerle nasıl bağladık?

Hipotez testi:

1. Dahili/harici proxy'li kaynakların web yuvalarına sertifika kullanmak için (hiçbirinin olmayacağını bilerek) böyle bir istisna yapılandırmak mümkündür.

Burada 2 çözüm bulundu:

a) Düzeyde

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

erişim düzeyini değiştirin.

Bu yöntemin aşağıdaki nüansları vardır:

  • Sertifika doğrulaması, vekil kaynağa yapılan bir istekten sonra, yani istek sonrası el sıkışmadan sonra gerçekleşir. Bu, proxy'nin önce korunan hizmete olan isteği yükleyeceği ve ardından keseceği anlamına gelir. Bu kötü ama kritik değil;
  • Http2 protokolünde. Hala taslak halinde ve tarayıcı üreticileri bunu nasıl uygulayacaklarını bilmiyorlar #info about tls1.3 http2 sonrası el sıkışma (şu anda çalışmıyor) RFC 8740'ı uygulayın "TLS 1.3'ü HTTP/2 ile kullanma";
  • Bu işlemenin nasıl birleştirileceği açık değildir.

b) Temel düzeyde, sertifikasız SSL'ye izin verin.

SSLVerifyClient gerektirir => SSLVerifyClient isteğe bağlıdır, ancak bu, böyle bir bağlantı sertifika olmadan işleneceği için proxy sunucusunun güvenlik düzeyini azaltır. Ancak, aşağıdaki yönergeyi kullanarak proxy hizmetlere erişimi daha da reddedebilirsiniz:

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"

SSL ile ilgili makalede daha ayrıntılı bilgi bulunabilir: Apache Sunucusu İstemci Sertifikası Kimlik Doğrulaması

Her iki seçenek de test edildi; çok yönlülüğü ve http2 protokolüyle uyumluluğu nedeniyle "b" seçeneği seçildi.

Bu hipotezin doğrulanmasını tamamlamak için konfigürasyonla ilgili birçok deney yapılması gerekti; aşağıdaki tasarımlar test edildi:

if = gerektirir = yeniden yaz

Sonuç aşağıdaki temel tasarımdır:

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>

Sertifika sahibinin mevcut yetkilendirmesini hesaba katarak, ancak eksik bir sertifikayla, var olmayan bir sertifika sahibini mevcut SSl_PROTOCOL değişkenlerinden biri (SSL_CLIENT_S_DN_CN yerine) biçiminde eklemek zorunda kaldım, daha fazla ayrıntı belgelerde:

Apache Modülü mod_ssl

ZeroTech olarak Apple Safari'yi ve istemci sertifikalarını websocket'lerle nasıl bağladık?

2. Websocket'ler için, normal (websocket olmayan) bir tarayıcı isteği sırasında oluşturulan geçici oturumları kullanarak benzersiz, güvenli ve korumalı bir bağlantı oluşturabilirsiniz.

Önceki deneyimlere dayanarak, normal (web soketi olmayan) bir istek sırasında web soketi bağlantıları için geçici tokenlar hazırlamak için konfigürasyona ek bir bölüm eklemeniz gerekir.

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

Testler işe yaradığını gösterdi. Kullanıcının tarayıcısı aracılığıyla Çerezleri kendinize aktarmanız mümkündür.

3. Geçici oturumlar tek bir proxy web sunucusu kullanılarak gerçekleştirilebilir (yalnızca yerleşik modüller ve işlevler).

Daha önce de öğrendiğimiz gibi Apache'nin koşullu yapılar oluşturmanıza olanak tanıyan pek çok temel işlevi vardır. Ancak, bilgilerimizi kullanıcının tarayıcısındayken korumak için araçlara ihtiyacımız var; bu nedenle neyi, neden saklayacağımızı ve hangi yerleşik işlevleri kullanacağımızı belirliyoruz:

  • Kolayca çözülemeyecek bir jetona ihtiyacımız var.
  • İçinde eskime bulunan ve sunucudaki eskimeyi kontrol etme yeteneğine sahip bir tokena ihtiyacımız var.
  • Sertifikanın sahibiyle ilişkilendirilecek bir jetona ihtiyacımız var.

Bu, bir karma işlevi, bir tuz ve belirteci yaşlandırmak için bir tarih gerektirir. Belgelere dayanarak Apache HTTP Sunucusunda İfadeler sha1 ve %{TIME} kutudan çıkan her şeye sahibiz.

Sonuç şu tasarımdı:

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

Hedefe ulaşıldı, ancak sunucunun eskimesiyle ilgili sorunlar var (bir yıllık Çerez kullanabilirsiniz), bu da tokenlerin dahili kullanım için güvenli olmasına rağmen endüstriyel (toplu) kullanım için güvensiz olduğu anlamına gelir.

ZeroTech olarak Apple Safari'yi ve istemci sertifikalarını websocket'lerle nasıl bağladık?

4. Geçici oturum belirteçleri halihazırda hazır Apache modülleri olarak uygulanmıştır.

Önceki yinelemeden önemli bir sorun kaldı: token yaşlanmasının kontrol edilememesi.

Şu sözlere göre bunu yapan hazır bir modül arıyoruz: apache token json two faktörlü auth

Evet, hazır modüller var, ancak hepsi belirli eylemlere bağlı ve bir oturum başlatma ve ek Çerezler şeklinde yapılara sahip. Yani bir süreliğine değil.
Aramamız beş saat sürdü ama somut bir sonuç vermedi.

5. Geçici oturum belirteçleri, etkileşimlerin yapısı mantıksal olarak tasarlanarak uygulanabilir.

Hazır modüller çok karmaşık çünkü yalnızca birkaç fonksiyona ihtiyacımız var.

Bununla birlikte, tarihle ilgili sorun, Apache'nin yerleşik işlevlerinin gelecekten bir tarih oluşturmaya izin vermemesi ve eskime olup olmadığını kontrol ederken yerleşik işlevlerde herhangi bir matematiksel toplama/çıkarma işleminin olmamasıdır.

Yani şunu yazamazsınız:

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

Yalnızca iki sayıyı karşılaştırabilirsiniz.

Safari sorununa geçici çözüm ararken ilginç bir makale buldum: HomeAssistant'ın güvenliğini istemci sertifikalarıyla sağlama (Safari/iOS ile çalışır)
Nginx için Lua'daki bir kod örneğini açıklıyor ve ortaya çıktığı gibi, hash için hmac tuzlama yönteminin kullanılması dışında, halihazırda uyguladığımız konfigürasyon bölümünün mantığını büyük ölçüde tekrarlıyor ( bu Apache'de bulunamadı).

Lua'nın açık mantığa sahip bir dil olduğu ve Apache için basit bir şey yapmanın mümkün olduğu ortaya çıktı:

Nginx ve Apache ile farkı inceledikten sonra:

Ve Lua dil üreticisinin sunduğu işlevler:
22.1 – Tarih ve Saat

Mevcut tarihle karşılaştırmak üzere gelecekten bir tarih belirlemek için env değişkenlerini küçük bir Lua dosyasına ayarlamanın bir yolunu bulduk.

Basit bir Lua betiği şöyle görünür:

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

Çerez sayısının optimizasyonu ve eski Çerezin (belirtecin) süresi dolmadan yarısı geldiğinde belirtecin değiştirilmesiyle her şey toplamda bu şekilde çalışır:

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 

Çünkü LuaHookAccessChecker ancak Nginx'ten gelen bu bilgiye göre erişim kontrolleri yapıldıktan sonra aktif hale gelecektir.

ZeroTech olarak Apple Safari'yi ve istemci sertifikalarını websocket'lerle nasıl bağladık?

Kaynağa bağlantı görüntü.

Başka bir nokta.

Genel olarak, Apache (muhtemelen Nginx) yapılandırmasında direktiflerin hangi sırayla yazıldığı önemli değildir, çünkü sonunda her şey, işleme şemasına karşılık gelen kullanıcıdan gelen isteğin sırasına göre sıralanacaktır. Lua komut dosyaları.

Р - Р ° РІРµСЂС € ение:

Uygulamadan sonraki görünür durum (hedef):
hizmetlerin ve altyapının yönetimi, IOS'taki bir cep telefonundan ek programlara (VPN) gerek kalmadan, birleşik ve güvenli bir şekilde yapılabilir.

Hedefe ulaşıldı, web soketleri çalışıyor ve bir sertifikadan daha az olmayan bir güvenlik seviyesine sahip.

ZeroTech olarak Apple Safari'yi ve istemci sertifikalarını websocket'lerle nasıl bağladık?

Kaynak: habr.com

Yorum ekle