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.
- Host: GitHub
- URL: https://github.com/sufield/ephemos
- Owner: sufield
- License: mit
- Created: 2025-08-08T17:04:11.000Z (12 months ago)
- Default Branch: main
- Last Pushed: 2026-02-24T17:08:03.000Z (5 months ago)
- Last Synced: 2026-02-24T20:52:35.088Z (5 months ago)
- Language: Go
- Size: 89.1 MB
- Stars: 1
- Watchers: 0
- Forks: 1
- Open Issues: 13
-
Metadata Files:
- Readme: README.md
- Contributing: docs/CONTRIBUTING.md
- License: LICENSE
- Codeowners: .github/CODEOWNERS
- Security: .github/SECURITY.md
Awesome Lists containing this project
README
# Ephemos
[](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.