شبكة الخدمات: Istio وLinkerd

mTLS وأمان الشبكة الخدماتية

18 دقيقة الدرس 5 من 27

mTLS وأمان الشبكة الخدماتية

شبكات عدم الثقة داخل مجموعة Kubernetes ليست رفاهية، بل هي متطلب إنتاجي صارم في أي مؤسسة اجتازت مراجعة SOC 2 أو PCI، أو تشغّل أحمال عمل متعددة المستأجرين على بنية تحتية مشتركة. تفرض شبكة الخدمات الخاصة بها هذا الضمان بشكل شفاف: كل اتصال يُوثَّق بشكل متبادل ويُشفَّر، وتُعلَن صلاحيات الوصول كسياسات، وكل ذلك دون أن يتغير سطر واحد من كود التطبيق. يشرح هذا الدرس كيف ينفّذ Istio هذا الضمان وأين يتعطل تحت ضغط الإنتاج.

لماذا mTLS على طبقة الشبكة الخدماتية؟

بدون شبكة خدمات، يكون حركة المرور الداخلية شرق-غرب داخل المجموعة نصًا عاديًا. يستطيع حاوية مخترقة التنصت على أي خدمة تصل إليها، وتزوير عناوين IP المصدر، وانتحال هوية أحمال العمل الأخرى. يمكن لـ Kubernetes NetworkPolicy حجب تدفقات الطبقة الثالثة، لكنه لا يستطيع التحقق من الهوية على طبقة التطبيقات. يحل mTLS مشكلة الهوية: يقدم الطرفان شهادات X.509، ويُرفض الاتصال إذا عجز أحد الطرفين عن إثبات هويته، وتُشفَّر جميع البيانات باستخدام TLS 1.3.

يشفّر Istio هوية حمل العمل وفق معيار SPIFFE: تحصل كل حاوية على شهادة تتضمن اسم بديل للموضوع (SAN) بالصيغة spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>. يعمل Istiod كمرجع اعتماد متوافق مع SPIFFE، يصدر شهادات قصيرة الأجل (افتراضيًا 24 ساعة، قابلة للتقليص إلى دقائق) تُجدَّد تلقائيًا بواسطة وكيل الجانب قبل انتهاء صلاحيتها.

Istio mTLS handshake between two sidecars Pod A App Container Envoy Sidecar SPIFFE ID (SAN) ns/frontend/sa/web Istiod (SPIFFE CA) Issues 24h certs via xDS Pod B App Container Envoy Sidecar SPIFFE ID (SAN) ns/backend/sa/api cert issuance cert issuance mTLS (TLS 1.3) Sidecars handle the full TLS handshake; application code sees plain HTTP internally
يُصدر Istiod شهادات SPIFFE لكل وكيل جانبي؛ وتُشفَّر جميع الاتصالات بين الحاويات ويُوثَّق كلا طرفيها تلقائيًا.

PeerAuthentication: فرض mTLS

يعمل Istio افتراضيًا بوضع PERMISSIVE — يقبل حركة المرور العادية وكذلك mTLS لتمكين النشر التدريجي. للإنتاج، يجب التبديل إلى STRICT. يتحكم مورد PeerAuthentication في ذلك على مستوى الشبكة الكاملة، أو مساحة الأسماء، أو حمل العمل بشكل فردي.

# mTLS صارم على مستوى الشبكة الكاملة — يُطبَّق مرة واحدة في istio-system apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: default namespace: istio-system spec: mtls: mode: STRICT --- # تجاوز على مستوى مساحة أسماء — مفيد خلال الترحيل التدريجي apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: default namespace: legacy-ns spec: mtls: mode: PERMISSIVE --- # لحمل عمل بعينه: مهمة دفعية قديمة لا تزال تستخدم النص العادي apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: legacy-batch namespace: jobs spec: selector: matchLabels: app: batch-importer mtls: mode: PERMISSIVE portLevelMtls: "8080": mode: DISABLE
فخ PERMISSIVE: كثير من الفرق تفعّل STRICT على مستوى الشبكة الكاملة لكنها تترك مساحات أسماء قديمة دون تعديل. شغّل istioctl x authz check <pod> وkubectl get peerauthentication -A بانتظام. إعداد PERMISSIVE على مستوى مساحة أسماء يتجاوز الإعداد الافتراضي للشبكة بصمت، ولن تكتشف ذلك إلا خلال تدقيق أو بعد اختراق.

سياسات التفويض: التحكم بالوصول على الطبقة السابعة

يعدّ AuthorizationPolicy جدار الحماية الخاص بـ Istio على طبقة التطبيقات. خلافًا لـ NetworkPolicy التي تعمل على L3/L4، يمكنه المطابقة على أسلوب HTTP والمسار والترويسات ومطالبات JWT والمُعرِّف SPIFFE للمُستدعي. ترتيب التقييم: تُقيَّم قواعد DENY أولًا، ثم قواعد ALLOW. يُرفض الطلب إذا طابقت أي قاعدة DENY، أو إذا لم تطابق أي قاعدة ALLOW في حال وجود سياسة ALLOW واحدة على الأقل.

# رفض كل حركة المرور داخل مساحة أسماء payments افتراضيًا، ثم الفتح بشكل انتقائي apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: deny-all namespace: payments spec: {} --- # السماح لخدمة checkout (في مساحة أسماء orders) بإرسال POST إلى /charge apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: allow-checkout namespace: payments spec: selector: matchLabels: app: payment-service action: ALLOW rules: - from: - source: principals: - "cluster.local/ns/orders/sa/checkout" to: - operation: methods: ["POST"] paths: ["/charge", "/refund"] --- # رفض أي استدعاء يحمل ترويسة قديمة مُهمَلة apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: deny-legacy-clients namespace: payments spec: action: DENY rules: - when: - key: request.headers[x-legacy-client] values: ["true"]
خط الأساس لمبدأ أقل الصلاحيات: انشر سياسة deny-all شاملة في كل مساحة أسماء منذ اليوم الأول، ثم أضف سياسات ALLOW تدريجيًا عند ثبوت حاجة الخدمات للتواصل. هذا هو نموذج عدم الثقة الذي تستخدمه Google داخليًا (BeyondProd). يُجبر ذلك المطورين على تعريف التبعيات بشكل صريح، مما يُحسّن أيضًا خريطة تبعيات الخدمات.

مصادقة JWT مع RequestAuthentication

لحركة المرور شمال-جنوب (الدخول)، اجمع RequestAuthentication (يتحقق من توقيع JWT) مع AuthorizationPolicy (يفرض المطالبات المسموح بها). لا يرفض RequestAuthentication الطلبات الخالية من رمز مميز — يرفض فقط الطلبات التي تحمل رمزًا غير صالح. المنطق في AuthorizationPolicy هو ما يفرض وجود الرمز.

apiVersion: security.istio.io/v1beta1 kind: RequestAuthentication metadata: name: jwt-auth namespace: api-gateway spec: selector: matchLabels: app: gateway jwtRules: - issuer: "https://auth.example.com" jwksUri: "https://auth.example.com/.well-known/jwks.json" audiences: - "api.example.com" forwardOriginalToken: true --- # اشتراط رمز مميز صالح وتقييد الوصول إلى دور reader أو admin apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: require-jwt namespace: api-gateway spec: selector: matchLabels: app: gateway action: ALLOW rules: - from: - source: requestPrincipals: ["https://auth.example.com/*"] when: - key: request.auth.claims[role] values: ["reader", "admin"]

تناوب الشهادات وقابلية إضافة مراجع اعتماد خارجية

مرجع اعتماد Istiod المدمج كافٍ لبيئات التطوير أحادية المجموعة، لكن مجموعات الإنتاج الكبيرة تستخدم مرجع اعتماد خارجيًا. الخيارات:

  • توقيع مرجع اعتماد وسيط: امنح Istiod شهادة وسيطة موقَّعة من شهادة المؤسسة؛ وهو يُصدر شهادات أحمال العمل مرتبطة بمرجع PKI الخاص بك. استخدم istio-ca-secret في istio-system.
  • تكامل cert-manager: استخدم وكيل istio-csr لتحويل جميع طلبات CSR من Envoy إلى cert-manager، الذي يمكنه بدوره التواصل مع Vault أو AWS ACM PCA أو أي مرجع اعتماد متوافق مع RFC 5280.
  • SPIRE: للهوية متعددة المجموعات أو متعددة الأنظمة الأساسية، استبدل مرجع اعتماد Istiod بالكامل بخادم SPIRE. تُدار جميع نطاقات الثقة مركزيًا؛ وتحصل أحمال العمل على الأجهزة الافتراضية والمعدنية وKubernetes على معرّفات SPIFFE متسقة.
حسابات تناوب المفاتيح: فترة صلاحية الشهادة الافتراضية البالغة 24 ساعة تعني أن المفتاح الخاص المخترق يكون صالحًا لمدة 24 ساعة كحد أقصى قبل التناوب. كثير من الفرق التي تشغّل أحمال عمل PCI-DSS تُقلّص هذه المدة إلى ساعة واحدة. فترات الصلاحية القصيرة تزيد من حِمل Istiod: بساعة واحدة و1,000 حاوية، يعالج Istiod ما يقارب 0.28 تجديد شهادة في الثانية — ضمن طاقته. عند 100,000 حاوية، خطّط لـ Istiod بتوفر عالٍ مع ضبط الموارد.

أنماط الفشل في الإنتاج

أكثر حوادث mTLS شيوعًا في الإنتاج:

  • حاوية مُحقونة تستدعي حاوية غير مُحقونة: وضع mTLS STRICT على جانب المُستدعي، لكن الهدف بلا وكيل جانبي. يتوقف الاتصال أو يعيد خطأ TLS. الحل: حقن الحاوية الهدف، أو إضافة تجاوز PERMISSIVE لحمل العمل، أو استخدام DestinationRule مع tls.mode: DISABLE لذلك المضيف تحديدًا.
  • فحوصات الصحة على مستوى العقدة: يستدعي kubelet فحوصات liveness/readiness مباشرة دون وكيل جانبي. يُعفي Istio هذه الاستدعاءات تلقائيًا عبر webhook الخاص بـ rewriteAppHTTPProbers.
  • السياسة لا تُطبَّق: محددات AuthorizationPolicy تستخدم تسميات الحاوية؛ خطأ إملائي يجعل السياسة لا تطابق أي شيء بصمت. تحقق دائمًا باستخدام istioctl x authz check <pod-name> -n <namespace>.
  • انحراف الساعة يُعطّل التحقق من JWT: فحوصات nbf/exp في JWT تتطلب مزامنة الساعات. انحراف NTP أكثر من 60 ثانية يُسبّب أخطاء 401 عشوائية.
وضع STRICT وعملاء المراقبة القديمون: عملاء Prometheus وDatadog وغيرها من أدوات المراقبة على مستوى العقدة غالبًا ما تعمل خارج الشبكة الخدماتية. عند تفعيل STRICT لمساحة أسماء، ستبدأ استدعاءات الجمع بالفشل بأخطاء TLS. أعفِها باستخدام PeerAuthentication بوضع PERMISSIVE على منافذ الجمع، أو أدرجها ضمن الشبكة الخدماتية أولًا.

التحقق من وضع الأمان

ثق لكن تحقق — تُنتج الشبكة الخدماتية البيانات اللازمة لمراجعة وضعها الأمني:

# التحقق من وضع mTLS النشط لحاوية معينة istioctl x describe pod <pod-name> -n <namespace> # عرض جميع سياسات التفويض السارية على حمل عمل istioctl x authz check <pod-name> -n <namespace> # التحقق من أن الاتصال فعلًا mTLS (ابحث عن TLSv1.3 في سجل Envoy) kubectl logs <pod-name> -c istio-proxy -n <namespace> | grep TLSv1 # قائمة بجميع سياسات PeerAuthentication عبر المجموعة kubectl get peerauthentication -A # قائمة بجميع سياسات AuthorizationPolicy kubectl get authorizationpolicy -A

الوضع الأمني الناضج لشبكة الخدمات في الإنتاج يعني: وضع STRICT على مستوى الشبكة الكاملة، وسياسة deny-all افتراضية في كل مساحة أسماء، وسياسات ALLOW مُدارة بالنسخ في Git جنبًا إلى جنب مع ملفات التطبيق، وتناوب الشهادات في أقل من أربع ساعات، وتأكيد Kiali (أو استعلام Prometheus مخصص على istio_requests_total{connection_security_policy="mutual_tls"}) أن أكثر من 99.9% من الاستدعاءات الداخلية مُشفَّرة بـ mTLS في جميع الأوقات.