ProHoster > Blog > διαχείριση > Πώς εμείς στη ZeroTech συνδέσαμε το Apple Safari και τα πιστοποιητικά πελατών με υποδοχές ιστού
Πώς εμείς στη ZeroTech συνδέσαμε το Apple Safari και τα πιστοποιητικά πελατών με υποδοχές ιστού
Το άρθρο θα είναι χρήσιμο σε όσους:
Γνωρίζει τι είναι το Client Cert και κατανοεί γιατί χρειάζεται υποδοχές web στο Safari για κινητά.
Θα ήθελα να δημοσιεύω υπηρεσίες ιστού σε έναν περιορισμένο κύκλο ατόμων ή μόνο στον εαυτό μου.
πιστεύει ότι όλα έχουν ήδη γίνει από κάποιον και θα ήθελε να κάνει τον κόσμο λίγο πιο βολικό και ασφαλέστερο.
Η ιστορία των websockets ξεκίνησε πριν από περίπου 8 χρόνια. Προηγουμένως, χρησιμοποιήθηκαν μέθοδοι με τη μορφή μεγάλων αιτημάτων http (στην πραγματικότητα απαντήσεων): το πρόγραμμα περιήγησης του χρήστη έστελνε ένα αίτημα στον διακομιστή και περίμενε να απαντήσει κάτι, μετά την απάντηση συνδέθηκε ξανά και περίμενε. Στη συνέχεια όμως εμφανίστηκαν οι δικτυακές υποδοχές.
Πριν από μερικά χρόνια, αναπτύξαμε τη δική μας υλοποίηση σε καθαρή PHP, η οποία δεν μπορεί να χρησιμοποιήσει αιτήματα https, καθώς αυτό είναι το επίπεδο σύνδεσης. Πριν από λίγο καιρό, σχεδόν όλοι οι διακομιστές ιστού έμαθαν να κάνουν proxy αιτήματα μέσω https και να υποστηρίζουν σύνδεση: αναβάθμιση.
Όταν συνέβη αυτό, τα websockets έγιναν σχεδόν η προεπιλεγμένη υπηρεσία για εφαρμογές SPA, επειδή πόσο βολικό είναι να παρέχεται περιεχόμενο στον χρήστη με πρωτοβουλία του διακομιστή (μετάδοση μηνύματος από άλλο χρήστη ή λήψη μιας νέας έκδοσης μιας εικόνας, ενός εγγράφου, μιας παρουσίασης που κάποιος άλλος επεξεργάζεται αυτήν τη στιγμή) .
Αν και το Πιστοποιητικό Πελάτη υπάρχει εδώ και αρκετό καιρό, εξακολουθεί να υποστηρίζεται ελάχιστα, καθώς δημιουργεί πολλά προβλήματα όταν προσπαθείτε να το παρακάμψετε. Και (πιθανόν :slightly_smiling_face: ) γι' αυτό τα προγράμματα περιήγησης IOS (όλα εκτός από το Safari) δεν θέλουν να το χρησιμοποιήσουν και να το ζητήσουν από το τοπικό κατάστημα πιστοποιητικών. Τα πιστοποιητικά έχουν πολλά πλεονεκτήματα σε σύγκριση με τα κλειδιά σύνδεσης/περάσματος ή ssh ή το κλείσιμο των απαραίτητων θυρών μέσω τείχους προστασίας. Αλλά δεν πρόκειται για αυτό.
Στο iOS, η διαδικασία εγκατάστασης ενός πιστοποιητικού είναι αρκετά απλή (όχι χωρίς ιδιαιτερότητες), αλλά σε γενικές γραμμές γίνεται σύμφωνα με οδηγίες, από τις οποίες υπάρχουν πολλές στο Διαδίκτυο και οι οποίες είναι διαθέσιμες μόνο για το πρόγραμμα περιήγησης Safari. Δυστυχώς, το Safari δεν ξέρει πώς να χρησιμοποιεί το Client Сert για υποδοχές Ιστού, αλλά υπάρχουν πολλές οδηγίες στο Διαδίκτυο για το πώς να δημιουργήσετε ένα τέτοιο πιστοποιητικό, αλλά στην πράξη αυτό είναι ανέφικτο.
Για να κατανοήσουμε τα websockets, χρησιμοποιήσαμε το ακόλουθο σχέδιο: πρόβλημα/υπόθεση/λύση.
Πρόβλημα: δεν υπάρχει υποστήριξη για υποδοχές Ιστού κατά την υποβολή αιτημάτων μεσολάβησης σε πόρους που προστατεύονται από πιστοποιητικό πελάτη στο πρόγραμμα περιήγησης για κινητά Safari για IOS και άλλες εφαρμογές που έχουν ενεργοποιήσει την υποστήριξη πιστοποιητικών.
Υποθέσεις:
Είναι δυνατό να διαμορφώσετε μια τέτοια εξαίρεση για τη χρήση πιστοποιητικών (γνωρίζοντας ότι δεν θα υπάρχουν) σε υποδοχές ιστού εσωτερικών/εξωτερικών πόρων μεσολάβησης.
Για τις υποδοχές ιστού, μπορείτε να δημιουργήσετε μια μοναδική, ασφαλή και υπερασπίσιμη σύνδεση χρησιμοποιώντας προσωρινές περιόδους σύνδεσης που δημιουργούνται κατά τη διάρκεια ενός κανονικού αιτήματος προγράμματος περιήγησης (χωρίς ιστό).
Οι προσωρινές συνεδρίες μπορούν να υλοποιηθούν χρησιμοποιώντας έναν διακομιστή μεσολάβησης web (μόνο ενσωματωμένες μονάδες και λειτουργίες).
Τα προσωρινά διακριτικά περιόδου λειτουργίας έχουν ήδη εφαρμοστεί ως έτοιμες μονάδες Apache.
Τα προσωρινά διακριτικά περιόδου λειτουργίας μπορούν να υλοποιηθούν σχεδιάζοντας λογικά τη δομή αλληλεπίδρασης.
Ορατή κατάσταση μετά την εφαρμογή.
Σκοπός: Η διαχείριση των υπηρεσιών και της υποδομής θα πρέπει να είναι προσβάσιμη από κινητό τηλέφωνο στο IOS χωρίς πρόσθετα προγράμματα (όπως VPN), ενοποιημένη και ασφαλής.
Επιπλέον στόχος: εξοικονόμηση χρόνου και πόρων/επισκεψιμότητας τηλεφώνου (ορισμένες υπηρεσίες χωρίς υποδοχές Ιστού δημιουργούν περιττά αιτήματα) με ταχύτερη παράδοση περιεχομένου στο Διαδίκτυο για κινητά.
Πώς να ελέγξετε;
1. Άνοιγμα σελίδων:
— например, 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. Ή στην κονσόλα προγραμματιστή:
Έλεγχος υποθέσεων:
1. Είναι δυνατό να διαμορφώσετε μια τέτοια εξαίρεση για χρήση πιστοποιητικών (γνωρίζοντας ότι δεν θα υπάρχουν) σε υποδοχές Ιστού εσωτερικών/εξωτερικών πόρων μεσολάβησης.
Η επαλήθευση του πιστοποιητικού πραγματοποιείται μετά από ένα αίτημα προς τον πόρο μεσολάβησης, δηλαδή, μετά από χειραψία αιτήματος. Αυτό σημαίνει ότι ο διακομιστής μεσολάβησης θα φορτώσει πρώτα και στη συνέχεια θα διακόψει το αίτημα προς την προστατευμένη υπηρεσία. Αυτό είναι κακό, αλλά όχι κρίσιμο.
Στο πρωτόκολλο http2. Είναι ακόμα σε προσχέδιο και οι κατασκευαστές προγραμμάτων περιήγησης δεν ξέρουν πώς να το εφαρμόσουν #info about tls1.3 http2 μετά τη χειραψία (δεν λειτουργεί τώρα) Εφαρμογή RFC 8740 "Using TLS 1.3 with HTTP/2";
Δεν είναι σαφές πώς να ενοποιηθεί αυτή η επεξεργασία.
β) Σε βασικό επίπεδο, να επιτρέπεται η ssl χωρίς πιστοποιητικό.
Το SSLVerifyClient απαιτεί => SSLVerifyClient προαιρετικό, αλλά αυτό μειώνει το επίπεδο ασφάλειας του διακομιστή μεσολάβησης, καθώς μια τέτοια σύνδεση θα υποβληθεί σε επεξεργασία χωρίς πιστοποιητικό. Ωστόσο, μπορείτε να αρνηθείτε περαιτέρω την πρόσβαση σε υπηρεσίες μεσολάβησης με την ακόλουθη οδηγία:
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"
Το αποτέλεσμα είναι ο ακόλουθος βασικός σχεδιασμός:
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>
Λαμβάνοντας υπόψη την υπάρχουσα εξουσιοδότηση από τον κάτοχο του πιστοποιητικού, αλλά με ένα πιστοποιητικό που λείπει, έπρεπε να προσθέσω έναν ανύπαρκτο κάτοχο πιστοποιητικού με τη μορφή μιας από τις διαθέσιμες μεταβλητές SSl_PROTOCOL (αντί για SSL_CLIENT_S_DN_CN), περισσότερες λεπτομέρειες στην τεκμηρίωση:
2. Για τις υποδοχές ιστού, μπορείτε να δημιουργήσετε μια μοναδική, ασφαλή και προστατευμένη σύνδεση χρησιμοποιώντας προσωρινές περιόδους σύνδεσης που δημιουργούνται κατά τη διάρκεια ενός κανονικού αιτήματος προγράμματος περιήγησης (μη πρίζας).
Με βάση την προηγούμενη εμπειρία, πρέπει να προσθέσετε μια πρόσθετη ενότητα στη διαμόρφωση, προκειμένου να προετοιμαστούν προσωρινά διακριτικά για συνδέσεις υποδοχής web κατά τη διάρκεια ενός κανονικού αιτήματος (μη πρίζας 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>
Η δοκιμή έδειξε ότι λειτουργεί. Είναι δυνατή η μεταφορά Cookies στον εαυτό σας μέσω του προγράμματος περιήγησης του χρήστη.
3. Οι προσωρινές συνεδρίες μπορούν να υλοποιηθούν χρησιμοποιώντας έναν διακομιστή μεσολάβησης web (μόνο ενσωματωμένες μονάδες και λειτουργίες).
Όπως ανακαλύψαμε νωρίτερα, ο Apache έχει πολλές βασικές λειτουργίες που σας επιτρέπουν να δημιουργήσετε δομές υπό όρους. Ωστόσο, χρειαζόμαστε μέσα για να προστατεύσουμε τις πληροφορίες μας ενώ βρίσκονται στο πρόγραμμα περιήγησης του χρήστη, επομένως καθορίζουμε τι να αποθηκεύσουμε και γιατί και ποιες ενσωματωμένες λειτουργίες θα χρησιμοποιήσουμε:
Χρειαζόμαστε ένα διακριτικό που δεν μπορεί να αποκωδικοποιηθεί εύκολα.
Χρειαζόμαστε ένα διακριτικό που να έχει ενσωματωμένη την απαξίωση και τη δυνατότητα ελέγχου της απαρχαιότητας στον διακομιστή.
Χρειαζόμαστε ένα διακριτικό που θα συσχετίζεται με τον κάτοχο του πιστοποιητικού.
Αυτό απαιτεί μια συνάρτηση κατακερματισμού, ένα αλάτι και μια ημερομηνία για να παλαιώσει το διακριτικό. Με βάση την τεκμηρίωση Εκφράσεις στον διακομιστή HTTP Apache τα έχουμε όλα εκτός κουτιού sha1 και %{TIME}.
Το αποτέλεσμα ήταν αυτό το σχέδιο:
#нет сертификата, и обращение к 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>
Ο στόχος έχει επιτευχθεί, αλλά υπάρχουν προβλήματα με την απαξίωση του διακομιστή (μπορείτε να χρησιμοποιήσετε ένα Cookie ενός έτους), πράγμα που σημαίνει ότι τα διακριτικά, αν και ασφαλή για εσωτερική χρήση, δεν είναι ασφαλή για βιομηχανική (μαζική) χρήση.
4. Τα διακριτικά προσωρινής περιόδου λειτουργίας έχουν ήδη εφαρμοστεί ως έτοιμες μονάδες Apache.
Ένα σημαντικό πρόβλημα παρέμεινε από την προηγούμενη επανάληψη - η αδυναμία να ελεγχθεί η συμβολική γήρανση.
Ψάχνουμε για μια έτοιμη ενότητα που να το κάνει αυτό, σύμφωνα με τις λέξεις: apache token json two factor auth
Ναι, υπάρχουν έτοιμες ενότητες, αλλά είναι όλες συνδεδεμένες με συγκεκριμένες ενέργειες και έχουν τεχνουργήματα με τη μορφή έναρξης μιας συνεδρίας και επιπλέον Cookies. Δηλαδή όχι για λίγο.
Μας πήρε πέντε ώρες για να ψάξουμε, κάτι που δεν έδωσε συγκεκριμένο αποτέλεσμα.
5. Τα προσωρινά διακριτικά περιόδου λειτουργίας μπορούν να υλοποιηθούν σχεδιάζοντας λογικά τη δομή των αλληλεπιδράσεων.
Οι έτοιμες μονάδες είναι πολύ περίπλοκες, γιατί χρειαζόμαστε μόνο μερικές λειτουργίες.
Τούτου λεχθέντος, το πρόβλημα με την ημερομηνία είναι ότι οι ενσωματωμένες συναρτήσεις του Apache δεν επιτρέπουν τη δημιουργία ημερομηνίας από το μέλλον και δεν υπάρχει μαθηματική πρόσθεση/αφαίρεση στις ενσωματωμένες συναρτήσεις κατά τον έλεγχο της απαρχαιότητας.
Δηλαδή, δεν μπορείτε να γράψετε:
(%{env:zt-cert-date} + 30) > %{DATE}
Μπορείτε να συγκρίνετε μόνο δύο αριθμούς.
Ενώ έψαχνα για μια λύση για το πρόβλημα του Safari, βρήκα ένα ενδιαφέρον άρθρο: Ασφάλεια του HomeAssistant με πιστοποιητικά πελάτη (λειτουργεί με Safari/iOS)
Περιγράφει ένα παράδειγμα κώδικα στο Lua για το Nginx και το οποίο, όπως αποδείχθηκε, επαναλαμβάνει πολύ τη λογική αυτού του τμήματος της διαμόρφωσης που έχουμε ήδη εφαρμόσει, με εξαίρεση τη χρήση της μεθόδου hmac salting για κατακερματισμό ( αυτό δεν βρέθηκε στο Apache).
Έγινε σαφές ότι η Lua είναι μια γλώσσα με ξεκάθαρη λογική και είναι δυνατόν να κάνουμε κάτι απλό για τους Apache:
Βρήκαμε έναν τρόπο να ορίσουμε μεταβλητές env σε ένα μικρό αρχείο Lua προκειμένου να ορίσουμε μια ημερομηνία από το μέλλον για σύγκριση με την τρέχουσα.
Έτσι μοιάζει ένα απλό σενάριο 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
Και κάπως έτσι λειτουργούν όλα συνολικά, με βελτιστοποίηση του αριθμού των Cookies και αντικατάσταση του διακριτικού όταν ο μισός χρόνος λήξει το παλιό Cookie (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
Επειδή το LuaHookAccessChecker θα ενεργοποιηθεί μόνο μετά από ελέγχους πρόσβασης με βάση αυτές τις πληροφορίες από το Nginx.
Γενικά, δεν έχει σημασία με ποια σειρά είναι γραμμένες οι οδηγίες στη διαμόρφωση Apache (πιθανώς και Nginx), αφού στο τέλος όλα θα ταξινομηθούν με βάση τη σειρά του αιτήματος από τον χρήστη, που αντιστοιχεί στο σχήμα για επεξεργασία Σενάρια Lua.
Ολοκλήρωση:
Ορατή κατάσταση μετά την υλοποίηση (στόχος):
η διαχείριση υπηρεσιών και υποδομής είναι διαθέσιμη από κινητό τηλέφωνο στο IOS χωρίς πρόσθετα προγράμματα (VPN), ενοποιημένη και ασφαλής.
Ο στόχος έχει επιτευχθεί, οι υποδοχές Ιστού λειτουργούν και έχουν επίπεδο ασφάλειας όχι λιγότερο από ένα πιστοποιητικό.