feat: implement secure cluster authentication (issue #13)

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>
This commit is contained in:
2025-10-02 22:19:40 +03:00
parent 2431d3cfb0
commit c7dcebb894
28 changed files with 477 additions and 230 deletions

View File

@@ -50,7 +50,8 @@ type Server struct {
backupMu sync.RWMutex // Protects backup status
// Authentication service
authService *auth.AuthService
authService *auth.AuthService
clusterAuthService *auth.ClusterAuthService
}
// NewServer initializes and returns a new Server instance
@@ -120,6 +121,11 @@ func NewServer(config *types.Config) (*Server, error) {
// Initialize authentication service
server.authService = auth.NewAuthService(db, logger, config)
// Initialize cluster authentication service (Issue #13)
if config.ClusteringEnabled {
server.clusterAuthService = auth.NewClusterAuthService(config.ClusterSecret, logger)
}
// Setup initial root account if needed (Issue #3)
if config.AuthEnabled {
if err := server.setupRootAccount(); err != nil {
@@ -219,7 +225,7 @@ func (s *Server) setupRootAccount() error {
func (s *Server) createRootUserAndToken() error {
rootNickname := "root"
adminGroupName := "admin"
// Generate UUIDs
rootUserUUID := "root-" + time.Now().Format("20060102-150405")
adminGroupUUID := "admin-" + time.Now().Format("20060102-150405")
@@ -234,7 +240,7 @@ func (s *Server) createRootUserAndToken() error {
UpdatedAt: now,
}
// Create root user
// Create root user
rootUser := types.User{
UUID: rootUserUUID,
NicknameHash: hashUserNickname(rootNickname),
@@ -251,7 +257,7 @@ func (s *Server) createRootUserAndToken() error {
// Create API token with full administrative scopes
adminScopes := []string{
"admin:users:create", "admin:users:read", "admin:users:update", "admin:users:delete",
"admin:groups:create", "admin:groups:read", "admin:groups:update", "admin:groups:delete",
"admin:groups:create", "admin:groups:read", "admin:groups:update", "admin:groups:delete",
"admin:tokens:create", "admin:tokens:revoke",
"read", "write", "delete",
}
@@ -269,13 +275,13 @@ func (s *Server) createRootUserAndToken() error {
// Log the token securely (one-time display)
s.logger.WithFields(logrus.Fields{
"user_uuid": rootUserUUID,
"group_uuid": adminGroupUUID,
"expires_at": time.Unix(expiresAt, 0).Format(time.RFC3339),
"expires_in": "24 hours",
"user_uuid": rootUserUUID,
"group_uuid": adminGroupUUID,
"expires_at": time.Unix(expiresAt, 0).Format(time.RFC3339),
"expires_in": "24 hours",
}).Warn("Root account created - SAVE THIS TOKEN:")
// Display token prominently
// Display token prominently
fmt.Printf("\n" + strings.Repeat("=", 80) + "\n")
fmt.Printf("🔐 ROOT ACCOUNT CREATED - INITIAL SETUP TOKEN\n")
fmt.Printf("===========================================\n")
@@ -309,7 +315,7 @@ func (s *Server) storeUserAndGroup(user *types.User, group *types.Group) error {
if err != nil {
return fmt.Errorf("failed to marshal user data: %v", err)
}
if err := txn.Set([]byte(auth.UserStorageKey(user.UUID)), userData); err != nil {
return fmt.Errorf("failed to store user: %v", err)
}
@@ -319,7 +325,7 @@ func (s *Server) storeUserAndGroup(user *types.User, group *types.Group) error {
if err != nil {
return fmt.Errorf("failed to marshal group data: %v", err)
}
if err := txn.Set([]byte(auth.GroupStorageKey(group.UUID)), groupData); err != nil {
return fmt.Errorf("failed to store group: %v", err)
}
@@ -327,4 +333,3 @@ func (s *Server) storeUserAndGroup(user *types.User, group *types.Group) error {
return nil
})
}