An open API service indexing awesome lists of open source software.

https://github.com/sufield/ephemos

Ephemos is a Go library that provides identity-based authentication for backend services using SPIFFE/SPIRE, replacing plaintext API keys with automatic mTLS.
https://github.com/sufield/ephemos

Last synced: 5 months ago
JSON representation

Ephemos is a Go library that provides identity-based authentication for backend services using SPIFFE/SPIRE, replacing plaintext API keys with automatic mTLS.

Awesome Lists containing this project

README

          

# Ephemos

[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/sufield/ephemos/badge)](https://securityscorecards.dev/viewer/?uri=github.com/sufield/ephemos)

**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):**
```go
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):**
```go
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):**
```go
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):**
```go
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:
```go
// 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):
```yaml
service:
name: "time-server"
```

**time-client.yaml** (client configuration):
```yaml
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):**
```bash
# 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
```bash
go get github.com/sufield/ephemos
```

### 2. Replace Your API Key Code

**Instead of this:**
```go
// Old way with API keys
apiKey := os.Getenv("SERVICE_API_KEY")
req.Header.Set("Authorization", "Bearer " + apiKey)
```

**Write this:**
```go
// 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:
```bash
# 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):**
```yaml
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):**
```yaml
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**
```go
// 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

```bash
# 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)
- [Complete HTTP Examples](examples/) - Working client/server code using core
- [Migration Guide](docs/migration.md) - Step-by-step API key to Ephemos migration
- [Kubernetes Setup](docs/kubernetes.md) - Deploy without secret management

### Framework Integration (Contrib)
- [Chi Middleware](contrib/middleware/chi/) - SPIFFE authentication for Chi router
- [Gin Middleware](contrib/middleware/gin/) - SPIFFE authentication for Gin framework
- [HTTP Client Examples](contrib/examples/) - Using core primitives with `net/http`
- [HTTP Integration Guide](contrib/docs/HTTP_CLIENT.md) - Detailed HTTP setup instructions

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