feat: add comprehensive testing infrastructure and security hardening

- Enhanced CI/CD pipeline with coverage reporting, benchmarks, and artifact uploads
- Implemented rate limiter IP validation with proxy support and spoofing protection
- Added extensive Makefile test targets for coverage, benchmarks, and continuous testing
- Expanded middleware chain with request validation, size limits, and suspicious activity logging
This commit is contained in:
juanatsap
2025-11-11 21:43:12 +00:00
parent 1f5aeb1c4c
commit 92dffe8c60
41 changed files with 8077 additions and 523 deletions
+52 -4
View File
@@ -7,9 +7,11 @@ import (
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"github.com/joho/godotenv"
"github.com/juanatsap/cv-site/internal/config"
"github.com/juanatsap/cv-site/internal/handlers"
"github.com/juanatsap/cv-site/internal/middleware"
@@ -24,6 +26,13 @@ func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
log.Println("🚀 Starting CV Server v" + version)
// Load .env file (ignore error if file doesn't exist)
if err := godotenv.Load(); err != nil {
log.Println("⚠️ No .env file found, using system environment variables")
} else {
log.Println("✓ .env file loaded")
}
// Load configuration
cfg := config.Load()
log.Printf("✓ Configuration loaded (env: %s)", os.Getenv("GO_ENV"))
@@ -64,9 +73,28 @@ func main() {
// Setup router
mux := http.NewServeMux()
// Configure rate limiter with secure IP validation
behindProxy, _ := strconv.ParseBool(os.Getenv("BEHIND_PROXY"))
trustedProxyIP := os.Getenv("TRUSTED_PROXY_IP")
rateLimiterConfig := middleware.RateLimiterConfig{
BehindProxy: behindProxy,
TrustedProxyIP: trustedProxyIP,
}
if behindProxy {
if trustedProxyIP != "" {
log.Printf("🔒 Rate limiter: Behind proxy mode (trusted proxy: %s)", trustedProxyIP)
} else {
log.Printf("🔒 Rate limiter: Behind proxy mode (all proxies trusted)")
}
} else {
log.Printf("🔒 Rate limiter: Direct connection mode (spoofing protection enabled)")
}
// Create rate limiter for PDF endpoint
// Allow 3 PDF generations per minute per IP
pdfRateLimiter := middleware.NewRateLimiter(3, 1*time.Minute)
pdfRateLimiter := middleware.NewRateLimiter(3, 1*time.Minute, rateLimiterConfig)
// Routes
mux.HandleFunc("/", cvHandler.Home)
@@ -85,10 +113,21 @@ func main() {
staticHandler := http.StripPrefix("/static/", http.FileServer(http.Dir("static")))
mux.Handle("/static/", cacheControl(staticHandler))
// Apply middleware chain
// Apply comprehensive middleware chain with security-first approach
// Order matters: validation happens before processing
handler := middleware.Recovery(
middleware.Logger(
middleware.SecurityHeaders(mux),
middleware.LogSuspiciousActivity(
middleware.SanitizeHeaders(
middleware.ValidateQueryStrings(
middleware.ValidateRequestPath(
middleware.MaxRequestSize(10 * 1024 * 1024)( // 10MB max request size
middleware.SecurityHeaders(mux),
),
),
),
),
),
),
)
@@ -131,7 +170,16 @@ func main() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Attempt graceful shutdown
// Shutdown rate limiter first
log.Println("🧹 Shutting down rate limiter...")
if err := pdfRateLimiter.Shutdown(ctx); err != nil {
log.Printf("⚠️ Rate limiter shutdown error: %v", err)
} else {
log.Println("✓ Rate limiter stopped gracefully")
}
// Attempt graceful shutdown of HTTP server
log.Println("🛑 Shutting down HTTP server...")
if err := server.Shutdown(ctx); err != nil {
log.Printf("⚠️ Graceful shutdown failed, forcing: %v", err)
if err := server.Close(); err != nil {