Files

15 KiB

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

// 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

-- 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: activeused (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

// aggregator-server/internal/api/middleware/auth.go:13-17
type AgentClaims struct {
    AgentID string `json:"agent_id"`
    jwt.RegisteredClaims
}

User JWT Claims

// 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

// 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

-- 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

// 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

// 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

// 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

// 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

// 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
  1. Selective Field Encryption

    {
      "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

    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.