Security Architecture
Overview
Centralize Management System menerapkan security secara berlapis:
- Authentication - Keycloak SSO + JWT
- Authorization - RBAC (Role-Based Access Control)
- Network Security - Docker network isolation
- Data Security - Encryption at rest & in transit
- Audit Trail - Immutable audit logging
Authentication Flow
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Frontend │────▶│ Backend │────▶│ Keycloak │
│ │ │ │ │ │
│ Login │ │ Password │ │ Validate │
│ Form │ │ Grant │ │ Credentials│
└──────────┘ └──────────┘ └──────────┘
│ │
│◀───────────JWT Token─────────────│
│ │
│─────Bearer Token────────────────▶│
│ │
│◀─────────User Info───────────────│
Login Process
- User enters credentials di Frontend
- Frontend mengirim request ke Backend
/api/login - Backend melakukan Password Grant ke Keycloak
- Keycloak return access_token + refresh_token
- Backend return token ke Frontend
- Frontend menyimpan token di
localStorage - Semua subsequent requests menggunakan
Authorization: Bearer <token>
Token Validation
// Backend JWT validation
func (s *Server) withAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := bearerToken(r.Header.Get("Authorization"))
if token == "" {
writeError(w, http.StatusUnauthorized, "missing bearer token")
return
}
claims, err := s.verifier.Validate(token)
if err != nil {
writeError(w, http.StatusUnauthorized, "unauthorized")
return
}
// Extract tenant_id from claims
tenantID := s.cfg.DefaultTenant
if tid, ok := claims["tenant_id"].(string); ok && tid != "" {
tenantID = tid
}
ctx := contextWithClaims(r.Context(), claims)
ctx = context.WithValue(ctx, "tenant_id", tenantID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
Authorization (RBAC)
Role-Based Access Control
Sistem menggunakan 7 predefined roles:
| Role | Description | Default Permissions |
|---|---|---|
executive | Executive management | dashboard:read, analytics:read |
it-ops | IT Operations | logs:read, dashboard:read, alerts:read |
network-infra | Network & Infrastructure | logs:read, dashboard:read, infra:read |
security | Security team | alerts:read, alerts:write, audit:read |
compliance | Compliance team | audit:read, audit:export, compliance:read |
helpdesk | Help desk support | logs:read, alerts:read |
admin | System administrator | Full access |
Permission Model
Permissions diorganisasi per resource dan action:
-- Permission structure
resource:action
-- Examples
logs:read -- Read logs
logs:write -- Write logs (if applicable)
alerts:read -- Read alerts
alerts:write -- Create/update alerts
audit:read -- Read audit logs
audit:export -- Export audit logs
dashboard:read -- Read dashboard data
analytics:read -- Read analytics
tenants:manage -- Manage tenants
users:manage -- Manage users
roles:manage -- Manage roles
Middleware Stack
// Permission check middleware
func (s *Server) requirePermission(appSlug, resource, action string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tenantID := tenantFromContext(r.Context())
username := usernameFromContext(r.Context())
allowed, err := s.db.CheckPermission(username, tenantID, appSlug, resource, action)
if err != nil {
writeError(w, http.StatusInternalServerError, "permission check failed")
return
}
if !allowed {
writeError(w, http.StatusForbidden, "permission denied")
return
}
next.ServeHTTP(w, r)
})
}
}
Route Permission Mapping
| Route | Permission | Description |
|---|---|---|
GET /api/v1/logs/* | logs:read | Log viewing |
GET /api/v1/alerts/* | alerts:read | Alert listing |
POST /api/v1/alerts/* | alerts:write | Alert management |
GET /api/v1/log-audit/* | audit:read | Audit log viewing |
GET /api/v1/log-audit/export/* | audit:export | Audit log export |
GET /api/v1/dashboard/* | dashboard:read | Dashboard data |
GET /api/v1/analytics/* | analytics:read | Analytics data |
POST /api/tenants | tenants:manage | Tenant CRUD |
POST /api/users | users:manage | User CRUD |
POST /api/roles | roles:manage | Role CRUD |
Network Security
Docker Network Isolation
networks:
monitoring:
name: monitoring
driver: bridge
- Semua services berada di internal network
monitoring - Hanya ports yang diperlukan yang di-expose ke host
- Inter-service communication menggunakan Docker DNS
TLS Configuration
# OpenSearch HTTPS
OPENSEARCH_SSL_VERIFICATIONMODE: none # Self-signed certs untuk PoC
CORS Configuration
func (s *Server) withCORS(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
if s.originAllowed(origin) {
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Vary", "Origin")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Tenant-ID")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
}
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
Audit Trail
Immutable Audit Logging
Semua operasi penting dicatat ke OpenSearch audit-log-* index:
{
"@timestamp": "2026-07-06T10:30:00Z",
"actor": "admin",
"action": "login",
"target": "system",
"result": "success",
"ip": "10.8.0.48",
"details": {
"method": "password"
},
"source_type": "audit",
"app_slug": "clm-backend",
"tenant_id": "bjb-syariah"
}
Audit Events
| Event | Description | Trigger |
|---|---|---|
login | User login | POST /api/login |
search | Log search | POST /api/v1/logs/list/paginate |
export | Data export | GET /api/v1/log-audit/export/* |
create | Resource creation | POST /api/* |
update | Resource update | PUT /api/* |
delete | Resource deletion | DELETE /api/* |
Multi-Tenant Security
Data Isolation
// Tenant filtering in OpenSearch queries
func (s *Server) handleLogPaginate(w http.ResponseWriter, r *http.Request) {
tenantSlug := tenantSlugFromContext(r.Context())
// Try to get index patterns from database aliases first
var indices []string
if s.db != nil && tenantSlug != "" {
aliases, err := s.db.GetTenantAliases(tenantSlug)
if err == nil && len(aliases) > 0 {
for _, alias := range aliases {
if strings.HasPrefix(alias.AliasName, "clm-") {
indices = append(indices, alias.IndexPattern)
}
}
}
}
// Always pass tenantID for filtering
tenantID := tenantSlug
result, err := s.search.SearchLogs(r.Context(), indices, req, "@timestamp", tenantID)
}
OpenSearch Alias Isolation
// Create tenant-specific alias with filter
filter := map[string]any{
"term": map[string]any{
"tenant_id.keyword": body.Slug,
},
}
_ = s.search.CreateAlias(r.Context(), "clm-"+body.Slug, "clm-v2-logs-*", filter)
API Key Authentication
Untuk service-to-service communication:
func (s *Server) withAPIKey(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
key := r.Header.Get("X-API-Key")
if key == "" || key != s.cfg.N8NAPIKey {
writeError(w, http.StatusUnauthorized, "invalid API key")
return
}
next.ServeHTTP(w, r)
})
}
Rate Limiting
func (s *Server) withRateLimit(next http.Handler) http.Handler {
rateLimit := 100 // requests per minute
window := time.Minute
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := r.RemoteAddr
if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
ip = strings.Split(forwarded, ",")[0]
}
// Check rate limit per IP
if v.count > rateLimit {
w.Header().Set("Retry-After", "60")
writeError(w, http.StatusTooManyRequests, "rate limit exceeded")
return
}
next.ServeHTTP(w, r)
})
}
Security Best Practices
Production Recommendations
- TLS Termination - Use reverse proxy (nginx/traefik) for TLS
- Secret Management - Use Vault or AWS Secrets Manager
- Network Policies - Implement Kubernetes NetworkPolicy
- Audit Log Retention - Set appropriate retention policies
- Regular Key Rotation - Rotate JWT signing keys periodically
- MFA - Enable Multi-Factor Authentication in Keycloak
- WAF - Deploy Web Application Firewall
Environment Variables Security
# Use .env files (not committed to git)
# Prefix sensitive values
KEYCLOAK_CLIENT_SECRET=<secret>
OPENSEARCH_PASSWORD=<secret>
DB_PASSWORD=<secret>
N8N_API_KEY=<secret>
Next Steps
- Keycloak Setup - Konfigurasi Keycloak
- RBAC - Role-Based Access Control
- Audit Trail - Audit logging
- ISO Compliance - Compliance features