This commit is contained in:
2025-08-23 17:22:37 -04:00
parent 632473a7d8
commit d659a47764
6 changed files with 797 additions and 55 deletions

View File

@ -2,8 +2,14 @@ package middleware
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"net"
"net/http"
"io"
"strconv"
"strings"
"sync"
"time"
@ -13,6 +19,7 @@ import (
"github.com/kms/api-key-service/internal/cache"
"github.com/kms/api-key-service/internal/config"
"github.com/kms/api-key-service/internal/repository"
)
// SecurityMiddleware provides various security features
@ -20,17 +27,19 @@ type SecurityMiddleware struct {
config config.ConfigProvider
logger *zap.Logger
cacheManager *cache.CacheManager
appRepo repository.ApplicationRepository
rateLimiters map[string]*rate.Limiter
mu sync.RWMutex
}
// NewSecurityMiddleware creates a new security middleware
func NewSecurityMiddleware(config config.ConfigProvider, logger *zap.Logger) *SecurityMiddleware {
func NewSecurityMiddleware(config config.ConfigProvider, logger *zap.Logger, appRepo repository.ApplicationRepository) *SecurityMiddleware {
cacheManager := cache.NewCacheManager(config, logger)
return &SecurityMiddleware{
config: config,
logger: logger,
cacheManager: cacheManager,
appRepo: appRepo,
rateLimiters: make(map[string]*rate.Limiter),
}
}
@ -364,8 +373,46 @@ func (s *SecurityMiddleware) RequestSignatureMiddleware(next http.Handler) http.
return
}
// TODO: Implement actual signature validation
// This would involve validating the HMAC signature using the client's secret
// Implement HMAC signature validation
appID := r.Header.Get("X-App-ID")
if appID == "" {
s.logger.Warn("Missing App-ID header for signature validation",
zap.String("path", r.URL.Path),
zap.String("client_ip", s.getClientIP(r)))
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"missing_app_id","message":"X-App-ID header required for signature validation"}`))
return
}
// Retrieve application to get HMAC key
ctx := r.Context()
app, err := s.appRepo.GetByID(ctx, appID)
if err != nil {
s.logger.Warn("Failed to retrieve application for signature validation",
zap.String("app_id", appID),
zap.Error(err),
zap.String("client_ip", s.getClientIP(r)))
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"error":"invalid_application","message":"Invalid application ID"}`))
return
}
// Validate the signature
if !s.validateHMACSignature(r, app.HMACKey, signature, timestamp) {
s.logger.Warn("Invalid request signature",
zap.String("app_id", appID),
zap.String("path", r.URL.Path),
zap.String("client_ip", s.getClientIP(r)))
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"error":"invalid_signature","message":"Request signature is invalid"}`))
return
}
next.ServeHTTP(w, r)
})
@ -417,3 +464,33 @@ func (s *SecurityMiddleware) GetSecurityMetrics() map[string]interface{} {
return metrics
}
// validateHMACSignature validates HMAC-SHA256 signature for request integrity
func (s *SecurityMiddleware) validateHMACSignature(r *http.Request, hmacKey, signature, timestamp string) bool {
// Create the signing string: METHOD + PATH + BODY + TIMESTAMP
var bodyBytes []byte
if r.Body != nil {
var err error
bodyBytes, err = io.ReadAll(r.Body)
if err != nil {
s.logger.Warn("Failed to read request body for signature validation", zap.Error(err))
return false
}
// Restore the body for downstream handlers
r.Body = io.NopCloser(strings.NewReader(string(bodyBytes)))
}
signingString := fmt.Sprintf("%s\n%s\n%s\n%s",
r.Method,
r.URL.Path,
string(bodyBytes),
timestamp)
// Calculate expected signature
mac := hmac.New(sha256.New, []byte(hmacKey))
mac.Write([]byte(signingString))
expectedSignature := hex.EncodeToString(mac.Sum(nil))
// Compare signatures (constant time comparison to prevent timing attacks)
return hmac.Equal([]byte(signature), []byte(expectedSignature))
}