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.
Every service needs to authenticate to other services. The traditional approach uses API keys:
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
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 documentWhat 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
Ephemos follows a modular architecture:
- 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.
- 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.
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
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.
For Developers (every time they need service-to-service communication):
- 🌐 Log into company dashboard/admin panel
- 🔑 Navigate to "API Keys" section
- ➕ Create new API key for each service pair (e.g., "time-client-to-time-server")
- 📋 Copy the generated key
- 💾 Store key in environment variables, Kubernetes secrets, or config management
- 🔄 Repeat for every service that needs to talk to another service
- 📅 Set up rotation schedules and alerts for key expiration
- 🚨 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
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:1000For 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
go get github.com/sufield/ephemosInstead 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)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:1000Your 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 secretsAfter (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- No authentication logic cluttering your business logic
- No API key management code
- No error handling for expired/invalid keys
- 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
- 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
// 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!
}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.
# 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! - Complete HTTP Examples - Working client/server code using core
- Migration Guide - Step-by-step API key to Ephemos migration
- Kubernetes Setup - Deploy without secret management
- Chi Middleware - SPIFFE authentication for Chi router
- Gin Middleware - SPIFFE authentication for Gin framework
- HTTP Client Examples - Using core primitives with
net/http - HTTP Integration Guide - Detailed HTTP setup instructions
- 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.