Files

415 lines
15 KiB
Markdown

# RedFlag Token Authentication System Analysis
Based on comprehensive analysis of the RedFlag codebase, here's a detailed breakdown of the token authentication system:
## Executive Summary
RedFlag uses a three-tier token system with different lifetimes and purposes:
1. **Registration Tokens** - One-time use for initial agent enrollment (multi-seat capable)
2. **JWT Access Tokens** - Short-lived (24h) stateless tokens for API authentication
3. **Refresh Tokens** - Long-lived (90d) rotating tokens for automatic renewal
**Important Clarification**: The "rotating token system" is **ACTIVE and working** (not discontinued). It refers to the refresh token system that rotates every 24h during renewal.
## 1. Registration Tokens (One-Time Use Multi-Seat Tokens)
### Purpose & Characteristics
- **Initial agent registration/enrollment** with the server
- **Multi-seat support** - Single token can register multiple agents
- **One-time use per agent** - Each agent uses it once during registration
- **Configurable expiration** - Admins set expiration (max 7 days)
### Technical Implementation
#### Token Generation
```go
// aggregator-server/internal/config/config.go:138-144
func GenerateSecureToken() string {
bytes := make([]byte, 32)
if _, err := rand.Read(bytes); err != nil {
return ""
}
return hex.EncodeToString(bytes)
}
```
- **Method**: Cryptographically secure 32-byte random token → 64-character hex string
- **Algorithm**: `crypto/rand.Read()` for entropy
#### Database Schema
```sql
-- 011_create_registration_tokens_table.up.sql
CREATE TABLE registration_tokens (
token VARCHAR(64) UNIQUE PRIMARY KEY,
max_seats INT DEFAULT 1,
seats_used INT DEFAULT 0,
expires_at TIMESTAMP NOT NULL,
status ENUM('active', 'used', 'expired', 'revoked') DEFAULT 'active',
metadata JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
```
#### Seat Tracking System
- **Validation**: `status = 'active' AND expires_at > NOW() AND seats_used < max_seats`
- **Usage Tracking**: `registration_token_usage` table maintains audit trail
- **Status Flow**: `active``used` (seats exhausted) or `expired` (time expires)
### Registration Flow
```
1. Admin creates registration token with seat limit
2. Token distributed to agents (via config, environment variable, etc.)
3. Agent uses token for initial registration at /api/v1/agents/register
4. Server validates token and decrements available seats
5. Server generates AgentID + JWT + Refresh token
6. Agent saves AgentID, discards registration token
```
## 2. JWT Access Tokens (Stateless Short-Lived Tokens)
### Purpose & Characteristics
- **API authentication** for agent-server communication
- **Web dashboard authentication** for users
- **Stateless validation** - No database lookup required
- **Short lifetime** - 24 hours for security
### Token Structure
#### Agent JWT Claims
```go
// aggregator-server/internal/api/middleware/auth.go:13-17
type AgentClaims struct {
AgentID string `json:"agent_id"`
jwt.RegisteredClaims
}
```
#### User JWT Claims
```go
// aggregator-server/internal/api/handlers/auth.go:41-47
type UserClaims struct {
UserID int `json:"user_id"`
Username string `json:"username"`
Role string `json:"role"`
jwt.RegisteredClaims
}
```
### Security Properties
- **Algorithm**: HS256 using shared secret
- **Secret Storage**: `REDFLAG_JWT_SECRET` environment variable
- **Validation**: Bearer token in `Authorization: Bearer {token}` header
- **Stateless**: Server validates using secret, no database lookup needed
### Key Security Consideration
```go
// aggregator-server/cmd/server/main.go:130
if cfg.Admin.JWTSecret == "" {
cfg.Admin.JWTSecret = GenerateSecureToken()
log.Printf("Generated JWT secret: %s", cfg.Admin.JWTSecret) // Debug exposure!
}
```
- **Development Risk**: JWT secret logged in debug mode
- **Production Requirement**: Must set `REDFLAG_JWT_SECRET` consistently
## 3. Refresh Tokens (Rotating Long-Lived Tokens)
### Purpose & Characteristics
- **Automatic agent renewal** without re-registration
- **Long lifetime** - 90 days with sliding window
- **Rotating mechanism** - New tokens issued on each renewal
- **Secure storage** - Only SHA-256 hashes stored in database
### Database Schema
```sql
-- 008_create_refresh_tokens_table.up.sql
CREATE TABLE refresh_tokens (
agent_id UUID REFERENCES agents(id) PRIMARY KEY,
token_hash VARCHAR(64) UNIQUE NOT NULL,
expires_at TIMESTAMP NOT NULL,
last_used_at TIMESTAMP DEFAULT NOW(),
revoked BOOLEAN DEFAULT FALSE
);
```
### Token Generation & Security
```go
// aggregator-server/internal/database/queries/refresh_tokens.go
func GenerateRefreshToken() (string, error) {
bytes := make([]byte, 32)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
func HashRefreshToken(token string) string {
hash := sha256.Sum256([]byte(token))
return hex.EncodeToString(hash[:])
}
```
### Renewal Process (The "Rotating Token System")
```
1. Agent JWT expires (after 24h)
2. Agent sends refresh request to /api/v1/agents/renew
3. Server validates refresh token hash against database
4. Server generates NEW JWT access token (24h)
5. Server updates refresh_token.last_used_at
6. Server resets refresh_token.expires_at to NOW() + 90 days (sliding window)
7. Agent updates config with new JWT token
```
#### Key Features
- **Sliding Window Expiration**: 90-day window resets on each use
- **Hash Storage**: Only SHA-256 hashes stored, plaintext tokens never persisted
- **Rotation**: New JWT issued each time, refresh token extended
- **Revocation Support**: Manual revocation possible via database
## 4. Agent Configuration & Token Usage
### Configuration Structure
```go
// aggregator-agent/internal/config/config.go:48-90
type Config struct {
// ... other fields ...
RegistrationToken string `json:"registration_token,omitempty"` // One-time registration token
Token string `json:"token"` // JWT access token (24h)
RefreshToken string `json:"refresh_token"` // Refresh token (90d)
}
```
### File Storage & Security
```go
// config.go:274-280
func (c *Config) Save() error {
// ... validation logic ...
jsonData, err := json.MarshalIndent(c, "", " ")
if err != nil {
return err
}
return os.WriteFile(c.Path, jsonData, 0600) // Owner read/write only
}
```
- **Storage**: Plaintext JSON configuration file
- **Permissions**: 0600 (owner read/write only)
- **Location**: Typically `/etc/redflag/agent.json` or user-specified path
### Agent Registration Flow
```go
// aggregator-agent/cmd/agent/main.go:450-476
func runRegistration(cfg *config.Config) (*config.Config, error) {
if cfg.RegistrationToken == "" {
return nil, fmt.Errorf("registration token required for initial setup")
}
// Create temporary client with registration token
client := api.NewClient("", cfg.ServerURL, cfg.SkipTLSVerify)
// Register with server
regReq := api.RegisterRequest{
RegistrationToken: cfg.RegistrationToken,
Hostname: cfg.Hostname,
Version: version.Version,
}
// Process registration response
// ...
cfg.Token = resp.Token // JWT access token
cfg.RefreshToken = resp.RefreshToken
cfg.AgentID = resp.AgentID
return cfg, nil
}
```
### Token Renewal Logic
```go
// aggregator-agent/cmd/agent/main.go:484-519
func renewTokenIfNeeded(cfg *config.Config) error {
if cfg.RefreshToken == "" {
return fmt.Errorf("no refresh token available")
}
// Create temporary client without auth for renewal
client := api.NewClient("", cfg.ServerURL, cfg.SkipTLSVerify)
renewReq := api.RenewRequest{
AgentID: cfg.AgentID,
RefreshToken: cfg.RefreshToken,
}
resp, err := client.RenewToken(renewReq)
if err != nil {
return err // Falls back to re-registration
}
// Update config with new JWT token
cfg.Token = resp.Token
return cfg.Save() // Persist updated config
}
```
## 5. Security Analysis & Configuration Encryption Implications
### Current Security Posture
#### Strengths
- **Strong Token Generation**: Cryptographically secure random tokens
- **Proper Token Separation**: Different tokens for different purposes
- **Hash Storage**: Refresh tokens stored as hashes only
- **JWT Stateless Validation**: No database storage for access tokens
- **File Permissions**: Config files with 0600 permissions
#### Vulnerabilities
- **Plaintext Storage**: All tokens stored in clear text JSON
- **JWT Secret Exposure**: Debug logging in development
- **Registration Token Exposure**: Stored in plaintext until used
- **Config File Access**: Anyone with file access can steal tokens
### Configuration Encryption Impact Analysis
#### Critical Challenge: Token Refresh Workflow
```
Current Flow:
1. Agent reads config (plaintext) → gets refresh_token
2. Agent calls /api/v1/agents/renew with refresh_token
3. Server returns new JWT access_token
4. Agent writes new access_token to config (plaintext)
Encrypted Config Flow:
1. Agent must decrypt config to get refresh_token
2. Agent calls /api/v1/agents/renew
3. Server returns new JWT access_token
4. Agent must encrypt and write updated config
```
#### Key Implementation Challenges
1. **Key Management**
- Where to store encryption keys?
- How to handle key rotation?
- Agent process must have access to keys
2. **Atomic Operations**
- Decrypt → Modify → Encrypt must be atomic
- Prevent partial writes during token updates
- Handle encryption/decryption failures gracefully
3. **Debugging & Recovery**
- Encrypted configs complicate debugging
- Lost encryption keys = lost agent registration
- Backup/restore complexity increases
4. **Performance Overhead**
- Decryption on every config read
- Encryption on every token renewal
- Memory footprint for decrypted config
#### Recommended Encryption Strategy
1. **Selective Field Encryption**
```json
{
"agent_id": "123e4567-e89b-12d3-a456-426614174000",
"token": "enc:v1:aes256gcm:encrypted_jwt_token_here",
"refresh_token": "enc:v1:aes256gcm:encrypted_refresh_token_here",
"server_url": "https://redflag.example.com"
}
```
- Encrypt only sensitive fields (tokens)
- Preserve JSON structure for compatibility
- Include version prefix for future migration
2. **Key Storage Options**
- **Environment Variables**: `REDFLAG_ENCRYPTION_KEY`
- **Kernel Keyring**: Store keys in OS keyring
- **Dedicated KMS**: AWS KMS, Azure Key Vault, etc.
- **File-Based**: Encrypted key file with strict permissions
3. **Graceful Degradation**
```go
func LoadConfig() (*Config, error) {
// Try encrypted first
if cfg, err := loadEncryptedConfig(); err == nil {
return cfg, nil
}
// Fallback to plaintext for migration
return loadPlaintextConfig()
}
```
4. **Migration Path**
- Detect plaintext configs and auto-encrypt on first load
- Provide migration utilities for existing deployments
- Support both encrypted and plaintext during transition
## 6. Token Lifecycle Summary
```
Registration Token Lifecycle:
┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌─────────────┐
│ Generated │───▶│ Distributed │───▶│ Used │───▶│ Expired/ │
│ (Admin UI) │ │ (To Agents) │ │ (Agent Reg) │ │ Revoked │
└─────────────┘ └──────────────┘ └─────────────┘ └─────────────┘
┌──────────────────┐
│ Agent Registration│
│ (Creates: │
│ AgentID, JWT, │
│ RefreshToken) │
└──────────────────┘
JWT Access Token Lifecycle:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Generated │───▶│ Valid │───▶│ Expired │───▶│ Renewed │
│ (Reg/Renew) │ │ (24h) │ │ │ │ (via Refresh)│
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
┌──────────────┐
│ API Requests │
│ (Bearer Auth)│
└──────────────┘
Refresh Token Lifecycle (The "Rotating System"):
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Generated │───▶│ Valid │───▶│ Used for │───▶│ Rotated │
│(Registration)│ │ (90d) │ │ Renewal │ │ (90d Reset) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
```
## 7. Security Recommendations
### Immediate Improvements
1. **Remove JWT Secret Logging** in production builds
2. **Implement Config File Encryption** for sensitive fields
3. **Add Token Usage Monitoring** and anomaly detection
4. **Secure Registration Token Distribution** beyond config files
### Configuration Encryption Implementation
1. **Use AES-256-GCM** for field-level encryption
2. **Store encryption keys** in kernel keyring or secure environment
3. **Implement atomic config updates** to prevent corruption
4. **Provide migration utilities** for existing deployments
5. **Add config backup/restore** functionality
### Long-term Security Enhancements
1. **Hardware Security Modules (HSMs)** for key management
2. **Certificate-based authentication** as alternative to tokens
3. **Zero-trust architecture** for agent-server communication
4. **Regular security audits** and penetration testing
## 8. Conclusion
The RedFlag token authentication system is well-designed with proper separation of concerns and appropriate token lifetimes. The main security consideration is the plaintext storage of tokens in agent configuration files.
**Key Takeaways:**
- The rotating token system is **ACTIVE** and refers to refresh token rotation
- Config encryption is feasible but requires careful key management
- Token refresh workflow must remain functional after encryption
- Gradual migration path is essential for existing deployments
The recommended approach is **selective field encryption** with strong key management practices, ensuring the token refresh workflow remains operational while significantly improving security at rest.