mTLS Everywhere: How to Configure TLS for APISIX
July 31, 2023
TLS باختصار
TLS توفر عدة إمكانيات:
- مصادقة الخادم: يتأكد العميل من أن الخادم الذي يتبادل البيانات معه هو الخادم الصحيح. هذا يمنع إرسال البيانات، التي قد تكون سرية، إلى جهة خاطئة.
- مصادقة العميل الاختيارية: بالعكس، يسمح الخادم فقط للعملاء الذين يمكن التحقق من هويتهم.
- السرية: لا يمكن لأي طرف ثالث قراءة البيانات المتبادلة بين العميل والخادم.
- التكامل: لا يمكن لأي طرف ثالث التلاعب بالبيانات.
يعمل TLS من خلال الشهادات. الشهادة تشبه بطاقة الهوية، تثبت هوية حامل الشهادة. تمامًا مثل بطاقة الهوية، يجب أن تثق في الجهة التي أصدرتها. يتم بناء الثقة من خلال سلسلة: إذا كنت أثق بـ "أليس"، التي تثق بـ "بوب"، الذي يثق بـ "تشارلي"، الذي أصدر الشهادة، فأنا أثق بهذه الشهادة. في هذا السيناريو، تُعرف "أليس" باسم سلطة الشهادات الجذرية.
تعتمد مصادقة TLS على تشفير المفتاح العام. تقوم "أليس" بإنشاء زوج من المفاتيح (مفتاح عام/مفتاح خاص) وتنشر المفتاح العام. إذا قام أحد بتشفير البيانات باستخدام المفتاح العام، فإن المفتاح الخاص الذي أنشأ المفتاح العام هو الوحيد الذي يمكنه فك التشفير. الاستخدام الآخر هو تشفير البيانات باستخدام المفتاح الخاص، ويمكن لأي شخص لديه المفتاح العام فك التشفير، مما يثبت هويته.
أخيرًا، TLS المتبادل، المعروف أيضًا باسم mTLS، هو تكوين TLS ثنائي الاتجاه: مصادقة الخادم للعميل كالمعتاد، ولكن أيضًا مصادقة العميل للخادم.
لدينا الآن فهم كافٍ للمفاهيم لنبدأ في التطبيق العملي.
إنشاء الشهادات باستخدام cert-manager
يتم تثبيت عدد من سلطات الشهادات الجذرية في المتصفحات بشكل افتراضي. هذه هي الطريقة التي يمكننا من خلالها تصفح مواقع HTTPS بأمان، مع الثقة بأن https://apache.org هو الموقع الذي يدعي أنه كذلك. البنية التحتية لا تحتوي على شهادات مثبتة مسبقًا، لذا يجب أن نبدأ من الصفر.
نحتاج إلى شهادة جذر واحدة على الأقل. بدورها، ستقوم بإنشاء جميع الشهادات الأخرى. بينما يمكن القيام بكل شيء يدويًا، سأعتمد على cert-manager في Kubernetes. كما يوحي اسمه، cert-manager هو حل لإدارة الشهادات.
تثبيته باستخدام Helm بسيط:
helm repo add jetstack https://charts.jetstack.io #1 helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ #2 --create-namespace \ #2 --version v1.11.0 \ --set installCRDs=true \ --set prometheus.enabled=false #3
- إضافة مستودع Charts
- تثبيت الكائنات في مساحة اسم مخصصة
- عدم المراقبة، في نطاق هذا المنشور
يمكننا التأكد من أن كل شيء يعمل كما هو متوقع من خلال النظر إلى الـ pods:
kubectl get pods -n cert-manager
cert-manager-cainjector-7f694c4c58-fc9bk 1/1 Running 2 (2d1h ago) 7d cert-manager-cc4b776cf-8p2t8 1/1 Running 1 (2d1h ago) 7d cert-manager-webhook-7cd8c769bb-494tl 1/1 Running 1 (2d1h ago) 7d
يمكن لـ cert-manager توقيع الشهادات من مصادر متعددة: HashiCorp Vault، Let's Encrypt، إلخ. لتبسيط الأمور:
- سنقوم بإنشاء شهادة جذر مخصصة، أي
Self-Signed
- لن نتعامل مع تدوير الشهادات
لنبدأ بما يلي:
apiVersion: cert-manager.io/v1 kind: ClusterIssuer #1 metadata: name: selfsigned-issuer spec: selfSigned: {} --- apiVersion: v1 kind: Namespace metadata: name: tls #2 --- apiVersion: cert-manager.io/v1 kind: Certificate #3 metadata: name: selfsigned-ca namespace: tls spec: isCA: true commonName: selfsigned-ca secretName: root-secret issuerRef: name: selfsigned-issuer kind: ClusterIssuer group: cert-manager.io --- apiVersion: cert-manager.io/v1 kind: Issuer #4 metadata: name: ca-issuer namespace: tls spec: ca: secretName: root-secret
- سلطة الشهادات التي تنشئ الشهادات على مستوى الكلستر
- إنشاء مساحة اسم لعرضنا التوضيحي
- شهادة جذر في مساحة اسم باستخدام المُصدر على مستوى الكلستر. تُستخدم فقط لإنشاء مُصدر في مساحة اسم
- مُصدر في مساحة اسم. يُستخدم لإنشاء جميع الشهادات الأخرى في هذا المنشور
بعد تطبيق البيان السابق، يجب أن نتمكن من رؤية الشهادة الوحيدة التي أنشأناها:
kubectl get certificate -n tls
NAME READY SECRET AGE selfsigned-ca True root-secret 7s
البنية التحتية للشهادات جاهزة؛ لنلقِ نظرة على Apache APISIX.
نظرة سريعة على عينة من بنية Apache APISIX
Apache APISIX هو بوابة API. بشكل افتراضي، يقوم بتخزين تكوينه في etcd، وهو مخزن قيم-مفاتيح موزع - نفس المخزن المستخدم من قبل Kubernetes. لاحظ أنه في سيناريوهات العالم الحقيقي، يجب علينا إعداد تجميع etcd لتحسين مرونة الحل. في هذا المنشور، سنقتصر على نسخة واحدة من etcd. يوفر Apache APISIX واجهة برمجة تطبيقات إدارية عبر نقاط نهاية HTTP. أخيرًا، تقوم البوابة بتوجيه المكالمات من العميل إلى upstream. إليك نظرة عامة على البنية والشهادات المطلوبة:
لنبدأ باللبنات الأساسية: etcd و Apache APISIX. نحتاج إلى شهادتين: واحدة لـ etcd، في دور الخادم، وواحدة لـ Apache APISIX، كعميل etcd.
لنقم بإعداد الشهادات من المُصدر في مساحة الاسم:
apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: etcd-server #1 namespace: tls spec: secretName: etcd-secret #2 isCA: false usages: - client auth #3 - server auth #3 dnsNames: - etcd #4 issuerRef: name: ca-issuer #5 kind: Issuer --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: apisix-client #6 namespace: tls spec: secretName: apisix-client-secret isCA: false usages: - client auth emailAddresses: - apisix@apache.org #7 issuerRef: name: ca-issuer #5 kind: Issuer
- شهادة لـ etcd
- اسم
Secret
في Kubernetes، انظر أدناه - استخدامات هذه الشهادة
- اسم
Service
في Kubernetes، انظر أدناه - الإشارة إلى المُصدر في مساحة الاسم الذي تم إنشاؤه مسبقًا
- شهادة لـ Apache APISIX كعميل لـ etcd
- سمة إلزامية للعملاء
بعد تطبيق البيان أعلاه، يمكننا سرد الشهادات في مساحة الاسم tls
:
kubectl get certificates -n tls
NAME READY SECRET AGE selfsigned-ca True root-secret 8m59s //1 apisix-client True apisix-client-secret 8m22s //2 etcd-server True etcd-secret 8m54s //2
- الشهادة التي تم إنشاؤها مسبقًا
- الشهادات التي تم إنشاؤها حديثًا وتم توقيعها بواسطة
selfsigned-ca
شهادات cert-manager
حتى الآن، قمنا بإنشاء كائنات Certificate
، ولكننا لم نشرح ما هي. في الواقع، هي ببساطة تعريفات موارد مخصصة (CRD) في Kubernetes مقدمة من cert-manager. تحت الغطاء، يقوم cert-manager بإنشاء Secret
في Kubernetes من Certificate
. يدير دورة الحياة الكاملة، لذا فإن حذف Certificate
يحذف Secret
المقيد. تحدد سمة secretName
في البيان أعلاه اسم Secret
.
kubectl get secrets -n tls
NAME TYPE DATA AGE apisix-client-secret kubernetes.io/tls 3 35m etcd-secret kubernetes.io/tls 3 35m root-secret kubernetes.io/tls 3 35m
لنلقِ نظرة على Secret
، على سبيل المثال، apisix-client-secret
:
kubectl describe apisix-client-secret -n tls
Name: apisix-client-secret Namespace: tls Labels: controller.cert-manager.io/fao=true Annotations: cert-manager.io/alt-names: cert-manager.io/certificate-name: apisix-client cert-manager.io/common-name: cert-manager.io/ip-sans: cert-manager.io/issuer-group: cert-manager.io/issuer-kind: Issuer cert-manager.io/issuer-name: ca-issuer cert-manager.io/uri-sans: Type: kubernetes.io/tls Data ==== ca.crt: 1099 bytes tls.crt: 1115 bytes tls.key: 1679 bytes
يوفر Secret
الذي تم إنشاؤه بواسطة Certificate
ثلاث سمات:
tls.crt
: الشهادة نفسهاtls.key
: المفتاح الخاصca.crt
: شهادة التوقيع في سلسلة الشهادات، أيroot-secret/tls.crt
يقوم Kubernetes بتشفير محتوى Secret
في base 64. للحصول على أي من المحتويات أعلاه كنص عادي، يجب فك تشفيره، على سبيل المثال:
kubectl get secret etcd-secret -n tls -o jsonpath='{ .data.tls\.crt }' | base64
-----BEGIN CERTIFICATE----- MIIDBjCCAe6gAwIBAgIQM3JUR8+R0vuUndjGK/aOgzANBgkqhkiG9w0BAQsFADAY MRYwFAYDVQQDEw1zZWxmc2lnbmVkLWNhMB4XDTIzMDMxNjEwMTYyN1oXDTIzMDYx NDEwMTYyN1owADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMQpMj/0 giDVOjOosSRRKUwTzl1Wo2R9YYAeteOW3fuMiAd+XaBGmRO/+GWZQN1tyRQ3pITM ezBgogYAUUNcuqN/UAsgH/JM58niMjZdjRKn4+it94Nj1e24jFL4ts2snCn7FfKJ 3zRtY9tyS7Agw3tCwtXV68Xpmf3CsfhPmn3rGdWHXyYctzAZhqYfEswN3hxpJZxR YVeb55WgDoPo5npZo3+yYiMtoOimIprcmZ2Ye8Wai9S4QKDafUWlvU5GQ65VVLzH PEdOMwbWcwiLqwUv889TiKiC5cyAD6wJOuPRF0KKxxFnG+lHlg9J2S1i5sC3pqoc i0pEQ+atOOyLMMECAwEAAaNkMGIwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUF BwMBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAU2ZaAdEficKUWPFRjdsKSEX/l gbMwEgYDVR0RAQH/BAgwBoIEZXRjZDANBgkqhkiG9w0BAQsFAAOCAQEABcNvYTm8 ZJe3jUq6f872dpNVulb2UvloTpWxQ8jwXgcrhekSKU6pZ4p9IPwfauHLjceMFJLp t2eDi5fSQ1upeqXOofeyKSYjjyA/aVf1zMI8ReCCQtQuAVYyJWBlNLc3XMMecbcp JLGtd/OAZnKDeYYkUX7cJ2wN6Wl/wGLM2lxsqDhEHEZwvGL0DmsdHw7hzSjdVmxs 0Qgkh4jVbNUKdBok5U9Ivr3P1xDPaD/FqGFyM0ssVOCHxtPxhOUA/m3DSr6klfEF McOfudZE958bChOrJgVrUnY3inR0J335bGQ1luEp5tYwPgyD9dG4MQEDD3oLwp+l +NtTUqz8WVlMxQ== -----END CERTIFICATE-----
تكوين mTLS بين etcd و APISIX
مع توفر الشهادات، يمكننا الآن تكوين TLS المتبادل بين etcd و APISIX. لنبدأ بـ etcd:
apiVersion: v1 kind: Pod metadata: name: etcd namespace: tls labels: role: config spec: containers: - name: etcd image: bitnami/etcd:3.5.7 ports: - containerPort: 2379 env: - name: ETCD_TRUSTED_CA_FILE #1 value: /etc/ssl/private/ca.crt - name: ETCD_CERT_FILE #2 value: /etc/ssl/private/tls.crt - name: ETCD_KEY_FILE #3 value: /etc/ssl/private/tls.key - name: ETCD_ROOT_PASSWORD value: whatever - name: ETCD_CLIENT_CERT_AUTH #4 value: "true" - name: ETCD_LISTEN_CLIENT_URLS value: https://0.0.0.0:2379 volumeMounts: - name: ssl mountPath: /etc/ssl/private #5 volumes: - name: ssl secret: secretName: etcd-secret #5
- تعيين CA الموثوق به
- تعيين الشهادة
- تعيين المفتاح الخاص
- يتطلب من العملاء تقديم شهادتهم، مما يضمن المصادقة المتبادلة
- تحميل
Secret
الذي تم إنشاؤه مسبقًا في الحاوية للوصول
الآن، حان دور Apache APISIX:
apiVersion: v1 kind: ConfigMap #1 metadata: name: apisix-config namespace: tls data: config.yaml: >- apisix: ssl: ssl_trusted_certificate: /etc/ssl/certs/ca.crt #2 deployment: etcd: host: - https://etcd:2379 tls: cert: /etc/ssl/certs/tls.crt #2 key: /etc/ssl/certs/tls.key #2 admin: allow_admin: - 0.0.0.0/0 https_admin: true #3 admin_api_mtls: admin_ssl_cert: /etc/ssl/private/tls.crt #3 admin_ssl_cert_key: /etc/ssl/private/tls.key #3 admin_ssl_ca_cert: /etc/ssl/private/ca.crt #3 --- apiVersion: v1 kind: Pod metadata: name: apisix namespace: tls labels: role: gateway spec: containers: - name: apisix image: apache/apisix:3.2.0-debian ports: - containerPort: 9443 #4 - containerPort: 9180 #5 volumeMounts: - name: config #1 mountPath: /usr/local/apisix/conf/config.yaml subPath: config.yaml - name: ssl #6 mountPath: /etc/ssl/private - name: etcd-client #7 mountPath: /etc/ssl/certs volumes: - name: config configMap: name: apisix-config - name: ssl #6,8 secret: secretName: apisix-server-secret - name: etcd-client #7,8 secret: secretName: apisix-client-secret
- لا يوفر Apache APISIX التكوين عبر متغيرات البيئة. نحتاج إلى استخدام
ConfigMap
الذي يعكس ملفconfig.yaml
العادي - تكوين مصادقة العميل لـ etcd
- تكوين مصادقة الخادم لواجهة برمجة التطبيقات الإدارية
- منفذ HTTPS العادي
- منفذ HTTPS الإداري
- الشهادات لمصادقة الخادم
- الشهادات لمصادقة العميل
- يتم استخدام مجموعتين من الشهادات، واحدة لمصادقة الخادم لواجهة برمجة التطبيقات الإدارية و HTTPS العادي، وواحدة لمصادقة العميل لـ etcd.
في هذه المرحلة، يمكننا تطبيق البيانات أعلاه ورؤية الـ pods يتواصلون. عند الاتصال، يرسل Apache APISIX شهادة apisix-client
عبر HTTPS. لأن الشهادة موقعة من قبل سلطة تثق بها etcd، فإنها تسمح بالاتصال.
لقد حذفت تعريف Service
للإيجاز، ولكن يمكنك التحقق منها في مستودع GitHub.
NAME READY STATUS RESTARTS AGE apisix 1/1 Running 0 179m etcd 1/1 Running 0 179m
وصول العميل
الآن بعد أن قمنا بإعداد البنية التحتية الأساسية، يجب أن نختبر الوصول إليها باستخدام عميل. سنستخدم curl
المخلص، ولكن أي عميل يسمح بتكوين الشهادات يجب أن يعمل، على سبيل المثال، httpie.
الخطوة الأولى هي إنشاء زوج مفتاح-شهادة مخصص للعميل:
apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: curl-client namespace: tls spec: secretName: curl-secret isCA: false usages: - client auth emailAddresses: - curl@localhost.dev issuerRef: name: ca-issuer kind: Issuer
يتطلب curl
مسارًا إلى ملف الشهادة بدلاً من المحتوى. يمكننا تجاوز هذا القيد من خلال سحر zsh: بناء الجملة =( ... )
يسمح بإنشاء ملف مؤقت. إذا كنت تستخدم shell آخر، فستحتاج إلى العثور على بناء الجملة المكافئ أو تنزيل الملفات يدويًا.
لنستعلم عن واجهة برمجة التطبيقات الإدارية لجميع المسارات الموجودة. هذا الأمر البسيط يسمح بالتحقق من أن Apache APISIX متصل بـ etcd، ويمكنه قراءة تكوينه من هناك.
curl --resolve 'admin:32180:127.0.0.1' https://admin:32180/apisix/admin/routes \ #1 --cert =(kubectl get secret curl-secret -n tls -o jsonpath='{ .data.tls\.crt }' | base64 -d) \ #2 --key =(kubectl get secret curl-secret -n tls -o jsonpath='{ .data.tls\.key }' | base64 -d) \ #2 --cacert =(kubectl get secret curl-secret -n tls -o jsonpath='{ .data.ca\.crt }' | base64 -d) \ #2 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1'
--resolve
يتجنب تلويث ملف/etc/hosts
. سيترجمcurl
admin
إلىlocalhost
، ولكن يتم إرسال الاستعلام إلىadmin
داخل مجموعة Kubernetes، وبالتالي استخدامService
الصحيح- الحصول على البيانات المطلوبة داخل
Secret
، وفك تشفيرها، واستخدامها كملف مؤقت
إذا كان كل شيء يعمل، ويجب أن يعمل، يجب أن تكون النتيجة كما يلي:
{"total":0,"list":[]}
لا توجد مسارات متاحة حتى الآن لأننا لم نقم بإنشاء أي منها.
TLS مع Upstreams
أخيرًا وليس آخرًا، يجب علينا تكوين TLS لـ upstreams. في ما يلي، سأستخدم نسخة بسيطة من nginx تستجيب بمحتوى ثابت. استخدمها كتوضيح لـ upstreams أكثر تعقيدًا.
الخطوة الأولى، كما هو الحال دائمًا، هي إنشاء Certificate
مخصص لـ upstream. سأتخطى كيفية القيام بذلك لأننا أنشأنا بالفعل عددًا قليلاً. أسميها upstream-server
و Secret
الخاص بها، بشكل غير مبتكر، upstream-secret
. يمكننا الآن استخدام الأخير لتأمين NGINX:
apiVersion: v1 kind: ConfigMap #1 metadata: name: nginx-config namespace: tls data: nginx.conf: >- events { worker_connections 1024; } http { server { listen 443 ssl; server_name upstream; ssl_certificate /etc/ssl/private/tls.crt; #2 ssl_certificate_key /etc/ssl/private/tls.key; #2 root /www/data; location / { index index.json; } } } --- apiVersion: v1 kind: Pod metadata: name: upstream namespace: tls labels: role: upstream spec: containers: - name: upstream image: nginx:1.23-alpine ports: - containerPort: 443 volumeMounts: - name: config mountPath: /etc/nginx/nginx.conf #1 subPath: nginx.conf - name: content mountPath: /www/data/index.json #3 subPath: index.json - name: ssl #2 mountPath: /etc/ssl/private volumes: - name: config configMap: name: nginx-config - name: ssl #2 secret: secretName: upstream-secret - name: content #3 configMap: name: nginx-content
- لا يسمح NGINX بالتكوين عبر متغيرات البيئة؛ نحتاج إلى استخدام نهج
ConfigMap
- استخدام زوج المفتاح-الشهادة الذي تم إنشاؤه عبر
Certificate
- بعض المحتوى الثابت غير المهم في نطاق هذا المنشور
الخطوة التالية هي إنشاء المسار بمساعدة واجهة برمجة التطبيقات الإدارية. لقد أعددنا كل شيء في الخطوة السابقة؛ الآن يمكننا استخدام الواجهة:
curl --resolve 'admin:32180:127.0.0.1' https://admin:32180/apisix/admin/routes/1 \ --cert =(kubectl get secret curl-secret -n tls -o jsonpath='{ .data.tls\.crt }' | base64 -d) \ #1 --key =(kubectl get secret curl-secret -n tls -o jsonpath='{ .data.tls\.key }' | base64 -d) \ #1 --cacert =(kubectl get secret curl-secret -n tls -o jsonpath='{ .data.ca\.crt }' | base64 -d) \ #1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d "{ \"uri\": \"/\", \"upstream\": { \"scheme\": \"https\", #2 \"nodes\": { \"upstream:443\": 1 }, \"tls\": { \"client_cert\": \"$(kubectl get secret curl-secret -n tls -o jsonpath='{ .data.tls\.crt }' | base64 -d)\", #3 \"client_key\": \"$(kubectl get secret curl-secret -n tls -o jsonpath='{ .data.tls\.key }' | base64 -d)\" #3 } } }"
- مصادقة العميل لواجهة برمجة التطبيقات الإدارية، كما هو مذكور أعلاه
- استخدام HTTPS لـ upstream
- تكوين زوج المفتاح-الشهادة للمسار. يقوم Apache APISIX بتخزين البيانات في etcd وسيستخدمها عند استدعاء المسار. بدلاً من ذلك، يمكنك الاحتفاظ بالزوج ككائن مخصص واستخدام المرجع الذي تم إنشاؤه حديثًا (تمامًا كما هو الحال مع upstreams). يعتمد ذلك على عدد المسارات التي تحتاجها الشهادة. لمزيد من المعلومات، تحقق من نقطة نهاية SSL
أخيرًا، يمكننا التحقق من أنها تعمل كما هو متوقع:
curl --resolve 'upstream:32443:127.0.0.1' https://upstream:32443/ \ --cert =(kubectl get secret curl-secret -n tls -o jsonpath='{ .data.tls\.crt }' | base64 -d) \ --key =(kubectl get secret curl-secret -n tls -o jsonpath='{ .data.tls\.key }' | base64 -d) \ --cacert =(kubectl get secret curl-secret -n tls -o jsonpath='{ .data.ca\.crt }' | base64 -d)
وهي تعمل:
{ "hello": "world" }
الخلاصة
في هذا المنشور، وصفت بنية عمل Apache APISIX وقمت بتنفيذ TLS المتبادل بين جميع المكونات: etcd و APISIX، العميل و APISIX، وأخيرًا، العميل و upstream. آمل أن يساعدك هذا على تحقيق نفس الشيء.
اتصل بنا إذا كان لديك أي أسئلة أو استفسارات حول APISIX وإدارة API.
للمضي قدمًا: