Skip to content

Latest commit

 

History

History
404 lines (321 loc) · 13.6 KB

File metadata and controls

404 lines (321 loc) · 13.6 KB

Ephemos

OpenSSF Scorecard

Core = SPIFFE identity + mTLS (HTTP-focused). Framework extensions via contrib middleware for Chi/Gin.

No more plaintext API keys or secrets between your services. Ephemos provides a lightweight core for SPIFFE-based service identity and HTTP authentication, with framework-specific extensions available through contrib modules.

The Problem with API Keys

Every service needs to authenticate to other services. The traditional approach uses API keys:

❌ Before: API Keys Everywhere

Time Server (has to validate API keys):

func (s *TimeService) GetTime(w http.ResponseWriter, r *http.Request) {
    // 😫 Every service method starts with API key validation
    apiKey := r.Header.Get("Authorization")
    if apiKey != "Bearer time-client-secret-abc123" {
        http.Error(w, "Unauthorized", 401)
        return
    }
    
    // Your actual business logic buried under auth code
    timezone := r.URL.Query().Get("timezone")
    loc, _ := time.LoadLocation(timezone)
    currentTime := time.Now().In(loc)
    
    json.NewEncoder(w).Encode(map[string]string{
        "time": currentTime.Format("2006-01-02 15:04:05 MST"),
    })
}

Time Client (has to manage API keys):

func main() {
    // 😫 Hard-coded secrets or environment variables
    apiKey := os.Getenv("TIME_SERVICE_API_KEY") // "time-client-secret-abc123"
    if apiKey == "" {
        log.Fatal("TIME_SERVICE_API_KEY not set")
    }
    
    req, _ := http.NewRequest("GET", "https://time-service:8080/time?timezone=UTC", nil)
    req.Header.Set("Authorization", "Bearer "+apiKey)
    
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        log.Fatal("Request failed:", err)
    }
    // Handle response...
}

Problems with this approach:

  • 🔑 API keys in code, environment variables, or config files
  • 🔄 Manual rotation when keys get compromised
  • 💾 Storing secrets securely (Kubernetes secrets, HashiCorp Vault, etc.)
  • 🐛 Services break when keys expire or change
  • 📋 Managing different keys for each service pair
  • 🚨 Keys can be stolen, logged, or accidentally committed to git

✅ After: No API Keys with Ephemos (HTTP Core)

Time Server (HTTP with automatic authentication):

func timeHandler(w http.ResponseWriter, r *http.Request) {
    // 🎉 No API key validation - SPIFFE authentication is automatic!
    // If this function runs, the client is already authenticated via mTLS
    
    timezone := r.URL.Query().Get("timezone")
    if timezone == "" {
        timezone = "UTC"
    }
    
    loc, err := time.LoadLocation(timezone)
    if err != nil {
        http.Error(w, "Invalid timezone", http.StatusBadRequest)
        return
    }
    
    currentTime := time.Now().In(loc)
    json.NewEncoder(w).Encode(map[string]string{
        "time": currentTime.Format("2006-01-02 15:04:05 MST"),
    })
}

func main() {
    // Core provides HTTP server with SPIFFE authentication
    config := &ports.Configuration{
        Service: ports.ServiceConfig{
            Name: "time-server",
            Domain: "prod.company.com",
        },
    }
    
    server, _ := ephemos.IdentityServer(ctx, 
        ephemos.WithServerConfig(config), 
        ephemos.WithAddress(":8080"))
    
    // Register your HTTP handlers
    http.HandleFunc("/time", timeHandler)
    server.ListenAndServe(ctx) // SPIFFE mTLS handled automatically
}

Time Client (HTTP with automatic authentication):

func main() {
    // Core provides HTTP client with SPIFFE authentication
    config := &ports.Configuration{
        Service: ports.ServiceConfig{
            Name: "time-client",
            Domain: "prod.company.com",
        },
    }
    
    client, _ := ephemos.HTTPClient(ctx, ephemos.WithConfig(config))
    
    // Request automatically uses SPIFFE mTLS certificates
    resp, err := client.Get("https://time-server.prod.company.com:8080/time?timezone=UTC")
    if err != nil {
        log.Fatal("SPIFFE authentication failed:", err) // But no secrets involved
    }
    
    var result map[string]string
    json.NewDecoder(resp.Body).Decode(&result)
    fmt.Printf("Current time: %s\n", result["time"])
}

Framework Middleware via Contrib: For framework integration, use contrib middleware that consumes core primitives:

// See contrib/middleware/chi/ or contrib/middleware/gin/
import "github.com/sufield/ephemos/contrib/middleware/chi"

r := chi.NewRouter()
r.Use(chimiddleware.IdentityAuthentication(ephemos.IdentitySetting{
    AllowedServices: []string{"spiffe://prod.company.com/time-client"},
})) // Uses core cert/bundle/authorizer
r.Get("/time", timeHandler) // Handler gets authenticated identity document

What changed:

  • Zero secrets in code - no API keys anywhere
  • Zero secret management - no environment variables or secret stores
  • Automatic authentication - SPIFFE mTLS happens transparently
  • Automatic rotation - certificates refresh every hour
  • Simple failure mode - connection either works or doesn't
  • HTTP-first architecture - Core focuses on HTTP + mTLS, gRPC planned for v2.0

Architecture: Core + Contrib

Ephemos follows a modular architecture:

🏗️ Core (this repository)

  • SPIFFE identity management - X.509 SVID certificates and trust bundles
  • HTTP over mTLS - Authenticated HTTP connections using X.509 SVIDs
  • Core primitives - Certificate fetching, trust bundle management, TLS configuration
  • Configuration - Service identity and trust domain management

Core provides: Certificates, trust bundles, authorizers, and HTTP + mTLS primitives.

🧩 Contrib (extensions for frameworks)

  • Framework middleware - Chi, Gin, and other HTTP framework integrations
  • Examples and guides - HTTP client examples using core primitives
  • Framework adapters - Consume core certificates/bundles for HTTP authentication

Contrib consumes: Core primitives like IdentityService.GetCertificate(), GetTrustBundle(), and HTTP client builders.

📁 Repository Structure

ephemos/                    # Core library
├── pkg/ephemos/           # Public API (HTTP-focused)
├── internal/              # Core implementation
└── contrib/               # Framework extensions
    ├── middleware/
    │   ├── chi/          # Chi router middleware  
    │   └── gin/          # Gin framework middleware
    ├── examples/         # HTTP client examples
    └── docs/             # HTTP integration guides

Configuration (No Secrets Here Either!)

Instead of managing API keys, you just configure your service identity:

time-server.yaml (server configuration):

service:
  name: "time-server"

time-client.yaml (client configuration):

service:
  name: "time-client"

No secrets in these config files! Authentication happens using certificates that are automatically managed behind the scenes.

Workflow Comparison: Admin Registration vs Dashboard Management

❌ Old Workflow: Dashboard + API Key Management

For Developers (every time they need service-to-service communication):

  1. 🌐 Log into company dashboard/admin panel
  2. 🔑 Navigate to "API Keys" section
  3. ➕ Create new API key for each service pair (e.g., "time-client-to-time-server")
  4. 📋 Copy the generated key
  5. 💾 Store key in environment variables, Kubernetes secrets, or config management
  6. 🔄 Repeat for every service that needs to talk to another service
  7. 📅 Set up rotation schedules and alerts for key expiration
  8. 🚨 Handle key rotation across all deployments when keys expire

Problems:

  • Developers need dashboard access and training
  • Keys proliferate rapidly (N×M keys for N services talking to M services)
  • Manual rotation procedures
  • Keys can be forgotten, logged, or mismanaged

✅ New Workflow: Admin Registration (One-Time Setup)

For Administrators (one-time setup per service):

# Admin registers each service once using SPIRE CLI
spire-server entry create -spiffeID spiffe://example.org/time-client -parentID spiffe://example.org/spire-agent -selector unix:uid:1000
spire-server entry create -spiffeID spiffe://example.org/time-server -parentID spiffe://example.org/spire-agent -selector unix:uid:1000

For Developers (zero setup needed):

  • No dashboard login required
  • No API keys to create or manage
  • No secrets to store or rotate
  • Just write code and configure your service identity

Key Difference:

  • Before: Developers had to manage secrets for every service interaction
  • After: Admin registers services once using SPIRE CLI; developers just configure service identity in YAML

Quick Start

1. Install Ephemos

go get github.com/sufield/ephemos

2. Replace Your API Key Code

Instead of this:

// Old way with API keys
apiKey := os.Getenv("SERVICE_API_KEY")
req.Header.Set("Authorization", "Bearer " + apiKey)

Write this:

// New way with Ephemos
client, _ := ephemos.NewClient("config.yaml")
conn, _ := client.Connect("other-service")
service := ephemos.NewServiceClient(conn)

3. Set Up Service Identity (One Time)

Instead of generating API keys, register your services using SPIRE CLI:

# One-time setup per service (like creating a database user)
spire-server entry create -spiffeID spiffe://example.org/time-client -parentID spiffe://example.org/spire-agent -selector unix:uid:1000
spire-server entry create -spiffeID spiffe://example.org/time-server -parentID spiffe://example.org/spire-agent -selector unix:uid:1000

4. Deploy Without Secrets

Your deployment files no longer need secret management:

Before (Kubernetes with API keys):

apiVersion: v1
kind: Secret
metadata:
  name: api-keys
data:
  TIME_SERVICE_API_KEY: dGltZS1zZXJ2aWNlLXNlY3JldA== # base64 encoded secret 😫
  TIME_CLIENT_API_KEY: dGltZS1jbGllbnQtc2VjcmV0

---
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: time-client
        env:
        - name: TIME_SERVICE_API_KEY
          valueFrom:
            secretKeyRef:
              name: api-keys
              key: TIME_SERVICE_API_KEY # 😫 Managing secrets

After (Kubernetes with Ephemos):

apiVersion: apps/v1  
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: time-client
        # 🎉 No secrets needed!
        volumeMounts:
        - name: ephemos-config
          mountPath: /config
      volumes:
      - name: ephemos-config
        configMap:
          name: ephemos-config # Just regular config, no secrets

Benefits for Developers

🧹 Simpler Code

  • No authentication logic cluttering your business logic
  • No API key management code
  • No error handling for expired/invalid keys

🔒 Better Security

  • No secrets to accidentally commit to git
  • No secrets in environment variables or config files
  • No secrets to store in Kubernetes/Docker secrets
  • Certificates automatically rotate every hour

🚀 Easier Operations

  • No secret rotation procedures
  • No "service X is down because API key expired" incidents
  • No managing different keys for different environments
  • No secrets in CI/CD pipelines

🧪 Simpler Testing

// Test your business logic without mocking authentication
func TestGetCurrentTime(t *testing.T) {
    client := &TimeClient{} 
    time, err := client.GetCurrentTime("UTC")
    // No need to mock API key validation!
}

How It Works

Like Caddy for your internal services - just as Caddy automatically handles HTTPS certificates, Ephemos automatically handles service authentication:

  • Caddy: Issues short-lived certificates (12 hours) from its local CA for HTTPS
  • Ephemos: Issues short-lived certificates (1 hour) from SPIRE for service identity
  • Both: Auto-rotate certificates before expiration - zero manual management

The key difference: Caddy secures browser-to-server, Ephemos secures service-to-service.

Installation & Setup

# 1. Get Ephemos
go get github.com/sufield/ephemos

# 2. Install the identity system (one-time setup)
./scripts/setup-ephemos.sh

# 3. Register your services using SPIRE CLI (like creating database users)
spire-server entry create -spiffeID spiffe://example.org/my-service -parentID spiffe://example.org/spire-agent -selector unix:uid:1000
spire-server entry create -spiffeID spiffe://example.org/other-service -parentID spiffe://example.org/spire-agent -selector unix:uid:1000

# 4. Replace your API key code with Ephemos code
# (see examples above)

# 5. Deploy without secrets! 

Examples

HTTP (Core + Contrib)

Framework Integration (Contrib)

Requirements

  • Go 1.24+
  • Linux/macOS (Windows coming soon)

🎯 Bottom Line: Replace API keys with automatic authentication. Your services prove who they are using certificates instead of secrets, and certificates rotate automatically every hour. No more secret management headaches.