diff --git a/api.config.yaml b/api.config.yaml index d039a62..b4d2fef 100644 --- a/api.config.yaml +++ b/api.config.yaml @@ -1,7 +1,7 @@ api: enabled: true - host: localhost - port: 8081 + host: admin.domain.com + port: 8085 database: path: "./auth.db" diff --git a/internal/admin/handlers.go b/internal/admin/handlers.go index ad92742..b4507e8 100644 --- a/internal/admin/handlers.go +++ b/internal/admin/handlers.go @@ -5,6 +5,7 @@ import ( "encoding/json" "net/http" + admin "github.com/unkn0wn-root/terraster/internal/admin/middleware" apierr "github.com/unkn0wn-root/terraster/internal/auth" "github.com/unkn0wn-root/terraster/internal/config" "github.com/unkn0wn-root/terraster/internal/middleware" @@ -29,10 +30,16 @@ func (a *AdminAPI) Handler() http.Handler { middleware.WithExcludePaths([]string{"/api/auth/login", "/api/auth/refresh"}), ) + adminApiHost := a.config.AdminAPI.Host + if adminApiHost == "" { + adminApiHost = "localhost" + } + var middlewares []middleware.Middleware middlewares = append(middlewares, logger, - NewAdminAccessLogMiddleware(), + admin.NewAdminAccessLogMiddleware(a.logger), + admin.NewHostnameMiddleware(adminApiHost, a.logger), ) chain := middleware.NewMiddlewareChain(middlewares...) diff --git a/internal/admin/middleware/host.go b/internal/admin/middleware/host.go new file mode 100644 index 0000000..c1f0d96 --- /dev/null +++ b/internal/admin/middleware/host.go @@ -0,0 +1,47 @@ +package admin + +import ( + "net" + "net/http" + + "github.com/unkn0wn-root/terraster/internal/middleware" + "go.uber.org/zap" +) + +// HostnameMiddleware validates incoming requests against a configured hostname +type HostnameMiddleware struct { + hostname string // Expected hostname to validate against + logger *zap.Logger +} + +func NewHostnameMiddleware(hostname string, logger *zap.Logger) middleware.Middleware { + return &HostnameMiddleware{ + hostname: hostname, + logger: logger, + } +} + +func (m *HostnameMiddleware) Middleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Extract hostname from request, ignoring port number + host, _, err := net.SplitHostPort(r.Host) + if err != nil { + m.logger.Error("Could not split host", zap.Error(err)) + http.Error(w, "Could not verify target host", http.StatusInternalServerError) + return + } + + // If does not match - you shall not pass + if host != m.hostname { + m.logger.Warn("Invalid hostname", + zap.String("expected", m.hostname), + zap.String("received", host), + zap.String("ip", r.RemoteAddr), + ) + http.Error(w, "Invalid host", http.StatusForbidden) + return + } + + next.ServeHTTP(w, r) + }) +} diff --git a/internal/admin/middleware.go b/internal/admin/middleware/log.go similarity index 57% rename from internal/admin/middleware.go rename to internal/admin/middleware/log.go index a3545af..6baf1ee 100644 --- a/internal/admin/middleware.go +++ b/internal/admin/middleware/log.go @@ -1,25 +1,36 @@ package admin import ( - "log" "net/http" "github.com/unkn0wn-root/terraster/internal/middleware" + "go.uber.org/zap" ) -type AdminAccessLogMiddleware struct{} +type AdminAccessLogMiddleware struct { + logger *zap.Logger +} -func NewAdminAccessLogMiddleware() middleware.Middleware { - return &AdminAccessLogMiddleware{} +func NewAdminAccessLogMiddleware(logger *zap.Logger) middleware.Middleware { + return &AdminAccessLogMiddleware{ + logger: logger, + } } func (m *AdminAccessLogMiddleware) Middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Printf("Admin API access: %s %s from %s", r.Method, r.URL.Path, r.RemoteAddr) + m.logger.Info("Request to Admin API", + zap.String("method", r.Method), + zap.String("request url", r.URL.Path), + zap.String("request addr.", r.RemoteAddr)) + // Wrap response writer to capture status code sw := &statusResponseWriter{ResponseWriter: w} next.ServeHTTP(sw, r) - log.Printf("Admin API response: %d for %s %s", sw.status, r.Method, r.URL.Path) + m.logger.Info("Response from Admin API", + zap.Int("status", sw.status), + zap.String("method", r.Method), + zap.String("request path", r.URL.Path)) }) } diff --git a/internal/server/server.go b/internal/server/server.go index 9125981..47f313e 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -273,11 +273,6 @@ func (s *Server) startServiceServer(svc *service.ServiceInfo) error { // We could use cert manager to get certificates for admin server as well // but it's better to guard api via load balancer so use LB if you want more advanced config func (s *Server) startAdminServer() error { - adminApiHost := s.apiConfig.AdminAPI.Host - if adminApiHost == "" { - s.apiConfig.AdminAPI.Host = "localhost" - } - // try to load api certificate var cert *tls.Certificate if s.apiConfig.AdminAPI.TLS != nil { @@ -290,7 +285,7 @@ func (s *Server) startAdminServer() error { } - adminAddr := net.JoinHostPort(adminApiHost, strconv.Itoa(s.servicePort(s.apiConfig.AdminAPI.Port))) + adminAddr := fmt.Sprintf(":%d", s.servicePort(s.apiConfig.AdminAPI.Port)) s.adminServer = &http.Server{ Addr: adminAddr, Handler: s.adminAPI.Handler(), @@ -379,7 +374,7 @@ func (s *Server) runServer( ) { defer s.wg.Done() n := strings.ToUpper(name) - s.logger.Info("Server started", zap.String("name", n), zap.String("server_addr", server.Addr)) + s.logger.Info("Server started", zap.String("service_name", n), zap.String("listen_on", server.Addr)) var err error if serviceType == service.HTTPS {