Auth

Official Stable

Authentication and authorization agent supporting JWT, OIDC, API keys, Basic auth, SAML SSO, mTLS, Cedar policies, and token exchange.

Version: 0.2.0 Author: Sentinel Core Team License: Apache-2.0 Protocol: vv2 View Source

Quick Install

Cargo
cargo install sentinel-agent-auth

Overview

The Auth agent provides comprehensive authentication and authorization for your services. It supports multiple authentication methods including JWT, OIDC/OAuth 2.0, API keys, Basic auth, SAML SSO, and mTLS client certificates. Authorization is powered by the Cedar policy engine for fine-grained access control.

Features

Authentication (AuthN)

  • JWT Validation: Verify JWTs with HS256, RS256, ES256 algorithms
  • OIDC/OAuth 2.0: OpenID Connect with automatic JWKS key rotation
  • API Keys: Simple header-based authentication
  • Basic Auth: Username/password authentication (RFC 7617)
  • SAML SSO: Enterprise single sign-on with SP-initiated flow
  • mTLS Client Certificates: X.509 certificate-based authentication

Authorization (AuthZ)

  • Cedar Policy Engine: Policy-as-code authorization (principal, action, resource evaluation)

Token Services

  • Token Exchange (RFC 8693): Convert between token types (SAML→JWT, external→internal)

General

  • Session Persistence: Built-in session store using embedded database
  • Claims Forwarding: Pass validated claims/attributes to upstream services
  • Fail-Open Mode: Graceful degradation for non-critical paths

Protocol v2

  • gRPC Transport: High-performance gRPC transport for production deployments
  • Health Reporting: Automatic health status reporting to proxy
  • Metrics Export: Built-in metrics (auth success/failure rates, request counts)
  • Capability Negotiation: Dynamic feature discovery during handshake
  • Graceful Lifecycle: Proper drain and shutdown handling

Installation

Using Cargo

cargo install sentinel-agent-auth

From Source

git clone https://github.com/raskell-io/sentinel-agent-auth
cd sentinel-agent-auth
cargo build --release

Quick Start

Command Line

# UDS transport (default)
sentinel-auth-agent \
  --socket /var/run/sentinel/auth.sock \
  --jwt-secret "your-secret-key-at-least-32-characters"

# gRPC transport (v2 protocol)
sentinel-auth-agent \
  --grpc-address "[::1]:50051" \
  --jwt-secret "your-secret-key-at-least-32-characters"

# With API keys
sentinel-auth-agent \
  --socket /var/run/sentinel/auth.sock \
  --api-keys "sk_live_abc123:production,sk_test_xyz:development"

# With Basic auth
sentinel-auth-agent \
  --socket /var/run/sentinel/auth.sock \
  --basic-auth-users "admin:secretpass,readonly:userpass"

Environment Variables

export AGENT_SOCKET="/var/run/sentinel/auth.sock"
export JWT_SECRET="your-secret-key-at-least-32-characters"
export JWT_ALGORITHM="HS256"
export JWT_ISSUER="https://auth.example.com"
export API_KEYS="sk_live_abc123:production"

Configuration

Sentinel Proxy Configuration

agents {
    // UDS transport
    agent "auth" {
        type "custom"
        transport "unix_socket" {
            path "/var/run/sentinel/auth.sock"
        }
        events ["request_headers" "request_body_chunk"]
        timeout-ms 100
        failure-mode "closed"
        protocol-version 2

        config {
            // JWT configuration
            jwt-secret "your-secret-key-at-least-32-characters"
            jwt-algorithm "HS256"
            jwt-issuer "https://auth.example.com"
            jwt-audience "my-api"

            // API keys (key:name pairs)
            api-keys "sk_live_abc123:production,sk_test_xyz:staging"
            api-key-header "X-API-Key"

            // Basic auth (user:password pairs)
            basic-auth-users "admin:secret,readonly:password"

            // Headers added on successful auth
            user-id-header "X-User-Id"
            auth-method-header "X-Auth-Method"

            // Behavior
            fail-open false
        }
    }
}

routes {
    route "api" {
        matches { path-prefix "/api" }
        upstream "backend"
        agents ["auth"]
    }
}

gRPC Transport (v2)

agents {
    agent "auth" {
        type "custom"
        transport "grpc" {
            address "127.0.0.1:50051"
        }
        events ["request_headers" "request_body_chunk"]
        timeout-ms 100
        failure-mode "closed"
        protocol-version 2
    }
}

Command Line Options

OptionEnvironmentDescriptionDefault
--socketAGENT_SOCKETUnix socket path/tmp/sentinel-auth.sock
--grpc-addressGRPC_ADDRESSgRPC listen address (v2 protocol)-
--jwt-secretJWT_SECRETJWT secret key (HS256)-
--jwt-public-keyJWT_PUBLIC_KEYJWT public key file (RS256/ES256)-
--jwt-algorithmJWT_ALGORITHMAlgorithm: HS256, RS256, ES256HS256
--jwt-issuerJWT_ISSUERRequired issuer claim-
--jwt-audienceJWT_AUDIENCERequired audience claim-
--api-keysAPI_KEYSAPI keys as key:name,key:name-
--api-key-headerAPI_KEY_HEADERAPI key header nameX-API-Key
--basic-auth-usersBASIC_AUTH_USERSUsers as user:pass,user:pass-
--user-id-headerUSER_ID_HEADERHeader for user IDX-User-Id
--auth-method-headerAUTH_METHOD_HEADERHeader for auth methodX-Auth-Method
--fail-openFAIL_OPENAllow on auth failurefalse

Authentication Methods

JWT/Bearer Token

# Configure with HS256 secret
sentinel-auth-agent --jwt-secret "your-32-char-minimum-secret-key"

# Configure with RS256 public key
sentinel-auth-agent --jwt-algorithm RS256 --jwt-public-key /path/to/public.pem

# With issuer and audience validation
sentinel-auth-agent \
  --jwt-secret "secret" \
  --jwt-issuer "https://auth.example.com" \
  --jwt-audience "my-api"

Client request:

curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." http://localhost:8080/api

API Key

sentinel-auth-agent --api-keys "sk_live_abc123:production,sk_test_xyz:development"

Client request:

curl -H "X-API-Key: sk_live_abc123" http://localhost:8080/api

Basic Auth

sentinel-auth-agent --basic-auth-users "admin:secretpass,user:userpass"

Client request:

curl -u "admin:secretpass" http://localhost:8080/api

OIDC/OAuth 2.0

OIDC authentication with automatic JWKS key fetching and rotation:

config {
    oidc {
        enabled true
        issuer "https://auth.example.com"
        jwks-url "https://auth.example.com/.well-known/jwks.json"
        audience "my-api"
        required-scopes ["read" "write"]
        jwks-refresh-secs 3600
    }
}

Client request:

curl -H "Authorization: Bearer <oauth2-access-token>" http://localhost:8080/api

OIDC Options

OptionTypeDefaultDescription
enabledboolfalseEnable OIDC authentication
issuerstringrequiredExpected token issuer
jwks-urlstringrequiredURL to fetch JWKS
audiencestring-Expected audience claim
required-scopesarray[]Scopes that must be present
jwks-refresh-secsint3600JWKS cache refresh interval
clock-skew-secsint30Clock tolerance

OIDC Provider Examples

Auth0:

oidc {
    enabled true
    issuer "https://your-tenant.auth0.com/"
    jwks-url "https://your-tenant.auth0.com/.well-known/jwks.json"
    audience "https://your-api.example.com"
}

Okta:

oidc {
    enabled true
    issuer "https://your-org.okta.com/oauth2/default"
    jwks-url "https://your-org.okta.com/oauth2/default/v1/keys"
    audience "api://your-api"
}

Azure AD:

oidc {
    enabled true
    issuer "https://login.microsoftonline.com/{tenant-id}/v2.0"
    jwks-url "https://login.microsoftonline.com/{tenant-id}/discovery/v2.0/keys"
    audience "api://{client-id}"
}

mTLS Client Certificates

Authenticate services using X.509 client certificates. The Sentinel proxy terminates TLS and forwards the certificate:

config {
    mtls {
        enabled true
        client-cert-header "X-Client-Cert"
        allowed-dns ["CN=api-gateway,O=Example Corp" "CN=backend-service,O=Example Corp"]
        allowed-sans ["service@example.com"]
        extract-cn-as-user true
    }
}

Sentinel Proxy mTLS Configuration

listener {
    tls {
        client-auth "require"
        forward-client-cert-header "X-Client-Cert"
        ca-cert-file "/etc/ssl/ca.crt"
    }
}

mTLS Options

OptionTypeDefaultDescription
enabledboolfalseEnable mTLS authentication
client-cert-headerstringX-Client-CertHeader with client certificate
ca-cert-pathstring-CA cert for chain validation
allowed-dnsarray[]Allowed Distinguished Names
allowed-sansarray[]Allowed Subject Alternative Names
extract-cn-as-userbooltrueUse CN as user ID
extract-san-email-as-userboolfalseUse SAN email as user ID

SAML SSO

The agent supports SAML 2.0 SP-initiated SSO with built-in session persistence.

SAML Configuration

agent "auth" {
    socket "/var/run/sentinel/auth.sock"
    events ["request_headers" "request_body_chunk"]

    config {
        saml {
            enabled true

            // Service Provider settings
            entity-id "https://app.example.com/sp"
            acs-url "https://app.example.com/saml/acs"
            acs-path "/saml/acs"

            // Identity Provider settings
            idp-sso-url "https://idp.example.com/sso"
            idp-entity-id "https://idp.example.com"

            // Session settings
            session-ttl-secs 28800  // 8 hours
            session-store-path "/var/lib/sentinel-auth/sessions.redb"

            // Cookie settings
            session-cookie-name "sentinel_saml_session"
            cookie-secure true
            cookie-http-only true
            cookie-same-site "Lax"

            // Attribute mapping (SAML attribute -> HTTP header)
            attribute-mapping {
                "email" "X-Auth-Email"
                "groups" "X-Auth-Groups"
                "department" "X-Auth-Department"
            }

            // Path protection
            protected-paths ["/app" "/api" "/admin"]
            excluded-paths ["/health" "/metrics" "/public"]
        }
    }
}

SAML Options

OptionTypeDefaultDescription
enabledboolfalseEnable SAML authentication
entity-idstringrequiredSP entity ID
acs-urlstringrequiredAssertion Consumer Service URL
acs-pathstring/saml/acsACS path for matching
idp-sso-urlstring-IdP SSO endpoint
idp-entity-idstring-IdP entity ID
idp-metadata-urlstring-IdP metadata URL (alternative)
session-ttl-secsint28800Session lifetime (8 hours)
session-store-pathstring/var/lib/sentinel-auth/sessions.redbSession database path
clock-skew-secsint300Clock tolerance (5 minutes)
attribute-mappingobject{}Map SAML attributes to headers

SAML Authentication Flow

1. User visits protected resource without session
2. Agent redirects to IdP with SAML AuthnRequest
3. User authenticates at IdP
4. IdP posts SAML Response to ACS endpoint
5. Agent validates assertion, creates session
6. User redirected to original URL with session cookie
7. Subsequent requests validated via session

IdP Setup Examples

Okta

saml {
    enabled true
    entity-id "https://app.example.com/sp"
    acs-url "https://app.example.com/saml/acs"
    idp-metadata-url "https://your-org.okta.com/app/exk.../sso/saml/metadata"
}

Azure AD

saml {
    enabled true
    entity-id "https://app.example.com/sp"
    acs-url "https://app.example.com/saml/acs"
    idp-metadata-url "https://login.microsoftonline.com/{tenant}/federationmetadata/2007-06/federationmetadata.xml"
    attribute-mapping {
        "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" "X-Auth-Email"
    }
}

Keycloak

saml {
    enabled true
    entity-id "https://app.example.com/sp"
    acs-url "https://app.example.com/saml/acs"
    idp-metadata-url "https://keycloak.example.com/realms/myrealm/protocol/saml/descriptor"
}

Session Management

SAML sessions are persisted using an embedded database (redb), allowing sessions to survive agent restarts.

Session Features

  • Persistence: Sessions stored in embedded database
  • Caching: In-memory cache for hot sessions
  • Replay Prevention: Assertion IDs tracked to prevent replay attacks
  • Background Cleanup: Expired sessions automatically evicted

Session Configuration

OptionDefaultDescription
session-store-path/var/lib/sentinel-auth/sessions.redbDatabase file path
session-ttl-secs28800Session lifetime (8 hours)
cleanup-interval-secs300Cleanup task interval (5 min)

Authorization (Cedar Policy Engine)

After authentication, the Cedar policy engine evaluates whether the request is authorized.

Authorization Configuration

config {
    authz {
        enabled true
        policy-file "/etc/sentinel/policies/auth.cedar"
        default-decision "deny"
        principal-claim "sub"
        roles-claim "roles"
    }
}

Authorization Options

OptionTypeDefaultDescription
enabledboolfalseEnable authorization
policy-filestring-Path to Cedar policy file
policy-inlinestring-Inline Cedar policy text
default-decisionstringdenyDecision when no policy matches
principal-claimstringsubJWT claim for principal ID
roles-claimstring-JWT claim containing roles

Cedar Policy Example

// Allow authenticated users to read public API
permit(
    principal,
    action == Action::"GET",
    resource
) when {
    resource.path like "/api/public/*"
};

// Allow admins full access
permit(
    principal,
    action,
    resource
) when {
    context.roles.contains("admin")
};

// Users can only access their own resources
permit(
    principal,
    action,
    resource
) when {
    resource.path like "/api/users/*" &&
    resource.path.endsWith(principal.id)
};

// Deny access to admin endpoints for non-admins
forbid(
    principal,
    action,
    resource
) when {
    resource.path like "/admin/*" &&
    !context.roles.contains("admin")
};

Cedar Request Context

The agent builds Cedar requests with:

EntitySourceExample
PrincipalUser IDUser::"john@example.com"
ActionHTTP methodAction::"GET"
ResourceRequest pathResource::"/api/users/123"
ContextClaims, roles{"roles": ["admin"], "claims": {...}}

Note: The current Cedar integration evaluates principal, action, and resource. Entity hierarchies and complex context attributes are not yet supported. For advanced Cedar policies with entity relationships, use the dedicated Policy agent.

Token Exchange (RFC 8693)

Exchange one token type for another (e.g., SAML assertion → JWT).

Token Exchange Configuration

config {
    token-exchange {
        enabled true
        endpoint-path "/token/exchange"
        issuer "https://auth.internal.example.com"
        signing-key-file "/etc/sentinel/jwt-private.pem"
        signing-algorithm "RS256"
        token-ttl-secs 3600
        allowed-exchanges [
            { subject-token-type "saml2" issued-token-type "access_token" }
            { subject-token-type "jwt" issued-token-type "access_token" }
        ]
    }
}

Token Exchange Options

OptionTypeDefaultDescription
enabledboolfalseEnable token exchange
endpoint-pathstring/token/exchangeExchange endpoint path
issuerstringrequiredIssuer for new tokens
signing-key-filestringrequiredPath to signing key
signing-algorithmstringRS256Algorithm: RS256, ES256, HS256
token-ttl-secsint3600Token lifetime
allowed-exchangesarray[]Allowed conversions

Token Exchange Request

curl -X POST http://localhost:8080/token/exchange \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
  -d "subject_token=<saml_assertion_or_jwt>" \
  -d "subject_token_type=urn:ietf:params:oauth:token-type:saml2" \
  -d "requested_token_type=urn:ietf:params:oauth:token-type:access_token" \
  -d "audience=my-api"

Token Exchange Response

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "token_type": "Bearer",
  "expires_in": 3600
}

Headers Added

On successful authentication, the agent adds headers to the request:

HeaderDescriptionAuth Methods
X-User-IdAuthenticated user identifierAll
X-Auth-MethodMethod used: jwt, oidc, mtls, api_key, basic, samlAll
X-Auth-Claim-{name}Token claims (flattened)JWT, OIDC
X-Client-Cert-CNCertificate Common NamemTLS
X-Client-Cert-DNCertificate Distinguished NamemTLS
Custom headersVia attribute-mappingSAML

JWT Claims Example

JWT payload: {"sub": "user123", "role": "admin", "org_id": "acme"}

Headers added:
X-User-Id: user123
X-Auth-Method: jwt
X-Auth-Claim-sub: user123
X-Auth-Claim-role: admin
X-Auth-Claim-org_id: acme

Response Codes

StatusCondition
401Missing or invalid credentials
401Expired token
403Authorization denied (Cedar policy)
302SAML redirect to IdP
(passthrough)Valid credentials and authorized, request forwarded

The agent adds WWW-Authenticate: Bearer realm="sentinel" header on 401 responses.

Authentication Precedence

When multiple auth methods are configured, they are checked in order:

  1. mTLS Client Certificate - if X-Client-Cert header present
  2. Session cookie (SAML) - if present and valid
  3. Authorization: Bearer (OIDC) - if OIDC configured and header present
  4. Authorization: Bearer (JWT) - if JWT configured and header present
  5. API Key header - if configured header present
  6. Authorization: Basic - if header present

The first successful authentication wins. After authentication, if authorization is enabled, the Cedar policy engine evaluates the request.

Examples

Multi-Method Authentication

agent "auth" {
    socket "/var/run/sentinel/auth.sock"

    config {
        // JWT for API clients
        jwt-secret "your-secret-key"
        jwt-issuer "https://auth.example.com"

        // API keys for service-to-service
        api-keys "svc_orders:orders-service,svc_users:users-service"

        // SAML for browser users
        saml {
            enabled true
            entity-id "https://app.example.com/sp"
            acs-url "https://app.example.com/saml/acs"
            idp-sso-url "https://idp.example.com/sso"
            idp-entity-id "https://idp.example.com"
            protected-paths ["/app" "/dashboard"]
            excluded-paths ["/api"]  // API uses JWT/API keys
        }
    }
}

Production Kubernetes Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sentinel-auth-agent
spec:
  template:
    spec:
      containers:
        - name: auth-agent
          image: ghcr.io/raskell-io/sentinel-agent-auth:latest
          env:
            - name: AGENT_SOCKET
              value: /var/run/sentinel/auth.sock
            - name: JWT_SECRET
              valueFrom:
                secretKeyRef:
                  name: auth-secrets
                  key: jwt-secret
            - name: JWT_ISSUER
              value: https://auth.example.com
          volumeMounts:
            - name: socket
              mountPath: /var/run/sentinel
            - name: sessions
              mountPath: /var/lib/sentinel-auth
      volumes:
        - name: socket
          emptyDir: {}
        - name: sessions
          persistentVolumeClaim:
            claimName: auth-sessions

Security Recommendations

  1. Use environment variables for secrets, not command-line args
  2. Use RS256/ES256 for JWT in production (asymmetric keys)
  3. Set fail-open: false for security-critical routes
  4. Use HTTPS for all SAML and OIDC endpoints (required by spec)
  5. Rotate secrets regularly (JWT secrets, API keys, signing keys)
  6. Limit session TTL based on security requirements
  7. Secure session store file permissions (0600)
  8. Use default deny for Cedar authorization policies
  9. Validate JWKS sources - only configure trusted issuer URLs
  10. Use CA validation for mTLS when possible
  11. Rate limit token exchange endpoint to prevent abuse
AgentIntegration
PolicyAdvanced Cedar/Rego policy evaluation
WAFCombine auth with attack detection
DenylistBlock IPs before auth processing

Resources