Desain API untuk pengecualian kebijakan harus memperlakukan pengecualian sebagai objek berumur terbatas yang diminta, disetujui, ditegakkan, dan diaudit—bukan sebagai kondisi if admin then allow. Keputusan akses tetap deny-by-default; pengecualian hanya berlaku jika subjek, tindakan, sumber daya, alasan, persetujuan, dan masa berlakunya cocok dengan permintaan saat ini.
Konsekuensinya, API perlu memisahkan hak untuk meminta, menyetujui, menggunakan, dan mengaudit pengecualian. RBAC menentukan siapa yang boleh menjalankan fungsi tersebut, sedangkan ABAC membatasi konteksnya. Semua keputusan, termasuk penolakan dan penggunaan akses darurat, harus menghasilkan jejak audit yang sulit diubah.
Analogi aturan yang diterapkan tidak konsisten
Sebuah laporan Politico mengenai penonaktifan AC di kantor Komisi Eropa menggambarkan situasi ketika pegawai menghadapi pembatasan, sementara area atau pejabat tertentu memperoleh pengecualian. Kasus tersebut digunakan di sini semata-mata sebagai analogi rekayasa perangkat lunak, bukan sebagai ulasan politik.
Masalah teknisnya mudah dikenali: aturan utama ada, tetapi jalur pengecualiannya tidak transparan bagi pihak yang terkena aturan. Dalam sistem API, padanannya adalah akun biasa ditolak oleh policy engine, sedangkan akun istimewa lolos melalui kondisi tersembunyi di aplikasi. Pola seperti ini berbahaya karena:
- alasan pengecualian tidak dapat diverifikasi;
- akses dapat bertahan tanpa batas waktu;
- pemilik akun istimewa dapat menyetujui dirinya sendiri;
- auditor hanya melihat hasil akhir, bukan proses persetujuannya;
- perubahan kebijakan tidak otomatis mengevaluasi ulang pengecualian lama.
Prinsip utamanya: akun istimewa boleh memiliki hak untuk meminta atau menyetujui pengecualian, tetapi tidak boleh diam-diam dikecualikan dari evaluasi kebijakan.
Model otorisasi: deny-by-default, RBAC, dan ABAC
Deny-by-default sebagai titik awal
Policy enforcement point harus menolak operasi ketika tidak ada keputusan allow yang eksplisit. Kesalahan policy engine, data atribut yang hilang, kegagalan membaca penyimpanan pengecualian, atau status yang ambigu juga sebaiknya menghasilkan penolakan. Ini disebut fail closed.
Alur keputusan yang aman adalah:
- Evaluasi kebijakan normal.
- Jika diizinkan, lanjutkan dan catat keputusan.
- Jika ditolak, cari pengecualian aktif yang cocok secara tepat.
- Validasi ruang lingkup, jumlah dan identitas pemberi persetujuan, masa berlaku, status pencabutan, serta versi kebijakan.
- Jika salah satu pemeriksaan gagal, tolak operasi.
Pengecualian bukan pengganti kebijakan utama. Ia adalah masukan tambahan yang sempit untuk satu keputusan otorisasi.
Memisahkan peran dengan RBAC
RBAC cocok untuk mendefinisikan fungsi organisasi, misalnya:
exception.requester: mengajukan permintaan;exception.approver: memberikan keputusan;exception.auditor: membaca riwayat tanpa mengubahnya;breakglass.operator: memulai akses darurat;policy.administrator: mengelola kebijakan, tetapi tidak otomatis memperoleh akses ke sumber daya yang dilindungi.
Hindari peran global seperti superadmin yang langsung melewati middleware. Jika peran tersebut diperlukan untuk administrasi, tetap kirim permintaannya ke policy engine.
Membatasi konteks dengan ABAC
RBAC saja terlalu kasar. ABAC menilai atribut subjek, sumber daya, tindakan, dan lingkungan, misalnya unit organisasi, klasifikasi data, wilayah, jam akses, status perangkat, atau tingkat risiko sesi.
Sebuah pengecualian sebaiknya mengikat setidaknya:
- identitas subjek atau grup yang menerima pengecualian;
policy_iddan, bila relevan, versi kebijakan;- tindakan yang diperbolehkan;
- sumber daya atau pola sumber daya yang terbatas;
- waktu mulai dan waktu kedaluwarsa;
- alasan bisnis dan referensi bukti;
- batasan tambahan, seperti jaringan atau tingkat autentikasi.
Gunakan waktu dari server atau basis data, bukan waktu yang dikirim klien. Simpan waktu dalam UTC dan definisikan secara tegas apakah batas akhir bersifat inklusif atau eksklusif.
Kontrak API permintaan dan persetujuan pengecualian
Membuat permintaan yang idempoten
Endpoint pembuatan menerima Idempotency-Key. Server menyimpan kombinasi identitas pemanggil, endpoint, kunci, dan hash payload dalam periode retensi yang ditetapkan. Permintaan ulang dengan kunci dan payload yang sama mengembalikan hasil semula; kunci yang sama dengan payload berbeda menghasilkan 409 Conflict.
POST /v1/policy-exceptions
Authorization: Bearer <token>
Idempotency-Key: 7d85495a-3503-4b46-a3cc-36fc2e54e264
Content-Type: application/json
{
"policy_id": "data-export-restricted",
"policy_version": "2025-04",
"subject": {
"type": "user",
"id": "usr_1842"
},
"scope": {
"actions": ["reports.export"],
"resources": ["report:finance/monthly-2025-06"]
},
"justification": "Ekspor satu laporan untuk pemeriksaan insiden INC-4821.",
"evidence_refs": ["incident:INC-4821"],
"starts_at": "2025-06-18T09:00:00Z",
"expires_at": "2025-06-18T11:00:00Z"
}
Server tidak boleh membiarkan klien menetapkan status approved, daftar pemberi persetujuan, atau penanda audit. Respons awal dapat menggunakan 202 Accepted jika proses persetujuannya asinkron:
{
"id": "pex_01JY3Q3Q4C8T",
"status": "pending_approval",
"required_approvals": 2,
"received_approvals": 0,
"expires_at": "2025-06-18T11:00:00Z"
}
Contoh definisi OpenAPI ringkas
paths:
/v1/policy-exceptions:
post:
summary: Meminta pengecualian kebijakan
parameters:
- in: header
name: Idempotency-Key
required: true
schema:
type: string
maxLength: 128
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ExceptionRequest'
responses:
'202':
description: Permintaan tercatat dan menunggu persetujuan
'403':
description: Pemanggil tidak boleh meminta pengecualian ini
'409':
description: Konflik status atau penggunaan ulang kunci
/v1/policy-exceptions/{exceptionId}/decisions:
post:
summary: Memberikan keputusan atas permintaan
parameters:
- in: path
name: exceptionId
required: true
schema: { type: string }
- in: header
name: Idempotency-Key
required: true
schema: { type: string, maxLength: 128 }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [decision, reason]
properties:
decision:
type: string
enum: [approve, reject]
reason:
type: string
minLength: 10
responses:
'200': { description: Keputusan tercatat }
'403': { description: Pemanggil tidak berhak menyetujui }
'409': { description: Status permintaan tidak dapat diubah }
components:
schemas:
ExceptionRequest:
type: object
required:
- policy_id
- subject
- scope
- justification
- starts_at
- expires_at
properties:
policy_id: { type: string }
policy_version: { type: string }
subject:
type: object
required: [type, id]
properties:
type: { type: string, enum: [user, service] }
id: { type: string }
scope:
type: object
required: [actions, resources]
properties:
actions:
type: array
minItems: 1
items: { type: string }
resources:
type: array
minItems: 1
items: { type: string }
justification: { type: string, minLength: 20 }
evidence_refs:
type: array
items: { type: string }
starts_at: { type: string, format: date-time }
expires_at: { type: string, format: date-time }
Batas panjang bukan hanya masalah dokumentasi. Terapkan juga di server dan penyimpanan. Batasi pola sumber daya agar pemohon tidak menggunakan wildcard luas seperti * kecuali kebijakan khusus benar-benar mengizinkannya.
Dual approval dan pemisahan tugas
Dual approval berarti dua identitas pemberi persetujuan yang berbeda, bukan dua klik atau dua token milik orang yang sama. Pemohon tidak boleh menyetujui permintaannya sendiri. Untuk risiko tinggi, kedua approver dapat diwajibkan berasal dari fungsi berbeda, misalnya pemilik data dan keamanan.
Proses keputusan harus transaksional. Gunakan batas unik pada pasangan (exception_id, approver_id), kunci baris permintaan saat menghitung persetujuan, dan ubah status menjadi active hanya setelah semua syarat terpenuhi. Tanpa kontrol konkurensi, dua permintaan paralel dapat menyebabkan status atau jumlah persetujuan yang keliru.
Mengikat pengecualian pada versi kebijakan mencegah izin lama diterapkan pada aturan baru. Trade-off-nya adalah perubahan kebijakan dapat menonaktifkan pengecualian dan memerlukan persetujuan ulang. Untuk sistem berisiko tinggi, perilaku konservatif ini biasanya lebih aman daripada meneruskan pengecualian secara otomatis.
Makna respons 403 dan 409
- 403 Forbidden: identitas telah diautentikasi, tetapi tidak berhak meminta, menyetujui, atau menjalankan tindakan. Jangan mengubahnya menjadi 404 kecuali memang ada strategi khusus untuk menyembunyikan keberadaan sumber daya.
- 409 Conflict: permintaan valid secara sintaksis tetapi bertentangan dengan status saat ini, misalnya pengecualian sudah kedaluwarsa, keputusan sudah final, approver yang sama mengirim persetujuan kedua, atau
Idempotency-Keydigunakan dengan payload berbeda.
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
{
"type": "https://api.example.test/problems/policy-denied",
"title": "Operasi ditolak oleh kebijakan",
"status": 403,
"code": "POLICY_EXCEPTION_REQUIRED",
"trace_id": "trc_01JY3R7JQ6"
}
Respons eksternal jangan memaparkan atribut sensitif atau nama approver. Detail evaluasi lengkap disimpan pada audit internal dan dihubungkan melalui trace_id.
Retry yang aman
Klien hanya boleh mencoba ulang operasi mutasi dengan Idempotency-Key yang sama. Retry cocok untuk timeout, kegagalan jaringan, sebagian respons 5xx, atau 429 dengan mengikuti Retry-After jika tersedia. Gunakan exponential backoff dengan jitter dan batas jumlah percobaan.
Jangan otomatis mencoba ulang 403. Untuk 409, baca kode masalah, ambil status terbaru, lalu tentukan apakah pengguna perlu memperbaiki permintaan. Menghasilkan kunci idempotensi baru pada setiap retry dapat membuat beberapa permintaan pengecualian untuk kebutuhan yang sama.
Enforcement di middleware, bukan hanya di antarmuka
Tombol yang disembunyikan di frontend bukan kontrol keamanan. Setiap jalur backend—REST, GraphQL, proses batch, antrean, dan alat administrasi—harus melewati enforcement yang setara. Klaim dari klien seperti has_exception: true tidak boleh dipercaya.
function authorize(request, actor):
context = buildTrustedContext(actor, request)
baseDecision = policyEngine.evaluate(context)
if baseDecision == ALLOW:
audit.append(decisionEvent(context, "allow", "base_policy"))
return ALLOW
if baseDecision == ERROR:
audit.append(decisionEvent(context, "deny", "policy_error"))
return DENY
exception = exceptionStore.findActiveMatch(
subjectId = actor.id,
policyId = baseDecision.policyId,
policyVersion = baseDecision.policyVersion,
action = context.action,
resource = context.resource,
serverTime = clock.now()
)
if exception == null:
audit.append(decisionEvent(context, "deny", "no_matching_exception"))
return DENY
if not approvals.areValid(exception, requesterMustDiffer = true):
audit.append(decisionEvent(context, "deny", "invalid_approvals"))
return DENY
if exception.revoked or not constraints.match(exception, context):
audit.append(decisionEvent(context, "deny", "exception_constraints_failed"))
return DENY
audit.append(decisionEvent(
context,
"allow",
"approved_exception",
exceptionId = exception.id
))
return ALLOW
Pseudocode tersebut sengaja memperlakukan error policy engine sebagai penolakan. Untuk menjaga ketersediaan, cache keputusan dapat digunakan secara terbatas, tetapi TTL-nya harus pendek dan mekanisme pencabutan harus dipertimbangkan. Cache pengecualian yang terlalu lama dapat membuat akses tetap aktif setelah dicabut.
Break-glass tanpa jalur belakang permanen
Break-glass disediakan untuk insiden ketika alur persetujuan normal terlalu lambat, bukan untuk kenyamanan operasional. Ia tetap merupakan kontrak API eksplisit dan tidak boleh berupa akun bersama dengan kata sandi statis.
Endpoint seperti POST /v1/break-glass-sessions sebaiknya mewajibkan autentikasi kuat, nomor insiden, alasan, ruang lingkup sempit, dan TTL maksimum yang ditentukan server. Akses dapat aktif dengan satu otorisasi darurat, tetapi harus memicu notifikasi segera dan peninjauan setelah kejadian.
{
"incident_ref": "INC-4821",
"requested_actions": ["reports.export"],
"requested_resources": ["report:finance/monthly-2025-06"],
"reason": "Pemulihan data diperlukan untuk menghentikan insiden aktif."
}
Kontrol break-glass yang disarankan meliputi:
- TTL singkat yang tidak dapat diperpanjang diam-diam;
- notifikasi ke keamanan dan pemilik sumber daya;
- rekaman setiap operasi selama sesi, bukan hanya pembuatan sesi;
- pencabutan langsung dan post-incident review wajib;
- pembatasan jumlah sesi aktif per identitas atau insiden;
- larangan menggunakan break-glass untuk mengubah atau menghapus audit.
Trade-off-nya jelas: semakin ketat mekanisme darurat, semakin besar kemungkinan memperlambat respons insiden. Karena itu, uji alurnya secara berkala dan pastikan operator mengetahui prosedur sebelum keadaan darurat terjadi.
Audit log immutable dan dapat ditelusuri
Audit harus mencatat perubahan status serta penggunaan pengecualian. Catatan minimum mencakup waktu server, identitas aktor, identitas efektif, tindakan, sumber daya, keputusan, alasan keputusan, ID kebijakan dan versinya, ID pengecualian, approver, trace_id, serta hasil operasi.
Immutable tidak berarti hanya menambahkan kolom hash ke tabel yang masih dapat diedit administrator. Implementasi yang lebih kuat menggabungkan beberapa lapisan:
- penyimpanan append-only dan pemisahan hak tulis dari aplikasi utama;
- replikasi ke akun atau sistem keamanan terpisah;
- retensi atau WORM bila platform mendukungnya;
- rantai hash atau tanda tangan untuk mendeteksi perubahan;
- monitoring atas jeda pengiriman dan kegagalan penulisan audit.
Rantai hash membantu mendeteksi manipulasi, tetapi tidak mencegah penghapusan seluruh rangkaian jika penyerang menguasai semua salinan. Karena itu, isolasi administratif dan salinan eksternal tetap diperlukan.
Jangan merekam token, rahasia, atau isi data sensitif ke audit. Catat referensi dan metadata yang diperlukan. Tetapkan kebijakan retensi, akses auditor, serta prosedur ekspor bukti. Bila audit sinkron wajib untuk operasi berisiko tinggi, kegagalan audit dapat dibuat fail closed; konsekuensinya adalah penurunan ketersediaan ketika layanan audit bermasalah.
Threat model dan pengujian keamanan
Ancaman yang perlu dimodelkan
- Bypass akun istimewa: administrator memakai endpoint internal yang tidak menjalankan middleware.
- Self-approval: pemohon memakai peran lain atau akun kedua yang masih berada di bawah kendalinya.
- Scope inflation: wildcard atau pola sumber daya memperluas izin melebihi kebutuhan.
- Replay: keputusan atau token pengecualian lama digunakan kembali setelah kedaluwarsa.
- Race condition: persetujuan, pencabutan, dan penggunaan terjadi bersamaan.
- Audit tampering: pelaku menghapus jejak setelah memakai pengecualian.
- Confused deputy: service account yang berhak menjalankan operasi digunakan atas nama pengguna yang sebenarnya tidak berhak.
- Clock manipulation: waktu klien atau host aplikasi dimanipulasi agar pengecualian tampak aktif.
- Approval fatigue: approver menyetujui permintaan berulang tanpa memeriksa ruang lingkup dan alasan.
Mitigasi self-approval tidak cukup dengan membandingkan ID akun. Untuk sistem sensitif, pertimbangkan hubungan kepemilikan akun, grup organisasi, dan identitas manusia di belakang service account. Persetujuan dari dua akun yang dimiliki orang yang sama bukan dual approval yang independen.
Pengujian agar akun istimewa tidak lolos diam-diam
Bangun matriks pengujian berdasarkan identitas, tindakan, sumber daya, status pengecualian, dan waktu. Kasus minimum yang perlu diotomatisasi adalah:
- Akun biasa dan akun administrator sama-sama menerima
403ketika kebijakan menolak dan tidak ada pengecualian. - Administrator kebijakan tidak otomatis dapat membaca atau mengubah sumber daya yang dilindungi.
- Pemohon tidak dapat menyetujui permintaannya sendiri.
- Approver yang sama tidak dapat dihitung dua kali, termasuk melalui permintaan paralel.
- Pengecualian dengan tindakan atau sumber daya berbeda tidak cocok.
- Pengecualian yang belum mulai, kedaluwarsa, ditolak, atau dicabut menghasilkan
403. - Perubahan versi kebijakan menonaktifkan pengecualian lama sesuai aturan yang ditetapkan.
- Retry dengan kunci dan payload sama mengembalikan hasil yang sama tanpa duplikasi.
- Kunci sama dengan payload berbeda menghasilkan
409. - Break-glass berakhir otomatis dan seluruh penggunaannya tercatat.
- Kegagalan policy engine atau atribut yang hilang menghasilkan penolakan.
- Endpoint alternatif, proses batch, dan consumer antrean menerapkan keputusan yang sama.
Tambahkan pengujian konkurensi untuk persetujuan dan pencabutan, serta property-based testing untuk memastikan pelebaran peran tidak pernah mengurangi pembatasan kebijakan secara implisit. Uji integritas audit dengan mencoba mengubah atau menghapus event menggunakan kredensial aplikasi dan administrator biasa.
Di lingkungan pengujian, gunakan jam yang dapat dikendalikan agar batas starts_at dan expires_at dapat diuji tanpa penundaan nyata. Pastikan kode produksi tetap memakai sumber waktu tepercaya.
Observabilitas dan debugging
Sediakan kode alasan yang stabil seperti NO_MATCHING_EXCEPTION, INSUFFICIENT_APPROVALS, atau POLICY_VERSION_MISMATCH. Hubungkan respons, log aplikasi, keputusan policy engine, dan audit dengan trace_id. Metrik yang berguna mencakup jumlah permintaan, penolakan, aktivasi, penggunaan break-glass, konflik idempotensi, serta pengecualian yang segera kedaluwarsa.
Ketika pengguna melaporkan 403, periksa identitas efektif, kebijakan dan versinya, normalisasi nama tindakan, canonical resource ID, waktu server, status pencabutan, serta cache. Kesalahan umum adalah membandingkan pola sumber daya mentah yang berbeda format atau membaca keanggotaan grup yang sudah kedaluwarsa.
Checklist implementasi
- Terapkan deny-by-default dan fail closed pada semua jalur backend.
- Pisahkan requester, approver, auditor, policy administrator, dan break-glass operator.
- Gunakan ABAC untuk membatasi tindakan, sumber daya, identitas, waktu, dan konteks.
- Wajibkan alasan, referensi bukti, waktu mulai, dan waktu berakhir.
- Terapkan dual approval independen serta larangan self-approval.
- Gunakan
Idempotency-Keydan simpan hash payload untuk mutasi yang dapat diulang. - Definisikan
403,409, kode masalah, dan perilaku retry secara konsisten. - Catat permintaan, keputusan, penggunaan, pencabutan, dan break-glass pada audit append-only.
- Uji akun istimewa melalui endpoint publik, internal, batch, dan antrean.
- Pantau pengecualian yang terlalu luas, terlalu lama, sering dipakai, atau berulang.
Desain ini tidak menghilangkan kebutuhan akan pengecualian. Ia mengubah pengecualian dari jalur belakang yang tidak terlihat menjadi kapabilitas terbatas dengan kontrak eksplisit, masa berlaku, pemisahan tugas, dan bukti audit. Hasil yang dicari bukan sekadar API yang dapat menyetujui permintaan, melainkan sistem yang dapat menjelaskan siapa mendapat pengecualian, untuk apa, atas persetujuan siapa, sampai kapan, dan kapan tepatnya akses tersebut digunakan.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!