Skip to content

AI Support Assistant

This example deploys an AI-powered support assistant that demonstrates three Riptides capabilities working together: transparent mTLS for database access, secretless credential injection for external APIs, and mixed credential sources (HashiCorp Vault and Kubernetes Secrets).

┌─────────────────┐
│ support- │
│ assistant │
│ (Next.js) │
└──┬─────┬─────┬──┘
│ │ │
mTLS │ │ │ credential injection
┌─────────────┘ │ └─────────────────┐
│ │ │
v v v
┌────────────────┐ ┌───────────────┐ ┌───────────────────┐
│ PostgreSQL │ │ Anthropic │ │ OpenAI API │
│ (internal) │ │ Claude API │ │ (external) │
│ │ │ (external) │ │ │
│ port 5432 │ │ port 443 │ │ port 443 │
│ MUTUAL TLS │ │ Bearer Token │ │ Vault-managed │
│ │ │ (K8s Secret) │ │ API key │
└────────────────┘ └───────────────┘ └───────────────────┘

How the connections work:

  • App to PostgreSQL — The kernel module performs a mutual TLS handshake. Both the app and database present SPIFFE certificates. No connection strings or TLS configuration in the application.
  • App to Anthropic API — The kernel module intercepts the HTTPS connection to api.anthropic.com and injects a Bearer token sourced from a Kubernetes Secret. The application makes a plain HTTPS call; the credential is added transparently.
  • App to OpenAI API — Same transparent injection, but the API key is fetched from HashiCorp Vault using JWT authentication. Riptides handles the Vault lease lifecycle automatically.

The main application identity.

apiVersion: core.riptides.io/v1alpha1
kind: WorkloadIdentity
metadata:
name: support-assistant
namespace: riptides-system
spec:
workloadID: support-assistant/app/assistant-api
scope:
daemonGroup:
id: riptides/daemongroup/dev-eu-west-1/on-demand-workers
selectors:
- k8s:label:app: support-assistant
k8s:pod:namespace: support-assistant
process:cmdline: next-server (v
- k8s:label:app: support-assistant
process:name: curl
connection:
tls:
mode: PERMISSIVE
intercept: true

Key points:

  • Two selector sets match the Next.js server process and curl health checks running in the same pod.
  • process:cmdline: next-server (v uses a prefix match on the process command line for precise workload attestation.
  • connection.tls.intercept: true tells the kernel module to intercept outbound TLS connections so it can inject credentials into HTTP headers. Which credentials are injected for which service is controlled by the propagation.injection.selectors in each CredentialBinding.

WorkloadIdentity: support-assistant-postgres

Section titled “WorkloadIdentity: support-assistant-postgres”

The database identity enforces strict mutual TLS. Only the support-assistant application can connect.

apiVersion: core.riptides.io/v1alpha1
kind: WorkloadIdentity
metadata:
name: support-assistant-postgres
namespace: riptides-system
spec:
workloadID: support-assistant/app/postgres
scope:
daemonGroup:
id: riptides/daemongroup/dev-eu-west-1/on-demand-workers
selectors:
- k8s:label:app: postgres
k8s:pod:namespace: support-assistant
process:cmdline: postgres
connection:
tls:
mode: MUTUAL

Key points:

  • MUTUAL TLS mode ensures every connection to the database requires a valid SPIFFE identity. No anonymous or plaintext connections are possible.
  • The process:cmdline selector matches the exact PostgreSQL server process, preventing other binaries in the same pod from claiming this identity.
apiVersion: core.riptides.io/v1alpha1
kind: Service
metadata:
name: support-assistant-postgres-svc
namespace: riptides-system
spec:
addresses:
- address: postgres-service.support-assistant.svc.cluster.local
port: 5432
tags:
- postgres
- database
external: false
labels:
api: postgres
apiVersion: core.riptides.io/v1alpha1
kind: Service
metadata:
name: anthropic-svc
namespace: riptides-system
spec:
addresses:
- address: api.anthropic.com
port: 443
tags:
- anthropic
- claude
external: true
labels:
api: anthropic
apiVersion: core.riptides.io/v1alpha1
kind: Service
metadata:
name: openai-svc
namespace: riptides-system
spec:
addresses:
- address: api.openai.com
port: 443
tags:
- openai
- chatgpt
external: true
labels:
api: openai

Note: External services (external: true) represent endpoints outside the cluster. Riptides does not manage identities for these services but can intercept outbound connections to inject credentials.

Credential sources define where secrets come from. This example uses two different backends to show that Riptides can federate credentials from multiple systems.

apiVersion: core.riptides.io/v1alpha1
kind: CredentialSource
metadata:
name: vault-openai-apikeys
namespace: riptides-system
spec:
vault:
address: https://vault.example.com
audience:
- example-audience
jwtAuthMethodPath: jwt_production
path: openai/creds/riptides-role
role: openai-apikeys
type:
token:
source: api_key

How it works:

  1. The Riptides control plane authenticates to Vault using a JWT signed with the workload’s SPIFFE identity.
  2. Vault validates the JWT against its configured OIDC provider (the Riptides trust domain).
  3. Vault returns a dynamic API key from the openai/creds/riptides-role path.
  4. Riptides extracts the api_key field from the Vault response and uses it as a bearer token.
  5. When the lease expires, Riptides automatically renews or fetches a new credential.
apiVersion: core.riptides.io/v1alpha1
kind: CredentialSource
metadata:
name: anthropic-bearer
namespace: riptides-system
spec:
kubernetes:
secretRef:
name: anthropic-api-key
key: token
type: BearerToken

This reads the token key from the anthropic-api-key Kubernetes Secret and formats it as a Bearer token for HTTP Authorization headers.

Credential bindings connect a credential source to a workload identity and define how the credential is delivered.

apiVersion: core.riptides.io/v1alpha1
kind: CredentialBinding
metadata:
name: support-assistant-openai-binding
namespace: riptides-system
spec:
workloadID: support-assistant/app/assistant-api
credentialSource: vault-openai-apikeys
propagation:
injection:
selectors:
- api: openai
sysfs: {}

Propagation methods:

  • injection — The kernel module injects the credential into HTTP requests destined for services matching the selector (api: openai). The application never sees or handles the API key.
  • sysfs — The credential is also written to the kernel sysfs filesystem, making it available at a well-known path for workloads that need to read credentials directly (e.g., for non-HTTP protocols).
apiVersion: core.riptides.io/v1alpha1
kind: CredentialBinding
metadata:
name: support-assistant-claude-binding
namespace: riptides-system
spec:
workloadID: support-assistant/app/assistant-api
credentialSource: anthropic-bearer
propagation:
injection:
selectors:
- api: anthropic

This binding uses injection only. When the support assistant connects to api.anthropic.com:443, the kernel module adds the Authorization: Bearer <token> header before the request leaves the node.

  1. Identity issuance — The daemon attests the Next.js process using its Kubernetes labels, namespace, and command line. If the selectors match, the control plane issues a SPIFFE X.509 identity (spiffe://example.com/support-assistant/app/assistant-api).

  2. Database connection — When the app opens a TCP connection to postgres-service.support-assistant.svc.cluster.local:5432, the kernel module:

    • Intercepts the socket.
    • Performs an mTLS handshake, presenting the app’s SVID.
    • Validates that the database presents spiffe://example.com/support-assistant/app/postgres.
    • Proxies data over the encrypted channel.
  3. LLM API calls — When the app makes an HTTPS request to api.anthropic.com:443, the kernel module intercepts the TLS connection. The daemon evaluates the connection context and determines that a credential should be injected for this destination. The kernel checks the eval context and injects the Bearer token into the HTTP Authorization header before forwarding the request to the external API.

The application code simply calls fetch("https://api.anthropic.com/v1/messages", ...) without any secrets in its environment, mounted volumes, or configuration files.

  • No secrets in application code or environment — API keys are managed by Riptides and injected at the kernel level. They never appear in pod specs, environment variables, or application logs.
  • Mixed credential backends — Production secrets can live in Vault with dynamic rotation while development credentials use simpler Kubernetes Secrets. The application code is identical in both cases.
  • mTLS for internal services — The PostgreSQL database rejects any connection that does not present a valid SPIFFE certificate, eliminating credential-based database authentication entirely.
  • Credential scoping — Credentials are only injected for the three services configured in the CredentialBindings. Connections to any other destination carry no injected credentials.