diff --git a/cmd/cloud/main.go b/cmd/cloud/main.go index 447e049..2dd887e 100644 --- a/cmd/cloud/main.go +++ b/cmd/cloud/main.go @@ -41,6 +41,7 @@ func run() error { apiServer := api.NewAPIServer( api.WithMetricsManager(server.GetMetricsManager()), api.WithSNMPManager(server.GetSNMPManager()), + api.WithAPIKey(cfg.APIKey), // Pass the API key from config ) server.SetAPIServer(apiServer) diff --git a/pkg/cloud/api/server.go b/pkg/cloud/api/server.go index 64cf7ac..ec1baa0 100644 --- a/pkg/cloud/api/server.go +++ b/pkg/cloud/api/server.go @@ -19,13 +19,14 @@ func NewAPIServer(options ...func(server *APIServer)) *APIServer { s := &APIServer{ nodes: make(map[string]*NodeStatus), router: mux.NewRouter(), + apiKey: "", // Default empty API key } for _, o := range options { o(s) } - s.setupRoutes() + s.setupRoutes(s.apiKey) return s } @@ -42,16 +43,23 @@ func WithSNMPManager(m snmp.SNMPManager) func(server *APIServer) { } } +func WithAPIKey(apiKey string) func(server *APIServer) { + return func(server *APIServer) { + server.apiKey = apiKey + } +} + func WithDB(db db.Service) func(server *APIServer) { return func(server *APIServer) { server.db = db } } -func (s *APIServer) setupRoutes() { + +func (s *APIServer) setupRoutes(apiKey string) { // Create a middleware chain middlewareChain := func(next http.Handler) http.Handler { // Order matters: first API key check, then CORS headers - return srHttp.CommonMiddleware(srHttp.APIKeyMiddleware(next)) + return srHttp.CommonMiddleware(srHttp.APIKeyMiddleware(apiKey)(next)) } // Add middleware to router diff --git a/pkg/cloud/api/types.go b/pkg/cloud/api/types.go index cb04b9f..50965fc 100644 --- a/pkg/cloud/api/types.go +++ b/pkg/cloud/api/types.go @@ -57,4 +57,5 @@ type APIServer struct { snmpManager snmp.SNMPManager db db.Service knownPollers []string + apiKey string } diff --git a/pkg/cloud/types.go b/pkg/cloud/types.go index d18f5c4..591230d 100644 --- a/pkg/cloud/types.go +++ b/pkg/cloud/types.go @@ -31,6 +31,7 @@ type Config struct { Metrics Metrics `json:"metrics"` SNMP snmp.Config `json:"snmp"` Security *models.SecurityConfig `json:"security"` + APIKey string `json:"api_key,omitempty"` } type Server struct { diff --git a/pkg/http/middleware.go b/pkg/http/middleware.go index 32b6aa0..a8ebb80 100644 --- a/pkg/http/middleware.go +++ b/pkg/http/middleware.go @@ -27,34 +27,43 @@ func CommonMiddleware(next http.Handler) http.Handler { } // APIKeyMiddleware creates middleware that validates API keys. -func APIKeyMiddleware(next http.Handler) http.Handler { - apiKey := os.Getenv("API_KEY") +// It can accept an API key directly or read from the environment. +func APIKeyMiddleware(apiKeyParam string) func(next http.Handler) http.Handler { + apiKey := apiKeyParam + + // Fall back to environment variable if not provided directly if apiKey == "" { - log.Printf("WARNING: API_KEY environment variable not set, API endpoints are unprotected!") + apiKey = os.Getenv("API_KEY") } - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Skip API key check if it's not configured (development mode) - if apiKey == "" { - next.ServeHTTP(w, r) + if apiKey == "" { + log.Printf("WARNING: API_KEY not set, API endpoints are unprotected!") + } - return - } + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Skip API key check if it's not configured (development mode) + if apiKey == "" { + next.ServeHTTP(w, r) - // Check API key in header or query parameter - requestKey := r.Header.Get("X-API-Key") - if requestKey == "" { - requestKey = r.URL.Query().Get("api_key") - } + return + } - // Validate API key - if requestKey == "" || requestKey != apiKey { - log.Printf("Unauthorized API access attempt: %s %s", r.Method, r.URL.Path) - http.Error(w, "Unauthorized", http.StatusUnauthorized) + // Check API key in header or query parameter + requestKey := r.Header.Get("X-API-Key") + if requestKey == "" { + requestKey = r.URL.Query().Get("api_key") + } - return - } + // Validate API key + if requestKey == "" || requestKey != apiKey { + log.Printf("Unauthorized API access attempt: %s %s", r.Method, r.URL.Path) + http.Error(w, "Unauthorized", http.StatusUnauthorized) - next.ServeHTTP(w, r) - }) + return + } + + next.ServeHTTP(w, r) + }) + } } diff --git a/serviceradar-next/src/middleware.ts b/serviceradar-next/src/middleware.ts new file mode 100644 index 0000000..1f2dc33 --- /dev/null +++ b/serviceradar-next/src/middleware.ts @@ -0,0 +1,28 @@ +import { NextResponse } from 'next/server'; +import type { NextRequest } from 'next/server'; + +export function middleware(request: NextRequest) { + const url = request.nextUrl.clone(); + + // Only apply to /api/* paths + if (url.pathname.startsWith('/api/')) { + + const apiKey = process.env.API_KEY || ''; + + // Create a new request with added headers + const modifiedRequest = new Request(url, { + headers: { + ...request.headers, + 'X-API-Key': apiKey, + }, + }); + + return NextResponse.rewrite(url, { request: modifiedRequest }); + } + + return NextResponse.next(); +} + +export const config = { + matcher: '/api/:path*', // Apply middleware only to /api/* routes +}; \ No newline at end of file