Files
Charon/backend/internal/api/handlers/encryption_handler.go
akanealw eec8c28fb3
Some checks failed
Go Benchmark / Performance Regression Check (push) Has been cancelled
Cerberus Integration / Cerberus Security Stack Integration (push) Has been cancelled
Upload Coverage to Codecov / Backend Codecov Upload (push) Has been cancelled
Upload Coverage to Codecov / Frontend Codecov Upload (push) Has been cancelled
CodeQL - Analyze / CodeQL analysis (go) (push) Has been cancelled
CodeQL - Analyze / CodeQL analysis (javascript-typescript) (push) Has been cancelled
CrowdSec Integration / CrowdSec Bouncer Integration (push) Has been cancelled
Docker Build, Publish & Test / build-and-push (push) Has been cancelled
Quality Checks / Auth Route Protection Contract (push) Has been cancelled
Quality Checks / Codecov Trigger/Comment Parity Guard (push) Has been cancelled
Quality Checks / Backend (Go) (push) Has been cancelled
Quality Checks / Frontend (React) (push) Has been cancelled
Rate Limit integration / Rate Limiting Integration (push) Has been cancelled
Security Scan (PR) / Trivy Binary Scan (push) Has been cancelled
Supply Chain Verification (PR) / Verify Supply Chain (push) Has been cancelled
WAF integration / Coraza WAF Integration (push) Has been cancelled
Docker Build, Publish & Test / Security Scan PR Image (push) Has been cancelled
Repo Health Check / Repo health (push) Has been cancelled
History Rewrite Dry-Run / Dry-run preview for history rewrite (push) Has been cancelled
Prune Renovate Branches / prune (push) Has been cancelled
Renovate / renovate (push) Has been cancelled
Nightly Build & Package / sync-development-to-nightly (push) Has been cancelled
Nightly Build & Package / Trigger Nightly Validation Workflows (push) Has been cancelled
Nightly Build & Package / build-and-push-nightly (push) Has been cancelled
Nightly Build & Package / test-nightly-image (push) Has been cancelled
Nightly Build & Package / verify-nightly-supply-chain (push) Has been cancelled
Update GeoLite2 Checksum / update-checksum (push) Has been cancelled
Container Registry Prune / prune-ghcr (push) Has been cancelled
Container Registry Prune / prune-dockerhub (push) Has been cancelled
Container Registry Prune / summarize (push) Has been cancelled
Supply Chain Verification / Verify SBOM (push) Has been cancelled
Supply Chain Verification / Verify Release Artifacts (push) Has been cancelled
Supply Chain Verification / Verify Docker Image Supply Chain (push) Has been cancelled
Monitor Caddy Major Release / check-caddy-major (push) Has been cancelled
Weekly Nightly to Main Promotion / Verify Nightly Branch Health (push) Has been cancelled
Weekly Nightly to Main Promotion / Create Promotion PR (push) Has been cancelled
Weekly Nightly to Main Promotion / Trigger Missing Required Checks (push) Has been cancelled
Weekly Nightly to Main Promotion / Notify on Failure (push) Has been cancelled
Weekly Nightly to Main Promotion / Workflow Summary (push) Has been cancelled
changed perms
2026-04-22 18:19:14 +00:00

211 lines
5.9 KiB
Go
Executable File

// Package handlers provides HTTP request handlers for the API.
package handlers
import (
"encoding/json"
"net/http"
"strconv"
"github.com/Wikid82/charon/backend/internal/crypto"
"github.com/Wikid82/charon/backend/internal/logger"
"github.com/Wikid82/charon/backend/internal/models"
"github.com/Wikid82/charon/backend/internal/services"
"github.com/gin-gonic/gin"
)
// EncryptionHandler manages encryption key operations and rotation.
type EncryptionHandler struct {
rotationService *crypto.RotationService
securityService *services.SecurityService
}
// NewEncryptionHandler creates a new encryption handler.
func NewEncryptionHandler(rotationService *crypto.RotationService, securityService *services.SecurityService) *EncryptionHandler {
return &EncryptionHandler{
rotationService: rotationService,
securityService: securityService,
}
}
// GetStatus returns the current encryption key rotation status.
// GET /api/v1/admin/encryption/status
func (h *EncryptionHandler) GetStatus(c *gin.Context) {
// Admin-only check (via middleware or direct check)
if !isAdmin(c) {
c.JSON(http.StatusForbidden, gin.H{"error": "admin access required"})
return
}
status, err := h.rotationService.GetStatus()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, status)
}
// Rotate triggers re-encryption of all credentials with the next key.
// POST /api/v1/admin/encryption/rotate
func (h *EncryptionHandler) Rotate(c *gin.Context) {
// Admin-only check
if !isAdmin(c) {
c.JSON(http.StatusForbidden, gin.H{"error": "admin access required"})
return
}
// Log rotation start
if err := h.securityService.LogAudit(&models.SecurityAudit{
Actor: getActorFromGinContext(c),
Action: "encryption_key_rotation_started",
EventCategory: "encryption",
Details: "{}",
IPAddress: c.ClientIP(),
UserAgent: c.Request.UserAgent(),
}); err != nil {
logger.Log().WithError(err).Warn("Failed to log audit event")
}
// Perform rotation
result, err := h.rotationService.RotateAllCredentials(c.Request.Context())
if err != nil {
// Log failure
detailsJSON, _ := json.Marshal(map[string]interface{}{
"error": err.Error(),
})
_ = h.securityService.LogAudit(&models.SecurityAudit{
Actor: getActorFromGinContext(c),
Action: "encryption_key_rotation_failed",
EventCategory: "encryption",
Details: string(detailsJSON),
IPAddress: c.ClientIP(),
UserAgent: c.Request.UserAgent(),
})
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Log rotation completion
detailsJSON, _ := json.Marshal(map[string]interface{}{
"total_providers": result.TotalProviders,
"success_count": result.SuccessCount,
"failure_count": result.FailureCount,
"failed_providers": result.FailedProviders,
"duration": result.Duration,
"new_key_version": result.NewKeyVersion,
})
_ = h.securityService.LogAudit(&models.SecurityAudit{
Actor: getActorFromGinContext(c),
Action: "encryption_key_rotation_completed",
EventCategory: "encryption",
Details: string(detailsJSON),
IPAddress: c.ClientIP(),
UserAgent: c.Request.UserAgent(),
})
c.JSON(http.StatusOK, result)
}
// GetHistory returns audit logs related to encryption key operations.
// GET /api/v1/admin/encryption/history
func (h *EncryptionHandler) GetHistory(c *gin.Context) {
// Admin-only check
if !isAdmin(c) {
c.JSON(http.StatusForbidden, gin.H{"error": "admin access required"})
return
}
// Parse pagination parameters
page := 1
limit := 50
if pageParam := c.Query("page"); pageParam != "" {
if p, err := strconv.Atoi(pageParam); err == nil && p > 0 {
page = p
}
}
if limitParam := c.Query("limit"); limitParam != "" {
if l, err := strconv.Atoi(limitParam); err == nil && l > 0 && l <= 100 {
limit = l
}
}
// Query audit logs for encryption category
filter := services.AuditLogFilter{
EventCategory: "encryption",
}
audits, total, err := h.securityService.ListAuditLogs(filter, page, limit)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"audits": audits,
"total": total,
"page": page,
"limit": limit,
})
}
// Validate checks the current encryption key configuration.
// POST /api/v1/admin/encryption/validate
func (h *EncryptionHandler) Validate(c *gin.Context) {
// Admin-only check
if !isAdmin(c) {
c.JSON(http.StatusForbidden, gin.H{"error": "admin access required"})
return
}
if err := h.rotationService.ValidateKeyConfiguration(); err != nil {
// Log validation failure
detailsJSON, _ := json.Marshal(map[string]interface{}{
"error": err.Error(),
})
_ = h.securityService.LogAudit(&models.SecurityAudit{
Actor: getActorFromGinContext(c),
Action: "encryption_key_validation_failed",
EventCategory: "encryption",
Details: string(detailsJSON),
IPAddress: c.ClientIP(),
UserAgent: c.Request.UserAgent(),
})
c.JSON(http.StatusBadRequest, gin.H{
"valid": false,
"error": err.Error(),
})
return
}
// Log validation success
_ = h.securityService.LogAudit(&models.SecurityAudit{
Actor: getActorFromGinContext(c),
Action: "encryption_key_validation_success",
EventCategory: "encryption",
Details: "{}",
IPAddress: c.ClientIP(),
UserAgent: c.Request.UserAgent(),
})
c.JSON(http.StatusOK, gin.H{
"valid": true,
"message": "All encryption keys are valid",
})
}
// getActorFromGinContext extracts the user ID from Gin context for audit logging.
func getActorFromGinContext(c *gin.Context) string {
// Auth middleware sets "userID" (not "user_id")
if userID, exists := c.Get("userID"); exists {
if id, ok := userID.(uint); ok {
return strconv.FormatUint(uint64(id), 10)
}
if id, ok := userID.(string); ok {
return id
}
}
return "system"
}