Banyak developer yang paham pentingnya TLS buat public-facing services — HTTPS harus. Tapi kalau urusan internal communication? Service A ngobrol sama Service B lewat internal network? Seringnya… HTTP aja, santai. "Kan cuma internal, gak bisa diakses dari luar."
Argumen itu bahaya. Risikonya bukan cuma eavesdropping, tapi juga man-in-the-middle (MITM) attack dari dalam. Bayangin: attacker yang udah masuk ke satu container bisa sniff traffic antar service lo — API key, database credentials, session tokens — semuanya lewat dalam bentuk plain text. Gw udah lihat ini di beberapa klien: satu service kena eksploit, semua service lain ikut jebol karena traffic internal gak dienkripsi.
Artikel ini bakal nunjukin cara setup TLS untuk internal services dengan self-signed CA dan otomatisasi penuh. Bukan cuma bikin sertifikat manual, tapi infrastruktur yang scalable — dari 2 service sampai 50 service.
Kenapa Self-Signed CA?
Ada tiga opsi buat TLS internal:
| Opsi | Pro | Kontra |
|---|---|---|
| Let's Encrypt | Gratis, trusted by default | Rate limited, perlu public DNS, cert expire 90 hari, gak bisa buat .internal/.local |
| Self-signed CA | Full control, gak perlu public DNS, bisa wildcard, bisa SAN kustom | Perlu distribute CA cert ke setiap client |
| mTLS (mutual TLS) | Dua-arah auth, paling aman | Setup lebih kompleks, perlu manage client certs |
Self-signed CA adalah sweet spot buat majority self-hosted setup. Lo punya control penuh, no rate limits, dan bisa distribusi CA certificate via mekanisme yang lo tentuin sendiri — Ansible, Docker volume, atau config management tool.
Kalo lo butuh mutual authentication (service A dan service B saling verifikasi), mTLS adalah next level. Kita bakal bahas juga di bagian akhir.
Step 1: Setup Root CA
Pertama, bikin root CA — ini adalah "master key" yang bakal nandain semua sertifikat internal lo. Simpan CA private key di tempat aman, ideally offline atau di dedicated secrets management.
#!/bin/bash
# setup-ca.sh — Jalanin sekali aja di server yang aman
CA_DIR="/etc/internal-ca"
mkdir -p $CA_DIR {certs,crl,newcerts,private}
chmod 700 $CA_DIR/private
cd $CA_DIR
# Generate root CA key (4096-bit RSA)
openssl genrsa -aes256 -out private/ca.key.pem 4096
chmod 400 private/ca.key.pem
# Generate root CA certificate (10 tahun)
openssl req -config /etc/ssl/openssl.cnf -key private/ca.key.pem -new -x509 -days 3650 -sha256 -extensions v3_ca -out certs/ca.cert.pem -subj "/C=ID/O=Toolkuy Internal/CN=Toolkuy Root CA 2026"
# Verify
openssl x509 -noout -text -in certs/ca.cert.pem | head -5
echo "Root CA created: certs/ca.cert.pem"
Beberapa hal penting di atas: 4096-bit RSA key — meski lebih lambat dari 2048-bit, buat root CA yang dipake 5-10 tahun ke depan, ini adalah standar minimum. chmod 400 �� pastiin cuma root yang bisa baca private key. -aes256 — encrypt private key dengan passphrase, jadi kalo file-nya bocor, masih ada satu lapisan proteksi.
Step 2: Intermediate CA (Opsional Tapi Recommended)
Best practice: jangan pake root CA langsung buat sign sertifikat. Bikin intermediate CA yang jadi "daily driver", dan root CA disimpan offline. Kalo intermediate CA-nya compromise, lo bisa revoke dan re-issue dari root CA tanpa rebuild semuanya.
# Generate intermediate CA key
openssl genrsa -aes256 -out private/intermediate.key.pem 4096
chmod 400 private/intermediate.key.pem
# Generate CSR
openssl req -new -sha256 -key private/intermediate.key.pem -out csr/intermediate.csr.pem -subj "/C=ID/O=Toolkuy Internal/CN=Toolkuy Intermediate CA 2026"
# Sign dengan root CA
openssl ca -config /etc/ssl/openssl.cnf -extensions v3_intermediate_ca -days 1825 -notext -md sha256 -in csr/intermediate.csr.pem -out certs/intermediate.cert.pem
# Buat CA chain (root + intermediate)
cat certs/intermediate.cert.pem certs/ca.cert.pem > certs/ca-chain.cert.pem
Step 3: Generate Sertifikat Service (Dengan Otomatisasi)
Nah ini bagian yang paling penting — otomatisasi. Lo gak mungkin tiap kali bikin service baru, SSH ke server dan jalanin OpenSSL manual. Kita bikin script yang generate sertifikat + private key dalam satu langkah:
#!/bin/bash
# generate-service-cert.sh
# Usage: ./generate-service-cert.sh service-name.internal.toolkuy.local
SERVICE_NAME=$1
CA_DIR="/etc/internal-ca"
OUT_DIR="/etc/ssl/services/$SERVICE_NAME"
mkdir -p $OUT_DIR
# 1. Generate private key
openssl genrsa -out $OUT_DIR/$SERVICE_NAME.key.pem 2048
chmod 400 $OUT_DIR/$SERVICE_NAME.key.pem
# 2. Generate CSR dengan SANs
cat > /tmp/$SERVICE_NAME.cnf <<EOF
[req]
distinguished_name = req_distinguished_name
req_extensions = req_ext
prompt = no
[req_distinguished_name]
C = ID
O = Toolkuy Internal
CN = $SERVICE_NAME
[req_ext]
subjectAltName = @alt_names
[alt_names]
DNS.1 = $SERVICE_NAME
DNS.2 = $SERVICE_NAME.internal
DNS.3 = $SERVICE_NAME.local
DNS.4 = localhost
DNS.5 = *.${SERVICE_NAME%%.*}.svc.cluster.local
EOF
openssl req -new -sha256 -key $OUT_DIR/$SERVICE_NAME.key.pem -out /tmp/$SERVICE_NAME.csr.pem -config /tmp/$SERVICE_NAME.cnf
# 3. Sign dengan intermediate CA
openssl ca -config /etc/ssl/openssl.cnf -extensions server_cert -days 365 -notext -md sha256 -in /tmp/$SERVICE_NAME.csr.pem -out $OUT_DIR/$SERVICE_NAME.cert.pem -batch
# 4. Buat fullchain (sertifikat + chain)
cat $OUT_DIR/$SERVICE_NAME.cert.pem $CA_DIR/certs/intermediate.cert.pem > $OUT_DIR/$SERVICE_NAME.fullchain.pem
# 5. Verify
openssl verify -CAfile $CA_DIR/certs/ca-chain.cert.pem $OUT_DIR/$SERVICE_NAME.fullchain.pem
echo "Certificate for $SERVICE_NAME generated."
echo "Key: $OUT_DIR/$SERVICE_NAME.key.pem"
echo "Cert: $OUT_DIR/$SERVICE_NAME.cert.pem"
rm -f /tmp/$SERVICE_NAME.cnf /tmp/$SERVICE_NAME.csr.pem
Step 4: Distribusi CA Certificate ke Client
Ini bagian yang paling sering dilupain — client gak akan trust sertifikat lo kalo CA certificate gak di-install. Berikut cara distribusi ke berbagai platform:
Docker containers — tambahin di Dockerfile atau mount volume:
# Dockerfile
COPY ca.cert.pem /usr/local/share/ca-certificates/internal-ca.crt
RUN update-ca-certificates
# Atau docker-compose.yml (mount read-only)
volumes:
- /etc/internal-ca/certs/ca.cert.pem:/usr/local/share/ca-certificates/internal-ca.crt:ro
Linux hosts — distribusi via Ansible atau shell:
sudo cp ca.cert.pem /usr/local/share/ca-certificates/internal-ca.crt
sudo update-ca-certificates
# Verify
curl --cacert /etc/ssl/certs/ca-certificates.crt https://service.internal.local
Node.js / Python apps — set environment variable:
# Node.js
export NODE_EXTRA_CA_CERTS=/etc/ssl/certs/internal-ca.pem
# Python requests
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/internal-ca.pem
# Atau via code:
# import requests
# requests.get("https://service.internal", verify="/etc/ssl/certs/internal-ca.pem")
Step 5: mTLS — Mutual TLS
Untuk komunikasi yang benar-benar sensitive (misal antara API gateway dan payment service), pake mTLS. Dua arah: server nge-verify client certificate, client nge-verify server certificate.
# Generate client certificate
./generate-service-cert.sh api-gateway.client.internal
# Konfigurasi server (Nginx)
server {
listen 443 ssl;
ssl_certificate /etc/ssl/services/api-service/fullchain.pem;
ssl_certificate_key /etc/ssl/services/api-service/key.pem;
# mTLS: require client cert
ssl_client_certificate /etc/internal-ca/certs/ca-chain.cert.pem;
ssl_verify_client on;
ssl_verify_depth 2;
# Optional: map client cert CN to header
if ($ssl_client_verify != SUCCESS) {
return 403;
}
}
Monitoring & Auto-Renew
Sertifikat internal gak bisa pake Certbot (Let's Encrypt), jadi kita perlu cron job buat monitoring expiry dan auto-renew:
#!/bin/bash
# renew-certs.sh — Jalanin weekly via cron
# Cek sertifikat yang bakal expired dalam 30 hari
CA_DIR="/etc/internal-ca"
CERTS_DIR="/etc/ssl/services"
THRESHOLD=30
for cert in $CERTS_DIR/*/fullchain.pem; do
expiry=$(openssl x509 -enddate -noout -in "$cert" | cut -d= -f2)
expiry_epoch=$(date -d "$expiry" +%s)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
if [ $days_left -lt $THRESHOLD ]; then
service_name=$(basename $(dirname "$cert"))
echo "[WARN] $service_name expires in $days_left days — renewing..."
./generate-service-cert.sh $service_name
systemctl restart $service_name 2>/dev/null || docker compose restart $service_name 2>/dev/null
fi
done
Integrasi dengan monitoring — kirim notifikasi ke Plausible atau Uptime Kuma kalo ada sertifikat yang expired atau gagal renew.
Praktik Terbaik
- Simpan root CA offline ��� boot dari USB yang cuma dicolok pas renew intermediate CA
- Gunakan Hardware Security Module (HSM) kalo budget memungkinkan — YubiKey atau Nitrokey HSM bisa nyimpen CA key di hardware yang gak bisa diekstrak
- Audit log �� catat setiap sertifikat yang di-sign: siapa, kapan, untuk service apa
- Revocation — maintain CRL (Certificate Revocation List) atau pake OCSP responder kalo infrastruktur lo besar
- Short-lived certificates �� jangan 365 hari, makin pendek makin aman. Idealnya 30-90 hari dengan full auto-renew
- Gunakan dedicated subdomain — misal
*.internal.toolkuy.localbiar gak bentrok sama public services
Kesimpulan
TLS untuk internal services bukan opsional — ini fundamental security hygiene yang harus ada di setiap infrastruktur yang serius. Dengan self-signed CA + script otomatisasi di atas, lo bisa implementasi dalam waktu 1-2 jam dan skalanya bisa dari 2 sampai puluhan service tanpa tambahan biaya.
Yang paling penting: jangan pernah skip distribusi CA certificate ke client. Itu adalah satu-satunya bagian yang bikin orang males pake internal TLS — tapi begitu udah diotomatisasi via Ansible atau Docker build, lo gak perlu mikir lagi.
Buat yang lagi setup infrastruktur dari awal, baca juga container security hardening buat ngamaniin container lo dan panduan zero trust mTLS untuk pendekatan keamanan yang lebih komprehensif.
💬 Komentar (0)
Belum ada komentar. Jadilah yang pertama! 💬