kevo/pkg/transport/access_control.go
Jeremy Tregunna 1974dbfa7b
feat: implement access control and persistence for replicas
- Add access control system for replica authorization
- Implement persistence of replica information
- Add stale replica detection
- Create comprehensive tests for replica registration
- Update ReplicationServiceServer to use new components
2025-04-26 14:23:42 -06:00

194 lines
4.8 KiB
Go

package transport
import (
"errors"
"sync"
"time"
)
var (
// ErrAccessDenied indicates the replica is not authorized
ErrAccessDenied = errors.New("access denied")
// ErrAuthenticationFailed indicates authentication failure
ErrAuthenticationFailed = errors.New("authentication failed")
// ErrInvalidToken indicates an invalid or expired token
ErrInvalidToken = errors.New("invalid or expired token")
)
// AuthMethod defines authentication methods for replicas
type AuthMethod string
const (
// AuthNone means no authentication required (not recommended for production)
AuthNone AuthMethod = "none"
// AuthToken uses a pre-shared token for authentication
AuthToken AuthMethod = "token"
)
// AccessLevel defines permission levels for replicas
type AccessLevel int
const (
// AccessNone has no permissions
AccessNone AccessLevel = iota
// AccessReadOnly can only read from the primary
AccessReadOnly
// AccessReadWrite can read and receive updates from the primary
AccessReadWrite
// AccessAdmin has full control including management operations
AccessAdmin
)
// ReplicaCredentials contains authentication information for a replica
type ReplicaCredentials struct {
ReplicaID string
AuthMethod AuthMethod
Token string // Token for authentication (in a production system, this would be hashed)
AccessLevel AccessLevel
ExpiresAt time.Time // Token expiration time (zero means no expiration)
}
// AccessController manages authentication and authorization for replicas
type AccessController struct {
mu sync.RWMutex
credentials map[string]*ReplicaCredentials // Map of replicaID -> credentials
enabled bool
defaultAuth AuthMethod
}
// NewAccessController creates a new access controller
func NewAccessController(enabled bool, defaultAuth AuthMethod) *AccessController {
return &AccessController{
credentials: make(map[string]*ReplicaCredentials),
enabled: enabled,
defaultAuth: defaultAuth,
}
}
// IsEnabled returns whether access control is enabled
func (ac *AccessController) IsEnabled() bool {
return ac.enabled
}
// DefaultAuthMethod returns the default authentication method
func (ac *AccessController) DefaultAuthMethod() AuthMethod {
return ac.defaultAuth
}
// RegisterReplica registers a new replica with credentials
func (ac *AccessController) RegisterReplica(creds *ReplicaCredentials) error {
if !ac.enabled {
// If access control is disabled, we still register the replica but don't enforce controls
creds.AccessLevel = AccessAdmin
}
ac.mu.Lock()
defer ac.mu.Unlock()
// Store credentials (in a real system, we'd hash tokens here)
ac.credentials[creds.ReplicaID] = creds
return nil
}
// RemoveReplica removes a replica's credentials
func (ac *AccessController) RemoveReplica(replicaID string) {
ac.mu.Lock()
defer ac.mu.Unlock()
delete(ac.credentials, replicaID)
}
// AuthenticateReplica authenticates a replica based on the provided credentials
func (ac *AccessController) AuthenticateReplica(replicaID, token string) error {
if !ac.enabled {
return nil // Authentication disabled
}
ac.mu.RLock()
defer ac.mu.RUnlock()
creds, exists := ac.credentials[replicaID]
if !exists {
return ErrAccessDenied
}
// Check if credentials are expired
if !creds.ExpiresAt.IsZero() && time.Now().After(creds.ExpiresAt) {
return ErrInvalidToken
}
// Authenticate based on method
switch creds.AuthMethod {
case AuthNone:
return nil // No authentication required
case AuthToken:
// In a real system, we'd compare hashed tokens
if token != creds.Token {
return ErrAuthenticationFailed
}
return nil
default:
return ErrAuthenticationFailed
}
}
// AuthorizeReplicaAction checks if a replica has permission for an action
func (ac *AccessController) AuthorizeReplicaAction(replicaID string, requiredLevel AccessLevel) error {
if !ac.enabled {
return nil // Authorization disabled
}
ac.mu.RLock()
defer ac.mu.RUnlock()
creds, exists := ac.credentials[replicaID]
if !exists {
return ErrAccessDenied
}
// Check permissions
if creds.AccessLevel < requiredLevel {
return ErrAccessDenied
}
return nil
}
// GetReplicaAccessLevel returns the access level for a replica
func (ac *AccessController) GetReplicaAccessLevel(replicaID string) (AccessLevel, error) {
if !ac.enabled {
return AccessAdmin, nil // If disabled, return highest access level
}
ac.mu.RLock()
defer ac.mu.RUnlock()
creds, exists := ac.credentials[replicaID]
if !exists {
return AccessNone, ErrAccessDenied
}
return creds.AccessLevel, nil
}
// SetReplicaAccessLevel updates the access level for a replica
func (ac *AccessController) SetReplicaAccessLevel(replicaID string, level AccessLevel) error {
ac.mu.Lock()
defer ac.mu.Unlock()
creds, exists := ac.credentials[replicaID]
if !exists {
return ErrAccessDenied
}
creds.AccessLevel = level
return nil
}