Melakukan Validasi JWT Melalui Kong Ingress Controller
Pada latihan-k8s, saya menangani authentication di aplikasi Spring Boot dengan menggunakan Spring Security. Saya kemudian membuat service baru, sebuah aplikasi Python, di artikel sebelumnya. Salah satu masalah keamanan pada service baru tersebut adalah endpoint-nya tidak melakukan validasi JWT sehingga bisa diakses oleh siapa saja. Saya bisa saja menggunakan library seperty PyJWT untuk menambahkan validasi JWT. Namun, daripada setiap kali membuat service baru harus menangani JWT, akan lebih elegan bila validasi JWT dapat langsung dilakukan dari Ingress Controller. Kelebihannya adalah saya dapat melakukan konfigurasi di satu tempat yang sama tanpa harus mengubah kode program aplikasi (misalnya untuk mematikan authentication, cukup mengubah Ingress controller).
Di artikel “Memakai Ingress Controller”, saya sudah menggunakan ingress-nginx
sebagai Ingress Controller. Ia sangat mudah dipakai di minikube karena tersedia sebagai addon. Namun sayangnya, ingress-nginx
masih belum mendukung validasi JWT saat artikel ini ditulis. Sebagai perbandingan, salah satu pesaing NGINX, Envoy sudah mendukung validasi JWT. Terdapat lumayan banyak Ingress Controller berbasis Envoy seperti Istio, Ambassador dan sebagainya. Namun, pada artikel ini, saya akan memakai Kong Ingress Controller yang juga mendukung validasi JWT. Kong Ingress Controller pada dasarnya akan memakai Kong API Gateway yang berbasis NGINX.
Apa beda Ingress Controller dan API Gateway? Bila dilihat dari perannya, mereka sangat berbeda dan tidak dapat dibandingkan secara langsung. Ingress adalah sebuah fasilitas untuk mendeklarasikan gateway dalam bentuk resource yang dikelola oleh Kubernetes. Salah satu tujuan utamanya adalah pengguna tidak perlu melakukan perubahan file konfigurasi secara langsung pada NGINX atau Envoy yang dipakai oleh Ingress Controller. Sebagai contoh, saya dapat mengaktifkan HTTPS cukup dengan menambahkan tls.hosts
dan tls.secretName
di resource Ingress. Bila melakukan perubahan ini secara langsung di NGINX, saya perlu meng-edit file nginx.conf
dan menambahkan pengaturan seperti ssl_certificate
, ssl_certificate_key
, dan sebagainya. Bukan hanya itu, bila beralih ke Envoy, struktur fike konfigurasinya tentu berbeda lagi.
Kubernetes sendiri tidak mengatur apa yang harus Ingress Controller lakukan selain melakukan routing! Implementasi Ingress Controller juga bebas asalkan ia dapat mengerjakan apa yang tertera di Ingress. Pada umumnya, implementasi Ingress Controller berupa reverse proxy dan load-balancer seperti pada ingress-nginx
dan Istio Kubernetes Ingress. Ada juga implementasi Ingress Controller yang menawarkan fitur API Gateway seperti Kong Ingress Controller, Ambassador Ingress Controller, dan sebagainya. Selain itu, ada juga implementasi Ingress Controller yang mendukung service mesh seperti Kuma dan Service Mesh Hub.
Istilah API gateway sendiri lebih merupakan istilah pemasaran, bukan sebuah spesifikasi baku yang diatur dan diawasi oleh badan tertentu (bandingkan dengan spesifikasi jaringan yang dikelola di RFC IETF atau spesifikasi HTML5 yang selalu sama di browser manapun). Dengan demikian, setiap produk API Gateway bisa menawarkan fasilitas yang bervariasi. Secara umum, API gateway menawarkan fitur seperti routing, rate limiting, validasi JWT, dokumentasi OpenAPI, dan sebagainya. ingress-nginx
juga mendukung rate limiting dengan annotation seperti nginx.ingress.kubernetes.io/limit-rate
, namun karena lebih bekerja untuk mendukung jaringan (bukan condong untuk mendukung aplikasi), ia tidak disebut API Gateway.
Untuk melakukan instalasi Kong Ingress Controller, saya dapat memberikan perintah berikut ini:
Langkah berikutnya, saya perlu mengubah ingressClassName
di setiap manifest yang sebelumnya memiliki nginx
menjadi kong
seperti:
Nilai metadata.annotations
yang diawali oleh nginx.ingress.kubernetes.io
yang saya berikan untuk pengaturan CORS kini sudah tidak valid lagi karena tidak akan dimengerti oleh Kong Ingress Controller. Sebagai alternatif-nya, Kong menggunakan CDR KongPlugin yang mewakili plugin Kong API Gateway. Agar bisa mendapatkan fasilitas CORS kembali, saya akan memakai plugin CORS. Untuk itu, saya membuat sebuah file manifest baru dengan nama kong/cors-plugin.yaml
yang isinya terlihat seperti berikut ini:
Setelah itu, saya akan mengganti annotation nginx.ingress.kubernetes.io
pada setiap Ingress yang perlu mendukung CORS menjadi seperti berikut ini:
Nilai kong-cors-plugin
pada annotation konghq.com/plugins
akan meng-asosiasi-kan plugin CORS dengan Ingress tersebut. Setelah mengaplikasikan file yang berubah dengan kubectl apply -f
, saya bisa memastikan resource KongPlugin sudah dibuat dengan memberikan perintah:
Sekarang saatnya mengaktifkan dukungan validasi JWT. Kong juga menyediakan fitur ini dalam bentuk plugin, JWT. Saya segera membuat file baru dengan nama kong/jwt-plugin.yaml
dengan isi seperti berikut ini:
Spring Security mendukung discovery dari OpenID Connect (OIDC) dimana ia akan mendapatkan informasi yang dibutuhkan dengan mengakses URL seperti https://auth.latihan.jocki.me/auth/realms/latihan/.well-known/openid-configuration. Dengan demikian, saya hampir tidak perlu menyediakan informasi seperti algoritma signing dan isi public key sama sekali. Sayangnya, plugin JWT dari Kong tidak mendukung OIDC, sehingga saya perlu mengisi informasi yang dibutuhkan secara manual. Sebagai contoh, saya membuat sebuah Secret baru dengan perintah seperti berikut ini:
Untuk mendapatkan nilai rsa_public_key
, saya bisa membuka URL untuk realm yang dipakai di https://auth.latihan.jocki.me/auth/realms/latihan dan men-copy nilai public_key
dari JSON yang ditampilkan di halaman tersebut. Saya perlu memastikan untuk menambahkan -----BEGIN PUBLIC KEY-----
dan -----END PUBLIC KEY-----
karena plugin JWT dari Kong akan menolak public key tanpa baris tersebut.
Berikutnya, saya membuat sebuah KongConsumer dengan nama web-consumer.yaml
yang isinya seperti berikut ini:
Nilai username
pada file diatas tidak begitu berpengaruh untuk saat ini. Yang penting adalah saya melakukan referensi ke Secret yang sebelumnya saya buat di credentials
dan menambahkan annotation kubernetes.io/ingress.class: kong
karena ingin menangani validasi JWT melalui Ingress.
Sebagai langkah terakhir, saya perlu menambahkan plugin app-jwt-plugin
yang saya deklarasikan ke file manifest Ingress. Sebagai contoh, pada file ingress-api.yaml
, saya menambahkan annotation konghq.com/plugins
seperti berikut ini:
Konfigurasi di atas akan menyebabkan seluruh akses ke path di ingress-api
harus menyertakan JWT yang valid. Bila tidak ada JWT yang valid, pemanggil akan mendapatkan pesan kesalahan {"message": "Unauthorized"}
. Pesan ini dikembalikan oleh Kong Ingress Controller, bukan oleh aplikasi, sehingga Elasticsearch yang tidak aktif fitur authentication-nya pun ikut terlindungi.
Saya tetap bisa membuat endpoint yang boleh diakses tanpa validasi JWT. Sebagai contoh, bila ingin file yang di-upload dapat dipakai secara bebas (asalkan tahu link-nya), saya cukup tidak menyertakan app-jwt-plugin
pada nilai konghq.com/plugins
. Sebagai contoh, saya mengubah file ingress-file-read.yaml
sehingga terlihat seperti pada contoh berikut ini:
Karena tidak ada app-jwt-plugin
di file di atas, path pada Ingress tersebut dapat di-akses secara bebas.
Sementara itu, untuk upload file, karena ingin hanya user yang sudah login saja yang boleh meng-upload file, saya bisa membuat file ingress-file-write.yaml
dengan isi seperti:
Setelah mengaplikasikan seluruh file manifest yang saya ubah di atas, kini validasi JWT akan ditangani oleh Ingress Controller. Saya boleh menghapus Spring Security dari aplikasi Spring Boot karena ia tidak dibutuhkan lagi. Bila JWT tidak valid atau user belum login, request tidak akan pernah sampai di aplikasi.
Kelebihan lainnya adalah saat bekerja di setiap service dan menjalankan service di komputer lokal secara individual, saya tidak perlu mengkhawatirkan masalah authentication lagi. Saya bisa memanggil endpoint selama development secara langsung tanpa perlu melewatkan JWT (selama aplikasi dijalankan di luar Kubernetes atau diakses tanpa melalui API Gateway). Bila ingin mematikan validasi JWT secara global, juga bukanlah hal yang sulit karena bisa dilakukan dengan memodifikasi manifest Ingress dengan menghapus plugin app-jwt-plugin
tanpa melakukan perubahan kode program di sisi aplikasi sama sekali.
Bila validasi JWT tidak bekerja sesuai dengan yang diharapkan, saya dapat memberikan perintah seperti berikut ini untuk melihat log pesan kesalahan dari Kong Ingress Controller:
Pod untuk Kong Ingress Controller terdiri atas dua container, ingress-controller
dan proxy
. Saya dapat menggunakan -c
untuk memilih log dari container mana yang hendak ditampilkan.