Bagaimana kami di ZeroTech membuat sertifikat dan teman klien Apple Safari dengan websockets

Artikel ini akan bermanfaat bagi mereka yang:



  • tahu apa Cert Klien dan memahami mengapa soket web di Safari seluler untuknya;
  • ingin menerbitkan layanan web kepada lingkaran orang terbatas atau hanya untuk saya sendiri;
  • berpikir bahwa semuanya telah dilakukan oleh seseorang, dan ingin membuat dunia sedikit lebih nyaman dan lebih aman.


Sejarah soket web dimulai sekitar 8 tahun yang lalu. Sebelumnya, metode bentuk permintaan http panjang (pada kenyataannya, tanggapan) digunakan: browser pengguna mengirim permintaan ke server dan menunggu untuk menjawab sesuatu, setelah tanggapan itu terhubung lagi dan menunggu. Tapi kemudian muncul juga websockets.







Beberapa tahun yang lalu, kami mengembangkan implementasi php murni kami sendiri, yang tidak tahu cara menggunakan permintaan https, karena ini adalah lapisan tautan data. Belum lama ini, hampir semua server web mempelajari permintaan proxy melalui https dan dukungan koneksi: peningkatan.



Ketika ini terjadi, soket web hampir menjadi layanan default untuk aplikasi SPA, karena itu nyaman untuk menyediakan pengguna dengan konten pada inisiatif server (mengirim pesan dari pengguna lain atau mengunduh versi baru dari gambar, dokumen, presentasi yang sedang diedit oleh orang lain sekarang) ...



Meskipun Cert Klien telah ada selama beberapa waktu, namun masih tetap kurang didukung, karena hal itu menciptakan banyak masalah dengan mencoba menyiasatinya. Dan (mungkin: sedikit_smiling_face :) jadi browser iOS (semua kecuali Safari) tidak ingin menggunakannya dan meminta toko sertifikat lokal. Sertifikat memiliki banyak keunggulan dibandingkan login / pass atau kunci ssh atau firewall port yang benar. Tapi bukan itu intinya.



Di iOS, prosedur untuk memasang sertifikat cukup sederhana (bukan tanpa spesifikasi), tetapi secara umum dilakukan sesuai dengan instruksi, yang banyak terdapat di jaringan dan yang hanya tersedia untuk browser Safari. Sayangnya, Safari tidak tahu cara menggunakan Client ert untuk soket web, tetapi ada banyak instruksi di Internet tentang cara membuat sertifikat seperti itu, tetapi dalam praktiknya hal ini tidak mungkin tercapai.







Untuk memahami soket web, kami menggunakan paket berikut: masalah / hipotesis / solusi.



Masalah: tidak ada dukungan untuk soket web saat mem-proxy permintaan untuk sumber daya yang dilindungi oleh sertifikat klien di browser seluler Safari untuk iOS dan aplikasi lain yang menyertakan dukungan sertifikat.



Hipotesa:



  1. Dimungkinkan untuk mengkonfigurasi pengecualian seperti itu untuk menggunakan sertifikat (mengetahui bahwa sertifikat itu tidak akan tersedia) ke soket web sumber daya proksi internal / eksternal.
  2. Untuk soket web, Anda dapat membuat koneksi aman dan aman yang unik menggunakan sesi sementara yang dihasilkan selama permintaan browser (non-web socket) biasa.
  3. Sesi sementara dapat diimplementasikan menggunakan server web proxy tunggal (modul dan fungsi bawaan saja).
  4. Token sesi sementara telah diterapkan sebagai modul apache siap pakai.
  5. Token sesi sementara dapat diimplementasikan dengan mendesain secara logis struktur interaksi.


Keadaan terlihat setelah penyebaran.



Tujuan pekerjaan: manajemen layanan dan infrastruktur harus dapat diakses dari ponsel ke iOS tanpa program tambahan (seperti VPN), terpadu dan aman.



Tujuan tambahan: menghemat waktu dan sumber daya / lalu lintas telepon (beberapa layanan tanpa soket web menghasilkan permintaan yang tidak perlu) sembari mempercepat pengiriman konten di Internet seluler.



Bagaimana cara mengeceknya?



1. Halaman pembuka:



β€” , 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. Atau di konsol pengembang:







Menguji hipotesis:



1. Dimungkinkan untuk mengkonfigurasi pengecualian seperti itu untuk menggunakan sertifikat (mengetahui bahwa itu tidak akan tersedia) ke soket sumber daya proksi internal / eksternal yang ada di web.



Di sini 2 solusi ditemukan:



a) Di tingkat



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


ubah tingkat akses.



Metode ini memiliki nuansa berikut:



  • Sertifikat diperiksa setelah permintaan ke sumber daya proksi, yaitu, jabat tangan permintaan pos. Ini berarti bahwa proksi akan memuat pertama dan kemudian memotong permintaan ke layanan yang dilindungi. Ini buruk, tetapi tidak kritis;
  • Dalam protokol http2. Itu masih dalam konsep, dan vendor browser tidak tahu bagaimana menerapkannya # info tentang tls1.3 http2 post handshake (tidak berfungsi sekarang) Menerapkan RFC 8740 "Menggunakan TLS 1.3 dengan HTTP / 2" ;
  • Tidak jelas bagaimana menyatukan pemrosesan ini.


b) Pada tingkat dasar, aktifkan ssl tanpa sertifikat.



SSLVerifyClient membutuhkan => SSLVerifyClient opsional, tetapi ini mengurangi tingkat perlindungan dari server proxy, karena koneksi seperti itu akan diproses tanpa sertifikat. Namun, Anda selanjutnya dapat menolak akses ke layanan proxy dengan arahan berikut:



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"


Untuk informasi lebih lanjut, lihat artikel di ssl: Apache Server Client Authentication Certificate.



Kedua opsi diperiksa, opsi "b" dipilih untuk universalitas dan kompatibilitas dengan protokol http2.



Untuk menyelesaikan verifikasi hipotesis ini, butuh banyak percobaan dengan konfigurasi, konstruksi diuji:



jika = membutuhkan = menulis ulang



Konstruksi dasar berikut ini ternyata:
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>




Mempertimbangkan otorisasi yang ada oleh pemilik sertifikat, tetapi dengan sertifikat yang hilang, saya harus menambahkan pemilik sertifikat yang tidak ada dalam bentuk salah satu variabel SSl_PROTOCOL yang tersedia (alih-alih SSL_CLIENT_S_DN_CN), untuk perincian lebih lanjut lihat dokumentasi:



Modul Apache mod_ssl







2. Untuk soket web, Anda dapat membuat koneksi yang unik dengan aman dan dilindungi. menggunakan sesi sementara yang dihasilkan selama permintaan browser normal (bukan web socket).



Berdasarkan pengalaman sebelumnya, Anda perlu menambahkan bagian tambahan ke konfigurasi, sehingga selama permintaan reguler (bukan soket web), token sementara untuk koneksi soket web disiapkan.



#   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-name\s*=\s*some-val(;|$)/ >
</If

</If>
</If>


Pengujian menunjukkan bahwa itu berfungsi. Dimungkinkan untuk mengirimkan cookie ke browser pengguna.



3. Sesi sementara dapat diimplementasikan menggunakan satu server web proxy (hanya modul dan fungsi bawaan).



Seperti yang kami ketahui sebelumnya, Apache memiliki banyak fungsi inti yang memungkinkan Anda membuat konstruksi bersyarat. Namun, kami memerlukan sarana untuk melindungi informasi kami saat berada di browser pengguna, jadi kami menetapkan apa dan untuk apa yang akan disimpan, dan fungsi bawaan apa yang akan kami gunakan:



  • Kami membutuhkan token yang tidak dapat dengan mudah diterjemahkan.
  • Kami membutuhkan token di mana keusangan dan kemampuan untuk memeriksa keusangan di server ditransfer.
  • Kami membutuhkan token yang akan dikaitkan dengan pemilik sertifikat.


Untuk melakukan ini, Anda memerlukan fungsi hash, garam, dan tanggal agar token untuk kedaluwarsa. Berdasarkan Ekspresi dalam dokumentasi Apache HTTP Server , kami memiliki semuanya di luar kotak sha1 dan% {TIME}.



Kami mendapat konstruksi berikut:
# ,    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>




Tujuannya telah tercapai, tetapi ada masalah dengan keusangan server (Anda dapat menggunakan cookie dari tahun lalu), yang berarti bahwa token, meskipun aman untuk penggunaan internal, tidak aman untuk penggunaan industri (massal).







4. Token sesi sementara telah diterapkan sebagai modul Apache yang sudah jadi.



Dari iterasi sebelumnya, satu masalah signifikan tetap - ketidakmampuan untuk mengontrol token usang.



Kami mencari modul yang sudah jadi yang melakukan ini, sesuai dengan kata-kata: apache token json two factor auth





Ya, ada modul yang sudah jadi, tetapi semua terkait dengan tindakan spesifik dan memiliki artefak dalam bentuk sesi awal dan Cookie tambahan. Yaitu, tidak untuk sementara waktu.

Kami memerlukan waktu lima jam untuk mencari, yang tidak menghasilkan hasil nyata.



5. Token sesi sementara dapat diimplementasikan dengan secara logis merancang struktur interaksi.



Modul yang sudah jadi terlalu rumit karena kita hanya perlu beberapa fungsi.



Yang mengatakan, masalah dengan tanggal adalah bahwa fungsi bawaan dari Apache tidak memungkinkan menghasilkan tanggal dari masa depan, dan tidak ada penambahan / pengurangan matematis dalam fungsi bawaan saat memeriksa keusangan.



Artinya, Anda tidak bisa menulis:



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


Hanya dua angka yang bisa dibandingkan.



Saat mencari solusi untuk Safari, saya menemukan artikel yang menarik: Mengamankan HomeAssistant dengan sertifikat klien (bekerja dengan Safari / iOS)

Ini menjelaskan contoh kode Lua untuk Nginx, dan yang ternyata mengulangi logika bagian dari konfigurasi yang sudah kami implementasikan, kecuali untuk penggunaan metode hmac mengatur garam untuk hashing (ini tidak ditemukan di Apache).



Menjadi jelas bahwa Lua adalah bahasa dengan logika yang jelas, adalah mungkin untuk melakukan sesuatu yang sederhana untuk Apache:





Setelah memeriksa perbedaannya dengan Nginx dan Apache:





Dan fungsi yang tersedia dari produsen bahasa Lua:

22.1 - Tanggal dan Waktu



Menemukan cara untuk mengatur variabel env dalam file Lua kecil untuk mengatur tanggal dari masa depan untuk memeriksa dengan yang sekarang.



Beginilah bentuk skrip Lua sederhana:
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




Dan ini adalah cara kerjanya, dengan optimalisasi jumlah Cookie dan penggantian token ketika separuh waktu datang sebelum berakhirnya Cookie lama (token):
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 




Karena LuaHookAccessChecker akan diaktifkan hanya setelah akses memeriksa berdasarkan informasi ini dari Nginx.







Tautan ke sumber gambar .



Poin lain.



Secara umum, tidak masalah dalam urutan apa arahan ditulis dalam konfigurasi Apache (mungkin Nginx), karena pada akhirnya semuanya akan diurutkan berdasarkan urutan permintaan dari pengguna, yang sesuai dengan skema untuk mengerjakan skrip Lua.



Penyelesaian:



Status terlihat setelah implementasi (sasaran):

layanan dan manajemen infrastruktur tersedia dari ponsel di iOS tanpa program tambahan (VPN), disatukan dan aman.



Tujuannya tercapai, soket web berfungsi dan memiliki keamanan yang tidak kalah dengan sertifikat.






All Articles