Mengapa Verifikasi Otorisasi Harus Deterministik
Pemeriksaan keamanan berbasis DAST (Dynamic Application Security Testing) sering kali lambat, rentan false positive, dan berjalan di luar siklus feedback loop utama developer. Ketika tim melakukan refactoring middleware, mengubah hirarki rute API, atau memodifikasi dependensi injeksi konteks, celah otorisasi seperti Broken Object Level Authorization (BOLA) atau eskalasi hak akses horizontal/vertikal kerap muncul tanpa disadari.
Pendekatan pragmatis untuk mencegah regresi ini adalah mengganti pemindaian heuristik dengan pengujian matriks kontrol akses otomatis pada pipeline CI. Matriks otorisasi diperlakukan sebagai kontrak spesifikasi yang dieksekusi langsung pada layer routing HTTP secara deterministik.
Struktur Table-Driven Test Matrix
Pengujian kontrol akses paling efektif dimodelkan menggunakan pola table-driven tests. Pendekatan ini memetakan relasi eksplisit antara identitas pemanggil (subject), tindakan dan rute (action/resource), kepemilikan data (tenant/object ownership), serta target status HTTP (biasanya 200 OK, 401 Unauthorized, atau 403 Forbidden).
Berikut contoh implementasi pada Go menggunakan standard package net/http/httptest:
package auth_test
import (
"net/http"
"net/http/httptest"
"testing"
)
type TestCase struct {
Name string
UserRole string
UserID string
ResourceOwnerID string
Method string
Path string
ExpectedStatus int
}
func TestAccessControlMatrix(t *testing.T) {
// Matriks izin:
// - Admin: Akses penuh ke semua resource.
// - Member: Hanya membaca/mengubah resource miliknya sendiri.
// - Anonymous: Ditolak di semua endpoint terproteksi.
matrix := []TestCase{
{
Name: "Admin can delete any document",
UserRole: "admin",
UserID: "usr-999",
ResourceOwnerID: "usr-001",
Method: http.MethodDelete,
Path: "/api/v1/documents/doc-123",
ExpectedStatus: http.StatusOK,
},
{
Name: "Member cannot delete other member document (BOLA prevention)",
UserRole: "member",
UserID: "usr-002",
ResourceOwnerID: "usr-001",
Method: http.MethodDelete,
Path: "/api/v1/documents/doc-123",
ExpectedStatus: http.StatusForbidden,
},
{
Name: "Member can update own document",
UserRole: "member",
UserID: "usr-001",
ResourceOwnerID: "usr-001",
Method: http.MethodPut,
Path: "/api/v1/documents/doc-123",
ExpectedStatus: http.StatusOK,
},
{
Name: "Anonymous request must be rejected",
UserRole: "",
UserID: "",
ResourceOwnerID: "usr-001",
Method: http.MethodPut,
Path: "/api/v1/documents/doc-123",
ExpectedStatus: http.StatusUnauthorized,
},
}
app := setupTestApp() // Inisialisasi router dan middleware aplikasi
for _, tc := range matrix {
t.Run(tc.Name, func(t *testing.T) {
req := httptest.NewRequest(tc.Method, tc.Path, nil)
if tc.UserRole != "" {
// Inject mock token claims langsung tanpa external auth server
token := generateMockToken(tc.UserID, tc.UserRole)
req.Header.Set("Authorization", "Bearer "+token)
}
// Seed metadata ownership untuk simulasi DB/context state
seedDocumentOwnership("doc-123", tc.ResourceOwnerID)
rec := httptest.NewRecorder()
app.ServeHTTP(rec, req)
if rec.Code != tc.ExpectedStatus {
t.Fatalf("FAIL: %s | Expected: %d, Got: %d", tc.Name, tc.ExpectedStatus, rec.Code)
}
})
}
}
Optimasi Eksekusi CI: Cepat, Terisolasi, dan Bebas Flaky
Agar verifikasi ini dapat berjalan pada setiap pull request tanpa membebani pipeline, perhatikan tiga batasan teknis berikut:
1. Mock Token Tanpa Network I/O
Jangan mengarahkan test suite ke Identity Provider eksternal (Auth0, Keycloak, Firebase) melalui jaringan. Gunakan library JWT lokal dengan fixed symmetric secret (seperti HMAC-SHA256) untuk menandatangani token uji secara instan di memori. Verifikasi middleware cukup memeriksa tanda tangan lokal tersebut.
2. Transactional Rollback untuk State Database
Jika validasi otorisasi bergantung pada relasi di database (misal: verifikasi tenant_id), hindari pembersihan data via TRUNCATE atau membuat database baru per uji coba. Bungkus setiap iterasi pengujian dalam transaksi database:
- Buka transaksi:
tx, _ := db.Begin() - Jalankan assertion HTTP dengan router yang diikat ke
tx. - Batalkan perubahan seketika:
tx.Rollback()
Pola ini menjaga isolasi data antar uji coba dan memangkas waktu eksekusi IO secara drastis.
3. In-Memory Handler Execution
Hindari menjalankan server HTTP nyata yang mendengarkan port TCP lokal (misal: localhost:8080). Gunakan abstraction in-memory (seperti httptest.ResponseRecorder di Go atau supertest di Node.js) yang memanggil handler secara langsung melalui pointer memori.
Menangani Verifikasi BOLA (Broken Object Level Authorization)
Pemisahan role (RBAC) saja tidak cukup. Celah BOLA terjadi ketika user dengan role valid mengakses resource milik entitas lain. Pastikan test matrix mencakup skenario cross-tenant access:
- Kasus Positif: User A mengakses resource milik Tenant A →
200 OK. - Kasus Negatif (BOLA): User A mengakses resource milik Tenant B →
403 Forbiddenatau404 Not Found(tergantung preferensi arsitektur agar tidak membocorkan keberadaan ID resource).
Trade-off dan Batasan
Pengujian matriks berbasis kode ini mengharuskan developer memperbarui tabel pengujian setiap kali endpoint baru diperkenalkan atau hak akses diubah secara sengaja. Matriks ini juga tidak memvalidasi kompleksitas izin dinamis di level presentation layer (UI visibility), melainkan murni mengunci proteksi data di pintu masuk API backend.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!