Skip to content

Commit

Permalink
Merge pull request #21 from unkn0wn-root/verify_api_hostname
Browse files Browse the repository at this point in the history
feat(api): add hostname verify middleware
  • Loading branch information
unkn0wn-root authored Dec 21, 2024
2 parents 92d823f + d710194 commit 0689b55
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 16 deletions.
4 changes: 2 additions & 2 deletions api.config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
api:
enabled: true
host: localhost
port: 8081
host: admin.domain.com
port: 8085

database:
path: "./auth.db"
Expand Down
9 changes: 8 additions & 1 deletion internal/admin/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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...)
Expand Down
47 changes: 47 additions & 0 deletions internal/admin/middleware/host.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
23 changes: 17 additions & 6 deletions internal/admin/middleware.go → internal/admin/middleware/log.go
Original file line number Diff line number Diff line change
@@ -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))
})
}

Expand Down
9 changes: 2 additions & 7 deletions internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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(),
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 0689b55

Please sign in to comment.