https://github.com/sufield/e5s
Identity based authentication library based on go-spiffe SDK. Replace API keys with cryptographic identity and reduce the attack surface that comes with leaked credentials, secret sprawl, and rotation headaches
https://github.com/sufield/e5s
go golang spiffe spire
Last synced: 6 months ago
JSON representation
Identity based authentication library based on go-spiffe SDK. Replace API keys with cryptographic identity and reduce the attack surface that comes with leaked credentials, secret sprawl, and rotation headaches
- Host: GitHub
- URL: https://github.com/sufield/e5s
- Owner: sufield
- License: mit
- Created: 2025-10-27T16:31:25.000Z (9 months ago)
- Default Branch: main
- Last Pushed: 2025-11-17T21:47:06.000Z (8 months ago)
- Last Synced: 2025-11-17T23:26:24.779Z (8 months ago)
- Topics: go, golang, spiffe, spire
- Language: Go
- Homepage:
- Size: 66.7 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Security: .github/SECURITY.md
Awesome Lists containing this project
README
# e5s - Identity Based Authentication Library
[](https://www.bestpractices.dev/projects/11425)
[](https://goreportcard.com/report/github.com/sufield/e5s)
A Go library for building applications that use identity-based authentication instead of API keys.
Build mutual TLS services with SPIFFE identity verification and automatic certificate rotation with almost no boilerplate code. Built on the battle-tested go-spiffe SDK.
Eliminate API keys and plaintext secrets from your services, dramatically reducing the attack surface that comes with leaked credentials, secret sprawl, and rotation headaches.
# The Problem
## ❌ Before: Static API Keys
Every service call required a shared secret.
**Weather Server**
```go
func (s *WeatherServer) GetForecast(w http.ResponseWriter, r *http.Request) {
apiKey := r.Header.Get("Authorization")
if apiKey != "Bearer weather-client-secret" {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
// business logic
forecast := s.GetData()
json.NewEncoder(w).Encode(forecast)
}
```
**Weather Client**
```go
func main() {
apiKey := os.Getenv("WEATHER_API_KEY")
req, _ := http.NewRequest("GET", "https://weather-service:8080/forecast", nil)
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
}
```
**Problems**
* Secrets in code or environment variables
* Manual rotation and redeployment when keys change
* Leaks through logs, git, or config files
* Separate keys for every service pair
---
# The Solution
## ✅ After: Identity-Based Authentication with e5s
Each service has its own **cryptographic identity** (SPIFFE ID).
No API keys, no secrets, no manual rotation.
**Weather Server**
```go
func forecastHandler(w http.ResponseWriter, r *http.Request) {
// Client already authenticated via mTLS
forecast := map[string]string{"forecast": "Sunny"}
json.NewEncoder(w).Encode(forecast)
}
func main() {
http.HandleFunc("/forecast", forecastHandler)
if err := e5s.Serve("server-config.yaml", http.DefaultServeMux); err != nil {
log.Fatal(err)
}
}
```
**server-config.yaml:**
```yaml
version: 1
spire:
workload_socket: "unix:///tmp/spire-agent/public/api.sock"
server:
listen_addr: ":8080"
allowed_client_spiffe_id: "spiffe://prod.company.com/weather-client"
```
**Weather Client**
```go
func main() {
client, cleanup, err := e5s.Client("client-config.yaml")
if err != nil {
log.Fatal(err)
}
defer cleanup()
resp, err := client.Get("https://weather-service:8080/forecast")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
var result map[string]string
json.NewDecoder(resp.Body).Decode(&result)
fmt.Println(result["forecast"])
}
```
**client-config.yaml:**
```yaml
version: 1
spire:
workload_socket: "unix:///tmp/spire-agent/public/api.sock"
client:
expected_server_spiffe_id: "spiffe://prod.company.com/weather-service"
```
---
## What Changed
| Aspect | Before (API Keys) | After (e5s) |
| -------------- | ----------------------- | ------------------------- |
| Authentication | Shared secret in header | mTLS using SPIFFE IDs |
| Rotation | Manual | Automatic |
| Secret Storage | Env vars / config | None |
| Breach Risk | High (key leaks) | Low (certs rotate hourly) |
| Setup | Add key per service | Register identity once |
| Maintenance | Continuous | Zero-touch |
---
## Example Setup
```bash
# One-time identity registration
spire-server entry create -spiffeID spiffe://prod.company.com/weather-client \
-parentID spiffe://prod.company.com/spire-agent -selector unix:uid:1000
spire-server entry create -spiffeID spiffe://prod.company.com/weather-service \
-parentID spiffe://prod.company.com/spire-agent -selector unix:uid:1000
```
Now, your services prove who they are through short-lived certificates—no shared keys, no dashboards, no secret rotation scripts.
## How does e5s work?
e5s solves the challenges of implementing secure, identity-based mutual TLS (mTLS) in distributed systems by adopting the proven SPIFFE standard. It simplifies
- SPIFFE-based authentication
- Automates certificate rotation without downtime
- Enforces peer ID verification
- Minimizes manual certificate management
Ideal for microservices in zero-trust environments. It reduces security risks from expired certs or weak auth, while offering high-level APIs for ease and low-level controls for customization.
## Why Not Use go-spiffe SDK Directly?
Using e5s over the go-spiffe SDK directly offers these advantages for developers building mTLS services:
- **Simpler Abstraction**: The high-level API (e.g., `e5s.Run()`) handles configuration, SPIRE connections, certificate rotation, and verification with minimal code—often one line—versus manual SDK setup and boilerplate.
- **Config-Driven**: YAML-based config (`e5s.dev.yaml` for dev, `e5s.prod.yaml` for prod) streamlines setup for servers/clients, including timeouts and trust domains, without custom coding.
- **Built-in Features**: Automatic zero-downtime rotation, policy-based SPIFFE ID verification, TLS 1.3 enforcement, graceful shutdown, health checks, structured logging and thread-safety are ready out-of-the-box.
- **Low-Level Flexibility**: Direct access to pkg/spiffehttp and pkg/spire for customization, minimizing dependencies (core uses stdlib only).
- **Ease of Adoption**: Comprehensive docs, quickstarts, and examples reduce integration time compared to raw SDK usage.
Use go-spiffe directly only if you need non-HTTP services or custom workflows.
## Features
- **Simple High-Level API** - Config-driven with `e5s.Start()` and `e5s.Client()`
- **Low-Level Control** - Direct access to `pkg/spiffehttp` and `pkg/spire` for custom use cases
- **SDK-Based Implementation** - Uses official go-spiffe SDK API
- **SPIRE Adapter** - Production-ready SPIRE Workload API implementation
- **Automatic Rotation** - Zero-downtime certificate and trust bundle updates
- **SPIFFE ID Verification** - Policy-based peer authentication
- **TLS 1.3 Enforcement** - Strong cipher suites and security defaults
- **Thread-Safe** - Share sources across multiple servers and clients
- **Minimal Dependencies**
- Core (`pkg/spiffehttp`): stdlib only.
- SPIRE adapter (`pkg/spire`): `go-spiffe/v2`.
- High-level API (`e5s.go`): adds `yaml.v3`.
- Examples add `chi` (see `go.mod` for details)
## Documentation
- **[docs/](docs/)** - Documentation Table of Contents
- **[Examples](examples/)** - Working code for all use cases
## Quick Start
### Installation
```bash
go get github.com/sufield/e5s@latest
```
## Two Ways to Use e5s
A **high-level** and a **low-level** APIs are provided because they serve different developer roles and abstraction levels:
### High-Level API (`e5s.Start`, `e5s.Client`)
**For:** Application developers
**Use Case:** Secure HTTP services quickly
**Goal:** Make identity-based mTLS work with one line of code
- Handles configuration, SPIRE connection, certificate rotation, and verification internally
- Reads `e5s.dev.yaml` (default) or specify via `-config` flag or `E5S_CONFIG` env var
- Ideal when you just want secure communication without caring how certificates or trust domains are wired
- Example use: web apps, microservices, APIs
`examples/highlevel/`** - Start here for application development (production behavior, minimal code)
**Benefits:** Zero boilerplate, hard to misuse, easy to run locally and in production with the same config
### Low-Level API (`pkg/spiffehttp`, `pkg/spire`)
**For:** Platform/Infra Engineer
**Use Case:** Build custom SPIRE integrations or non-HTTP services
**Goal:** Allow full control over mTLS internals
- Build custom TLS configs and integrate with the go-spiffe SDK directly
- Exposes `spiffehttp.NewServerTLSConfig` and `spire.NewIdentitySource`
- Ideal for customizing certificate rotation intervals, trust domain logic, or integrating into non-HTTP systems
**Benefits:** Extensible for advanced use cases, can plug in custom identity providers, useful for testing/debugging or building frameworks
**`examples/minikube-lowlevel/`** - Platform/infrastructure example (full SPIRE + mTLS stack in Kubernetes)
### 1. High-Level API
#### Recommended for Most Users
Simple configuration approach - create `e5s.yaml` with your SPIRE socket path and authorization policy.
**Example Server:**
```go
package main
import (
"fmt"
"log"
"net/http"
"github.com/sufield/e5s"
)
func main() {
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
id, ok := e5s.PeerID(r)
if !ok {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
fmt.Fprintf(w, "Hello, %s!\n", id)
})
// Start mTLS server - handles config, SPIRE, and graceful shutdown
if err := e5s.Serve("e5s.yaml", http.DefaultServeMux); err != nil {
log.Fatal(err)
}
}
```
**Example Client:**
```go
package main
import (
"fmt"
"io"
"log"
"github.com/sufield/e5s"
)
func main() {
// Create mTLS HTTP client
client, cleanup, err := e5s.Client("e5s.yaml")
if err != nil {
log.Fatal(err)
}
defer func() {
if err := cleanup(); err != nil {
log.Printf("Cleanup error: %v", err)
}
}()
// Perform mTLS GET request
resp, err := client.Get("https://localhost:8443/hello")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
**Config File:**
The e5s library requires explicit configuration and never assumes a default environment.
**Config files live in YOUR application codebase, not in the e5s library.**
Copy the example config from `examples/highlevel/e5s.yaml` to your project and customize it:
In your application directory:
```bash
cp path/to/e5s/examples/highlevel/e5s.yaml ./e5s.yaml
```
Then edit for your environment (e5s.dev.yaml, e5s.prod.yaml, etc.)
Then provide the config path:
**Simple approach - blocks with signal handling:**
```go
if err := e5s.Serve("e5s.yaml", handler); err != nil {
log.Fatal(err)
}
```
**Advanced approach - custom shutdown:**
```go
shutdown, err := e5s.Start("e5s.yaml", handler)
if err != nil {
log.Fatal(err)
}
defer shutdown()
// Your custom shutdown logic here
```
**Example config structure:**
See the complete annotated config file: **[examples/highlevel/e5s.yaml](examples/highlevel/e5s.yaml)**
This single file contains both server and client configuration with detailed comments explaining each option.
In your application, create separate config files (`e5s.dev.yaml`, `e5s.staging.yaml`, `e5s.prod.yaml`) with environment-specific values for socket paths, trust domains, and timeouts. These files are part of your application codebase, not the e5s library.
**For advanced usage** like environment variables, context timeouts, retry logic, and structured logging → See [examples/highlevel/ADVANCED.md](examples/highlevel/ADVANCED.md)
**For a production-ready example** → See [examples/highlevel/](examples/highlevel/) for a server with chi router, graceful shutdown, health checks, and structured logging.
### 2. Low-Level API
#### For Advanced Use Cases
The low-level API provides programmatic control over TLS configuration when you need customization beyond what the high-level API offers.
**Use the low-level API when:**
- Building libraries or frameworks on top of e5s
- Integrating with custom identity providers
- Need fine-grained control over TLS settings
- Building non-HTTP services
**Core packages:**
```go
import (
"github.com/sufield/e5s/spire" // SPIRE Workload API client
"github.com/sufield/e5s/spiffehttp" // mTLS TLS config builders
)
```
**Basic Use:**
1. Create SPIRE identity source: `spire.NewIdentitySource(ctx, config)`
2. Build TLS config: `spiffehttp.NewServerTLSConfig()` or `spiffehttp.NewClientTLSConfig()`
3. Create `http.Server` or `http.Client` with the TLS config
4. Handle graceful shutdown manually
**Comparison to high-level API:**
| What | High-Level | Low-Level |
|------|-----------|-----------|
| Config | YAML file | Go code |
| Setup code | ~10 lines | ~75+ lines |
| Shutdown | Automatic | Manual |
| Hardcoded values | None | Ports, IDs, domains in code |
**Examples:**
- **API reference:** [docs/reference/api.md](docs/reference/api.md)
- **Working code:** [examples/middleware/](examples/middleware/)
- **Infrastructure setup:** [examples/minikube-lowlevel/](examples/minikube-lowlevel/)
## Architecture
```
e5s.go # High-level config-driven API
├── e5s.Start() # Server with config file
├── e5s.Client() # Client with config file
└── e5s.PeerID() # Extract authenticated peer
pkg/
├── spiffehttp/ # Core mTLS library (provider-agnostic)
│ ├── client.go # Client TLS config builder
│ ├── server.go # Server TLS config builder
│ ├── peer.go # SPIFFE ID extraction/validation
│ └── context.go # Context helpers for peer info
└── spire/ # SPIRE Workload API adapter
└── source.go # SPIRE Workload API client
internal/config/ # Config file loading (not exported)
├── config.go # Config structs
├── load.go # YAML loader
└── validate.go # Config validation
```
**Two Levels:**
1. **High-level** (`e5s.go`) - Config-driven, minimal code, works with any HTTP framework
2. **Low-level** (`pkg/spiffehttp` + `pkg/spire`) - Full control over TLS, rotation, verification
**Clear separation:**
- `pkg/spiffehttp` - TLS configuration using go-spiffe SDK (no SPIRE dependency)
- `pkg/spire` - SPIRE Workload API client
- `e5s.go` - Wires everything together based on config file
The examples are separate modules (each has its own `go.mod`) so you can vendor/copy them without pulling extra dependencies into your service. The core library has minimal dependencies.
## Development
```bash
# Run all CI checks locally before pushing
make ci
# Individual checks
make lint # Run golangci-lint (what CI runs)
make vet # Run go vet
make fmt # Format code
make test # Run tests
make build # Build example binaries
```
### Common Tasks
```bash
# Testing
make test # Run tests quickly
make test-race # Run with race detector (recommended before push)
make test-coverage # Generate coverage report
make test-coverage-html # Open coverage in browser
# Code Quality
make lint # Lint code (matches CI)
make lint-fix # Auto-fix linting issues
make fmt # Format all code
make fmt-check # Check if code is formatted
make vet # Run go vet
make tidy # Tidy go modules
make verify # Verify go modules
# Building
make build # Build examples/basic-server and examples/basic-client
make examples # Build all examples (4 binaries)
make example-highlevel-server # Application developer example
make example-highlevel-client
make example-minikube-server # Platform/infra example
make example-minikube-client
# Security
make sec-all # Run all security checks
make sec-deps # Check for vulnerabilities
make sec-lint # Security-focused static analysis
# All available targets
make help
```
## Security
e5s implements defense-in-depth security with multiple layers:
- **Static Analysis**: gosec, golangci-lint, govulncheck, gitleaks
- **Container Security**: Pinned digests, non-root users, minimal images
- **mTLS Enforcement**: TLS 1.3, SPIFFE identity verification, certificate rotation
- **Runtime Monitoring**: Optional Falco integration for threat detection
- **Supply Chain**: Signed releases with Cosign/Sigstore
**For detailed security information:**
- [Report Security Issues](https://github.com/sufield/e5s/security/advisories/new) - Private vulnerability disclosure
- [Security Policy](.github/SECURITY.md) - Vulnerability disclosure policy
**Run security scans:**
```bash
make sec-all # Run all security checks
```
## License
MIT License - See [LICENSE](LICENSE) file for details.