security: Terminal-Endpoint JWT-only (kein API-Key Zugriff)

- Neue JWTOnlyAuth Middleware: akzeptiert nur JWT, kein API-Key
- Terminal-Route aus allgemeinem CombinedAuth raus, eigener Mount
- API-Keys für Monitoring/Automation erlaubt, aber Terminal braucht
  echten Login mit User/Pass + 2FA
- ?token= Query-Param weiterhin erlaubt (Browser-WS kann kein Header setzen)
This commit is contained in:
cynfo3000 2026-03-09 23:44:12 +01:00
parent e9a47bee26
commit 91385b42d1
4 changed files with 41 additions and 3 deletions

View File

@ -136,6 +136,38 @@ func CombinedAuth(validKeys []string, auth *AuthHandler, next http.Handler) http
}) })
} }
// JWTOnlyAuth - Nur JWT akzeptieren, kein API-Key (z.B. fuer Terminal-Zugriff)
// Erfordert echten Login mit User/Pass (+2FA), kein Maschinentoken reicht.
func JWTOnlyAuth(auth *AuthHandler, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var jwtToken string
// Authorization Header
authHeader := r.Header.Get("Authorization")
if strings.HasPrefix(authHeader, "Bearer ") {
jwtToken = strings.TrimPrefix(authHeader, "Bearer ")
}
// ?token= Query-Parameter (fuer WebSocket, Browser kann keinen Header setzen)
if jwtToken == "" {
jwtToken = r.URL.Query().Get("token")
}
if jwtToken == "" {
http.Error(w, `{"error":"unauthorized: JWT erforderlich"}`, http.StatusUnauthorized)
return
}
claims, err := auth.ValidateToken(jwtToken)
if err != nil {
http.Error(w, `{"error":"unauthorized: ungültiger Token"}`, http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), UserContextKey, claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// CORS Middleware // CORS Middleware
func CORS(next http.Handler) http.Handler { func CORS(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

View File

@ -17,8 +17,8 @@ var termUpgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true }, CheckOrigin: func(r *http.Request) bool { return true },
} }
// terminalHandler bridget Browser-WebSocket mit Agent-PTY // TerminalHandler bridget Browser-WebSocket mit Agent-PTY (JWT-only, kein API-Key)
func (h *Handler) terminalHandler(hub *ws.Hub) http.HandlerFunc { func (h *Handler) TerminalHandler(hub *ws.Hub) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
agentID := r.PathValue("id") agentID := r.PathValue("id")
if agentID == "" { if agentID == "" {

View File

@ -45,7 +45,7 @@ func (h *Handler) setupTunnelRoutes(mux *http.ServeMux, hub *ws.Hub) {
mux.HandleFunc("GET /api/v1/agents/{id}/backups/diff", h.diffBackups()) mux.HandleFunc("GET /api/v1/agents/{id}/backups/diff", h.diffBackups())
// Terminal-Endpoint (Browser -> Agent PTY) // Terminal-Endpoint (Browser -> Agent PTY)
mux.HandleFunc("GET /api/v1/agents/{id}/terminal", h.terminalHandler(hub)) // Terminal wird NICHT hier registriert — wird in main.go unter JWTOnlyAuth eingebunden
// WebSocket-Endpoint // WebSocket-Endpoint
mux.HandleFunc("GET /api/v1/agent/ws", ws.NewWebSocketHandler(hub)) mux.HandleFunc("GET /api/v1/agent/ws", ws.NewWebSocketHandler(hub))

View File

@ -93,6 +93,12 @@ func main() {
mux := http.NewServeMux() mux := http.NewServeMux()
mux.Handle("POST /api/v1/auth/login", authMux) mux.Handle("POST /api/v1/auth/login", authMux)
mux.Handle("POST /api/v1/auth/2fa/validate", authMux) // 2FA-Schritt 2 ebenfalls ohne JWT mux.Handle("POST /api/v1/auth/2fa/validate", authMux) // 2FA-Schritt 2 ebenfalls ohne JWT
// Terminal: JWT-only (kein API-Key reicht — erfordert echten Login mit 2FA)
terminalMux := http.NewServeMux()
terminalMux.HandleFunc("GET /api/v1/agents/{id}/terminal", handler.TerminalHandler(hub))
mux.Handle("GET /api/v1/agents/{id}/terminal", api.JWTOnlyAuth(authHandler, terminalMux))
mux.Handle("/", api.CombinedAuthWithCache(apiKeyCache, authHandler, protectedMux)) mux.Handle("/", api.CombinedAuthWithCache(apiKeyCache, authHandler, protectedMux))
// Middleware-Chain: CORS -> Logging -> Handler // Middleware-Chain: CORS -> Logging -> Handler