Compare commits
3 Commits
3aff0ab5ef
...
2431d3cfb0
Author | SHA1 | Date | |
---|---|---|---|
2431d3cfb0 | |||
b4f57b3604 | |||
e6d87d025f |
13
CLAUDE.md
13
CLAUDE.md
@@ -99,15 +99,21 @@ type StoredValue struct {
|
|||||||
|
|
||||||
### Configuration Architecture
|
### Configuration Architecture
|
||||||
|
|
||||||
The system uses feature toggles extensively (`types/Config:271-276`):
|
The system uses feature toggles extensively (`types/Config:271-280`):
|
||||||
```yaml
|
```yaml
|
||||||
auth_enabled: true # JWT authentication system
|
auth_enabled: true # JWT authentication system
|
||||||
tamper_logging_enabled: true # Cryptographic audit trail
|
tamper_logging_enabled: true # Cryptographic audit trail
|
||||||
clustering_enabled: true # Gossip protocol and sync
|
clustering_enabled: true # Gossip protocol and sync
|
||||||
rate_limiting_enabled: true # Per-client rate limiting
|
rate_limiting_enabled: true # Per-client rate limiting
|
||||||
revision_history_enabled: true # Automatic versioning
|
revision_history_enabled: true # Automatic versioning
|
||||||
|
|
||||||
|
# Anonymous access control (Issue #5 - when auth_enabled: true)
|
||||||
|
allow_anonymous_read: false # Allow unauthenticated read access to KV endpoints
|
||||||
|
allow_anonymous_write: false # Allow unauthenticated write access to KV endpoints
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Security Note**: DELETE operations always require authentication when `auth_enabled: true`, regardless of anonymous access settings.
|
||||||
|
|
||||||
### Testing Strategy
|
### Testing Strategy
|
||||||
|
|
||||||
#### Integration Test Suite (`integration_test.sh`)
|
#### Integration Test Suite (`integration_test.sh`)
|
||||||
@@ -115,6 +121,11 @@ revision_history_enabled: true # Automatic versioning
|
|||||||
- **Basic functionality** - Single-node CRUD operations
|
- **Basic functionality** - Single-node CRUD operations
|
||||||
- **Cluster formation** - 2-node gossip protocol and data replication
|
- **Cluster formation** - 2-node gossip protocol and data replication
|
||||||
- **Conflict resolution** - Automated conflict detection and resolution using `test_conflict.go`
|
- **Conflict resolution** - Automated conflict detection and resolution using `test_conflict.go`
|
||||||
|
- **Authentication middleware** - Comprehensive security testing (Issue #4):
|
||||||
|
- Admin endpoints properly reject unauthenticated requests
|
||||||
|
- Admin endpoints work with valid JWT tokens
|
||||||
|
- KV endpoints respect anonymous access configuration
|
||||||
|
- Automatic root account creation and token extraction
|
||||||
|
|
||||||
The test suite uses sophisticated retry logic and timing to handle the eventually consistent nature of the system.
|
The test suite uses sophisticated retry logic and timing to handle the eventually consistent nature of the system.
|
||||||
|
|
||||||
|
12
README.md
12
README.md
@@ -113,6 +113,10 @@ clustering_enabled: true # Gossip protocol and sync
|
|||||||
rate_limiting_enabled: true # Rate limiting
|
rate_limiting_enabled: true # Rate limiting
|
||||||
revision_history_enabled: true # Automatic versioning
|
revision_history_enabled: true # Automatic versioning
|
||||||
|
|
||||||
|
# Anonymous access control (when auth_enabled: true)
|
||||||
|
allow_anonymous_read: false # Allow unauthenticated read access to KV endpoints
|
||||||
|
allow_anonymous_write: false # Allow unauthenticated write access to KV endpoints
|
||||||
|
|
||||||
# Backup configuration
|
# Backup configuration
|
||||||
backup_enabled: true # Automated backups
|
backup_enabled: true # Automated backups
|
||||||
backup_schedule: "0 0 * * *" # Daily at midnight (cron format)
|
backup_schedule: "0 0 * * *" # Daily at midnight (cron format)
|
||||||
@@ -134,7 +138,7 @@ backup_retention: 7 # Days to keep backups
|
|||||||
```bash
|
```bash
|
||||||
PUT /kv/{path}
|
PUT /kv/{path}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
Authorization: Bearer <jwt-token> # Required if auth_enabled
|
Authorization: Bearer <jwt-token> # Required if auth_enabled && !allow_anonymous_write
|
||||||
|
|
||||||
# Basic storage
|
# Basic storage
|
||||||
curl -X PUT http://localhost:8080/kv/users/john/profile \
|
curl -X PUT http://localhost:8080/kv/users/john/profile \
|
||||||
@@ -158,7 +162,7 @@ curl -X PUT http://localhost:8080/kv/cache/session/abc123 \
|
|||||||
#### Retrieve Data
|
#### Retrieve Data
|
||||||
```bash
|
```bash
|
||||||
GET /kv/{path}
|
GET /kv/{path}
|
||||||
Authorization: Bearer <jwt-token> # Required if auth_enabled
|
Authorization: Bearer <jwt-token> # Required if auth_enabled && !allow_anonymous_read
|
||||||
|
|
||||||
curl -H "Authorization: Bearer eyJ..." http://localhost:8080/kv/users/john/profile
|
curl -H "Authorization: Bearer eyJ..." http://localhost:8080/kv/users/john/profile
|
||||||
|
|
||||||
@@ -177,7 +181,7 @@ curl -H "Authorization: Bearer eyJ..." http://localhost:8080/kv/users/john/profi
|
|||||||
#### Delete Data
|
#### Delete Data
|
||||||
```bash
|
```bash
|
||||||
DELETE /kv/{path}
|
DELETE /kv/{path}
|
||||||
Authorization: Bearer <jwt-token> # Required if auth_enabled
|
Authorization: Bearer <jwt-token> # Always required when auth_enabled (no anonymous delete)
|
||||||
|
|
||||||
curl -X DELETE -H "Authorization: Bearer eyJ..." http://localhost:8080/kv/users/john/profile
|
curl -X DELETE -H "Authorization: Bearer eyJ..." http://localhost:8080/kv/users/john/profile
|
||||||
# Returns: 204 No Content
|
# Returns: 204 No Content
|
||||||
@@ -532,6 +536,8 @@ type StoredValue struct {
|
|||||||
| `bootstrap_max_age_hours` | Max historical data to sync | 720 hours | 30 days default |
|
| `bootstrap_max_age_hours` | Max historical data to sync | 720 hours | 30 days default |
|
||||||
| **Feature Toggles** |
|
| **Feature Toggles** |
|
||||||
| `auth_enabled` | JWT authentication system | true | Complete auth/authz system |
|
| `auth_enabled` | JWT authentication system | true | Complete auth/authz system |
|
||||||
|
| `allow_anonymous_read` | Allow unauthenticated read access | false | When auth_enabled, controls KV GET endpoints |
|
||||||
|
| `allow_anonymous_write` | Allow unauthenticated write access | false | When auth_enabled, controls KV PUT endpoints |
|
||||||
| `clustering_enabled` | Gossip protocol and sync | true | Distributed mode |
|
| `clustering_enabled` | Gossip protocol and sync | true | Distributed mode |
|
||||||
| `compression_enabled` | ZSTD compression | true | Reduces storage size |
|
| `compression_enabled` | ZSTD compression | true | Reduces storage size |
|
||||||
| `rate_limiting_enabled` | Rate limiting | true | Per-client limits |
|
| `rate_limiting_enabled` | Rate limiting | true | Per-client limits |
|
||||||
|
@@ -26,13 +26,15 @@ type AuthContext struct {
|
|||||||
type AuthService struct {
|
type AuthService struct {
|
||||||
db *badger.DB
|
db *badger.DB
|
||||||
logger *logrus.Logger
|
logger *logrus.Logger
|
||||||
|
config *types.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAuthService creates a new authentication service
|
// NewAuthService creates a new authentication service
|
||||||
func NewAuthService(db *badger.DB, logger *logrus.Logger) *AuthService {
|
func NewAuthService(db *badger.DB, logger *logrus.Logger, config *types.Config) *AuthService {
|
||||||
return &AuthService{
|
return &AuthService{
|
||||||
db: db,
|
db: db,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
config: config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -138,11 +138,12 @@ func (s *RateLimitService) RateLimitMiddleware(next http.HandlerFunc) http.Handl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// isAuthEnabled checks if authentication is enabled (would be passed from config)
|
// isAuthEnabled checks if authentication is enabled from config
|
||||||
func (s *AuthService) isAuthEnabled() bool {
|
func (s *AuthService) isAuthEnabled() bool {
|
||||||
// This would normally be injected from config, but for now we'll assume enabled
|
if s.config != nil {
|
||||||
// TODO: Inject config dependency
|
return s.config.AuthEnabled
|
||||||
return true
|
}
|
||||||
|
return true // Default to enabled if no config
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to check rate limits (simplified version)
|
// Helper method to check rate limits (simplified version)
|
||||||
|
@@ -55,6 +55,10 @@ func Default() *types.Config {
|
|||||||
ClusteringEnabled: true,
|
ClusteringEnabled: true,
|
||||||
RateLimitingEnabled: true,
|
RateLimitingEnabled: true,
|
||||||
RevisionHistoryEnabled: true,
|
RevisionHistoryEnabled: true,
|
||||||
|
|
||||||
|
// Default anonymous access settings (both disabled by default for security)
|
||||||
|
AllowAnonymousRead: false,
|
||||||
|
AllowAnonymousWrite: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -91,6 +91,8 @@ port: 8090
|
|||||||
data_dir: "./basic_data"
|
data_dir: "./basic_data"
|
||||||
seed_nodes: []
|
seed_nodes: []
|
||||||
log_level: "error"
|
log_level: "error"
|
||||||
|
allow_anonymous_read: true
|
||||||
|
allow_anonymous_write: true
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Start node
|
# Start node
|
||||||
@@ -134,6 +136,8 @@ log_level: "error"
|
|||||||
gossip_interval_min: 5
|
gossip_interval_min: 5
|
||||||
gossip_interval_max: 10
|
gossip_interval_max: 10
|
||||||
sync_interval: 10
|
sync_interval: 10
|
||||||
|
allow_anonymous_read: true
|
||||||
|
allow_anonymous_write: true
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Node 2 config
|
# Node 2 config
|
||||||
@@ -147,6 +151,8 @@ log_level: "error"
|
|||||||
gossip_interval_min: 5
|
gossip_interval_min: 5
|
||||||
gossip_interval_max: 10
|
gossip_interval_max: 10
|
||||||
sync_interval: 10
|
sync_interval: 10
|
||||||
|
allow_anonymous_read: true
|
||||||
|
allow_anonymous_write: true
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Start nodes
|
# Start nodes
|
||||||
@@ -242,6 +248,8 @@ data_dir: "./conflict1_data"
|
|||||||
seed_nodes: []
|
seed_nodes: []
|
||||||
log_level: "info"
|
log_level: "info"
|
||||||
sync_interval: 3
|
sync_interval: 3
|
||||||
|
allow_anonymous_read: true
|
||||||
|
allow_anonymous_write: true
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat > conflict2.yaml <<EOF
|
cat > conflict2.yaml <<EOF
|
||||||
@@ -252,6 +260,8 @@ data_dir: "./conflict2_data"
|
|||||||
seed_nodes: ["127.0.0.1:8111"]
|
seed_nodes: ["127.0.0.1:8111"]
|
||||||
log_level: "info"
|
log_level: "info"
|
||||||
sync_interval: 3
|
sync_interval: 3
|
||||||
|
allow_anonymous_read: true
|
||||||
|
allow_anonymous_write: true
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Start nodes
|
# Start nodes
|
||||||
@@ -351,6 +361,79 @@ EOF
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Test 5: Authentication middleware (Issue #4)
|
||||||
|
test_authentication_middleware() {
|
||||||
|
test_start "Authentication middleware test (Issue #4)"
|
||||||
|
|
||||||
|
# Create auth test config
|
||||||
|
cat > auth_test.yaml <<EOF
|
||||||
|
node_id: "auth-test"
|
||||||
|
bind_address: "127.0.0.1"
|
||||||
|
port: 8095
|
||||||
|
data_dir: "./auth_test_data"
|
||||||
|
seed_nodes: []
|
||||||
|
log_level: "error"
|
||||||
|
auth_enabled: true
|
||||||
|
allow_anonymous_read: false
|
||||||
|
allow_anonymous_write: false
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Start node
|
||||||
|
$BINARY auth_test.yaml >auth_test.log 2>&1 &
|
||||||
|
local pid=$!
|
||||||
|
|
||||||
|
if wait_for_service 8095; then
|
||||||
|
sleep 2 # Allow root account creation
|
||||||
|
|
||||||
|
# Extract the token from logs
|
||||||
|
local token=$(grep "Token:" auth_test.log | sed 's/.*Token: //' | tr -d '\n\r')
|
||||||
|
|
||||||
|
if [ -z "$token" ]; then
|
||||||
|
log_error "Failed to extract authentication token from logs"
|
||||||
|
kill $pid 2>/dev/null || true
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test 1: Admin endpoints should fail without authentication
|
||||||
|
local no_auth_response=$(curl -s -X POST http://localhost:8095/api/users -H "Content-Type: application/json" -d '{"nickname":"test","password":"test"}')
|
||||||
|
if echo "$no_auth_response" | grep -q "Unauthorized"; then
|
||||||
|
log_success "Admin endpoints properly reject unauthenticated requests"
|
||||||
|
else
|
||||||
|
log_error "Admin endpoints should reject unauthenticated requests, got: $no_auth_response"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test 2: Admin endpoints should work with valid authentication
|
||||||
|
local auth_response=$(curl -s -X POST http://localhost:8095/api/users -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d '{"nickname":"authtest","password":"authtest"}')
|
||||||
|
if echo "$auth_response" | grep -q "uuid"; then
|
||||||
|
log_success "Admin endpoints work with valid authentication"
|
||||||
|
else
|
||||||
|
log_error "Admin endpoints should work with authentication, got: $auth_response"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test 3: KV endpoints should require auth when anonymous access is disabled
|
||||||
|
local kv_no_auth=$(curl -s -X PUT http://localhost:8095/kv/test/auth -H "Content-Type: application/json" -d '{"test":"auth"}')
|
||||||
|
if echo "$kv_no_auth" | grep -q "Unauthorized"; then
|
||||||
|
log_success "KV endpoints properly require authentication when anonymous access disabled"
|
||||||
|
else
|
||||||
|
log_error "KV endpoints should require auth when anonymous access disabled, got: $kv_no_auth"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test 4: KV endpoints should work with valid authentication
|
||||||
|
local kv_auth=$(curl -s -X PUT http://localhost:8095/kv/test/auth -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d '{"test":"auth"}')
|
||||||
|
if echo "$kv_auth" | grep -q "uuid\|timestamp" || [ -z "$kv_auth" ]; then
|
||||||
|
log_success "KV endpoints work with valid authentication"
|
||||||
|
else
|
||||||
|
log_error "KV endpoints should work with authentication, got: $kv_auth"
|
||||||
|
fi
|
||||||
|
|
||||||
|
kill $pid 2>/dev/null || true
|
||||||
|
sleep 2
|
||||||
|
else
|
||||||
|
log_error "Auth test node failed to start"
|
||||||
|
kill $pid 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Main test execution
|
# Main test execution
|
||||||
main() {
|
main() {
|
||||||
echo "=================================================="
|
echo "=================================================="
|
||||||
@@ -368,6 +451,7 @@ main() {
|
|||||||
test_basic_functionality
|
test_basic_functionality
|
||||||
test_cluster_formation
|
test_cluster_formation
|
||||||
test_conflict_resolution
|
test_conflict_resolution
|
||||||
|
test_authentication_middleware
|
||||||
|
|
||||||
# Results
|
# Results
|
||||||
echo "=================================================="
|
echo "=================================================="
|
||||||
|
@@ -11,10 +11,33 @@ func (s *Server) setupRoutes() *mux.Router {
|
|||||||
// Health endpoint (always available)
|
// Health endpoint (always available)
|
||||||
router.HandleFunc("/health", s.healthHandler).Methods("GET")
|
router.HandleFunc("/health", s.healthHandler).Methods("GET")
|
||||||
|
|
||||||
// KV endpoints (always available - see issue #5 for anonymous access control)
|
// KV endpoints (with conditional authentication based on anonymous access settings)
|
||||||
router.HandleFunc("/kv/{path:.+}", s.getKVHandler).Methods("GET")
|
// GET endpoint - require auth if anonymous read is disabled
|
||||||
router.HandleFunc("/kv/{path:.+}", s.putKVHandler).Methods("PUT")
|
if s.config.AuthEnabled && !s.config.AllowAnonymousRead {
|
||||||
router.HandleFunc("/kv/{path:.+}", s.deleteKVHandler).Methods("DELETE")
|
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)
|
// Member endpoints (available when clustering is enabled)
|
||||||
if s.config.ClusteringEnabled {
|
if s.config.ClusteringEnabled {
|
||||||
@@ -32,20 +55,44 @@ func (s *Server) setupRoutes() *mux.Router {
|
|||||||
|
|
||||||
// Authentication and user management endpoints (available when auth is enabled)
|
// Authentication and user management endpoints (available when auth is enabled)
|
||||||
if s.config.AuthEnabled {
|
if s.config.AuthEnabled {
|
||||||
// User Management endpoints
|
// User Management endpoints (with authentication middleware)
|
||||||
router.HandleFunc("/api/users", s.createUserHandler).Methods("POST")
|
router.Handle("/api/users", s.authService.Middleware(
|
||||||
router.HandleFunc("/api/users/{uuid}", s.getUserHandler).Methods("GET")
|
[]string{"admin:users:create"}, nil, "",
|
||||||
router.HandleFunc("/api/users/{uuid}", s.updateUserHandler).Methods("PUT")
|
)(s.createUserHandler)).Methods("POST")
|
||||||
router.HandleFunc("/api/users/{uuid}", s.deleteUserHandler).Methods("DELETE")
|
|
||||||
|
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
|
// Group Management endpoints (with authentication middleware)
|
||||||
router.HandleFunc("/api/groups", s.createGroupHandler).Methods("POST")
|
router.Handle("/api/groups", s.authService.Middleware(
|
||||||
router.HandleFunc("/api/groups/{uuid}", s.getGroupHandler).Methods("GET")
|
[]string{"admin:groups:create"}, nil, "",
|
||||||
router.HandleFunc("/api/groups/{uuid}", s.updateGroupHandler).Methods("PUT")
|
)(s.createGroupHandler)).Methods("POST")
|
||||||
router.HandleFunc("/api/groups/{uuid}", s.deleteGroupHandler).Methods("DELETE")
|
|
||||||
|
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
|
// Token Management endpoints (with authentication middleware)
|
||||||
router.HandleFunc("/api/tokens", s.createTokenHandler).Methods("POST")
|
router.Handle("/api/tokens", s.authService.Middleware(
|
||||||
|
[]string{"admin:tokens:create"}, nil, "",
|
||||||
|
)(s.createTokenHandler)).Methods("POST")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Revision History endpoints (available when revision history is enabled)
|
// Revision History endpoints (available when revision history is enabled)
|
||||||
|
@@ -118,7 +118,7 @@ func NewServer(config *types.Config) (*Server, error) {
|
|||||||
server.revisionService = storage.NewRevisionService(storageService)
|
server.revisionService = storage.NewRevisionService(storageService)
|
||||||
|
|
||||||
// Initialize authentication service
|
// Initialize authentication service
|
||||||
server.authService = auth.NewAuthService(db, logger)
|
server.authService = auth.NewAuthService(db, logger, config)
|
||||||
|
|
||||||
// Setup initial root account if needed (Issue #3)
|
// Setup initial root account if needed (Issue #3)
|
||||||
if config.AuthEnabled {
|
if config.AuthEnabled {
|
||||||
|
@@ -273,4 +273,8 @@ type Config struct {
|
|||||||
ClusteringEnabled bool `yaml:"clustering_enabled"` // Enable/disable clustering/gossip
|
ClusteringEnabled bool `yaml:"clustering_enabled"` // Enable/disable clustering/gossip
|
||||||
RateLimitingEnabled bool `yaml:"rate_limiting_enabled"` // Enable/disable rate limiting
|
RateLimitingEnabled bool `yaml:"rate_limiting_enabled"` // Enable/disable rate limiting
|
||||||
RevisionHistoryEnabled bool `yaml:"revision_history_enabled"` // Enable/disable revision history
|
RevisionHistoryEnabled bool `yaml:"revision_history_enabled"` // Enable/disable revision history
|
||||||
|
|
||||||
|
// Anonymous access control (Issue #5)
|
||||||
|
AllowAnonymousRead bool `yaml:"allow_anonymous_read"` // Allow unauthenticated read access to KV endpoints
|
||||||
|
AllowAnonymousWrite bool `yaml:"allow_anonymous_write"` // Allow unauthenticated write access to KV endpoints
|
||||||
}
|
}
|
Reference in New Issue
Block a user