Skip to content
Blog DevOps

Bagaimana Saya Membuka Message Broker ke Internet Tanpa Membiarkan Siapa Pun Menyamar Jadi Orang Lain

By Romi Fadli, S.Kom. 21 min read Email & Messaging
On this page
A browser with a token and a device with a password reach nginx on ports 443 and 8883, where TLS ends; nginx forwards them to the broker, whose WebSocket listener accepts only tokens and whose TCP listener accepts only passwords

Pendahuluan

Message broker gampang dijalankan di jaringan privat, tempat semua yang bisa menjangkaunya adalah program yang saya taruh sendiri di sana. Begitu ia harus terjangkau dari internet, oleh browser di satu sisi dan perangkat di sisi lain, setiap koneksi adalah orang asing sampai terbukti sebaliknya. Saya ingin stack broker yang bisa didapat project baru dengan satu perintah dan langsung dibuka ke internet, tanpa ada yang bisa terhubung sebagai orang lain, atau masuk membawa token yang diterbitkan untuk aplikasi lain.

A. Apa itu broker, dan siapa yang berbicara dengannya

Message broker menyampaikan pesan di antara program-program yang tak pernah berbicara langsung satu sama lain: satu program mem-publish ke satu topic, dan siapa pun yang sudah subscribe ke topic itu menerimanya. Stack saya menjalankan dua broker. EMQX berbicara MQTT, protokol publish-subscribe ringan yang dirancang untuk perangkat dan untuk browser yang butuh pembaruan langsung. RabbitMQ berbicara AMQP, protokol yang lebih berat yang dipakai service backend saya di antara mereka sendiri untuk antrean dan event. Database PostgreSQL kecil menyimpan user MQTT beserta aturan aksesnya.

Dua jenis client MQTT datang dengan cara berbeda. Perangkat, atau service di luar server, terhubung lewat TCP dengan username dan password. Browser tak bisa membuka koneksi TCP mentah, jadi ia terhubung lewat WebSocket, dan ia sudah memegang bekal yang lebih baik daripada password: token login yang diterima aplikasinya saat user login. Broker yang melayani keduanya harus menerima keduanya, dan dari situlah kerumitannya mulai.

Setiap project mendapat salinan stack ini sendiri. Generator menanyakan nama project, network Docker-nya, kredensialnya, user MQTT pertama beserta topic yang boleh dipakainya, dan apakah browser perlu login dengan token. Sesudah itu ia menulis folder terpisah dengan broker sendiri, database user sendiri, dan konfigurasi sendiri, supaya client satu project tak pernah berbagi broker, user, atau topic dengan client project lain. Apa pun yang sudah benar di stack ini harus benar lagi di setiap project baru, tanpa ada yang perlu mengingat caranya.

B. Kenapa endpoint publik mengubah segalanya

Di jaringan privat, aturan broker kebanyakan melindungi dari kekeliruan. Di internet, aturan itu melindungi dari orang. Pemeriksaan password di port publik mengundang siapa saja untuk mencoba-coba password. Pemeriksaan token yang menerima token apa pun dari identity provider saya ikut menerima token dari setiap aplikasi yang dilayani provider itu, bukan cuma yang memang ditujukan untuk broker ini. Dan aturan akses yang memercayai nama yang disebut client tentang dirinya sendiri memercayai siapa pun yang mau mengetik nama lain.

Tak satu pun kegagalan ini menghasilkan error. Masing-masing bekerja sempurna dari sudut pandang client yang menyalahgunakannya, dan justru karena itulah semuanya harus ditutup sejak rancangan, bukan ditemukan belakangan di log. Ujian sebenarnya bukan apakah client yang sah bisa terhubung, melainkan apakah client yang tak sah ditolak, dan ditolak sedini mungkin.

C. Jalan pintas yang saya tolak

Rancangan paling sederhana adalah satu metode autentikasi untuk semuanya: setiap client, browser maupun perangkat, menyerahkan username dan password. Rancangan itu saya tolak karena ia menaruh pemeriksaan password di endpoint WebSocket publik dan memaksa browser membawa rahasia kedua di samping token yang sudah dipegangnya. Jalan pintas kedua adalah membuka port MQTT polos milik broker secara langsung dan menunda enkripsi. Itu juga saya tolak: kredensial yang dikirim lewat port tak terenkripsi di internet sama saja dengan kredensial yang diumumkan.

Jalan pintas ketiga paling halus: membiarkan aturan akses broker mencari client berdasarkan username yang dikirimnya saat terhubung. Itulah perilaku bawaannya, itulah yang diperlihatkan setiap contoh, dan di jaringan privat ia tak berbahaya. Di endpoint publik, artinya username menjadi klaim yang tak diperiksa siapa pun. Rancangan yang akhirnya saya pakai memberi tiap jenis client tepat satu jalan masuk, mengikat setiap token ke identitas di dalamnya, dan mengakhiri enkripsi di depan broker, bukan di dalamnya.

D. Tiga belas hambatan yang menghalangi

Gampang dirumuskan, canggung dibangun. Ada tiga belas rintangan di antara broker yang terbuka dan broker yang hanya meloloskan yang berhak: lima soal identitas, satu soal enkripsi, dan tujuh soal membuat broker setiap project menyala sebagai dirinya sendiri, bukan salinan broker project sebelumnya.

Setiap pintu masuk hanya boleh menerima satu jenis kredensial. Secara bawaan, broker menjalankan setiap koneksi lewat rantai authenticator yang sama, jadi client WebSocket masih bisa mencoba password dan client TCP masih bisa mencoba token. Satu rantai untuk semua listener adalah alternatif yang ditolak, karena ia mempertahankan pemeriksaan password di endpoint publik yang semestinya tak punya pemeriksaan semacam itu.

Token yang sah tak boleh cukup untuk menjadi orang lain. Broker mencari aturan akses berdasarkan username di paket connect milik client, bukan berdasarkan identitas di dalam token. Memercayai username itu adalah alternatif yang ditolak; saya membuktikannya lewat tes, ketika client yang memegang token sahnya sendiri bisa memilih nama user lain lalu menerima subscription milik user itu.

Token harus ditujukan untuk broker ini, dan broker harus sanggup memeriksanya. Audience token wajib menyebut broker, bukan aplikasi yang memintanya. Versi broker yang saya pakai di awal membandingkan audience itu sebagai string polos, padahal identity provider saya mengirimnya sebagai list, sehingga setiap token gagal. Dan pengambilan signing key milik provider gagal total sampai broker diberi tahu sertifikat mana yang dipercaya dan nama server mana yang disebutkan.

Enkripsi harus berakhir di depan broker, untuk dua protokol berbeda. WebSocket aman berjalan di atas HTTP, sedangkan MQTT over TLS adalah TCP mentah, yang ditangani web server di blok konfigurasi berbeda dan wajib berada di level paling atas. Membiarkan broker memegang sertifikatnya sendiri adalah alternatif yang ditolak, karena setiap pembaruan sertifikat akan berarti me-restart broker.

User token butuh aturan akses, dan tabel aturannya menolak mereka. Aturan akses merujuk user password lewat foreign key, sedangkan user yang login dengan token tak punya baris password, jadi menambahkan aturan untuknya gagal. Mempertahankan foreign key itu, beserta pembersihan otomatis yang dibawanya, adalah alternatif yang saya lepaskan: tabel aturan kini menjadi gerbangnya sendiri, dan user tanpa aturan ditolak.

Dashboard broker menyala dengan password bawaan image. EMQX tak mengambil password administratornya dari konfigurasi, jadi stack baru akan membuka dashboard manajemennya dengan password yang tercetak di dokumentasi publik. Membiarkannya untuk diganti orang sesudah start pertama adalah alternatif yang ditolak.

Run kedua untuk project yang sama akan diam-diam memakai ulang database lama. Data broker tinggal di path ber-prefix di luar folder output, jadi menimpa output membiarkan database user, antrean, dan state broker yang lama tetap di tempatnya. Membiarkan data itu adalah alternatif yang ditolak, karena stack baru menyala sehat di atas user milik orang lain.

Container meninggalkan file yang hanya bisa dihapus root. Database, RabbitMQ, dan cookie cluster-nya menulis file sebagai user di dalam image masing-masing, yang tak bisa dihapus atau disalin oleh orang yang menjalankan generator. Menjalankan generator sebagai root, atau memakai sudo, adalah alternatif yang ditolak, karena setiap file yang ditulisnya akan menjadi milik root.

Broker hasil salinan membawa identitas node lama. EMQX menyimpan identifier node dan cluster-nya beserta session yang tersimpan di dalam folder datanya. Menyalin data template utuh-utuh adalah alternatif yang ditolak, karena dua project akan berbagi satu identitas broker.

Broker bisa menyala tanpa autentikasi dan tampak sehat sempurna. Broker membaca konfigurasinya dari path ter-mount di bawah prefix data, bukan dari folder output tempat generator menulis. Menyunting konfigurasi di folder output saja adalah alternatif yang ditolak: broker akan menyala dengan konfigurasi template, tanpa pemeriksaan database dan tanpa aturan akses.

User project sebelumnya tertanam di migration. Migration database user berisi user MQTT project lama beserta aturan aksesnya, ditambah satu migration untuk user login milik satu project. Menjalankannya apa adanya adalah alternatif yang ditolak, karena user lama akan tiba di project baru lengkap dengan izin lamanya.

RabbitMQ mengabaikan administrator yang diberikan lewat environment-nya. Begitu RabbitMQ memuat definisinya dari file, ia melewati user bawaan yang disebut di environment variable-nya, jadi administratornya diam-diam tak pernah ada. Mengandalkan variable itu adalah alternatif yang ditolak.

Batas disk bawaan RabbitMQ memblokir publisher di host kecil. Template mengatur batas disk kosong relatif terhadap memori, satu setengah kali RAM, yang di host berdisk kecil langsung memicu alarm disk dan memblokir setiap publisher. Mempertahankan batas relatif itu adalah alternatif yang ditolak.

Urutannya mengikuti satu koneksi dari luar ke dalam, lalu kelahiran stack itu sendiri. Hambatan pertama menentukan pintu mana yang boleh dipakai client. Yang kedua dan ketiga menentukan apakah identitas yang dibawanya ke pintu itu asli dan memang ditujukan untuk broker ini. Yang kelima dan keenam menutup dua jalan masuk tersisa yang tak bergantung pada client: tabel aturan yang tak bisa menampung user token, dan dashboard berpassword publik. Yang keempat menentukan apakah semua itu bisa menyeberangi internet dengan aman, dan ia datang sesudah tiga yang pertama karena baru masuk akal setelah ada yang layak dilindungi di belakangnya. Tujuh yang terakhir menentukan apakah broker project baru benar-benar baru: datanya sendiri, file-nya sendiri, identitasnya sendiri, konfigurasinya sendiri, user-nya sendiri, dan RabbitMQ yang punya administrator dan tak berhenti di disk penuh pertama.

Metode

A. Satu kredensial per pintu masuk

Inilah jawaban atas hambatan pertama: setiap authenticator di rantai broker membawa precondition yang menyebut satu-satunya listener tempat ia berlaku. Pemeriksaan password PostgreSQL hanya berlaku untuk listener TCP, dan pemeriksaan token hanya berlaku untuk listener WebSocket. Browser di endpoint publik tak pernah dimintai password, dan password yang dikirim ke sana tak pernah dicocokkan ke database.

ini
authentication = [
  { mechanism = password_based, backend = postgresql, …
    precondition = "str_eq(listener, 'tcp:default')" },
  { mechanism = jwt, use_jwks = true, …
    precondition = "str_eq(listener, 'ws:default')" }
]

Generator menulis template dengan dua placeholder dan baru mengisinya bila login dengan token diaktifkan. Tanpa itu, kedua placeholder dibuang dan broker kembali ke satu rantai password, jadi template yang sama melayani broker privat maupun broker publik. Template juga menyalakan listener TLS milik broker sendiri, di port 8883 dan 8084 di dalam container, dan tak satu pun precondition menyebutnya; 8883 tak pernah dipublikasikan, dan 8084 hanya dipublikasikan di alamat internal, jadi keduanya tak terjangkau dari internet, tapi mematikannya di template akan menutup celah itu untuk selamanya.

B. Mengikat token ke nama

Inilah jawaban atas hambatan kedua: pemeriksaan token memverifikasi klaim yang mengikat username milik token itu sendiri ke username di paket connect. Aturan akses tetap dicari berdasarkan username di paket connect, tapi kini client hanya bisa mengirim nama yang memang tertera di token-nya.

ini
authorization.sources = [
  { type = postgresql,
    query = "SELECT action, permission, topic FROM mqtt_acl WHERE username = ${username}" }
]
ini
verify_claims = {
  iss = "https://id.example.com/realms/main",
  aud = "broker.example.com",
  preferred_username = "${username}"
}

Sebelum klaim ini dipasang, client yang login sebagai alice lalu terhubung dengan username mallory diberi subscription milik Mallory. Sesudahnya, percobaan yang sama ditolak di tahap connect, sebelum aturan akses mana pun sempat diperiksa. Apa pun yang tak diizinkan secara eksplisit akan ditolak, jadi user tanpa aturan tak menerima apa-apa sama sekali.

C. Audience yang tepat, dan key yang bisa diambil

Inilah jawaban atas hambatan ketiga, dalam tiga bagian. Bagian pertama soal makna: audience adalah pihak yang dituju token, jadi ia menyebut broker. Client identifier aplikasi adalah nilai yang keliru: itu identitas pihak yang meminta, bukan pihak yang menerima. Kini broker hanya meminta tepat tiga nilai — issuer, endpoint signing key, dan audience — dan tak mengambil apa pun dari konfigurasi OAuth2 milik aplikasi, karena konfigurasi itu milik client, bukan milik resource seperti broker.

Bagian kedua soal versi. Identity provider saya menerbitkan audience sebagai list, misalnya broker ditambah service akun. Versi broker yang saya pakai di awal membandingkan audience yang diharapkan dengan list itu sebagai satu string persis, sehingga pemeriksaannya gagal untuk setiap token. Perbaikannya ada di broker itu sendiri, dirilis di versi patch berikutnya, dan template kini mengunci versi tersebut.

Bagian ketiga adalah koneksi yang mengambil signing key milik provider. Dengan verifikasi sertifikat menyala dan tanpa konfigurasi lain, pengambilannya gagal dengan error yang menyatakan sertifikat tepercaya tak terdefinisi. Broker butuh bundle sertifikat untuk dipercaya dan nama server untuk disebutkan, dan generator menurunkan nama itu dari endpoint yang diketik user.

bash
JWKS_SNI="$(printf '%s' "$JWKS_ENDPOINT" | sed -E 's|^https?://||; s|[:/].*$||')"
ini
ssl {
  enable = true
  verify = verify_peer
  cacertfile = "/etc/ssl/certs/ca-certificates.crt"
  server_name_indication = "id.example.com"
}

D. Aturan yang tak butuh user password

Satu migration membuang foreign key dari aturan akses ke user password dan menambahkan index sebagai gantinya, jadi satu aturan bisa menyebut user yang hanya pernah login dengan token. Tabel aturan menjadi gerbang otorisasi: broker menolak apa pun yang tak diizinkan aturan, jadi token sah tanpa aturan tetap ditolak. Harganya, aturan tak lagi ikut hilang bersama user-nya, jadi aplikasi yang mengelola user menghapusnya sendiri. Aturan yang berubah baru berlaku sesudah cache otorisasi broker dibersihkan, yang dikerjakan script provisioning sesudah setiap perubahan.

sql
-- access rules no longer require a password user
ALTER TABLE mqtt_acl DROP CONSTRAINT IF EXISTS mqtt_acl_username_fkey;
CREATE INDEX IF NOT EXISTS mqtt_acl_username_idx ON mqtt_acl (username);

E. Mengganti password dashboard

Begitu EMQX melapor siap, generator memasang password administrator lewat tool command-line milik broker sendiri, mencoba ulang selama semenit, karena perintah itu gagal selagi broker masih menyala.

bash
for _ in $(seq 1 30); do
  docker exec "$EMQX_CONTAINER" emqx ctl admins passwd admin "$DASHBOARD_PASSWORD" && break
  sleep 2
done

F. Enkripsi di depan broker

Enkripsi ditangani di luar broker: nginx di host mengakhiri TLS untuk kedua protokol, dan broker hanya pernah melihat koneksi polos di alamat internal. WebSocket aman di port 443 adalah location HTTP biasa dengan protocol upgrade. MQTT over TLS di port 8883 adalah proxy TCP, yang hanya diterima nginx di dalam blok stream di level paling atas konfigurasi utamanya — bukan di file site, yang di-include di dalam blok http. Karena itu generator menulis dua file, dan script pemasangnya menaruh file kedua di direktori terpisah yang di-include dari satu blok stream di level teratas.

nginx
server {
    listen 8883 ssl;
    ssl_certificate      /etc/letsencrypt/live/broker.example.com/fullchain.pem;
    ssl_certificate_key  /etc/letsencrypt/live/broker.example.com/privkey.pem;
    ssl_protocols        TLSv1.2 TLSv1.3;
    proxy_pass           mqtt_backend;
    proxy_timeout        7200s;
}

Dua detail di blok itu lahir dari kegagalan. Koneksi MQTT tetap terbuka dan diam di antara pesan, dan nginx menutup koneksi stream yang menganggur sesudah sepuluh menit kecuali diatur lain, jadi timeout-nya dinaikkan. Lalu port 8883 tak boleh ikut dipublikasikan oleh container broker: bila ikut, proxy Docker memegang port itu, dan nginx gagal start dengan pesan "address already in use" — bukan saat konfigurasi dites atau di-reload, yang keduanya lolos, melainkan saat restart berikutnya, bisa berjam-jam kemudian. Script pemasang memeriksa hal ini sebelum mengubah apa pun. Ia butuh root khusus untuk pemeriksaan ini, karena tanpa root daftar port memperlihatkan bahwa port terpakai tapi tidak menyebut proses mana yang memegangnya; port yang dipegang nginx sendiri adalah sisa pemasangan sebelumnya dan diizinkan.

Sisi WebSocket punya timeout sendiri: nginx menutup koneksi proxy HTTP yang diam selama enam puluh detik, jadi browser yang tak menerima pesan selama semenit akan terputus; timeout itu dinaikkan ke dua jam yang sama dengan batas idle broker sendiri. Site-nya mendengarkan di port 443 polos, bukan di satu alamat, karena nginx mengirim request ke listener yang paling spesifik untuk satu alamat, dan site lain yang mengklaim alamat itu akan mengambil domain broker. Modul stream wajib dipasang sebagai paket dan tak boleh juga dimuat dengan tangan, kalau tidak nginx memuatnya dua kali dan menolak start. Satu batas perlu diketahui: langkah aktivasi milik generator sendiri hanya memasang site WebSocket; file stream, include-nya di level teratas, dan pemeriksaan port dipasang oleh script pemasang terpisah, jadi MQTT over TLS butuh script itu dijalankan.

bash
line="$(sudo ss -tlnpH 2>/dev/null | awk -v p=":$port\$" '$4 ~ p' | head -1)"
[[ -z "$line" ]] && continue
holder="$(printf '%s' "$line" | grep -oE '"[^"]+"' | head -1 | tr -d '"')"
[[ "$holder" == "nginx" ]] && continue
err "port $port is already used by: $holder"

Generator sendiri tak pernah berjalan sebagai root. Kalau iya, setiap file yang ditulisnya — termasuk file environment yang menyimpan password — akan menjadi milik root, dan setiap langkah sesudahnya juga harus berjalan sebagai root. Hak lebih tinggi hanya ia minta untuk beberapa perintah nginx, dan hanya bila saya setuju.

G. Mulai dari data kosong

Sebelum menulis stack baru, generator menghapus data run sebelumnya di path ber-prefix, jadi database user, antrean RabbitMQ, dan state broker semuanya mulai kosong. Komentar di samping langkah itu menyebut alasannya dalam satu baris: tanpanya, database lama diam-diam terpakai ulang.

H. Menghapus yang dimiliki container

Ketika orang yang menjalankan generator tak bisa menghapus file yang ditulis container, container berumur pendek dengan mount yang sama menghapusnya, dan template disalin dengan direktori-direktori itu dikecualikan, jadi tak ada langkah yang butuh root.

bash
rm -rf "${DATA_PREFIX}${OUT}" 2>/dev/null || docker run --rm \
  -v "${DATA_PREFIX}${OUT}:/target" "$BUSYBOX_IMAGE" \
  sh -c 'rm -rf /target/* /target/.[!.]*'

I. Identitas baru untuk broker

Dari data broker milik template, hanya dua file yang ikut: konfigurasi cluster dan aturan akses berbasis file. Semua yang mengidentifikasi node lama — identifier node dan cluster-nya, session yang tersimpan, dan database-nya — ditinggalkan, jadi broker baru menyala dengan identitasnya sendiri.

J. Konfigurasi di tempat broker membacanya

Sesudah menyunting konfigurasi broker, generator memindahkannya ke path ber-prefix yang di-mount file compose. Tanpa pemindahan itu, broker menyala dengan konfigurasi template: sehat, terjangkau, tanpa pemeriksaan database dan tanpa aturan akses.

K. User yang memang milik project ini

Generator membuang user dan aturan lama dari migration, menghapus migration milik login satu project, lalu menulis migration seed baru untuk user MQTT pertama project ini, dengan hash bcrypt dan migration down pasangannya, jadi database user dibuat dengan tepat satu user beserta aturannya.

L. Administrator yang benar-benar dibuat RabbitMQ

Administrator ditulis ke file definisi RabbitMQ, dengan hash SHA-256 bergaram dalam format yang diharapkan RabbitMQ, bersama permission-nya di virtual host bawaan dan virtual host milik project.

json
{ "name": "admin",
  "password_hash": "…",
  "hashing_algorithm": "rabbit_password_hashing_sha256",
  "tags": ["administrator"] }

M. Batas disk yang cocok dengan host

Generator menanyakan batas disk kosong yang absolut, dua gigabyte secara bawaan, lalu menuliskannya menggantikan batas relatif.

ini
disk_free_limit.absolute = 2GB

Hasil dan Pembahasan

A. Apa yang terlihat dari internet

Stack ini dites dari internet, dengan sertifikat publik dan token sungguhan dari identity provider, bukan dari dalam server. Port 8883 menerima MQTT over TLS dengan username dan password. Port 443 menerima MQTT lewat WebSocket aman dengan token. Port MQTT polos sama sekali tak terjangkau dari luar, karena secara bawaan port broker hanya dipublikasikan di alamat internal dan nginx satu-satunya yang mendengarkan secara publik.

PortProtokolEnkripsiKredensialDari internet
8883MQTT over TCPTLSusername dan passwordterbuka
443MQTT over WebSocketTLStokenterbuka
1883MQTT over TCPtidak ada—tertutup
Tiga pintu masuk MQTT, dilihat dari luar server.

Dua temuan dari tes itu layak disimpan. Client yang terhubung ke 8883 tanpa TLS tidak mendapat error; ia menggantung sampai timeout, karena server menunggu handshake yang tak pernah datang, dan gejalanya mirip masalah jaringan, bukan penolakan; password yang salah langsung dijawab dengan kode penolakan. Lalu, ini bukan mutual TLS: client memverifikasi sertifikat server dengan trust store sistemnya, dan server tidak meminta sertifikat client, jadi identitas client tetap urusan broker.

B. Apa yang ditolak, dan di mana

Kasus yang paling penting justru yang gagal. Token yang login sebagai satu user tapi terhubung dengan nama lain ditolak di tahap connect. Token sah dari aplikasi lain di identity provider yang sama ditolak di tahap yang sama, karena audience-nya tidak menyebut broker. Keduanya tak sampai ke aturan akses, yang merupakan titik paling awal penolakan bisa terjadi.

C. Di mana ini berhenti

RabbitMQ sengaja tidak menjadi bagian dari permukaan publik. Ia membawa lalu lintas di antara service saya sendiri, port-nya hanya dipublikasikan di alamat internal, dan user-nya, termasuk administrator, dibuat dari definisi yang ditulis generator. Dashboard manajemen kedua broker juga tetap internal. Menambahkan mutual TLS untuk perangkat, atau membuka AMQP ke service luar, akan menjadi rancangan lain dengan hambatan lain.

Simpulan

Membuka broker ke internet ternyata lebih banyak soal identitas ketimbang enkripsi. Kini setiap pintu masuk hanya menerima satu jenis kredensial, setiap token terikat ke nama yang dipakai client-nya dan wajib ditujukan untuk broker ini, dan TLS berakhir di nginx di depannya untuk kedua protokol. Broker setiap project juga menyala sebagai dirinya sendiri: data kosong, identitas node baru, user-nya sendiri, password dashboard yang bukan bawaan, dan RabbitMQ dengan administrator sungguhan serta batas disk yang pas. Generator menulis semua itu dari segelintir jawaban, sehingga project baru mendapat broker publik yang menolak penyusup sejak start pertamanya, bukan sesudah insiden pertamanya.

Istilah

MQTT
Protokol pesan publish-subscribe yang ringan, dipakai perangkat dan aplikasi yang butuh pembaruan langsung.
AMQP
Protokol pesan untuk antrean dan event yang dirutekan, umumnya dipakai di antara service backend.
Listener
Satu port dan protokol tempat broker menerima koneksi, misalnya TCP atau WebSocket.
JWT
Token bertanda tangan yang membawa klaim tentang user, seperti namanya, siapa penerbitnya, dan untuk siapa token itu ditujukan.
JWKS
Kumpulan public key yang dipublikasikan identity provider untuk menandatangani token-nya, diambil oleh siapa pun yang perlu memverifikasinya.
Audience
Klaim di dalam token yang menyebut service tujuan token itu.
ACL
Access control list: aturan yang menentukan user mana boleh publish atau subscribe ke topic mana.
TLS terminator
Server yang menangani enkripsi atas nama server lain dan meneruskan lalu lintas yang sudah didekripsi kepadanya.
SNI
Server Name Indication: nama server yang disebutkan client saat handshake TLS supaya server bisa memilih sertifikat yang tepat.

Technologies covered

  • Docker Compose
  • EMQX
  • nginx
  • RabbitMQ
  • JWT
  • MQTT
  • TLS

About the author

Photo of Romi Fadli

Romi Fadli, S.Kom.

Solution Architect · Backend Engineer · Embedded Systems

In my day job I work as a solution architect. I design the system, lead the team that builds it — quality assurance, backend developers, a UI developer, DevOps and DevSecOps — and write the most critical modules myself, such as a certificate authority, a long-term PDF signer, and the core API of a personnel system.

I work across the whole stack that a running system actually needs. On the hardware side that means PCB design, microcontrollers, and embedded firmware — including RISC-V boards running FreeRTOS and Embassy, speaking MQTT over mutually authenticated TLS on a microcontroller with 64 KB of SRAM.

Most of my day-to-day work is software: Rust backends and API modules built on Clean Architecture, an OAuth2/OpenID Connect authorization server written from scratch, PAdES document signing, and certificate authorities — root CAs, timestamping authorities, and OCSP responders. I also build the frontends that sit on top of them.

Running all of that means infrastructure: Docker Compose stacks, networking, Mikrotik and RADIUS, observability with OpenTelemetry, and a steady habit of writing command-line tools and utilities for Linux when the existing ones do not fit.

Share

Enjoyed this?

Get an email when a new article is published.

Referenced by

A checklist card on the left, a three-part token in the middle with a check mark, and an identity provider marked with a key on the right

Script Python interaktif yang membuktikan identity provider siap untuk satu client: ia menggladi login dan logout PKCE utuh seperti aplikasi, lalu memeriksa token yang diterimanya terhadap key milik provider sebagaimana yang wajib dilakukan aplikasi.

11 min read Read more →

Related Articles

Employees and administrators use the Web App, which talks to the Core API; the Core API uses storage, regions, face and preview services, sends documents to the Aurograf signing platform, sends logs and traces to the log stack, and imports from the old system

SIMPEG

Programming

Sistem informasi kepegawaian Pemerintah Kota Tangerang Selatan: pegawai mengurus record-nya sendiri dan mengikuti layanannya, admin bekerja tepat pada bagian organisasinya, dan setiap aturan berasal dari peraturan yang menetapkannya. Saya pimpin dan bangun sejak 2023.

10 min read Read more →
One definition in the middle feeds both a phone layout with stacked cards and a bottom bar, and a desktop layout with a top menu and a table

Aplikasi browser Vue dari sistem kepegawaian aparatur sipil negara: portal swalayan tempat pegawai mengurus record dan mengusulkan perubahan, serta administrasi bagan organisasi berversi, data referensi, dan akun — dibangun dari beberapa definisi bersama, bukan satu halaman per record.

9 min read Read more →
The core API at the centre, fed by storage, address, face and preview services, with documents sent across a boundary to an external signing platform

Inti Laravel sistem kepegawaian aparatur sipil negara: record pegawai dan riwayat karier, usulan swalayan yang diperiksa, keputusan kenaikan gaji berkala, kenaikan pangkat, dan pensiun, aturan dari peraturan, bagan organisasi berversi, dan akses berlingkup sampai satu unit.

11 min read Read more →