كيف صنعنا في ZeroTech أصدقاء Apple Safari وشهادات العملاء باستخدام Websockets

ستكون المقالة مفيدة لأولئك الذين:



  • يعرف ما هو Client Cert ويفهم لماذا Websockets على سفاري المحمول بالنسبة له ؛
  • ترغب في نشر خدمات الويب لدائرة محدودة من الأشخاص أو لنفسي فقط ؛
  • يعتقد أن كل شيء قد تم القيام به بالفعل من قبل شخص ما ، ويود أن يجعل العالم أكثر راحة وأمانًا.


بدأ تاريخ مآخذ الويب قبل حوالي 8 سنوات. في السابق ، تم استخدام الأساليب في شكل طلبات http طويلة (في الواقع ، ردود): أرسل متصفح المستخدم طلبًا إلى الخادم وانتظر رده عليه ، بعد أن اتصل الرد مرة أخرى وانتظر. ولكن بعد ذلك جاءت مآخذ الويب.







منذ عدة سنوات ، قمنا بتطوير تطبيق php الخالص الخاص بنا ، والذي لا يعرف كيفية استخدام طلبات https ، لأن هذه هي طبقة ارتباط البيانات. منذ وقت ليس ببعيد ، تعلمت جميع خوادم الويب تقريبًا طلبات الوكيل عبر https واتصال الدعم: الترقية.



عندما حدث ذلك ، أصبحت مآخذ الويب عمليا الخدمة الافتراضية لتطبيقات SPA ، لأن مدى ملاءمة تزويد المستخدم بالمحتوى الذي بدأه الخادم (إرسال رسالة من مستخدم آخر أو تحميل نسخة جديدة من صورة أو مستند أو عرض تقديمي يقوم شخص آخر بتحريره الآن) ...



على الرغم من أن Client ert كان موجودًا منذ فترة طويلة ، إلا أنه لا يزال مدعومًا بشكل سيئ ، لأنه يخلق الكثير من المشاكل مع محاولات تجاوزه. و (ربما: little_smiling_face :) لذا فإن متصفحات IOS (جميعها باستثناء Safari) لا تريد استخدامها وطلب متجر الشهادات المحلي. تتمتع الشهادات بالعديد من المزايا على مفاتيح تسجيل الدخول / المرور أو ssh أو جدار الحماية للمنافذ الصحيحة. ولكن هذا ليس نقطة.



في IOS ، يكون إجراء تثبيت الشهادة بسيطًا جدًا (ليس بدون تفاصيل) ، ولكن بشكل عام يتم ذلك وفقًا للتعليمات ، وهي كثيرة جدًا على الشبكة ومتوفرة فقط لمتصفح Safari. لسوء الحظ ، لا يعرف Safari كيفية استخدام Client ert لمقابس الويب ، ولكن هناك العديد من الإرشادات على الإنترنت حول كيفية إصدار مثل هذه الشهادة ، ولكن في الواقع لا يمكن تحقيق ذلك.







لفهم مآخذ الويب ، استخدمنا الخطة التالية: المشكلة / الفرضية / الحل.



المشكلة: لا يوجد دعم لمقابس الويب عند تقديم طلبات وكيل للموارد المحمية بشهادة عميل على متصفح Safari للهاتف المحمول لنظام التشغيل iOS والتطبيقات الأخرى التي تضمنت دعم الشهادة.



الفرضيات:



  1. من الممكن تكوين مثل هذا الاستثناء لاستخدام الشهادات (مع العلم أنها لن تكون موجودة) لمقابس الويب الخاصة بالموارد الداخلية / الخارجية.
  2. بالنسبة لمقابس الويب ، يمكنك إجراء اتصال آمن ومميز وفريد ​​باستخدام جلسات مؤقتة يتم إنشاؤها عن طريق طلب متصفح عادي (غير مأخذ للويب).
  3. يمكن تنفيذ الجلسات المؤقتة باستخدام خادم ويب وكيل واحد (فقط الوحدات والوظائف المضمنة).
  4. تم تنفيذ جلسات الرموز المؤقتة المؤقتة بالفعل كوحدات أباتشي جاهزة.
  5. يمكن تنفيذ جلسات الرموز المؤقتة المؤقتة من خلال التصميم المنطقي لهيكل التفاعل.


حالة مرئية بعد التنفيذ.



الغرض من العمل: يجب أن تكون إدارة الخدمات والبنية التحتية يمكن الوصول إليها من هاتف محمول إلى 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. من الممكن تكوين مثل هذا الاستثناء لاستخدام الشهادات (مع العلم أنها لن تكون موجودة) لمقابس الويب الخاصة بالموارد الداخلية / الخارجية.



هنا تم العثور على حلين:



أ) على مستوى



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


تغيير مستوى الوصول.



تحتوي هذه الطريقة على الفروق الدقيقة التالية:



  • يتم التحقق من الشهادة بعد طلب إلى المورد الوكيل ، أي تبادل تأكيد الطلب بعد الطلب. وهذا يعني أنه سيتم تحميل الوكيل أولاً ثم قطع الطلب على الخدمة المحمية. هذا أمر سيئ ، ولكن ليس حرجًا.
  • في http2. لا يزال في المسودة ، ولا يعرف مصنعو المستعرضات كيفية تنفيذه #info حول مصافحة tls1.3 http2 (لا يعمل الآن) تنفيذ RFC 8740 "باستخدام TLS 1.3 مع 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"


لمزيد من المعلومات ، راجع مقالة ssl: مصادقة شهادة عميل خادم Apache



تم اختبار كلا الخيارين ، تم اختيار الخيار "b" من أجل العالمية والتوافق مع بروتوكول http2.



لإكمال التحقق من هذه الفرضية ، كانت هناك حاجة إلى الكثير من تجارب التهيئة ، وتم فحص الإنشاءات:



if = required = rewrite



اتضح البناء الأساسي التالي:
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) ، لمزيد من التفاصيل ، راجع الوثائق:



Apache Module mod_ssl







2. بالنسبة لمقابس الويب ، يمكنك إجراء اتصال فريد وآمن ومحمي باستخدام مآخذ الويب. باستخدام جلسات مؤقتة يتم إنشاؤها أثناء طلب متصفح عادي (وليس مقبس ويب).



استنادًا إلى الخبرة السابقة ، تحتاج إلى إضافة قسم إضافي إلى التكوين من أجل إعداد الرموز المؤقتة المؤقتة للاتصال بمقبس الويب مع طلب عادي (وليس مقبس ويب).



#   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>


أظهر الاختبار أنه يعمل. من الممكن إرسال ملف تعريف ارتباط إلى متصفح المستخدم.



3. يمكن تنفيذ جلسات عابرة باستخدام خادم ويب وكيل واحد (فقط الوحدات والوظائف المضمنة).



كما اكتشفنا سابقًا ، يحتوي Apache على الكثير من الوظائف الأساسية التي تسمح لك بإنشاء بنيات شرطية. ومع ذلك ، نحن بحاجة إلى وسيلة لحماية معلوماتنا أثناء وجودها في متصفح المستخدم ، لذلك قمنا بتعيين ما يجب تخزينه وماذا يتم تخزينه ، وما هي الوظائف المضمنة التي سنستخدمها:



  • نحن بحاجة إلى رمز مميز لا يمكن فك تشفيره بسهولة.
  • أنت بحاجة إلى رمز مميز يحتوي على تقادم وقدرة على التحقق من التقادم على الخادم.
  • أنت بحاجة إلى رمز مميز مرتبط بمالك الشهادة.


يتطلب هذا دالة تجزئة وملحًا وتاريخًا لانتهاء صلاحية الرمز المميز. استنادًا إلى التعبيرات الواردة في وثائق خادم Apache HTTP ، لدينا جميعًا خارج الصندوق 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>




تم تحقيق الهدف ، ولكن هناك مشاكل في تقادم الخادم (يمكنك استخدام ملف تعريف الارتباط قبل عام) ، وهو ما يعني الرموز المميزة ، على الرغم من أنها آمنة للاستخدام الداخلي ، ولكنها غير آمنة للصناعة (الكتلة).







4. تم بالفعل تنفيذ الرموز المميزة للجلسات المؤقتة كوحدات Apache الجاهزة.



من التكرار السابق ، بقيت مشكلة واحدة كبيرة - عدم القدرة على التحكم في انتهاء صلاحية الرمز المميز.



نحن نبحث عن وحدة جاهزة تقوم بذلك ، وفقًا لـ: apache token json two factor auth





نعم ، هناك وحدات جاهزة ، ولكن جميعها مرتبطة بإجراءات محددة ولديها قطع أثرية في شكل جلسة بداية وملفات تعريف ارتباط إضافية. هذا ليس لفترة من الوقت.

لقد استغرقنا خمس ساعات للبحث ، الأمر الذي لم يسفر عن أي نتائج ملموسة.



5. يمكن تنفيذ جلسات عمل رمزية مؤقتة من خلال التصميم المنطقي لهيكل التفاعل.



إن الوحدات الجاهزة معقدة للغاية لأننا نحتاج فقط إلى وظيفتين.



في الوقت نفسه ، فإن المشكلة في التاريخ هي أن وظائف Apache المدمجة لا تسمح بإنشاء تاريخ من المستقبل ، وعند التحقق من التقادم في الوظائف المضمنة ، لا توجد إضافة / طرح رياضي.



أي أنه لا يمكنك الكتابة:



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


يمكن مقارنة رقمين فقط.



عند البحث عن حل بديل لـ Safari ، تم العثور على مقالة مثيرة للاهتمام: تأمين HomeAssistant بشهادات العميل (يعمل مع Safari / iOS)

وهي تصف مثالًا لرمز Lua لـ Nginx ، والذي ، كما اتضح ، يكرر منطق جزء التكوين الذي قمنا بتطبيقه بالفعل ، باستثناء استخدام طريقة همك لترتيب الملح للتجزئة (لم يتم العثور على ذلك في أباتشي).



أصبح من الواضح أن لوا هي لغة ذات منطق واضح ، فمن الممكن القيام بشيء بسيط لأباتشي:





بعد فحص الاختلاف مع Nginx و Apache:





والوظائف المتاحة من الشركة المصنعة للغة Lua:

22.1 - التاريخ والوقت تم



العثور على طريقة لتعيين متغيرات 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




وهكذا يعمل كل شيء في المجموع ، مع تحسين عدد ملفات تعريف الارتباط واستبدال الرمز المميز عندما يمر نصف الوقت قبل انتهاء صلاحية ملفات تعريف الارتباط القديمة (الرمز المميز):
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.







رابط لمصدر الصورة .



نقطة أخرى.



بشكل عام ، لا يهم في أي تسلسل يتم كتابة التوجيهات في تكوين أباتشي (ربما Nginx أيضًا) ، لأنه في النهاية سيتم فرز كل شيء بناءً على الترتيب الذي يمر به الطلب من المستخدم ، والذي يتوافق مع مخطط معالجة النصوص البرمجية لوا.



الإكمال:



الحالة المرئية بعد التنفيذ (الهدف):

تتوفر إدارة الخدمة والبنية التحتية من هاتف محمول على IOS بدون برامج إضافية (VPN) ، موحدة وآمنة.



يتم تحقيق الهدف ، وتعمل مآخذ الويب وليس لديها أمان أقل من الشهادة.






All Articles