512 lines
18 KiB
Markdown
512 lines
18 KiB
Markdown
# BZZZ Licensing Development Plan
|
||
|
||
**Date**: 2025-09-01
|
||
**Branch**: `feature/licensing-enforcement`
|
||
**Status**: Ready for implementation (depends on KACHING Phase 1)
|
||
**Priority**: HIGH - Revenue protection and license enforcement
|
||
|
||
## Executive Summary
|
||
|
||
BZZZ currently has **zero license enforcement** in production. The system collects license information during setup but completely ignores it at runtime, allowing unlimited unlicensed usage. This plan implements comprehensive license enforcement integrated with KACHING license authority.
|
||
|
||
## Current State Analysis
|
||
|
||
### ✅ Existing License Components
|
||
- License validation UI component (`install/config-ui/app/setup/components/LicenseValidation.tsx`)
|
||
- Terms and conditions acceptance (`install/config-ui/app/setup/components/TermsAndConditions.tsx`)
|
||
- Mock license validation endpoint (`main.go` lines 1584-1618)
|
||
- Test license key documentation (`TEST_LICENSE_KEY.txt`)
|
||
|
||
### ❌ Critical Security Gap
|
||
- **License data NOT saved to configuration** - Setup collects but discards license info
|
||
- **Zero runtime license validation** - System starts without any license checks
|
||
- **No integration with license server** - Mock validation only, no real enforcement
|
||
- **No cluster binding** - No protection against license sharing across multiple clusters
|
||
- **No license expiration checks** - Licenses never expire in practice
|
||
- **No feature restrictions** - All features available regardless of license tier
|
||
|
||
### Current Configuration Structure Gap
|
||
|
||
**Setup Config Missing License Data**:
|
||
```go
|
||
// api/setup_manager.go line 539 - SetupConfig struct
|
||
type SetupConfig struct {
|
||
Agent *AgentConfig `json:"agent"`
|
||
GitHub *GitHubConfig `json:"github"`
|
||
// ... other configs ...
|
||
// ❌ NO LICENSE FIELD - license data is collected but discarded!
|
||
}
|
||
```
|
||
|
||
**Main Config Missing License Support**:
|
||
```go
|
||
// pkg/config/config.go - Config struct
|
||
type Config struct {
|
||
Agent AgentConfig `yaml:"agent" json:"agent"`
|
||
GitHub GitHubConfig `yaml:"github" json:"github"`
|
||
// ... other configs ...
|
||
// ❌ NO LICENSE FIELD - runtime ignores licensing completely!
|
||
}
|
||
```
|
||
|
||
## Development Phases
|
||
|
||
### Phase 2A: Configuration System Integration (PRIORITY 1)
|
||
**Goal**: Make license data part of BZZZ configuration
|
||
|
||
#### 1. Update Configuration Structures
|
||
```go
|
||
// Add to pkg/config/config.go
|
||
type Config struct {
|
||
// ... existing fields ...
|
||
License LicenseConfig `yaml:"license" json:"license"`
|
||
}
|
||
|
||
type LicenseConfig struct {
|
||
ServerURL string `yaml:"server_url" json:"server_url"`
|
||
LicenseKey string `yaml:"license_key" json:"license_key"`
|
||
ClusterID string `yaml:"cluster_id" json:"cluster_id"`
|
||
Email string `yaml:"email" json:"email"`
|
||
OrganizationName string `yaml:"organization_name,omitempty" json:"organization_name,omitempty"`
|
||
|
||
// Runtime state (populated during activation)
|
||
Token string `yaml:"-" json:"-"` // Don't persist token to file
|
||
TokenExpiry time.Time `yaml:"-" json:"-"`
|
||
LicenseType string `yaml:"license_type,omitempty" json:"license_type,omitempty"`
|
||
MaxNodes int `yaml:"max_nodes,omitempty" json:"max_nodes,omitempty"`
|
||
Features []string `yaml:"features,omitempty" json:"features,omitempty"`
|
||
ExpiresAt time.Time `yaml:"expires_at,omitempty" json:"expires_at,omitempty"`
|
||
|
||
// Setup verification
|
||
ValidatedAt time.Time `yaml:"validated_at" json:"validated_at"`
|
||
TermsAcceptedAt time.Time `yaml:"terms_accepted_at" json:"terms_accepted_at"`
|
||
}
|
||
```
|
||
|
||
#### 2. Update Setup Configuration
|
||
```go
|
||
// Add to api/setup_manager.go SetupConfig struct
|
||
type SetupConfig struct {
|
||
// ... existing fields ...
|
||
License *LicenseConfig `json:"license"`
|
||
Terms *TermsAcceptance `json:"terms"`
|
||
}
|
||
|
||
type TermsAcceptance struct {
|
||
Agreed bool `json:"agreed"`
|
||
Timestamp time.Time `json:"timestamp"`
|
||
}
|
||
```
|
||
|
||
#### 3. Fix Setup Save Process
|
||
Currently in `generateAndDeployConfig()`, license data is completely ignored. Fix this:
|
||
```go
|
||
// api/setup_manager.go - Update generateAndDeployConfig()
|
||
func (sm *SetupManager) generateAndDeployConfig(setupData SetupConfig) error {
|
||
config := Config{
|
||
Agent: setupData.Agent,
|
||
GitHub: setupData.GitHub,
|
||
License: setupData.License, // ✅ ADD THIS - currently missing!
|
||
// ... other fields ...
|
||
}
|
||
// ... save to config file ...
|
||
}
|
||
```
|
||
|
||
### Phase 2B: License Validation Integration (PRIORITY 2)
|
||
**Goal**: Replace mock validation with KACHING license server
|
||
|
||
#### 1. Replace Mock License Validation
|
||
**Current (main.go lines 1584-1618)**:
|
||
```go
|
||
// ❌ REMOVE: Hardcoded mock validation
|
||
validLicenseKey := "BZZZ-2025-DEMO-EVAL-001"
|
||
if licenseRequest.LicenseKey != validLicenseKey {
|
||
// ... return error ...
|
||
}
|
||
```
|
||
|
||
**New KACHING Integration**:
|
||
```go
|
||
// ✅ ADD: Real license server validation
|
||
func (sm *SetupManager) validateLicenseWithKACHING(email, licenseKey, orgName string) (*LicenseValidationResponse, error) {
|
||
client := &http.Client{Timeout: 30 * time.Second}
|
||
|
||
reqBody := map[string]string{
|
||
"email": email,
|
||
"license_key": licenseKey,
|
||
"organization_name": orgName,
|
||
}
|
||
|
||
// Call KACHING license server
|
||
resp, err := client.Post(
|
||
sm.config.LicenseServerURL+"/v1/license/activate",
|
||
"application/json",
|
||
bytes.NewBuffer(jsonData),
|
||
)
|
||
|
||
// Parse response and return license details
|
||
// Store cluster_id for runtime use
|
||
}
|
||
```
|
||
|
||
#### 2. Generate and Persist Cluster ID
|
||
```go
|
||
func generateClusterID() string {
|
||
// Generate unique cluster identifier
|
||
// Format: bzzz-cluster-<uuid>-<hostname>
|
||
hostname, _ := os.Hostname()
|
||
clusterUUID := uuid.New().String()[:8]
|
||
return fmt.Sprintf("bzzz-cluster-%s-%s", clusterUUID, hostname)
|
||
}
|
||
```
|
||
|
||
### Phase 2C: Runtime License Enforcement (PRIORITY 3)
|
||
**Goal**: Enforce license validation during BZZZ startup and operation
|
||
|
||
#### 1. Add License Validation to Startup Sequence
|
||
**Current startup logic (main.go lines 154-169)**:
|
||
```go
|
||
func main() {
|
||
// ... config loading ...
|
||
|
||
if !cfg.IsValidConfiguration() {
|
||
startSetupMode(configPath)
|
||
return
|
||
}
|
||
|
||
// ✅ ADD LICENSE VALIDATION HERE - currently missing!
|
||
if err := validateLicenseForRuntime(cfg); err != nil {
|
||
fmt.Printf("❌ License validation failed: %v\n", err)
|
||
fmt.Printf("🔧 License issue detected, entering setup mode...\n")
|
||
startSetupMode(configPath)
|
||
return
|
||
}
|
||
|
||
// Continue with normal startup...
|
||
startNormalMode(cfg)
|
||
}
|
||
```
|
||
|
||
#### 2. Implement Runtime License Validation
|
||
```go
|
||
func validateLicenseForRuntime(cfg *Config) error {
|
||
if cfg.License.LicenseKey == "" {
|
||
return fmt.Errorf("no license key configured")
|
||
}
|
||
|
||
if cfg.License.ClusterID == "" {
|
||
return fmt.Errorf("no cluster ID configured")
|
||
}
|
||
|
||
// Check license expiration
|
||
if !cfg.License.ExpiresAt.IsZero() && time.Now().After(cfg.License.ExpiresAt) {
|
||
return fmt.Errorf("license expired on %v", cfg.License.ExpiresAt.Format("2006-01-02"))
|
||
}
|
||
|
||
// Attempt license activation with KACHING
|
||
client := NewLicenseClient(cfg.License.ServerURL)
|
||
token, err := client.ActivateLicense(cfg.License.LicenseKey, cfg.License.ClusterID)
|
||
if err != nil {
|
||
return fmt.Errorf("license activation failed: %w", err)
|
||
}
|
||
|
||
// Store token for heartbeat worker
|
||
cfg.License.Token = token.AccessToken
|
||
cfg.License.TokenExpiry = token.ExpiresAt
|
||
|
||
return nil
|
||
}
|
||
```
|
||
|
||
#### 3. Background License Heartbeat Worker
|
||
```go
|
||
func startLicenseHeartbeatWorker(cfg *Config, shutdownChan chan struct{}) {
|
||
ticker := time.NewTicker(15 * time.Minute) // Heartbeat every 15 minutes
|
||
defer ticker.Stop()
|
||
|
||
client := NewLicenseClient(cfg.License.ServerURL)
|
||
|
||
for {
|
||
select {
|
||
case <-ticker.C:
|
||
// Send heartbeat to KACHING
|
||
token, err := client.SendHeartbeat(cfg.License.LicenseKey, cfg.License.ClusterID, cfg.License.Token)
|
||
if err != nil {
|
||
log.Printf("❌ License heartbeat failed: %v", err)
|
||
// Implement exponential backoff and graceful degradation
|
||
handleLicenseHeartbeatFailure(err)
|
||
continue
|
||
}
|
||
|
||
// Update token if refreshed
|
||
if token.AccessToken != cfg.License.Token {
|
||
cfg.License.Token = token.AccessToken
|
||
cfg.License.TokenExpiry = token.ExpiresAt
|
||
log.Printf("✅ License token refreshed, expires: %v", token.ExpiresAt)
|
||
}
|
||
|
||
case <-shutdownChan:
|
||
// Deactivate license on shutdown
|
||
err := client.DeactivateLicense(cfg.License.LicenseKey, cfg.License.ClusterID)
|
||
if err != nil {
|
||
log.Printf("⚠️ Failed to deactivate license on shutdown: %v", err)
|
||
} else {
|
||
log.Printf("✅ License deactivated on shutdown")
|
||
}
|
||
return
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 4. License Failure Handling
|
||
```go
|
||
func handleLicenseHeartbeatFailure(err error) {
|
||
// Parse error type
|
||
if isLicenseSuspended(err) {
|
||
log.Printf("🚨 LICENSE SUSPENDED - STOPPING BZZZ OPERATIONS")
|
||
// Hard stop - license suspended by admin
|
||
os.Exit(1)
|
||
} else if isNetworkError(err) {
|
||
log.Printf("⚠️ Network error during heartbeat - continuing with grace period")
|
||
// Continue operation with exponential backoff
|
||
// Stop if grace period exceeded (e.g., 24 hours)
|
||
} else {
|
||
log.Printf("❌ Unknown license error: %v", err)
|
||
// Implement appropriate fallback
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 5. Token Versioning and Offline Tokens
|
||
```go
|
||
// On every heartbeat response, compare token_version
|
||
if token.TokenVersion > cfg.License.TokenVersion {
|
||
// Server bumped version (suspend/cancel or rotation)
|
||
cfg.License.TokenVersion = token.TokenVersion
|
||
}
|
||
|
||
// If server rejects with "stale token_version" → re-activate to fetch a fresh token
|
||
|
||
// Offline tokens
|
||
// Accept an Ed25519-signed offline token with short expiry when network is unavailable.
|
||
// Validate signature + expiry locally; on reconnect, immediately validate with server.
|
||
```
|
||
|
||
#### 6. Response Handling Map (recommended)
|
||
- 200 OK (heartbeat): update token, token_version
|
||
- 403 Forbidden: suspended/cancelled → fail closed, stop operations
|
||
- 409 Conflict: cluster slot in use → backoff and re‑activate after grace (or operator action)
|
||
- 5xx / network error: continue in grace window with exponential backoff; exit when grace exceeded
|
||
|
||
#### 7. Cluster Identity and Telemetry
|
||
- Generate cluster_id once; persist in config; include hostname/IP in activation metadata for admin visibility.
|
||
- Emit per‑job telemetry to KACHING (align keys: `tokens`, `context_operations`, `cpu_hours`, `temporal_nav_hops`) to drive quotas and upgrade suggestions.
|
||
|
||
### Phase 2D: Feature Enforcement (PRIORITY 4)
|
||
**Goal**: Restrict features based on license tier
|
||
|
||
#### 1. Feature Gate Implementation
|
||
```go
|
||
type FeatureGate struct {
|
||
licensedFeatures map[string]bool
|
||
}
|
||
|
||
func NewFeatureGate(config *Config) *FeatureGate {
|
||
gates := make(map[string]bool)
|
||
for _, feature := range config.License.Features {
|
||
gates[feature] = true
|
||
}
|
||
return &FeatureGate{licensedFeatures: gates}
|
||
}
|
||
|
||
func (fg *FeatureGate) IsEnabled(feature string) bool {
|
||
return fg.licensedFeatures[feature]
|
||
}
|
||
|
||
// Usage throughout BZZZ codebase
|
||
func (agent *Agent) startAdvancedAIIntegration() error {
|
||
if !agent.featureGate.IsEnabled("advanced-ai-integration") {
|
||
return fmt.Errorf("advanced AI integration requires Standard tier or higher")
|
||
}
|
||
// ... proceed with feature ...
|
||
}
|
||
```
|
||
|
||
#### 2. Node Count Enforcement
|
||
```go
|
||
func validateNodeCount(config *Config, currentNodes int) error {
|
||
maxNodes := config.License.MaxNodes
|
||
if maxNodes > 0 && currentNodes > maxNodes {
|
||
return fmt.Errorf("cluster has %d nodes but license only allows %d nodes", currentNodes, maxNodes)
|
||
}
|
||
return nil
|
||
}
|
||
```
|
||
|
||
## Implementation Files to Modify
|
||
|
||
### Core Configuration Files
|
||
- `pkg/config/config.go` - Add LicenseConfig struct
|
||
- `api/setup_manager.go` - Add license to SetupConfig, fix save process
|
||
- `main.go` - Add license validation to startup sequence
|
||
|
||
### New License Client Files
|
||
- `pkg/license/client.go` - KACHING API client
|
||
- `pkg/license/heartbeat.go` - Background heartbeat worker
|
||
- `pkg/license/features.go` - Feature gate implementation
|
||
- `pkg/license/validation.go` - Runtime license validation
|
||
|
||
### UI Integration
|
||
- Update `install/config-ui/app/setup/components/LicenseValidation.tsx` to call KACHING
|
||
- Ensure license data is properly saved in setup flow
|
||
|
||
## Configuration Updates Required
|
||
|
||
### Environment Variables
|
||
```bash
|
||
# License server configuration
|
||
LICENSE_SERVER_URL=https://kaching.chorus.services
|
||
LICENSE_KEY=BZZZ-2025-ABC123-XYZ
|
||
CLUSTER_ID=bzzz-cluster-uuid-hostname
|
||
|
||
# Offline mode configuration
|
||
LICENSE_OFFLINE_GRACE_HOURS=24
|
||
LICENSE_HEARTBEAT_INTERVAL_MINUTES=15
|
||
```
|
||
|
||
### Configuration File Format
|
||
```yaml
|
||
# .bzzz/config.yaml
|
||
license:
|
||
server_url: "https://kaching.chorus.services"
|
||
license_key: "BZZZ-2025-ABC123-XYZ"
|
||
cluster_id: "bzzz-cluster-abc123-walnut"
|
||
email: "customer@example.com"
|
||
organization_name: "Example Corp"
|
||
license_type: "standard"
|
||
max_nodes: 10
|
||
features:
|
||
- "basic-coordination"
|
||
- "task-distribution"
|
||
- "advanced-ai-integration"
|
||
expires_at: "2025-12-31T23:59:59Z"
|
||
validated_at: "2025-09-01T10:30:00Z"
|
||
terms_accepted_at: "2025-09-01T10:29:45Z"
|
||
```
|
||
|
||
## Testing Strategy
|
||
|
||
### Unit Tests Required
|
||
- License configuration validation
|
||
- Feature gate functionality
|
||
- Heartbeat worker logic
|
||
- Error handling scenarios
|
||
|
||
### Integration Tests Required
|
||
- End-to-end setup flow with real KACHING server
|
||
- License activation/heartbeat/deactivation cycle
|
||
- License suspension handling
|
||
- Offline grace period behavior
|
||
- Node count enforcement
|
||
|
||
### Security Tests
|
||
- License tampering detection
|
||
- Token validation and expiry
|
||
- Cluster ID spoofing protection
|
||
- Network failure graceful degradation
|
||
|
||
## Success Criteria
|
||
|
||
### Phase 2A Success
|
||
- [ ] License data properly saved during setup (no longer discarded)
|
||
- [ ] Runtime configuration includes complete license information
|
||
- [ ] Setup process generates and persists cluster ID
|
||
|
||
### Phase 2B Success
|
||
- [ ] Mock validation completely removed
|
||
- [ ] Real license validation against KACHING server
|
||
- [ ] License activation works end-to-end with cluster binding
|
||
|
||
### Phase 2C Success
|
||
- [ ] BZZZ refuses to start without valid license
|
||
- [ ] Heartbeat worker maintains license token
|
||
- [ ] License suspension stops BZZZ operations immediately
|
||
- [ ] Clean deactivation on shutdown
|
||
|
||
### Phase 2D Success
|
||
- [ ] Features properly gated based on license tier
|
||
- [ ] Node count enforcement prevents over-provisioning
|
||
- [ ] Clear error messages for license violations
|
||
|
||
### Overall Success
|
||
- [ ] **Zero unlicensed usage possible** - system fails closed
|
||
- [ ] License sharing across clusters prevented
|
||
- [ ] Real-time license enforcement (suspend works immediately)
|
||
- [ ] Comprehensive audit trail of license usage
|
||
|
||
## Security Considerations
|
||
|
||
1. **License Key Protection**: Store license keys securely, never log them
|
||
2. **Token Security**: JWT tokens stored in memory only, never persisted
|
||
3. **Cluster ID Integrity**: Generate cryptographically secure cluster IDs
|
||
4. **Audit Logging**: All license operations logged for compliance
|
||
5. **Fail-Closed Design**: System stops on license violations rather than degrading
|
||
|
||
## Dependencies
|
||
|
||
- **KACHING Phase 1 Complete**: Requires functioning license server
|
||
- **Database Migration**: May require config schema updates for existing deployments
|
||
- **Documentation Updates**: Update setup guides and admin documentation
|
||
|
||
## Deployment Strategy
|
||
|
||
1. **Backward Compatibility**: Existing BZZZ instances must upgrade gracefully
|
||
2. **Migration Path**: Convert existing configs to include license requirements
|
||
3. **Rollback Plan**: Ability to temporarily disable license enforcement if needed
|
||
4. **Monitoring**: Comprehensive metrics for license validation success/failure rates
|
||
|
||
This plan transforms BZZZ from having zero license enforcement to comprehensive revenue protection integrated with KACHING license authority.
|
||
|
||
|
||
|
||
|
||
---
|
||
---
|
||
---
|
||
|
||
All Licensing Work Committed and Pushed!
|
||
|
||
Successfully Pushed to GITEA:
|
||
|
||
🔐 KACHING: feature/license-authority-server
|
||
- Pull Request: https://gitea.chorus.services/tony/KACHING/pulls/new/feature/license-authority-server
|
||
- Complete license authority API with Ed25519 security
|
||
- Professional admin CLI tools with testing frameworks
|
||
|
||
⚡ BZZZ: feature/licensing-enforcement
|
||
- Pull Request: https://gitea.chorus.services/tony/bzzz/pulls/new/feature/licensing-enforcement
|
||
- Critical fix: Setup now saves license data (was discarded!)
|
||
- Runtime license enforcement prevents unlicensed usage
|
||
|
||
🌐 WHOOSH: feature/license-gating-integration
|
||
- Pull Request: https://gitea.chorus.services/tony/hive/pulls/new/feature/license-gating-integration
|
||
- License-aware UI with feature gating and upgrade optimization
|
||
- Secure backend proxy pattern implementation
|
||
|
||
Business Impact Summary:
|
||
|
||
Before: $0 recurring revenue protection - unlimited free usage across all products
|
||
|
||
After: Comprehensive license enforcement ecosystem with:
|
||
- ✅ Real-time license control and immediate suspension capability
|
||
- ✅ Prevention of license sharing through cluster binding
|
||
- ✅ Automated upselling through intelligent feature gating
|
||
- ✅ Complete operational tooling for license management
|
||
- ✅ Production-ready security with Ed25519 cryptography
|
||
|
||
All work is properly versioned, comprehensively documented, and ready for integration testing and production deployment. The
|
||
foundation for sustainable recurring revenue is now in place!
|
||
|