TLS والأمان في Envoy Gateway: اضبط HTTPS وتوقف عن تشغيل الـ API لديك مكشوفًا

5 min read

تشغيل HTTP ليس إلا الإحماء. ما لم تكن خدمتك تعيش في العصر الحجري، فالإنتاج يحتاج شبه مؤكد إلى HTTPS. هذه المقالة تغطي أساسيات TLS في Envoy Gateway حتى تتوقف APIs لديك عن العمل بشكل مكشوف.

كيف يعمل TLS داخل Gateway

الأسلوب الأكثر شيوعًا هو: إنهاء TLS عند Gateway listener.

ومعناه:

  • يتصل العميل عبر HTTPS
  • يتولى الـ Gateway المصافحة والشهادة
  • ثم يتم تمرير المرور داخليًا إلى الـ Service من دون تشفير

Listener HTTPS أساسي يبدو هكذا:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: eg
spec:
  gatewayClassName: eg
  listeners:
    - name: http
      protocol: HTTP
      port: 80
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - kind: Secret
            group: ""
            name: example-cert

هناك أمران مهمان هنا:

  • protocol: HTTPS
  • tls.mode: Terminate

وهذا يعني أن TLS ينتهي هنا، وأن الـ Gateway هو من يدير الشهادة.

HTTPS + HTTPRoute مقابل TLSRoute: ما الفرق

هذه نقطة مهمة، لأن كثيرًا من الناس يرون TLSRoute ويسألون فورًا:

"أليس HTTPS listener كافيًا أصلًا؟ لماذا نحتاج إلى TLSRoute منفصل؟"

الفرق هو هل يقوم الـ Gateway بإنهاء TLS أم لا:

السيناريو التركيبة الشائعة الوصف
الـ Gateway ينهي TLS ثم يطبق قواعد HTTP HTTPS listener + HTTPRoute أكثر سيناريوهات المواقع والـ APIs شيوعًا
الـ Gateway لا يفك التشفير، بل يوجّه المرور المشفّر بالاعتماد على SNI فقط TLS listener + TLSRoute سيناريو TLS passthrough

بمعنى آخر:

  • إذا كنت تريد فحص path أو header أو method عند الـ Gateway، فعليك إنهاء TLS أولًا ثم استخدام HTTPRoute
  • إذا كنت تريد الحفاظ على التشفير من الطرف إلى الطرف وعدم فكّه عند الـ Gateway، فـ TLSRoute هو المسار الصحيح

ولا يوجد هنا خيار "أكثر تقدمًا" من الآخر، المهم أن تعرف ما الذي تريده: "إنهاء TLS وتنفيذ توجيه L7" أم "الحفاظ على التشفير سليمًا وتنفيذ توجيه مبني على SNI."

جهّز Secret الخاص بالشهادة

في بيئات الاختبار، تكفي شهادة self-signed. والأمثلة الرسمية تستخدم ذلك بالضبط:

openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 \
  -subj '/O=example Inc./CN=example.com' \
  -keyout example.com.key \
  -out example.com.crt
 
openssl req -out www.example.com.csr -newkey rsa:2048 -nodes \
  -keyout www.example.com.key \
  -subj "/CN=www.example.com/O=example organization"
 
openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key \
  -set_serial 0 -in www.example.com.csr -out www.example.com.crt

خزّن المفتاح/الشهادة كـ Kubernetes Secret:

kubectl create secret tls example-cert \
  --key=www.example.com.key \
  --cert=www.example.com.crt

بعدها يستطيع Gateway الإشارة إلى هذا الـ Secret عبر certificateRefs.

⚠️ شهادات self-signed مخصصة للاختبار فقط. في الإنتاج استخدم cert-manager أو آلية إدارة الشهادات الموجودة لديك. لا تنقل إعدادات الـ demo إلى الإنتاج مباشرة، فهذا يشبه العيش على النودلز الفورية: مقبول أحيانًا، لكنه ليس نمط حياة مستدامًا.

أضف HTTPS Listener إلى Gateway موجود

إذا كان لديك بالفعل eg Gateway من المقالة السابقة، فيمكنك ترقيعه مباشرة:

kubectl patch gateway eg --type=json --patch '
  - op: add
    path: /spec/listeners/-
    value:
      name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
        - kind: Secret
          group: ""
          name: example-cert
'

تحقّق من الحالة:

kubectl get gateway/eg -o yaml

إذا بدا الـ listener سليمًا، فاختبر HTTPS:

curl -v -HHost:www.example.com \
  --resolve "www.example.com:443:${GATEWAY_HOST}" \
  --cacert example.com.crt \
  https://www.example.com/get

هذا الأمر ممتاز للتحقق المحلي، خصوصًا عندما لا يكون DNS موصولًا بعد.

Gateway واحد، وعدة Domains على HTTPS

إذا كان لديك عدة domains، فأضف عدة HTTPS listeners إلى نفس الـ Gateway. مثلًا، لإضافة foo.example.com:

listeners:
  - name: https-main
    protocol: HTTPS
    port: 443
    hostname: www.example.com
    tls:
      mode: Terminate
      certificateRefs:
        - name: example-cert
  - name: https-foo
    protocol: HTTPS
    port: 443
    hostname: foo.example.com
    tls:
      mode: Terminate
      certificateRefs:
        - name: foo-cert

ولا تنس تحديث HTTPRoute المقابل بالـ hostname الصحيح، وإلا فقد يكون الـ listener يستمع فعلًا بينما الـ route لا يعرف إلى أين يوجّه المرور.

النموذج الذهني لـ TLSRoute

بالنسبة إلى TLS passthrough، فكر في TLSRoute بهذه الطريقة:

💡 قد يختلف apiVersion الخاص بـ TLSRoute بحسب نسخة Gateway API CRDs المثبتة لديك. المثال التالي لغرض الفهم فقط، لذا تحقّق من النسخة الموجودة في الـ cluster قبل التطبيق.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: eg-tls
spec:
  gatewayClassName: eg
  listeners:
    - name: tls
      protocol: TLS
      port: 443
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
  name: passthrough-route
spec:
  parentRefs:
    - name: eg-tls
  hostnames:
    - "db.example.com"
  rules:
    - backendRefs:
        - name: db-service
          port: 5432

الحالات الشائعة لهذا النمط:

  • التوجيه اعتمادًا فقط على SNI hostname
  • الحفاظ على التشفير حتى الـ backend
  • عدم تنفيذ تحليل path/header الخاص بـ HTTP عند الـ Gateway

ولهذا فإن الفرق الأكبر بين TLSRoute و HTTPRoute ليس الاسم فقط، بل إنهما يعملان على طبقات مختلفة تمامًا من المرور.

الإشارات إلى الشهادات عبر Namespaces مختلفة

تشير الوثائق الرسمية إلى قدرة مفيدة: يمكن لـ Gateway الإشارة إلى Secrets الخاصة بالشهادات في namespaces أخرى، لكن ذلك يتطلب ReferenceGrant.

على سبيل المثال، إذا كان فريق المنصة يخزّن الشهادات مركزيًا داخل envoy-gateway-system، بينما Gateway الخاص بالتطبيق موجود في default، فلا يمكنك ببساطة الإشارة عبر حدود الـ namespace من دون تصريح صريح.

مثال:

apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-default-to-read-cert
  namespace: envoy-gateway-system
spec:
  from:
    - group: gateway.networking.k8s.io
      kind: Gateway
      namespace: default
  to:
    - group: ""
      kind: Secret

ومعناه:

  • يوجد Gateway في namespace اسمه default
  • وهو مخوّل للإشارة إلى موارد Secret الموجودة في envoy-gateway-system

ومن دون ReferenceGrant، تُعامل هذه الإشارة عبر namespaces على أنها غير صالحة. هذه آلية حماية، وليست لأن Gateway API يحب التعقيد بلا داعٍ.

الخلاصة في سطر واحد

TLS في Envoy Gateway ليس غامضًا كما يبدو: جهّز Secret الخاص بالشهادة، واضبط HTTPS listener، وتأكد من توافق route و hostname، وهذا هو الأساس كله تقريبًا.

💡 أكثر خطأ شائع عند المبتدئين ليس أن TLS صعب، بل أن ثلاثة أشياء لا تكون مصطفّة معًا: hostname و certificateRefs و HTTPRoute hostnames. وإذا اختل واحد منها فقط، يبدأ المرور في التصرف بشكل سيئ.

الخطوة التالية

الآن وبعد أن غطيت التثبيت والمفاهيم والتوجيه و TLS، تجمع المقالة التالية ذلك كله في مجموعة ممارسات تقل معها احتمالات الانفجار في الإنتاج: 👉 أفضل الممارسات