Files
MOHPortalTest-AllAgents-All…/qwen/go/security/security.go
2025-10-24 16:29:40 -05:00

429 lines
12 KiB
Go

package security
import (
"crypto/subtle"
"fmt"
"log"
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
// SecurityConfig holds security-related configuration
type SecurityConfig struct {
JWTSecret string
AllowedOrigins []string
EnableRateLimiting bool
MaxRequestsPerMinute int
EnableCSP bool
CSPReportURI string
EnableHSTS bool
EnableXSSProtection bool
EnableContentTypeNosniff bool
EnableHSTSMaxAge int64
EnableFrameOptions bool
FrameOptionValue string
APIKey string
}
// DefaultSecurityConfig returns a default security configuration
func DefaultSecurityConfig() *SecurityConfig {
return &SecurityConfig{
AllowedOrigins: []string{"*"},
EnableRateLimiting: true,
MaxRequestsPerMinute: 100,
EnableCSP: true,
CSPReportURI: "/csp-report",
EnableHSTS: true,
EnableXSSProtection: true,
EnableContentTypeNosniff: true,
EnableHSTSMaxAge: 31536000, // 1 year
EnableFrameOptions: true,
FrameOptionValue: "DENY",
}
}
// SecurityMiddleware applies various security measures
func SecurityMiddleware(config *SecurityConfig) gin.HandlerFunc {
return func(c *gin.Context) {
// Apply security headers
applySecurityHeaders(c, config)
// Rate limiting (simplified implementation)
if config.EnableRateLimiting {
if !checkRateLimit(c, config.MaxRequestsPerMinute) {
c.JSON(http.StatusTooManyRequests, gin.H{"error": "Rate limit exceeded"})
c.Abort()
return
}
}
// Check for API key if required
if config.APIKey != "" {
if !validateAPIKey(c, config.APIKey) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid API key"})
c.Abort()
return
}
}
c.Next()
}
}
// applySecurityHeaders adds security-related headers to responses
func applySecurityHeaders(c *gin.Context, config *SecurityConfig) {
// Content Security Policy
if config.EnableCSP {
csp := fmt.Sprintf("default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://*.keycloak.org; frame-ancestors 'none'; report-uri %s", config.CSPReportURI)
c.Header("Content-Security-Policy", csp)
}
// HTTP Strict Transport Security
if config.EnableHSTS {
c.Header("Strict-Transport-Security", fmt.Sprintf("max-age=%d; includeSubDomains; preload", config.EnableHSTSMaxAge))
}
// X-XSS-Protection
if config.EnableXSSProtection {
c.Header("X-XSS-Protection", "1; mode=block")
}
// X-Content-Type-Options
if config.EnableContentTypeNosniff {
c.Header("X-Content-Type-Options", "nosniff")
}
// X-Frame-Options
if config.EnableFrameOptions {
c.Header("X-Frame-Options", config.FrameOptionValue)
}
// Referrer Policy
c.Header("Referrer-Policy", "strict-origin-when-cross-origin")
// Permissions Policy
c.Header("Permissions-Policy", "geolocation=(), microphone=(), camera=()")
// Cross-Origin Resource Sharing (CORS)
if len(config.AllowedOrigins) > 0 {
c.Header("Access-Control-Allow-Origin", strings.Join(config.AllowedOrigins, ", "))
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH")
c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization, X-Requested-With")
c.Header("Access-Control-Allow-Credentials", "true")
c.Header("Access-Control-Expose-Headers", "Content-Length, Content-Type, X-Total-Count")
}
}
// checkRateLimit implements a simple rate limiting mechanism
func checkRateLimit(c *gin.Context, maxRequests int) bool {
// In a real implementation, this would use Redis or similar to track requests per IP/user
// For now, we'll implement a simplified version
// Get client IP (this would be used in a real implementation)
clientIP := c.ClientIP()
_ = clientIP // Use the variable to avoid "declared but not used" error
// For demo purposes, always return true (no actual rate limiting)
// In a production environment, you would check against a request counter
return true
}
// validateAPIKey validates the API key in the request
func validateAPIKey(c *gin.Context, expectedAPIKey string) bool {
// Check API key in header
apiKey := c.GetHeader("X-API-Key")
if apiKey == "" {
// Check API key in query parameter as fallback
apiKey = c.Query("api_key")
}
if apiKey == "" {
return false
}
// Use constant-time comparison to prevent timing attacks
return subtle.ConstantTimeCompare([]byte(apiKey), []byte(expectedAPIKey)) == 1
}
// CSPReportHandler handles content security policy violation reports
func CSPReportHandler(c *gin.Context) {
// Log the CSP violation for monitoring
log.Printf("CSP Violation: %s", c.Request.URL.Path)
// In a real implementation, you would store these reports for security analysis
c.JSON(http.StatusOK, gin.H{"message": "CSP report received"})
}
// GDPRComplianceMiddleware ensures compliance with GDPR regulations
func GDPRComplianceMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Add privacy headers
c.Header("Privacy-Policy", "/privacy-policy")
// Check for explicit consent (simplified implementation)
consentGiven := c.GetHeader("X-Consent-Given")
if consentGiven != "true" {
// For sensitive operations, check consent
if isSensitiveOperation(c.Request.URL.Path) {
c.JSON(http.StatusPreconditionRequired, gin.H{
"error": "User consent required for this operation",
"required_consent": "privacy_policy"},
)
c.Abort()
return
}
}
c.Next()
}
}
// isSensitiveOperation checks if the requested operation involves personal data
func isSensitiveOperation(path string) bool {
sensitivePaths := []string{
"/api/v1/users",
"/api/v1/profile",
"/api/v1/applications",
"/api/v1/resumes",
}
for _, sensitivePath := range sensitivePaths {
if strings.HasPrefix(path, sensitivePath) {
return true
}
}
return false
}
// DataResidencyMiddleware ensures data residency requirements are met
func DataResidencyMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// In a real implementation, this would check the user's location
// and ensure their data is stored in the appropriate geographic region
// For now, we'll just pass through
// Add data residency headers
c.Header("X-Data-Residency", "US")
c.Next()
}
}
// AuditLogMiddleware logs security-relevant events
func AuditLogMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
// Log request details for audit purposes
log.Printf(
"AUDIT: %s %s %s %s %s %s %d %v",
c.ClientIP(),
c.Request.UserAgent(),
c.Request.Method,
c.Request.URL.Path,
c.Request.URL.Query(),
c.Params,
c.Writer.Status(),
time.Since(start),
)
}
}
// PCIComplianceMiddleware implements PCI DSS requirements
func PCIComplianceMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// For PCI compliance, we need to ensure sensitive data like credit cards
// are not stored or transmitted inappropriately
// In this job platform, we don't expect credit card info, but we'll check
// for any potentially sensitive data in the request
// Check request body for sensitive information
if isSensitiveDataInRequest(c) {
log.Printf("WARNING: Potential sensitive data detected in request: %s", c.Request.URL.Path)
}
c.Next()
}
}
// isSensitiveDataInRequest checks if the request contains sensitive data
func isSensitiveDataInRequest(c *gin.Context) bool {
// In a real implementation, this would scan the request body for:
// - Credit card numbers using regex patterns
// - SSNs using regex patterns
// - Other sensitive financial data
// For now, we'll just return false as this is a job platform
return false
}
// SocComplianceMiddleware implements SOC 2 compliance measures
func SocComplianceMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// SOC 2 focuses on security, availability, processing integrity,
// confidentiality, and privacy
// Ensure all operations are logged and monitored
c.Next()
}
}
// FedRAMPComplianceMiddleware implements FedRAMP requirements
func FedRAMPComplianceMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// FedRAMP requires strict access controls, continuous monitoring,
// and documentation of security controls
// This is a simplified implementation
// Check if request requires FedRAMP compliance
if requiresFedRAMP(c.Request.URL.Path) {
// Ensure proper authentication and authorization
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required for FedRAMP-compliant access"})
c.Abort()
return
}
// Log the access for compliance monitoring
log.Printf("FedRAMP Access: User %v accessed %s at %v", userID, c.Request.URL.Path, time.Now())
}
c.Next()
}
}
// requiresFedRAMP checks if a path requires FedRAMP compliance
func requiresFedRAMP(path string) bool {
// In a real implementation, this would check against FedRAMP-protected resources
// For now, we'll consider administrative paths as requiring FedRAMP compliance
protectedPaths := []string{
"/api/v1/admin",
"/api/v1/users",
"/api/v1/audit",
}
for _, protectedPath := range protectedPaths {
if strings.HasPrefix(path, protectedPath) {
return true
}
}
return false
}
// JWTAuthorizationMiddleware validates JWT tokens and ensures the user has required permissions
func JWTAuthorizationMiddleware(requiredPermissions ...string) gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header required"})
c.Abort()
return
}
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
if tokenString == authHeader {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Bearer token required"})
c.Abort()
return
}
// Parse and validate the token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte("supersecretkeyforjwt"), nil // In real implementation, use config
})
if err != nil || !token.Valid {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
c.Abort()
return
}
// Extract claims
if claims, ok := token.Claims.(jwt.MapClaims); ok {
// Extract user ID from claims
if userIDStr, ok := claims["user_id"].(string); ok {
userID, err := uuid.Parse(userIDStr)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid user ID in token"})
c.Abort()
return
}
// Store user ID in context for use in handlers
c.Set("user_id", userID)
// Check permissions if required
if len(requiredPermissions) > 0 {
userRole, ok := claims["role"].(string)
if !ok {
c.JSON(http.StatusForbidden, gin.H{"error": "Role not found in token"})
c.Abort()
return
}
if !hasPermission(userRole, requiredPermissions) {
c.JSON(http.StatusForbidden, gin.H{
"error": "Insufficient permissions",
"required_permissions": requiredPermissions,
"role": userRole,
})
c.Abort()
return
}
}
} else {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User ID not found in token"})
c.Abort()
return
}
} else {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token claims"})
c.Abort()
return
}
c.Next()
}
}
// hasPermission checks if a user role has the required permissions
func hasPermission(userRole string, requiredPermissions []string) bool {
// In a real implementation, this would check permissions database
// For now, we'll implement a simple role-based permission system:
// admin: can access everything
// job_provider: can create positions, manage applications
// job_seeker: can apply to positions, upload resumes
if userRole == "admin" {
return true
}
for _, perm := range requiredPermissions {
switch perm {
case "create_position", "manage_applications":
if userRole == "job_provider" || userRole == "admin" {
return true
}
case "apply_to_position", "upload_resume":
if userRole == "job_seeker" || userRole == "admin" {
return true
}
}
}
return false
}