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.
للمضي قدمًا: