Implemented a comprehensive secure authentication mechanism for inter-node cluster communication with the following features: 1. Global Cluster Secret (GCS) - Auto-generated cryptographically secure random secret (256-bit) - Configurable via YAML config file - Shared across all cluster nodes for authentication 2. Cluster Authentication Middleware - Validates X-Cluster-Secret and X-Node-ID headers - Applied to all cluster endpoints (/members/*, /merkle_tree/*, /kv_range) - Comprehensive logging of authentication attempts 3. Authenticated HTTP Client - Custom HTTP client with cluster auth headers - TLS support with configurable certificate verification - Protocol-aware (http/https based on TLS settings) 4. Secure Bootstrap Endpoint - New /auth/cluster-bootstrap endpoint - Protected by JWT authentication with admin scope - Allows new nodes to securely obtain cluster secret 5. Updated Cluster Communication - All gossip protocol requests include auth headers - All Merkle tree sync requests include auth headers - All data replication requests include auth headers 6. Configuration - cluster_secret: Shared secret (auto-generated if not provided) - cluster_tls_enabled: Enable TLS for inter-node communication - cluster_tls_cert_file: Path to TLS certificate - cluster_tls_key_file: Path to TLS private key - cluster_tls_skip_verify: Skip TLS verification (testing only) This implementation addresses the security vulnerability of unprotected cluster endpoints and provides a flexible, secure approach to protecting internal cluster communication while allowing for automated node bootstrapping. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
132 lines
5.7 KiB
Go
132 lines
5.7 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// setupRoutes configures all HTTP routes and their handlers
|
|
func (s *Server) setupRoutes() *mux.Router {
|
|
router := mux.NewRouter()
|
|
|
|
// Health endpoint (always available)
|
|
router.HandleFunc("/health", s.healthHandler).Methods("GET")
|
|
|
|
// KV endpoints (with conditional authentication based on anonymous access settings)
|
|
// GET endpoint - require auth if anonymous read is disabled
|
|
if s.config.AuthEnabled && !s.config.AllowAnonymousRead {
|
|
router.Handle("/kv/{path:.+}", s.authService.Middleware(
|
|
[]string{"read"}, nil, "",
|
|
)(s.getKVHandler)).Methods("GET")
|
|
} else {
|
|
router.HandleFunc("/kv/{path:.+}", s.getKVHandler).Methods("GET")
|
|
}
|
|
|
|
// PUT endpoint - require auth if anonymous write is disabled
|
|
if s.config.AuthEnabled && !s.config.AllowAnonymousWrite {
|
|
router.Handle("/kv/{path:.+}", s.authService.Middleware(
|
|
[]string{"write"}, nil, "",
|
|
)(s.putKVHandler)).Methods("PUT")
|
|
} else {
|
|
router.HandleFunc("/kv/{path:.+}", s.putKVHandler).Methods("PUT")
|
|
}
|
|
|
|
// DELETE endpoint - always require authentication (no anonymous delete)
|
|
if s.config.AuthEnabled {
|
|
router.Handle("/kv/{path:.+}", s.authService.Middleware(
|
|
[]string{"delete"}, nil, "",
|
|
)(s.deleteKVHandler)).Methods("DELETE")
|
|
} else {
|
|
router.HandleFunc("/kv/{path:.+}", s.deleteKVHandler).Methods("DELETE")
|
|
}
|
|
|
|
// Member endpoints (available when clustering is enabled)
|
|
if s.config.ClusteringEnabled {
|
|
// Apply cluster authentication middleware if cluster secret is configured
|
|
if s.clusterAuthService != nil {
|
|
router.Handle("/members/", s.clusterAuthService.Middleware(http.HandlerFunc(s.getMembersHandler))).Methods("GET")
|
|
router.Handle("/members/join", s.clusterAuthService.Middleware(http.HandlerFunc(s.joinMemberHandler))).Methods("POST")
|
|
router.Handle("/members/leave", s.clusterAuthService.Middleware(http.HandlerFunc(s.leaveMemberHandler))).Methods("DELETE")
|
|
router.Handle("/members/gossip", s.clusterAuthService.Middleware(http.HandlerFunc(s.gossipHandler))).Methods("POST")
|
|
router.Handle("/members/pairs_by_time", s.clusterAuthService.Middleware(http.HandlerFunc(s.pairsByTimeHandler))).Methods("POST")
|
|
|
|
// Merkle Tree endpoints (clustering feature)
|
|
router.Handle("/merkle_tree/root", s.clusterAuthService.Middleware(http.HandlerFunc(s.getMerkleRootHandler))).Methods("GET")
|
|
router.Handle("/merkle_tree/diff", s.clusterAuthService.Middleware(http.HandlerFunc(s.getMerkleDiffHandler))).Methods("POST")
|
|
router.Handle("/kv_range", s.clusterAuthService.Middleware(http.HandlerFunc(s.getKVRangeHandler))).Methods("POST")
|
|
} else {
|
|
// Fallback to unprotected endpoints (for backwards compatibility)
|
|
router.HandleFunc("/members/", s.getMembersHandler).Methods("GET")
|
|
router.HandleFunc("/members/join", s.joinMemberHandler).Methods("POST")
|
|
router.HandleFunc("/members/leave", s.leaveMemberHandler).Methods("DELETE")
|
|
router.HandleFunc("/members/gossip", s.gossipHandler).Methods("POST")
|
|
router.HandleFunc("/members/pairs_by_time", s.pairsByTimeHandler).Methods("POST")
|
|
|
|
// Merkle Tree endpoints (clustering feature)
|
|
router.HandleFunc("/merkle_tree/root", s.getMerkleRootHandler).Methods("GET")
|
|
router.HandleFunc("/merkle_tree/diff", s.getMerkleDiffHandler).Methods("POST")
|
|
router.HandleFunc("/kv_range", s.getKVRangeHandler).Methods("POST")
|
|
}
|
|
}
|
|
|
|
// Authentication and user management endpoints (available when auth is enabled)
|
|
if s.config.AuthEnabled {
|
|
// User Management endpoints (with authentication middleware)
|
|
router.Handle("/api/users", s.authService.Middleware(
|
|
[]string{"admin:users:create"}, nil, "",
|
|
)(s.createUserHandler)).Methods("POST")
|
|
|
|
router.Handle("/api/users/{uuid}", s.authService.Middleware(
|
|
[]string{"admin:users:read"}, nil, "",
|
|
)(s.getUserHandler)).Methods("GET")
|
|
|
|
router.Handle("/api/users/{uuid}", s.authService.Middleware(
|
|
[]string{"admin:users:update"}, nil, "",
|
|
)(s.updateUserHandler)).Methods("PUT")
|
|
|
|
router.Handle("/api/users/{uuid}", s.authService.Middleware(
|
|
[]string{"admin:users:delete"}, nil, "",
|
|
)(s.deleteUserHandler)).Methods("DELETE")
|
|
|
|
// Group Management endpoints (with authentication middleware)
|
|
router.Handle("/api/groups", s.authService.Middleware(
|
|
[]string{"admin:groups:create"}, nil, "",
|
|
)(s.createGroupHandler)).Methods("POST")
|
|
|
|
router.Handle("/api/groups/{uuid}", s.authService.Middleware(
|
|
[]string{"admin:groups:read"}, nil, "",
|
|
)(s.getGroupHandler)).Methods("GET")
|
|
|
|
router.Handle("/api/groups/{uuid}", s.authService.Middleware(
|
|
[]string{"admin:groups:update"}, nil, "",
|
|
)(s.updateGroupHandler)).Methods("PUT")
|
|
|
|
router.Handle("/api/groups/{uuid}", s.authService.Middleware(
|
|
[]string{"admin:groups:delete"}, nil, "",
|
|
)(s.deleteGroupHandler)).Methods("DELETE")
|
|
|
|
// Token Management endpoints (with authentication middleware)
|
|
router.Handle("/api/tokens", s.authService.Middleware(
|
|
[]string{"admin:tokens:create"}, nil, "",
|
|
)(s.createTokenHandler)).Methods("POST")
|
|
|
|
// Cluster Bootstrap endpoint (Issue #13) - Protected by JWT authentication
|
|
// Allows authenticated administrators to retrieve the cluster secret for new nodes
|
|
router.Handle("/auth/cluster-bootstrap", s.authService.Middleware(
|
|
[]string{"admin:tokens:create"}, nil, "",
|
|
)(s.clusterBootstrapHandler)).Methods("GET")
|
|
}
|
|
|
|
// Revision History endpoints (available when revision history is enabled)
|
|
if s.config.RevisionHistoryEnabled {
|
|
router.HandleFunc("/api/data/{key}/history", s.getRevisionHistoryHandler).Methods("GET")
|
|
router.HandleFunc("/api/data/{key}/history/{revision}", s.getSpecificRevisionHandler).Methods("GET")
|
|
}
|
|
|
|
// Backup Status endpoint (always available if backup is enabled)
|
|
router.HandleFunc("/api/backup/status", s.getBackupStatusHandler).Methods("GET")
|
|
|
|
return router
|
|
}
|