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

@@ -41,7 +41,7 @@ func NewAuthService(db *badger.DB, logger *logrus.Logger, config *types.Config)
// StoreAPIToken stores an API token in BadgerDB with TTL
func (s *AuthService) StoreAPIToken(tokenString string, userUUID string, scopes []string, expiresAt int64) error {
tokenHash := utils.HashToken(tokenString)
apiToken := types.APIToken{
TokenHash: tokenHash,
UserUUID: userUUID,
@@ -57,13 +57,13 @@ func (s *AuthService) StoreAPIToken(tokenString string, userUUID string, scopes
return s.db.Update(func(txn *badger.Txn) error {
entry := badger.NewEntry([]byte(TokenStorageKey(tokenHash)), tokenData)
// Set TTL to the token expiration time
ttl := time.Until(time.Unix(expiresAt, 0))
if ttl > 0 {
entry = entry.WithTTL(ttl)
}
return txn.SetEntry(entry)
})
}
@@ -71,7 +71,7 @@ func (s *AuthService) StoreAPIToken(tokenString string, userUUID string, scopes
// GetAPIToken retrieves an API token from BadgerDB by hash
func (s *AuthService) GetAPIToken(tokenHash string) (*types.APIToken, error) {
var apiToken types.APIToken
err := s.db.View(func(txn *badger.Txn) error {
item, err := txn.Get([]byte(TokenStorageKey(tokenHash)))
if err != nil {
@@ -209,22 +209,22 @@ func GetAuthContext(ctx context.Context) *AuthContext {
// HasUsers checks if any users exist in the database
func (s *AuthService) HasUsers() (bool, error) {
var hasUsers bool
err := s.db.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.PrefetchValues = false // We only need to check if keys exist
iterator := txn.NewIterator(opts)
defer iterator.Close()
// Look for any key starting with "user:"
prefix := []byte("user:")
for iterator.Seek(prefix); iterator.ValidForPrefix(prefix); iterator.Next() {
hasUsers = true
return nil // Found at least one user, can exit early
}
return nil
})
return hasUsers, err
}
}