Mengubah SECRET_KEY pada Django secara langsung akan langsung memutus sesi seluruh pengguna aktif dan membatalkan token yang belum kedaluwarsa. Fitur SECRET_KEY_FALLBACKS yang diperkenalkan pada Django 4.1 memungkinkan rotasi kunci kriptografi tanpa downtime dan tanpa memicu forced logout massal.
Root Cause: Kegagalan Verifikasi HMAC pada Rotasi Sepihak
Django menggunakan SECRET_KEY sebagai secret salt pada modul django.core.signing untuk pembuatan signature berbasis HMAC-SHA256. Komponen inti yang bergantung langsung pada signature ini meliputi:
- Session Cookies: Backend sesi seperti
signed_cookiesatau verifikasi integritas session ID bawaan memvalidasi signature cookie pada setiap HTTP request. - Password Reset Tokens:
PasswordResetTokenGeneratormenggunakan hash HMAC yang menyertakanSECRET_KEY. - Signed Cookies & Form Tokens: Proteksi anti-tampering untuk signed cookies independen dan beberapa mekanisme verifikasi payload (seperti
django.core.signing.dumps).
Ketika SECRET_KEY diubah secara mendadak di production, proses verifikasi payload lama akan mengeksekusi komputasi HMAC menggunakan secret baru. Perbedaan hash menghasilkan error django.core.signing.BadSignature. Handler sesi Django merespons kegagalan ini dengan menghapus session store lokal pada klien, memaksa seluruh user aktif kembali ke halaman login.
Implementasi SECRET_KEY_FALLBACKS via Environment Variable
Pengaturan SECRET_KEY_FALLBACKS menerima tipe data list of strings. Kunci utama pada SECRET_KEY digunakan untuk menandatangani (sign) semua payload baru. Jika proses verifikasi (unsign) payload masuk gagal menggunakan kunci utama, Django mencoba setiap kunci di dalam SECRET_KEY_FALLBACKS secara berurutan hingga lolos atau seluruh kandidat habis.
Hindari hardcoding secret di repositori. Parsing daftar fallback dari environment variable menggunakan pemisah standar seperti koma:
import os
# settings.py
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
# Membaca fallback keys dari comma-separated env var
_fallbacks = os.environ.get("DJANGO_SECRET_KEY_FALLBACKS", "")
SECRET_KEY_FALLBACKS = [key.strip() for key in _fallbacks.split(",") if key.strip()]Konfigurasi minimal: Satu baris parsing env var cukup. Pastikan list kosong jika env var tidak didefinisikan agar tidak memicu error string kosong.
Workflow 3 Tahap Rotasi Kunci
Rotasi zero-downtime memerlukan transisi bertahap untuk memastikan seluruh signature lama kedaluwarsa secara alami sebelum kuncinya dihapus total dari sistem.
- Tahap 1: Deploy Kunci Baru Bersama Kunci Lama sebagai Fallback
Generate kunci acak baru dengan entropi tinggi. Tetapkan kunci baru keDJANGO_SECRET_KEYdan geser kunci lama keDJANGO_SECRET_KEY_FALLBACKS.
Deploy aplikasi. Semua request baru akan menerima signature berbasisDJANGO_SECRET_KEY="new-super-secret-key-xyz" DJANGO_SECRET_KEY_FALLBACKS="old-deprecated-key-abc"new-super-secret-key-xyz, sedangkan request dengan cookie lama tetap valid viaold-deprecated-key-abc. - Tahap 2: Masa Transisi Verifikasi Signature
Biarkan aplikasi berjalan minimal selama durasiSESSION_COOKIE_AGE(default Django: 1.209.600 detik atau 2 minggu). Selama periode ini, setiap user yang berinteraksi dan memperbarui sesi akan mendapatkan cookie yang ditandatangani ulang denganSECRET_KEYutama. Pantau metrik error log untuk memastikan tidak ada lonjakanSuspiciousOperationatauBadSignatureyang tidak lazim. - Tahap 3: Pembersihan Fallback Usang
Setelah window transisi selesai (seluruh cookie lama kedaluwarsa), hapusold-deprecated-key-abcdari environment variable.
Deploy ulang aplikasi. Siklus rotasi selesai dan sistem kembali ke konfigurasi single-key.DJANGO_SECRET_KEY="new-super-secret-key-xyz" DJANGO_SECRET_KEY_FALLBACKS=""
Automated Test: Verifikasi Perilaku Fallback dan Penolakan Kunci Asing
Gunakan Django TestCase bersama override_settings untuk memverifikasi bahwa runtime menerima signature dari fallback key dan secara tegas menolak signature yang dibuat dengan kunci tak terdaftar.
from django.core.signing import Signer, BadSignature
from django.test import SimpleTestCase, override_settings
class SecretKeyRotationTest(SimpleTestCase):
OLD_KEY = "test-key-phase-1-very-old"
NEW_KEY = "test-key-phase-2-current-active"
ATTACKER_KEY = "unauthorized-unregistered-key"
def test_signer_accepts_fallback_key(self):
"""Payload yang di-sign menggunakan fallback key harus tetap valid."""
# Simulasikan pembuatan token di server versi lama
with override_settings(SECRET_KEY=self.OLD_KEY, SECRET_KEY_FALLBACKS=[]):
old_signer = Signer()
signed_data = old_signer.sign("session-payload-data")
# Simulasikan verifikasi di runtime baru dengan SECRET_KEY_FALLBACKS
with override_settings(
SECRET_KEY=self.NEW_KEY,
SECRET_KEY_FALLBACKS=[self.OLD_KEY]
):
new_signer = Signer()
# Verifikasi berhasil tanpa raise BadSignature
original_value = new_signer.unsign(signed_data)
self.assertEqual(original_value, "session-payload-data")
def test_signer_rejects_unregistered_key(self):
"""Payload dari key yang tidak terdaftar harus di-reject."""
with override_settings(SECRET_KEY=self.ATTACKER_KEY, SECRET_KEY_FALLBACKS=[]):
invalid_signer = Signer()
tampered_data = invalid_signer.sign("malicious-payload")
with override_settings(
SECRET_KEY=self.NEW_KEY,
SECRET_KEY_FALLBACKS=[self.OLD_KEY]
):
verifier = Signer()
with self.assertRaises(BadSignature):
verifier.unsign(tampered_data)Batasan dan Mitigasi
Meskipun SECRET_KEY_FALLBACKS menangani cryptographic signing bawaan Django, perhatikan beberapa batasan arsitektural berikut:
- Libraries Pihak Ketiga: Paket eksternal (misal: JWT library atau modul enkripsi field database) yang mengambil langsung
settings.SECRET_KEYsebagai raw string tanpa mengimplementasikan fallback loop tidak akan otomatis mendukung transisi ini. Library tersebut membutuhkan penanganan rotasi terpisah di level aplikasi. - Rotasi Darurat Akibat Kebocoran Kunci: Jika kunci lama bocor ke publik atau terkompromi (data leak), jangan gunakan alur transisi ini. Masukkan kunci baru langsung sebagai
SECRET_KEYdan kosongkanSECRET_KEY_FALLBACKS. Forced logout dan token invalidation massal adalah konsekuensi operasional yang harus diambil demi mencegah pemalsuan identitas (session hijacking).
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!