edgeauth

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 15, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package edgeauth provides middleware that trusts headers set by the Cloudflare Edge Router after Auth0 JWT validation.

The Cloudflare Edge Router (kombify-Gateway/cloudflare-edge):

  1. Validates the incoming Bearer JWT against Auth0 JWKS (RS256).
  2. Strips any client-injected x-user-*, x-org-*, and x-kombify-edge-* headers.
  3. Injects x-user-*, x-org-*, x-kombify-edge-auth, route context, and HMAC signature headers from the validated JWT claims or service identity.

In production, configure EDGE_AUTH_SECRET so the origin verifies that the downstream identity headers were set by the edge after real JWT validation.

New code must use identity.FromContext() for the canonical identity. The legacy kong package has been removed (Kong decommissioned 2026-04-17).

Self-hosted deployments (StackKits, Simulate CLI, SpeechKit) do not go through the CF Edge Router. They should set Enabled=false and handle auth locally.

Index

Constants

View Source
const (
	HeaderEdgeAuth      = "X-Kombify-Edge-Auth"
	EdgeAuthValueJWT    = "auth0-jwt"
	EdgeAuthValueAPIKey = "api-key"
)

HeaderEdgeAuth is the edge identity marker set by the CF Edge Router after successful edge validation. Marker presence alone is not authorization proof; middleware must also verify the signed edge envelope.

View Source
const (
	HeaderUserID         = "X-User-ID"
	HeaderOrgID          = "X-Org-ID"
	HeaderUserEmail      = "X-User-Email"
	HeaderUserRoles      = "X-User-Roles"
	HeaderUserTier       = "X-User-Tier"
	HeaderUserScope      = "X-User-Scope"
	HeaderRequestID      = "X-Request-ID"
	HeaderEdgeService    = "X-Kombify-Edge-Service"
	HeaderPublicPrefix   = "X-Kombify-Public-Prefix"
	HeaderEdgeSignature  = "X-Kombify-Edge-Signature"
	HeaderEdgeTimestamp  = "X-Kombify-Edge-Timestamp"
	HeaderEdgeNonce      = "X-Kombify-Edge-Nonce"
	HeaderEdgeSignedPath = "X-Kombify-Edge-Signed-Path"
	HeaderEdgeKeyID      = "X-Kombify-Edge-Key-ID"
	// HeaderEntitlements and HeaderKnowledgeTier are forwarded from the edge and
	// consumed by AI-Platform access policy / knowledge tiering. They are bound by
	// the v2 edge signature so they cannot be forged downstream.
	HeaderEntitlements  = "X-Kombify-Entitlements"
	HeaderKnowledgeTier = "X-Kombify-Knowledge-Tier"
)

Headers forwarded from the edge (same names as Kong used).

View Source
const (
	// HeaderFlags is a JSON object {"<dotted.key>": bool, ...}.
	HeaderFlags = "X-Kombify-Flags"
	// HeaderBudgets is a JSON object {"<dotted.key>": {<budget object>}, ...}.
	HeaderBudgets = "X-Kombify-Budgets"
	// HeaderFlagsSignature is "v1=<base64url-hmac>" over the flags payload.
	HeaderFlagsSignature = "X-Kombify-Flags-Signature"
	// HeaderFlagsTimestamp is the unix-seconds timestamp bound into the signature.
	HeaderFlagsTimestamp = "X-Kombify-Flags-Timestamp"
	// HeaderFlagsKeyID identifies which EDGE_AUTH secret signed the payload.
	HeaderFlagsKeyID = "X-Kombify-Flags-Key-ID"
)

Signed feature-flag / token-budget delivery headers.

ENTITLEMENTS-ARCHITECTURE §8.1 removes the feature_flags claim array from the session JWT. Instead the Cloudflare Edge Router evaluates Flagship per request (sub-ms) and forwards the result to the origin as signed per-request headers. This file is the origin-side reference verifier and the test/edge-side signer.

The flag headers carry their OWN signature (HeaderFlagsSignature), separate from the identity-header signature in edgeauth.go, so the two evolve and verify independently. Both reuse EDGE_AUTH_SECRET[_NEXT] and the same HMAC.

Variables

View Source
var (
	ErrCloudRuntimeCreditsInvalid      = errors.New("edgeauth: cloud runtime credits invalid")
	ErrVerifiedDecisionAuthorityAbsent = errors.New("edgeauth: verified decision authority absent")
)

Functions

func BuildDecisionSignaturePayload

func BuildDecisionSignaturePayload(
	keyID, method, signedPath, audience, publicPrefix, subjectID, tenantID,
	requestID, edgeKeyID, timestamp, nonce, edgeSignature, flagsJSON, budgetsJSON string,
) string

BuildDecisionSignaturePayload is the canonical v2 flags/budgets payload. Its field order is a cross-language wire contract shared with kombify-Gateway.

func FlagsToContext

func FlagsToContext(ctx context.Context, fs FlagSet) context.Context

FlagsToContext stores a verified FlagSet for downstream handlers.

func IsEdgeAuthenticated

func IsEdgeAuthenticated(r *http.Request) bool

IsEdgeAuthenticated reports whether the request carries a supported Cloudflare Edge Router identity marker. It does not verify the signature and must not be used as an authorization decision.

func Middleware

func Middleware(cfg Config) func(next http.Handler) http.Handler

Middleware returns an http.Handler middleware that:

  1. Skips if Enabled=false.
  2. Requires a supported X-Kombify-Edge-Auth marker value.
  3. Verifies the signed edge envelope fail-closed.
  4. On success: extracts identity, stores in context via identity.NewContext.
  5. On missing header: rejects (401) if RequireEdgeAuth=true, else passes through.

Use identity.FromContext(ctx) to retrieve the identity in handlers.

func SignDecisionHeaders

func SignDecisionHeaders(input DecisionSignInput) (http.Header, error)

SignDecisionHeaders signs a request-bound v2 decision. It does not mint the Edge identity envelope; callers must pass fields from the exact envelope already created for the same upstream request.

func SignFlagHeaders

func SignFlagHeaders(secret, keyID string, flags map[string]bool, budgets map[string]any, ts time.Time) (http.Header, error)

SignFlagHeaders produces the signed flag-delivery headers. Edge/runtime and tests use this; secret is an EDGE_AUTH secret, keyID its identifier. Map keys are marshalled in sorted order by encoding/json, so signing is deterministic.

Types

type CloudRuntimeCredits

type CloudRuntimeCredits struct {
	ManagedServers ManagedServerCredit
}

CloudRuntimeCredits is the closed Cloud-owned runtime budget projection.

func ParseCloudRuntimeCredits

func ParseCloudRuntimeCredits(raw json.RawMessage) (CloudRuntimeCredits, error)

ParseCloudRuntimeCredits parses the only supported budget variants: {"managed_servers":{"mode":"limited","limit":N}} or {"managed_servers":{"mode":"unlimited"}}. Unknown fields, nulls, fractional/zero limits, and a limit on unlimited mode fail closed.

type Config

type Config struct {
	// Enabled controls whether the middleware is active.
	// Defaults to false so self-hosted (StackKits, Simulate, SpeechKit) are safe.
	Enabled bool

	// RequireEdgeAuth forces a 401 when the X-Kombify-Edge-Auth header is
	// absent or has an unexpected value. Set to true for SaaS-only services.
	// When false, requests without the header proceed without identity context.
	RequireEdgeAuth bool

	// EdgeAuthSecret verifies HMAC-signed identity headers from the Cloudflare
	// Edge Router. When empty, EDGE_AUTH_SECRET is read from the environment.
	EdgeAuthSecret string

	// EdgeAuthNextSecret is accepted during dual-key rotation. When empty,
	// EDGE_AUTH_SECRET_NEXT is read from the environment.
	EdgeAuthNextSecret string

	// EdgeAuthKeyID identifies EdgeAuthSecret in the signed envelope. When
	// empty, EDGE_AUTH_KEY_ID is read from the environment, then "primary".
	EdgeAuthKeyID string

	// EdgeAuthNextKeyID identifies EdgeAuthNextSecret in the signed envelope.
	// When empty, EDGE_AUTH_KEY_ID_NEXT is read from the environment, then "next".
	EdgeAuthNextKeyID string

	// RequireSignature is retained for source compatibility. Edge-authenticated
	// requests and unsigned identity headers are always verified fail-closed.
	RequireSignature bool

	// SignatureWindow is the maximum allowed skew for X-Kombify-Edge-Timestamp.
	// Defaults to 5 minutes.
	SignatureWindow time.Duration
}

Config configures the edge auth middleware.

type DecisionBinding

type DecisionBinding struct {
	Version      string
	KeyID        string
	SubjectID    string
	TenantID     string
	Audience     string
	PublicPrefix string
	Method       string
	SignedPath   string
	RequestID    string
	EdgeKeyID    string
	Nonce        string
	IssuedAt     time.Time
}

DecisionBinding is the immutable provenance of a request-bound v2 flag and budget decision. Origins must compare SubjectID and TenantID with their authenticated resource operation before treating a budget as authority.

type DecisionSignInput

type DecisionSignInput struct {
	Secret        string
	KeyID         string
	Method        string
	SignedPath    string
	Audience      string
	PublicPrefix  string
	SubjectID     string
	TenantID      string
	RequestID     string
	EdgeKeyID     string
	EdgeTimestamp string
	EdgeNonce     string
	EdgeSignature string
	Flags         map[string]bool
	Budgets       map[string]any
}

DecisionSignInput binds one flags/budgets decision to the already minted Edge identity envelope and exact origin request. EdgeTimestamp, EdgeNonce, EdgeKeyID, and EdgeSignature must be copied from that same identity envelope.

type DecisionVerifyConfig

type DecisionVerifyConfig struct {
	PrimarySecret        string
	NextSecret           string
	PrimaryKeyID         string
	NextKeyID            string
	ExpectedAudience     string
	ExpectedPublicPrefix string
	SignatureWindow      time.Duration
	Now                  func() time.Time
	// IdentityConfig verifies the exact Edge identity envelope whose
	// signature/timestamp/nonce are bound into the decision signature. It must
	// use the Edge identity keys, which may differ from the decision keys.
	IdentityConfig Config
}

DecisionVerifyConfig fixes the trusted signing keys and intended origin audience. ExpectedAudience is mandatory; ExpectedPublicPrefix is optional and should be set when one origin serves multiple public route prefixes.

type FlagSet

type FlagSet struct {
	Flags   map[string]bool
	Budgets map[string]json.RawMessage
	// contains filtered or unexported fields
}

FlagSet is the verified set of per-request flags and budgets from the edge.

func FlagsFromContext

func FlagsFromContext(ctx context.Context) (FlagSet, bool)

FlagsFromContext retrieves a FlagSet previously stored by FlagsToContext.

func VerifyDecisionHeaders

func VerifyDecisionHeaders(r *http.Request, cfg DecisionVerifyConfig) (FlagSet, error)

VerifyDecisionHeaders verifies a request-bound v2 flags/budgets decision. It rejects detached v1 headers and binds the decision to subject, tenant, intended audience, method/path, request id, and the exact Edge identity timestamp/nonce/signature. Exact HTTP retries remain the origin's durable idempotency responsibility; changing any bound field invalidates the HMAC.

func VerifyFlagHeaders

func VerifyFlagHeaders(r *http.Request, cfg Config) (FlagSet, error)

VerifyFlagHeaders verifies and parses the signed flag headers on r. It is fail-closed: any missing header, bad signature, or stale timestamp yields an error and an empty FlagSet. Reuses the EDGE_AUTH secrets/window from Config.

func (FlagSet) Bool

func (f FlagSet) Bool(key string, def bool) bool

Bool returns the flag value, or def if absent (fail-closed: callers pass false for entitlement flags).

func (FlagSet) VerifiedBudget

func (f FlagSet) VerifiedBudget(key string) (json.RawMessage, DecisionBinding, bool)

VerifiedBudget returns a detached copy of one budget together with its request binding. It fails closed if application code changed any flag or budget after verification.

func (FlagSet) VerifiedCloudRuntimeCredits

func (f FlagSet) VerifiedCloudRuntimeCredits() (CloudRuntimeCredits, DecisionBinding, error)

VerifiedCloudRuntimeCredits atomically requires intact request-bound provenance, the canonical budget key, and the closed runtime-credit schema. Cost-bearing origins should use this accessor instead of reading Budgets directly.

func (FlagSet) VerifiedDecisionBinding

func (f FlagSet) VerifiedDecisionBinding() (DecisionBinding, bool)

VerifiedDecisionBinding returns request-bound commercial-decision provenance only when FlagSet came from VerifyDecisionHeaders. A FlagSet assembled by application code or restored from a job payload has no such provenance and must fail closed for cost-bearing authorization.

type ManagedServerCredit

type ManagedServerCredit struct {
	Mode  ManagedServerCreditMode
	Limit int
}

ManagedServerCredit is the validated managed-server portion of cloud.runtime.credits. Limit is zero only for explicit unlimited mode.

type ManagedServerCreditMode

type ManagedServerCreditMode string

ManagedServerCreditMode is the closed managed-server capacity mode.

const (
	// CloudRuntimeCreditsBudgetName is the Cloud-owned commercial capacity
	// decision delivered in the request-bound Edge decision envelope.
	CloudRuntimeCreditsBudgetName = "cloud.runtime.credits" // #nosec G101 -- public product budget name, not credential material.

	// ManagedServerCreditModeLimited carries a positive hard-accounting limit.
	ManagedServerCreditModeLimited ManagedServerCreditMode = "limited"
	// ManagedServerCreditModeUnlimited explicitly removes the managed-server
	// ceiling. Absence or zero never implies unlimited.
	ManagedServerCreditModeUnlimited ManagedServerCreditMode = "unlimited"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL