mTLS di Mana-mana: Cara Mengonfigurasi TLS untuk APISIX

Nicolas Fränkel

Nicolas Fränkel

July 31, 2023

Ecosystem

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
  1. Menambahkan repositori chart
  2. Menginstal objek di namespace khusus
  3. 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
  1. Otoritas sertifikat yang menghasilkan sertifikat seluruh kluster
  2. Membuat namespace untuk demo kita
  3. Root sertifikat yang terbatas pada namespace menggunakan issuer seluruh kluster. Hanya digunakan untuk membuat issuer yang terbatas pada namespace
  4. 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:

Arsitektur Apache APISIX

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
  1. Sertifikat untuk etcd
  2. Nama Secret Kubernetes, lihat di bawah
  3. Penggunaan untuk sertifikat ini
  4. Nama Service Kubernetes, lihat di bawah
  5. Merujuk issuer yang terbatas pada namespace yang dibuat sebelumnya
  6. Sertifikat untuk Apache APISIX sebagai klien etcd
  7. 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
  1. Sertifikat yang dibuat sebelumnya
  2. 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 sendiri
  • tls.key: Kunci pribadi
  • ca.crt: Sertifikat penandatangan dalam rantai sertifikat, yaitu root-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
  1. Mengatur CA yang dipercaya
  2. Mengatur sertifikat
  3. Mengatur kunci pribadi
  4. Meminta klien untuk memberikan sertifikat mereka, sehingga memastikan autentikasi mutual
  5. 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
  1. Apache APISIX tidak menawarkan konfigurasi melalui variabel lingkungan. Kita perlu menggunakan ConfigMap yang mencerminkan file config.yaml biasa
  2. Mengkonfigurasi autentikasi klien untuk etcd
  3. Mengkonfigurasi autentikasi server untuk Admin API
  4. Port HTTPS biasa
  5. Port Admin HTTPS
  6. Sertifikat untuk autentikasi server
  7. Sertifikat untuk autentikasi klien
  8. 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'
  1. --resolve menghindari mengotori file /etc/hosts. curl akan menerjemahkan admin ke localhost, tetapi query dikirim ke admin di dalam kluster Kubernetes, sehingga menggunakan Service yang benar
  2. 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
  1. NGINX tidak mengizinkan konfigurasi melalui variabel lingkungan; kita perlu menggunakan pendekatan ConfigMap
  2. Menggunakan pasangan kunci-sertifikat yang dibuat melalui Certificate
  3. 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 } } }"
  1. Autentikasi klien untuk Admin API, seperti di atas
  2. Menggunakan HTTPS untuk upstream
  3. 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:

Tags: