Initial commit: breakpilot-compliance - Compliance SDK Platform

Services: Admin-Compliance, Backend-Compliance,
AI-Compliance-SDK, Consent-SDK, Developer-Portal,
PCA-Platform, DSMS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Boenisch
2026-02-11 23:47:28 +01:00
commit 4435e7ea0a
734 changed files with 251369 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
# Build stage
FROM golang:1.22-alpine AS builder
WORKDIR /app
# Install dependencies
RUN apk add --no-cache git
# Copy go mod files
COPY go.mod go.sum* ./
RUN go mod download
# Copy source code
COPY . .
# Build binary
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o api-gateway .
# Runtime stage
FROM alpine:3.19
WORKDIR /app
# Install ca-certificates for HTTPS
RUN apk --no-cache add ca-certificates
# Copy binary from builder
COPY --from=builder /app/api-gateway .
# Create non-root user
RUN adduser -D -g '' appuser
USER appuser
# Expose port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
# Run
CMD ["./api-gateway"]

View File

@@ -0,0 +1,15 @@
module github.com/breakpilot/compliance-sdk/services/api-gateway
go 1.22
require (
github.com/gin-gonic/gin v1.9.1
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/google/uuid v1.5.0
github.com/redis/go-redis/v9 v9.3.1
github.com/spf13/viper v1.18.2
go.uber.org/zap v1.26.0
golang.org/x/time v0.5.0
gorm.io/driver/postgres v1.5.4
gorm.io/gorm v1.25.5
)

View File

@@ -0,0 +1,166 @@
// Package api provides HTTP handlers for the API Gateway
package api
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// =============================================================================
// Controls
// =============================================================================
// GetControls retrieves controls for a tenant
func GetControls(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"controls": []interface{}{},
"total": 0,
})
}
// CreateControl creates a new control
func CreateControl(c *gin.Context) {
var req map[string]interface{}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{
"id": uuid.New().String(),
"created_at": time.Now().Format(time.RFC3339),
})
}
// UpdateControl updates a control
func UpdateControl(c *gin.Context) {
controlID := c.Param("controlId")
c.JSON(http.StatusOK, gin.H{
"id": controlID,
"updated_at": time.Now().Format(time.RFC3339),
})
}
// DeleteControl deletes a control
func DeleteControl(c *gin.Context) {
c.JSON(http.StatusNoContent, nil)
}
// =============================================================================
// Evidence
// =============================================================================
// GetEvidence retrieves evidence for a tenant
func GetEvidence(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"evidence": []interface{}{},
"total": 0,
})
}
// UploadEvidence uploads new evidence
func UploadEvidence(c *gin.Context) {
// Handle file upload
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "No file provided"})
return
}
c.JSON(http.StatusCreated, gin.H{
"id": uuid.New().String(),
"filename": file.Filename,
"size": file.Size,
"uploaded_at": time.Now().Format(time.RFC3339),
})
}
// UpdateEvidence updates evidence metadata
func UpdateEvidence(c *gin.Context) {
evidenceID := c.Param("evidenceId")
c.JSON(http.StatusOK, gin.H{
"id": evidenceID,
"updated_at": time.Now().Format(time.RFC3339),
})
}
// DeleteEvidence deletes evidence
func DeleteEvidence(c *gin.Context) {
c.JSON(http.StatusNoContent, nil)
}
// =============================================================================
// Obligations
// =============================================================================
// GetObligations retrieves regulatory obligations
func GetObligations(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"obligations": []interface{}{},
"total": 0,
})
}
// RunAssessment runs a compliance assessment
func RunAssessment(c *gin.Context) {
var req map[string]interface{}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// In production, this would call the compliance engine
c.JSON(http.StatusOK, gin.H{
"assessment_id": uuid.New().String(),
"score": 78,
"trend": "UP",
"by_regulation": gin.H{
"DSGVO": 85,
"NIS2": 72,
"AI_Act": 65,
},
"completed_at": time.Now().Format(time.RFC3339),
})
}
// =============================================================================
// Export
// =============================================================================
// ExportPDF exports a PDF report
func ExportPDF(c *gin.Context) {
reportType := c.Query("type")
if reportType == "" {
reportType = "summary"
}
// In production, generate actual PDF
c.Header("Content-Type", "application/pdf")
c.Header("Content-Disposition", "attachment; filename=compliance-report.pdf")
c.JSON(http.StatusOK, gin.H{
"message": "PDF generation would happen here",
"type": reportType,
})
}
// ExportDOCX exports a Word document
func ExportDOCX(c *gin.Context) {
reportType := c.Query("type")
if reportType == "" {
reportType = "summary"
}
c.Header("Content-Type", "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
c.Header("Content-Disposition", "attachment; filename=compliance-report.docx")
c.JSON(http.StatusOK, gin.H{
"message": "DOCX generation would happen here",
"type": reportType,
})
}

View File

@@ -0,0 +1,363 @@
// Package api provides HTTP handlers for the API Gateway
package api
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
// TokenRequest represents an OAuth token request
type TokenRequest struct {
GrantType string `json:"grant_type" binding:"required"`
ClientID string `json:"client_id" binding:"required"`
ClientSecret string `json:"client_secret" binding:"required"`
}
// TokenResponse represents an OAuth token response
type TokenResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token,omitempty"`
}
// GetToken handles OAuth token requests
func GetToken(c *gin.Context) {
var req TokenRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Validate credentials (in production, check against database)
// For now, accept any valid-looking credentials
if req.GrantType != "client_credentials" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Unsupported grant type"})
return
}
// Extract tenant ID from client ID (format: sdk_tenantid)
tenantID := req.ClientID
if len(tenantID) > 4 {
tenantID = tenantID[4:] // Remove "sdk_" prefix
}
// Generate JWT
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"tenant_id": tenantID,
"user_id": req.ClientID,
"scopes": []string{"read", "write"},
"exp": time.Now().Add(24 * time.Hour).Unix(),
"iat": time.Now().Unix(),
})
// Sign token (in production, use config.JWTSecret)
tokenString, _ := token.SignedString([]byte("your-secret-key-change-in-production"))
c.JSON(http.StatusOK, TokenResponse{
AccessToken: tokenString,
TokenType: "Bearer",
ExpiresIn: 86400,
})
}
// RefreshToken handles token refresh requests
func RefreshToken(c *gin.Context) {
// In production, validate refresh token and issue new access token
c.JSON(http.StatusOK, gin.H{"message": "Token refreshed"})
}
// =============================================================================
// State Management
// =============================================================================
// GetState retrieves the SDK state for a tenant
func GetState(c *gin.Context) {
tenantID := c.Param("tenantId")
// In production, fetch from database
c.JSON(http.StatusOK, gin.H{
"tenant_id": tenantID,
"state": gin.H{
"version": 1,
"completedSteps": []string{},
"currentStep": "overview",
},
"updated_at": time.Now().Format(time.RFC3339),
})
}
// SaveState saves the SDK state for a tenant
func SaveState(c *gin.Context) {
var req map[string]interface{}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// In production, save to database with optimistic locking
c.JSON(http.StatusOK, gin.H{
"success": true,
"version": 1,
"updated_at": time.Now().Format(time.RFC3339),
})
}
// ResetState resets the SDK state for a tenant
func ResetState(c *gin.Context) {
tenantID, _ := c.Get("tenant_id")
c.JSON(http.StatusOK, gin.H{
"success": true,
"tenant_id": tenantID,
"message": "State reset successfully",
})
}
// =============================================================================
// DSGVO / Consent
// =============================================================================
// RecordConsent records a consent decision
func RecordConsent(c *gin.Context) {
var req map[string]interface{}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{
"id": uuid.New().String(),
"created_at": time.Now().Format(time.RFC3339),
"status": "ACTIVE",
})
}
// GetConsents retrieves consents for a user
func GetConsents(c *gin.Context) {
userID := c.Param("userId")
c.JSON(http.StatusOK, gin.H{
"user_id": userID,
"consents": []interface{}{},
})
}
// RevokeConsent revokes a consent
func RevokeConsent(c *gin.Context) {
consentID := c.Param("consentId")
c.JSON(http.StatusOK, gin.H{
"id": consentID,
"status": "REVOKED",
"revoked_at": time.Now().Format(time.RFC3339),
})
}
// =============================================================================
// DSGVO / DSR
// =============================================================================
// SubmitDSR submits a new DSR request
func SubmitDSR(c *gin.Context) {
var req map[string]interface{}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{
"id": uuid.New().String(),
"status": "PENDING",
"submitted_at": time.Now().Format(time.RFC3339),
"deadline": time.Now().AddDate(0, 1, 0).Format(time.RFC3339),
})
}
// ListDSRRequests lists DSR requests for a tenant
func ListDSRRequests(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"requests": []interface{}{},
"total": 0,
})
}
// UpdateDSRStatus updates the status of a DSR request
func UpdateDSRStatus(c *gin.Context) {
requestID := c.Param("requestId")
var req map[string]interface{}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"id": requestID,
"status": req["status"],
"updated_at": time.Now().Format(time.RFC3339),
})
}
// =============================================================================
// DSGVO / VVT
// =============================================================================
// GetProcessingActivities retrieves processing activities
func GetProcessingActivities(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"activities": []interface{}{},
"total": 0,
})
}
// CreateProcessingActivity creates a new processing activity
func CreateProcessingActivity(c *gin.Context) {
var req map[string]interface{}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{
"id": uuid.New().String(),
"created_at": time.Now().Format(time.RFC3339),
})
}
// UpdateProcessingActivity updates a processing activity
func UpdateProcessingActivity(c *gin.Context) {
activityID := c.Param("activityId")
c.JSON(http.StatusOK, gin.H{
"id": activityID,
"updated_at": time.Now().Format(time.RFC3339),
})
}
// DeleteProcessingActivity deletes a processing activity
func DeleteProcessingActivity(c *gin.Context) {
c.JSON(http.StatusNoContent, nil)
}
// =============================================================================
// DSGVO / TOM
// =============================================================================
// GetTOMs retrieves TOMs
func GetTOMs(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"toms": []interface{}{},
"total": 0,
})
}
// CreateTOM creates a new TOM
func CreateTOM(c *gin.Context) {
var req map[string]interface{}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{
"id": uuid.New().String(),
"created_at": time.Now().Format(time.RFC3339),
})
}
// UpdateTOM updates a TOM
func UpdateTOM(c *gin.Context) {
tomID := c.Param("tomId")
c.JSON(http.StatusOK, gin.H{
"id": tomID,
"updated_at": time.Now().Format(time.RFC3339),
})
}
// DeleteTOM deletes a TOM
func DeleteTOM(c *gin.Context) {
c.JSON(http.StatusNoContent, nil)
}
// =============================================================================
// DSGVO / DSFA
// =============================================================================
// GetDSFAs retrieves DSFAs
func GetDSFAs(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"dsfas": []interface{}{},
"total": 0,
})
}
// CreateDSFA creates a new DSFA
func CreateDSFA(c *gin.Context) {
var req map[string]interface{}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{
"id": uuid.New().String(),
"created_at": time.Now().Format(time.RFC3339),
})
}
// UpdateDSFA updates a DSFA
func UpdateDSFA(c *gin.Context) {
dsfaID := c.Param("dsfaId")
c.JSON(http.StatusOK, gin.H{
"id": dsfaID,
"updated_at": time.Now().Format(time.RFC3339),
})
}
// =============================================================================
// DSGVO / Retention
// =============================================================================
// GetRetentionPolicies retrieves retention policies
func GetRetentionPolicies(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"policies": []interface{}{},
"total": 0,
})
}
// CreateRetentionPolicy creates a new retention policy
func CreateRetentionPolicy(c *gin.Context) {
var req map[string]interface{}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{
"id": uuid.New().String(),
"created_at": time.Now().Format(time.RFC3339),
})
}
// UpdateRetentionPolicy updates a retention policy
func UpdateRetentionPolicy(c *gin.Context) {
policyID := c.Param("policyId")
c.JSON(http.StatusOK, gin.H{
"id": policyID,
"updated_at": time.Now().Format(time.RFC3339),
})
}
// DeleteRetentionPolicy deletes a retention policy
func DeleteRetentionPolicy(c *gin.Context) {
c.JSON(http.StatusNoContent, nil)
}

View File

@@ -0,0 +1,115 @@
// Package api provides HTTP handlers for the API Gateway
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// =============================================================================
// RAG / Search
// =============================================================================
// SearchRequest represents a RAG search request
type SearchRequest struct {
Query string `json:"query" binding:"required"`
RegulationCodes []string `json:"regulation_codes,omitempty"`
Limit int `json:"limit,omitempty"`
}
// SearchRAG performs a semantic search
func SearchRAG(c *gin.Context) {
var req SearchRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// In production, forward to RAG service
c.JSON(http.StatusOK, gin.H{
"query": req.Query,
"results": []gin.H{
{
"content": "Art. 9 Abs. 1 DSGVO verbietet grundsätzlich die Verarbeitung besonderer Kategorien personenbezogener Daten...",
"regulationCode": "DSGVO",
"article": "9",
"score": 0.95,
},
},
"total": 1,
})
}
// AskRequest represents a RAG question request
type AskRequest struct {
Question string `json:"question" binding:"required"`
Context string `json:"context,omitempty"`
}
// AskRAG asks a question to the legal assistant
func AskRAG(c *gin.Context) {
var req AskRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// In production, forward to RAG service
c.JSON(http.StatusOK, gin.H{
"question": req.Question,
"answer": "Art. 9 DSGVO regelt die Verarbeitung besonderer Kategorien personenbezogener Daten...",
"citations": []gin.H{
{
"regulationCode": "DSGVO",
"article": "9",
"relevance": 0.95,
},
},
"confidence": 0.92,
})
}
// GetRegulations returns available regulations
func GetRegulations(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"regulations": []gin.H{
{
"code": "DSGVO",
"name": "Datenschutz-Grundverordnung",
"chunks": 99,
"lastUpdated": "2024-01-01",
},
{
"code": "AI_ACT",
"name": "EU AI Act",
"chunks": 85,
"lastUpdated": "2024-01-01",
},
{
"code": "NIS2",
"name": "NIS 2 Directive",
"chunks": 46,
"lastUpdated": "2024-01-01",
},
},
})
}
// UploadDocument uploads a custom document for RAG
func UploadDocument(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "No file provided"})
return
}
c.JSON(http.StatusCreated, gin.H{
"id": uuid.New().String(),
"filename": file.Filename,
"size": file.Size,
"status": "PROCESSING",
"message": "Document uploaded and queued for processing",
})
}

View File

@@ -0,0 +1,206 @@
// Package api provides HTTP handlers for the API Gateway
package api
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// =============================================================================
// SBOM
// =============================================================================
// GenerateSBOM generates a Software Bill of Materials
func GenerateSBOM(c *gin.Context) {
var req map[string]interface{}
c.ShouldBindJSON(&req)
// In production, forward to security scanner service
c.JSON(http.StatusOK, gin.H{
"id": uuid.New().String(),
"format": "cyclonedx",
"version": "1.5",
"generated_at": time.Now().Format(time.RFC3339),
"components": 144,
"licenses": gin.H{
"MIT": 89,
"Apache-2.0": 42,
"BSD-3": 8,
"Other": 5,
},
})
}
// GetSBOMComponents returns SBOM components
func GetSBOMComponents(c *gin.Context) {
category := c.Query("category")
c.JSON(http.StatusOK, gin.H{
"category": category,
"components": []gin.H{
{
"name": "react",
"version": "18.2.0",
"category": "frontend",
"license": "MIT",
"vulnerabilities": 0,
},
{
"name": "express",
"version": "4.18.2",
"category": "backend",
"license": "MIT",
"vulnerabilities": 0,
},
},
"total": 144,
})
}
// ExportSBOM exports SBOM in requested format
func ExportSBOM(c *gin.Context) {
format := c.Param("format")
var contentType string
switch format {
case "cyclonedx":
contentType = "application/json"
case "spdx":
contentType = "application/spdx+json"
default:
c.JSON(http.StatusBadRequest, gin.H{"error": "Unsupported format"})
return
}
c.Header("Content-Type", contentType)
c.Header("Content-Disposition", "attachment; filename=sbom."+format+".json")
c.JSON(http.StatusOK, gin.H{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"serialNumber": uuid.New().String(),
"version": 1,
"metadata": gin.H{
"timestamp": time.Now().Format(time.RFC3339),
"tools": []gin.H{
{
"vendor": "BreakPilot",
"name": "compliance-sdk",
"version": "0.0.1",
},
},
},
"components": []gin.H{},
})
}
// =============================================================================
// Security Scanning
// =============================================================================
// ScanRequest represents a security scan request
type ScanRequest struct {
Tools []string `json:"tools,omitempty"`
TargetPath string `json:"target_path,omitempty"`
ExcludePaths []string `json:"exclude_paths,omitempty"`
}
// StartSecurityScan starts a security scan
func StartSecurityScan(c *gin.Context) {
var req ScanRequest
c.ShouldBindJSON(&req)
tools := req.Tools
if len(tools) == 0 {
tools = []string{"gitleaks", "semgrep", "trivy", "grype", "syft"}
}
// In production, forward to security scanner service
c.JSON(http.StatusAccepted, gin.H{
"scan_id": uuid.New().String(),
"status": "RUNNING",
"tools": tools,
"started_at": time.Now().Format(time.RFC3339),
"message": "Scan started. Check /findings for results.",
})
}
// GetSecurityFindings returns security findings
func GetSecurityFindings(c *gin.Context) {
severity := c.Query("severity")
tool := c.Query("tool")
c.JSON(http.StatusOK, gin.H{
"filters": gin.H{
"severity": severity,
"tool": tool,
},
"findings": []gin.H{
{
"id": uuid.New().String(),
"tool": "trivy",
"severity": "HIGH",
"title": "CVE-2024-1234",
"description": "Vulnerability in dependency",
"file": "package-lock.json",
"recommendation": "Update to version 2.0.0",
},
},
"summary": gin.H{
"critical": 0,
"high": 1,
"medium": 3,
"low": 5,
"total": 9,
},
})
}
// GetRecommendations returns fix recommendations
func GetRecommendations(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"recommendations": []gin.H{
{
"priority": "HIGH",
"category": "DEPENDENCIES",
"title": "Update vulnerable packages",
"description": "Several npm packages have known vulnerabilities. " +
"Run 'npm audit fix' to automatically update compatible versions.",
"affected": []string{"lodash@4.17.20", "axios@0.21.0"},
},
{
"priority": "MEDIUM",
"category": "SECRETS",
"title": "Review detected secrets",
"description": "Gitleaks detected potential secrets in the codebase. " +
"Review and rotate if they are real credentials.",
"affected": []string{".env.example:3"},
},
},
})
}
// GetSecurityReports returns security reports
func GetSecurityReports(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"reports": []gin.H{
{
"id": uuid.New().String(),
"name": "Weekly Security Scan",
"generated_at": time.Now().AddDate(0, 0, -7).Format(time.RFC3339),
"findings": 12,
"status": "COMPLETED",
},
{
"id": uuid.New().String(),
"name": "Monthly Compliance Audit",
"generated_at": time.Now().AddDate(0, -1, 0).Format(time.RFC3339),
"findings": 5,
"status": "COMPLETED",
},
},
})
}

View File

@@ -0,0 +1,93 @@
// Package config handles configuration loading for the API Gateway
package config
import (
"os"
)
// Config holds the application configuration
type Config struct {
Environment string
Port string
Version string
// Database
DatabaseURL string
// Redis
RedisURL string
// JWT
JWTSecret string
JWTExpiration int // hours
// Rate limiting
RateLimit RateLimitConfig
// Services
ComplianceEngineURL string
RAGServiceURL string
SecurityScannerURL string
// Storage
MinIOEndpoint string
MinIOAccessKey string
MinIOSecretKey string
MinIOBucket string
}
// RateLimitConfig holds rate limiting configuration
type RateLimitConfig struct {
RequestsPerSecond int
Burst int
}
// Load loads configuration from environment variables
func Load() (*Config, error) {
cfg := &Config{
Environment: getEnv("ENVIRONMENT", "development"),
Port: getEnv("PORT", "8080"),
Version: getEnv("VERSION", "0.0.1"),
DatabaseURL: getEnv("DATABASE_URL", "postgres://breakpilot:breakpilot@localhost:5432/compliance"),
RedisURL: getEnv("REDIS_URL", "redis://localhost:6379"),
JWTSecret: getEnv("JWT_SECRET", "your-secret-key-change-in-production"),
JWTExpiration: getEnvInt("JWT_EXPIRATION", 24),
RateLimit: RateLimitConfig{
RequestsPerSecond: getEnvInt("RATE_LIMIT_RPS", 100),
Burst: getEnvInt("RATE_LIMIT_BURST", 200),
},
ComplianceEngineURL: getEnv("COMPLIANCE_ENGINE_URL", "http://compliance-engine:8081"),
RAGServiceURL: getEnv("RAG_SERVICE_URL", "http://rag-service:8082"),
SecurityScannerURL: getEnv("SECURITY_SCANNER_URL", "http://security-scanner:8083"),
MinIOEndpoint: getEnv("MINIO_ENDPOINT", "minio:9000"),
MinIOAccessKey: getEnv("MINIO_ACCESS_KEY", "breakpilot"),
MinIOSecretKey: getEnv("MINIO_SECRET_KEY", "breakpilot123"),
MinIOBucket: getEnv("MINIO_BUCKET", "compliance"),
}
return cfg, nil
}
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
func getEnvInt(key string, defaultValue int) int {
if value := os.Getenv(key); value != "" {
// Simple conversion, production code should handle errors
var result int
for _, c := range value {
result = result*10 + int(c-'0')
}
return result
}
return defaultValue
}

View File

@@ -0,0 +1,117 @@
// Package middleware provides HTTP middleware for the API Gateway
package middleware
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
// Claims represents JWT claims
type Claims struct {
TenantID string `json:"tenant_id"`
UserID string `json:"user_id"`
Scopes []string `json:"scopes"`
jwt.RegisteredClaims
}
// Auth middleware validates JWT tokens or API keys
func Auth(jwtSecret string) gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "Missing authorization header",
})
return
}
// Check for Bearer token (JWT)
if strings.HasPrefix(authHeader, "Bearer ") {
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
// Check if it's an API key (starts with pk_ or sk_)
if strings.HasPrefix(tokenString, "pk_") || strings.HasPrefix(tokenString, "sk_") {
// API Key authentication
if err := validateAPIKey(c, tokenString); err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "Invalid API key",
})
return
}
} else {
// JWT authentication
claims, err := validateJWT(tokenString, jwtSecret)
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "Invalid token",
})
return
}
// Store claims in context
c.Set("tenant_id", claims.TenantID)
c.Set("user_id", claims.UserID)
c.Set("scopes", claims.Scopes)
}
} else {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "Invalid authorization format",
})
return
}
c.Next()
}
}
func validateJWT(tokenString, secret string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, jwt.ErrTokenInvalidClaims
}
func validateAPIKey(c *gin.Context, apiKey string) error {
// In production, this would validate against a database of API keys
// For now, we extract tenant info from the X-Tenant-ID header
tenantID := c.GetHeader("X-Tenant-ID")
if tenantID == "" {
tenantID = "default"
}
c.Set("tenant_id", tenantID)
c.Set("user_id", "api-key-user")
c.Set("scopes", []string{"read", "write"})
return nil
}
// TenantIsolation ensures requests only access their own tenant's data
func TenantIsolation() gin.HandlerFunc {
return func(c *gin.Context) {
tenantID, exists := c.Get("tenant_id")
if !exists || tenantID == "" {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
"error": "Tenant ID required",
})
return
}
// Add tenant ID to all database queries (handled by handlers)
c.Set("tenant_filter", tenantID)
c.Next()
}
}

View File

@@ -0,0 +1,119 @@
// Package middleware provides HTTP middleware for the API Gateway
package middleware
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.uber.org/zap"
"golang.org/x/time/rate"
)
// Logger middleware logs requests
func Logger(logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
// Process request
c.Next()
// Log after request
latency := time.Since(start)
clientIP := c.ClientIP()
method := c.Request.Method
statusCode := c.Writer.Status()
if raw != "" {
path = path + "?" + raw
}
logger.Info("request",
zap.String("method", method),
zap.String("path", path),
zap.Int("status", statusCode),
zap.String("ip", clientIP),
zap.Duration("latency", latency),
zap.String("request_id", c.GetString("request_id")),
)
}
}
// CORS middleware handles Cross-Origin Resource Sharing
func CORS() gin.HandlerFunc {
return func(c *gin.Context) {
origin := c.GetHeader("Origin")
if origin == "" {
origin = "*"
}
c.Header("Access-Control-Allow-Origin", origin)
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization, X-Tenant-ID, X-Request-ID")
c.Header("Access-Control-Allow-Credentials", "true")
c.Header("Access-Control-Max-Age", "86400")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}
// RequestID middleware adds a unique request ID to each request
func RequestID() gin.HandlerFunc {
return func(c *gin.Context) {
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
}
c.Set("request_id", requestID)
c.Header("X-Request-ID", requestID)
c.Next()
}
}
// RateLimitConfig holds rate limiting configuration
type RateLimitConfig struct {
RequestsPerSecond int
Burst int
}
// RateLimiter middleware limits request rate per client
func RateLimiter(config RateLimitConfig) gin.HandlerFunc {
// In production, use a distributed rate limiter with Redis
// This is a simple in-memory rate limiter per IP
limiters := make(map[string]*rate.Limiter)
return func(c *gin.Context) {
clientIP := c.ClientIP()
limiter, exists := limiters[clientIP]
if !exists {
limiter = rate.NewLimiter(rate.Limit(config.RequestsPerSecond), config.Burst)
limiters[clientIP] = limiter
}
if !limiter.Allow() {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
"error": "Rate limit exceeded",
"retry_after": "1s",
})
return
}
c.Next()
}
}
// Recovery middleware recovers from panics
func Recovery() gin.HandlerFunc {
return gin.Recovery()
}

View File

@@ -0,0 +1,188 @@
// BreakPilot Compliance SDK - API Gateway
//
// Main entry point for the API Gateway service.
// Handles authentication, rate limiting, and request routing.
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/breakpilot/compliance-sdk/services/api-gateway/internal/api"
"github.com/breakpilot/compliance-sdk/services/api-gateway/internal/config"
"github.com/breakpilot/compliance-sdk/services/api-gateway/internal/middleware"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
func main() {
// Initialize logger
logger, _ := zap.NewProduction()
defer logger.Sync()
// Load configuration
cfg, err := config.Load()
if err != nil {
logger.Fatal("Failed to load configuration", zap.Error(err))
}
// Set Gin mode
if cfg.Environment == "production" {
gin.SetMode(gin.ReleaseMode)
}
// Initialize router
router := gin.New()
// Global middleware
router.Use(gin.Recovery())
router.Use(middleware.Logger(logger))
router.Use(middleware.CORS())
router.Use(middleware.RequestID())
// Health check (no auth required)
router.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "healthy",
"service": "api-gateway",
"version": cfg.Version,
})
})
// API v1 routes
v1 := router.Group("/api/v1")
{
// Public routes (rate limited)
v1.Use(middleware.RateLimiter(cfg.RateLimit))
// Auth routes
auth := v1.Group("/auth")
{
auth.POST("/token", api.GetToken)
auth.POST("/refresh", api.RefreshToken)
}
// Protected routes
protected := v1.Group("")
protected.Use(middleware.Auth(cfg.JWTSecret))
protected.Use(middleware.TenantIsolation())
{
// State management
protected.GET("/state/:tenantId", api.GetState)
protected.POST("/state/save", api.SaveState)
protected.POST("/state/reset", api.ResetState)
// DSGVO
dsgvo := protected.Group("/dsgvo")
{
dsgvo.POST("/consent", api.RecordConsent)
dsgvo.GET("/consent/:userId", api.GetConsents)
dsgvo.DELETE("/consent/:consentId", api.RevokeConsent)
dsgvo.POST("/dsr", api.SubmitDSR)
dsgvo.GET("/dsr", api.ListDSRRequests)
dsgvo.PUT("/dsr/:requestId", api.UpdateDSRStatus)
dsgvo.GET("/vvt", api.GetProcessingActivities)
dsgvo.POST("/vvt", api.CreateProcessingActivity)
dsgvo.PUT("/vvt/:activityId", api.UpdateProcessingActivity)
dsgvo.DELETE("/vvt/:activityId", api.DeleteProcessingActivity)
dsgvo.GET("/tom", api.GetTOMs)
dsgvo.POST("/tom", api.CreateTOM)
dsgvo.PUT("/tom/:tomId", api.UpdateTOM)
dsgvo.DELETE("/tom/:tomId", api.DeleteTOM)
dsgvo.GET("/dsfa", api.GetDSFAs)
dsgvo.POST("/dsfa", api.CreateDSFA)
dsgvo.PUT("/dsfa/:dsfaId", api.UpdateDSFA)
dsgvo.GET("/retention", api.GetRetentionPolicies)
dsgvo.POST("/retention", api.CreateRetentionPolicy)
dsgvo.PUT("/retention/:policyId", api.UpdateRetentionPolicy)
dsgvo.DELETE("/retention/:policyId", api.DeleteRetentionPolicy)
}
// Compliance
compliance := protected.Group("/compliance")
{
compliance.GET("/controls", api.GetControls)
compliance.POST("/controls", api.CreateControl)
compliance.PUT("/controls/:controlId", api.UpdateControl)
compliance.DELETE("/controls/:controlId", api.DeleteControl)
compliance.GET("/evidence", api.GetEvidence)
compliance.POST("/evidence", api.UploadEvidence)
compliance.PUT("/evidence/:evidenceId", api.UpdateEvidence)
compliance.DELETE("/evidence/:evidenceId", api.DeleteEvidence)
compliance.GET("/obligations", api.GetObligations)
compliance.POST("/assessment", api.RunAssessment)
compliance.GET("/export/pdf", api.ExportPDF)
compliance.GET("/export/docx", api.ExportDOCX)
}
// RAG
rag := protected.Group("/rag")
{
rag.POST("/search", api.SearchRAG)
rag.POST("/ask", api.AskRAG)
rag.GET("/regulations", api.GetRegulations)
rag.POST("/documents", api.UploadDocument)
}
// SBOM & Security
security := protected.Group("/security")
{
security.POST("/sbom/generate", api.GenerateSBOM)
security.GET("/sbom/components", api.GetSBOMComponents)
security.GET("/sbom/export/:format", api.ExportSBOM)
security.POST("/scan", api.StartSecurityScan)
security.GET("/findings", api.GetSecurityFindings)
security.GET("/recommendations", api.GetRecommendations)
security.GET("/reports", api.GetSecurityReports)
}
}
}
// Create HTTP server
srv := &http.Server{
Addr: ":" + cfg.Port,
Handler: router,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
// Start server in goroutine
go func() {
logger.Info("Starting API Gateway", zap.String("port", cfg.Port))
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Fatal("Failed to start server", zap.Error(err))
}
}()
// Graceful shutdown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
logger.Info("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
logger.Fatal("Server forced to shutdown", zap.Error(err))
}
logger.Info("Server exited")
}