fix: update bootstrap service and routes for cluster authentication
- Updated bootstrap service to use authenticated HTTP client with cluster auth headers - Made GET /members/ endpoint unprotected for monitoring/inspection purposes - All other cluster communication endpoints remain protected by cluster auth middleware This ensures proper cluster formation while maintaining security for inter-node communication. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
		@@ -82,10 +82,19 @@ func (s *BootstrapService) attemptJoin(seedAddr string) bool {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client := &http.Client{Timeout: 10 * time.Second}
 | 
			
		||||
	url := fmt.Sprintf("http://%s/members/join", seedAddr)
 | 
			
		||||
	client := NewAuthenticatedHTTPClient(s.config, 10*time.Second)
 | 
			
		||||
	protocol := GetProtocol(s.config)
 | 
			
		||||
	url := fmt.Sprintf("%s://%s/members/join", protocol, seedAddr)
 | 
			
		||||
 | 
			
		||||
	resp, err := client.Post(url, "application/json", bytes.NewBuffer(jsonData))
 | 
			
		||||
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		s.logger.WithError(err).Error("Failed to create join request")
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	req.Header.Set("Content-Type", "application/json")
 | 
			
		||||
	AddClusterAuthHeaders(req, s.config)
 | 
			
		||||
 | 
			
		||||
	resp, err := client.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		s.logger.WithFields(logrus.Fields{
 | 
			
		||||
			"seed":  seedAddr,
 | 
			
		||||
 
 | 
			
		||||
@@ -124,7 +124,10 @@ EOF
 | 
			
		||||
# Test 3: Cluster formation
 | 
			
		||||
test_cluster_formation() {
 | 
			
		||||
    test_start "2-node cluster formation and Merkle Tree replication"
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    # Shared cluster secret for authentication (Issue #13)
 | 
			
		||||
    local CLUSTER_SECRET="test-cluster-secret-12345678901234567890"
 | 
			
		||||
 | 
			
		||||
    # Node 1 config
 | 
			
		||||
    cat > cluster1.yaml <<EOF
 | 
			
		||||
node_id: "cluster-1"
 | 
			
		||||
@@ -138,8 +141,9 @@ gossip_interval_max: 10
 | 
			
		||||
sync_interval: 10
 | 
			
		||||
allow_anonymous_read: true
 | 
			
		||||
allow_anonymous_write: true
 | 
			
		||||
cluster_secret: "$CLUSTER_SECRET"
 | 
			
		||||
EOF
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    # Node 2 config
 | 
			
		||||
    cat > cluster2.yaml <<EOF
 | 
			
		||||
node_id: "cluster-2"
 | 
			
		||||
@@ -153,6 +157,7 @@ gossip_interval_max: 10
 | 
			
		||||
sync_interval: 10
 | 
			
		||||
allow_anonymous_read: true
 | 
			
		||||
allow_anonymous_write: true
 | 
			
		||||
cluster_secret: "$CLUSTER_SECRET"
 | 
			
		||||
EOF
 | 
			
		||||
    
 | 
			
		||||
    # Start nodes
 | 
			
		||||
@@ -230,15 +235,18 @@ EOF
 | 
			
		||||
# but same path. The Merkle tree sync should then trigger conflict resolution.
 | 
			
		||||
test_conflict_resolution() {
 | 
			
		||||
    test_start "Conflict resolution test (Merkle Tree based)"
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    # Create conflicting data using our utility
 | 
			
		||||
    rm -rf conflict1_data conflict2_data 2>/dev/null || true
 | 
			
		||||
    mkdir -p conflict1_data conflict2_data
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    cd "$SCRIPT_DIR"
 | 
			
		||||
    if go run test_conflict.go "$TEST_DIR/conflict1_data" "$TEST_DIR/conflict2_data"; then
 | 
			
		||||
        cd "$TEST_DIR"
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        # Shared cluster secret for authentication (Issue #13)
 | 
			
		||||
        local CLUSTER_SECRET="conflict-cluster-secret-1234567890123"
 | 
			
		||||
 | 
			
		||||
        # Create configs
 | 
			
		||||
        cat > conflict1.yaml <<EOF
 | 
			
		||||
node_id: "conflict-1"
 | 
			
		||||
@@ -250,8 +258,9 @@ log_level: "info"
 | 
			
		||||
sync_interval: 3
 | 
			
		||||
allow_anonymous_read: true
 | 
			
		||||
allow_anonymous_write: true
 | 
			
		||||
cluster_secret: "$CLUSTER_SECRET"
 | 
			
		||||
EOF
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        cat > conflict2.yaml <<EOF
 | 
			
		||||
node_id: "conflict-2"
 | 
			
		||||
bind_address: "127.0.0.1"
 | 
			
		||||
@@ -262,6 +271,7 @@ log_level: "info"
 | 
			
		||||
sync_interval: 3
 | 
			
		||||
allow_anonymous_read: true
 | 
			
		||||
allow_anonymous_write: true
 | 
			
		||||
cluster_secret: "$CLUSTER_SECRET"
 | 
			
		||||
EOF
 | 
			
		||||
        
 | 
			
		||||
        # Start nodes
 | 
			
		||||
 
 | 
			
		||||
@@ -43,9 +43,11 @@ func (s *Server) setupRoutes() *mux.Router {
 | 
			
		||||
 | 
			
		||||
	// Member endpoints (available when clustering is enabled)
 | 
			
		||||
	if s.config.ClusteringEnabled {
 | 
			
		||||
		// Apply cluster authentication middleware if cluster secret is configured
 | 
			
		||||
		// GET /members/ is unprotected for monitoring/inspection
 | 
			
		||||
		router.HandleFunc("/members/", s.getMembersHandler).Methods("GET")
 | 
			
		||||
 | 
			
		||||
		// Apply cluster authentication middleware to all cluster communication endpoints
 | 
			
		||||
		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")
 | 
			
		||||
@@ -57,7 +59,6 @@ func (s *Server) setupRoutes() *mux.Router {
 | 
			
		||||
			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")
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user