mTLS di Mana-mana: Cara Mengonfigurasi TLS untuk APISIX
July 31, 2023
TLS Secara Singkat
TLS menawarkan beberapa kemampuan:
- Autentikasi server: klien yakin bahwa server yang bertukar data dengannya adalah server yang benar. Ini menghindari pengiriman data, yang mungkin bersifat rahasia, ke pihak yang salah.
- Autentikasi klien opsional: sebaliknya, server hanya mengizinkan klien yang identitasnya dapat diverifikasi.
- Kerahasiaan: pihak ketiga tidak dapat membaca data yang dipertukarkan antara klien dan server.
- Integritas: pihak ketiga tidak dapat memanipulasi data.
TLS bekerja melalui sertifikat. Sertifikat mirip dengan KTP, yang membuktikan identitas pemegang sertifikat. Sama seperti KTP, Anda perlu mempercayai siapa yang mengeluarkannya. Kepercayaan dibangun melalui rantai: jika saya mempercayai Alice, yang mempercayai Bob, yang pada gilirannya mempercayai Charlie, yang mengeluarkan sertifikat, maka saya mempercayai sertifikat tersebut. Dalam skenario ini, Alice dikenal sebagai root certificate authority.
Autentikasi TLS didasarkan pada kriptografi kunci publik. Alice menghasilkan pasangan kunci publik/kunci pribadi dan mempublikasikan kunci publik. Jika seseorang mengenkripsi data dengan kunci publik, hanya kunci pribadi yang menghasilkan kunci publik tersebut yang dapat mendekripsinya. Penggunaan lainnya adalah seseorang mengenkripsi data dengan kunci pribadi dan semua orang dengan kunci publik dapat mendekripsinya, sehingga membuktikan identitas mereka.
Akhirnya, mutual TLS, alias mTLS, adalah konfigurasi TLS dua arah: autentikasi server ke klien, seperti biasa, tetapi juga sebaliknya, autentikasi klien ke server.
Sekarang kita memiliki pemahaman yang cukup tentang konsep-konsep ini untuk mulai mempraktikkannya.
Menghasilkan Sertifikat dengan cert-manager
Beberapa root Certificate Authorities (CA) diinstal di browser secara default. Itulah cara kita dapat menjelajahi situs web HTTPS dengan aman, percaya bahwa https://apache.org adalah situs yang mereka klaim. Infrastruktur tidak memiliki sertifikat yang diinstal sebelumnya, jadi kita harus mulai dari awal.
Kita memerlukan setidaknya satu root sertifikat. Pada gilirannya, ini akan menghasilkan semua sertifikat lainnya. Meskipun mungkin untuk melakukan semuanya secara manual, saya akan mengandalkan cert-manager di Kubernetes. Sesuai namanya, cert-manager adalah solusi untuk mengelola sertifikat.
Menginstalnya dengan Helm cukup mudah:
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
- Menambahkan repositori chart
- Menginstal objek di namespace khusus
- Tidak memonitor, dalam lingkup posting ini
Kita dapat memastikan bahwa semuanya berfungsi seperti yang diharapkan dengan melihat pod:
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 dapat menandatangani sertifikat dari berbagai sumber: HashiCorp Vault, Let's Encrypt, dll. Untuk mempermudah:
- Kita akan menghasilkan root sertifikat khusus kita sendiri, yaitu
Self-Signed - Kita tidak akan menangani rotasi sertifikat
Mari kita mulai dengan yang berikut:
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
- Otoritas sertifikat yang menghasilkan sertifikat seluruh kluster
- Membuat namespace untuk demo kita
- Root sertifikat yang terbatas pada namespace menggunakan issuer seluruh kluster. Hanya digunakan untuk membuat issuer yang terbatas pada namespace
- Issuer yang terbatas pada namespace. Digunakan untuk membuat semua sertifikat lain dalam posting ini
Setelah menerapkan manifes sebelumnya, kita seharusnya dapat melihat sertifikat tunggal yang kita buat:
kubectl get certificate -n tls
NAME READY SECRET AGE selfsigned-ca True root-secret 7s
Infrastruktur sertifikat sudah siap; mari kita lihat Apache APISIX.
Ikhtisar Cepat tentang Arsitektur Contoh Apache APISIX
Apache APISIX adalah API Gateway. Secara default, ia menyimpan konfigurasinya di etcd, penyimpanan key-value terdistribusi - yang sama digunakan oleh Kubernetes. Perhatikan bahwa dalam skenario dunia nyata, kita harus menyiapkan kluster etcd untuk meningkatkan ketahanan solusi. Untuk posting ini, kita akan membatasi diri pada satu instance etcd. Apache APISIX menawarkan API admin melalui endpoint HTTP. Akhirnya, gateway meneruskan panggilan dari klien ke upstream. Berikut adalah ikhtisar arsitektur dan sertifikat yang diperlukan:
Mari kita mulai dengan batu fondasi: etcd dan Apache APISIX. Kita memerlukan dua sertifikat: satu untuk etcd, dalam peran server, dan satu untuk Apache APISIX, sebagai klien etcd.
Mari kita siapkan sertifikat dari issuer yang terbatas pada namespace:
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
- Sertifikat untuk etcd
- Nama
SecretKubernetes, lihat di bawah - Penggunaan untuk sertifikat ini
- Nama
ServiceKubernetes, lihat di bawah - Merujuk issuer yang terbatas pada namespace yang dibuat sebelumnya
- Sertifikat untuk Apache APISIX sebagai klien etcd
- Atribut wajib untuk klien
Setelah menerapkan manifes di atas, kita dapat mencantumkan sertifikat di namespace 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
- Sertifikat yang dibuat sebelumnya
- Sertifikat yang baru dibuat ditandatangani oleh
selfsigned-ca
Sertifikat dari cert-manager
Sejauh ini, kita telah membuat objek Certificate, tetapi kita belum menjelaskan apa itu. Memang, mereka adalah CRD Kubernetes sederhana yang disediakan oleh cert-manager. Di balik layar, cert-manager membuat Secret Kubernetes dari Certificate. Ini mengelola seluruh siklus hidup, jadi menghapus Certificate akan menghapus Secret yang terikat. Atribut secretName dalam manifes di atas mengatur nama 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
Mari kita lihat Secret, misalnya, 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 yang dibuat oleh Certificate menyediakan tiga atribut:
tls.crt: Sertifikat itu sendiritls.key: Kunci pribadica.crt: Sertifikat penandatangan dalam rantai sertifikat, yaituroot-secret/tls.crt
Kubernetes mengkodekan konten Secret dalam base 64. Untuk mendapatkan salah satu dari yang di atas dalam teks biasa, seseorang harus mendekodenya, misalnya:
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-----
Mengkonfigurasi mTLS Antara etcd dan APISIX
Dengan sertifikat yang tersedia, kita sekarang dapat mengkonfigurasi mutual TLS antara etcd dan APISIX. Mari kita mulai dengan 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
- Mengatur CA yang dipercaya
- Mengatur sertifikat
- Mengatur kunci pribadi
- Meminta klien untuk memberikan sertifikat mereka, sehingga memastikan autentikasi mutual
- Memasang secret yang dihasilkan sebelumnya di dalam kontainer untuk akses
Sekarang, giliran 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 tidak menawarkan konfigurasi melalui variabel lingkungan. Kita perlu menggunakan
ConfigMapyang mencerminkan fileconfig.yamlbiasa - Mengkonfigurasi autentikasi klien untuk etcd
- Mengkonfigurasi autentikasi server untuk Admin API
- Port HTTPS biasa
- Port Admin HTTPS
- Sertifikat untuk autentikasi server
- Sertifikat untuk autentikasi klien
- Dua set sertifikat digunakan, satu untuk autentikasi server untuk Admin API dan HTTPS biasa, dan satu untuk autentikasi klien untuk etcd.
Pada titik ini, kita dapat menerapkan manifes di atas dan melihat dua pod berkomunikasi. Saat terhubung, Apache APISIX mengirimkan sertifikat apisix-client melalui HTTPS. Karena sertifikat tersebut ditandatangani oleh otoritas yang dipercaya oleh etcd, ia mengizinkan koneksi.
Saya telah menghilangkan definisi Service untuk singkatnya, tetapi Anda dapat memeriksanya di repositori GitHub yang terkait.
NAME READY STATUS RESTARTS AGE apisix 1/1 Running 0 179m etcd 1/1 Running 0 179m
Akses Klien
Sekarang setelah kita menyiapkan infrastruktur dasar, kita harus menguji aksesnya dengan klien. Kita akan menggunakan curl yang setia, tetapi klien apa pun yang memungkinkan konfigurasi sertifikat seharusnya berfungsi, misalnya, httpie.
Langkah pertama adalah membuat pasangan sertifikat-kunci khusus untuk klien:
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 memerlukan path ke file sertifikat alih-alih kontennya. Kita dapat mengatasi batasan ini melalui keajaiban zsh: sintaks =( ... ) memungkinkan pembuatan file sementara. Jika Anda menggunakan shell lain, Anda perlu menemukan sintaks yang setara atau mengunduh file secara manual.
Mari kita query Admin API untuk semua rute yang ada. Perintah sederhana ini memungkinkan kita memeriksa bahwa Apache APISIX terhubung ke etcd, dan dapat membaca konfigurasinya dari sana.
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'
--resolvemenghindari mengotori file/etc/hosts.curlakan menerjemahkanadminkelocalhost, tetapi query dikirim keadmindi dalam kluster Kubernetes, sehingga menggunakanServiceyang benar- Mendapatkan data yang diperlukan di dalam
Secret, mendekodenya, dan menggunakannya sebagai file sementara
Jika semuanya berfungsi, dan seharusnya begitu, hasilnya adalah sebagai berikut:
{"total":0,"list":[]}
Tidak ada rute yang tersedia sejauh ini karena kita belum membuatnya.
TLS dengan Upstream
Terakhir tetapi tidak kalah penting, kita harus mengkonfigurasi TLS untuk upstream. Dalam contoh berikut, saya akan menggunakan instance nginx sederhana yang merespons dengan konten statis. Gunakan ini sebagai ilustrasi untuk upstream yang lebih kompleks.
Langkah pertama, seperti biasa, adalah menghasilkan Certificate khusus untuk upstream. Saya akan melewatkan cara melakukannya karena kita sudah membuat beberapa. Saya menyebutnya upstream-server dan Secret-nya, secara tidak imajinatif, upstream-secret. Kita sekarang dapat menggunakan yang terakhir untuk mengamankan 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 tidak mengizinkan konfigurasi melalui variabel lingkungan; kita perlu menggunakan pendekatan
ConfigMap - Menggunakan pasangan kunci-sertifikat yang dibuat melalui
Certificate - Beberapa konten statis yang tidak penting dalam lingkup posting ini
Langkah selanjutnya adalah membuat rute dengan bantuan Admin API. Kita telah menyiapkan semuanya di langkah sebelumnya; sekarang kita dapat menggunakan API:
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 } } }"
- Autentikasi klien untuk Admin API, seperti di atas
- Menggunakan HTTPS untuk upstream
- Mengkonfigurasi pasangan kunci-sertifikat untuk rute. Apache APISIX menyimpan data di etcd dan akan menggunakannya saat Anda memanggil rute. Alternatifnya, Anda dapat menyimpan pasangan sebagai objek khusus dan menggunakan referensi yang baru dibuat (seperti untuk upstream). Ini tergantung pada berapa banyak rute yang memerlukan sertifikat. Untuk informasi lebih lanjut, periksa endpoint SSL
Akhirnya, kita dapat memeriksa bahwa semuanya berfungsi seperti yang diharapkan:
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)
Dan itu berhasil:
{ "hello": "world" }
Kesimpulan
Dalam posting ini, saya telah menjelaskan arsitektur Apache APISIX yang berfungsi dan menerapkan mutual TLS antara semua komponen: etcd dan APISIX, klien dan APISIX, dan akhirnya, klien dan upstream. Saya harap ini akan membantu Anda mencapai hal yang sama.
Hubungi kami jika Anda memiliki pertanyaan atau pertanyaan tentang APISIX dan manajemen API.
Untuk lebih lanjut: