sctx Integration
slush integrates with sctx to provide cryptographically-verified service access control.
What sctx Provides
sctx is a cryptographic context library that:
- Issues signed tokens proving identity (certificate-based)
- Creates guards that verify token signatures and permissions
- Validates tokens haven't expired or been replayed
- Maps certificates to permissions via configurable policies
The Value Proposition
Traditional guards check context values set by middleware. An attacker who compromises middleware can set arbitrary values.
sctx guards verify cryptographic proofs:
// Traditional guard - trusts middleware
func requireAdmin(ctx context.Context) error {
if ctx.Value("role") != "admin" { // Attacker can forge this
return errors.New("not admin")
}
return nil
}
// sctx guard - verifies cryptographic proof
func sctxGuard(ctx context.Context) error {
token := sctx.TokenFromContext(ctx)
return dbGuard.Validate(ctx, token) // Signature verified
}
Setup
1. Initialize sctx admin
import "github.com/zoobzio/sctx"
// Your PKI setup
privateKey, _ := loadPrivateKey("server.key")
trustedCAs, _ := loadCACertPool("ca.crt")
// Create admin service
admin, err := sctx.NewAdminService[AppMeta](privateKey, trustedCAs)
if err != nil {
log.Fatal(err)
}
// Configure permission policy
admin.SetPolicy(func(cert *x509.Certificate) (*sctx.Context[AppMeta], error) {
// Map certificate to permissions
return &sctx.Context[AppMeta]{
Permissions: permissionsForCert(cert),
Metadata: AppMeta{...},
}, nil
})
2. Create sctx guards
// Admin token with guard-creation permission
adminToken := getAdminToken()
// Create a guard requiring specific permissions
dbGuard, err := admin.CreateGuard(ctx, adminToken, "db:read", "db:write")
if err != nil {
log.Fatal(err)
}
mailerGuard, err := admin.CreateGuard(ctx, adminToken, "mailer:send")
if err != nil {
log.Fatal(err)
}
3. Bridge to slush guards
// Wrap sctx guard as slush guard
func sctxGuard(guard sctx.Guard) slush.Guard {
return func(ctx context.Context) error {
token := sctx.TokenFromContext(ctx)
if token == "" {
return errors.New("no token in context")
}
return guard.Validate(ctx, token)
}
}
// Register services with cryptographic guards
slush.Register[Database](db).
Guard(sctxGuard(dbGuard))
slush.Register[Mailer](mailer).
Guard(sctxGuard(mailerGuard))
4. Set token in request context
In your HTTP middleware:
func tokenMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Extract token from header
token := sctx.SignedToken(r.Header.Get("X-Auth-Token"))
// Add to context
ctx := sctx.WithToken(r.Context(), token)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
Complete Example
package main
import (
"context"
"crypto/x509"
"errors"
"log"
"net/http"
"github.com/zoobzio/sctx"
"github.com/zoobzio/slush"
)
type AppMeta struct {
UserID string
}
func main() {
// === sctx setup ===
privateKey, _ := loadPrivateKey("server.key")
trustedCAs, _ := loadCACertPool("ca.crt")
admin, _ := sctx.NewAdminService[AppMeta](privateKey, trustedCAs)
admin.SetPolicy(policyFromCert)
// Create guards for different services
adminToken := bootstrapAdminToken(admin)
dbGuard, _ := admin.CreateGuard(context.Background(), adminToken, "db:access")
mailerGuard, _ := admin.CreateGuard(context.Background(), adminToken, "mailer:send")
// === slush setup ===
db := initDatabase()
mailer := initMailer()
slush.Register[Database](db).
Guard(wrapSctxGuard(dbGuard))
slush.Register[Mailer](mailer).
Guard(wrapSctxGuard(mailerGuard))
// === HTTP server ===
mux := http.NewServeMux()
mux.HandleFunc("/send-email", handleSendEmail)
handler := tokenMiddleware(mux)
log.Fatal(http.ListenAndServe(":8080", handler))
}
func wrapSctxGuard(guard sctx.Guard) slush.Guard {
return func(ctx context.Context) error {
token := sctx.TokenFromContext(ctx)
if token == "" {
return errors.New("missing authentication token")
}
return guard.Validate(ctx, token)
}
}
func handleSendEmail(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// This will verify the token has "mailer:send" permission
mailer, err := slush.Use[Mailer](ctx)
if err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
// Use the mailer...
}
What Gets Verified
When guard.Validate(ctx, token) runs:
- Signature verification — Token was signed by trusted key
- Expiry check — Token hasn't expired
- Replay protection — Nonce hasn't been seen before
- Permission check — Token's context has required permissions
Error Handling
sctx errors are descriptive:
db, err := slush.Use[Database](ctx)
if errors.Is(err, slush.ErrAccessDenied) {
// Unwrap to see sctx reason
// "token expired"
// "missing permission: db:write"
// "invalid signature"
log.Println(err)
}
Combining with Other Guards
sctx guards compose with regular guards:
slush.Register[Database](db).
Guard(wrapSctxGuard(dbGuard)). // Cryptographic verification
Guard(rateLimit(100)). // Rate limiting
Guard(circuitBreaker(5)) // Circuit breaker
Testing
In tests, you can bypass sctx or use test tokens:
func TestWithMockGuard(t *testing.T) {
slushtesting.ResetRegistry(t)
// Use AllowAllGuard instead of sctx in tests
slush.Register[Database](mockDB{}).
Guard(slushtesting.AllowAllGuard())
// Test proceeds without PKI setup
}
Or create test tokens with a test CA:
func TestWithTestToken(t *testing.T) {
admin, _ := sctx.NewAdminService[AppMeta](testKey, testCAs)
testToken := createTestToken(admin)
ctx := sctx.WithToken(context.Background(), testToken)
db, err := slush.Use[Database](ctx)
// ...
}
Next Steps
- Guards Guide — Guard composition patterns
- Architecture — How slush processes guards
- sctx Documentation — Full sctx guide