Kubernetes Secret-Based Bearer Tokens
This guide shows how to store API keys in Kubernetes Secrets and have Riptides deliver them transparently to workloads as Authorization: Bearer headers — no application code changes required.
Overview
Section titled “Overview”Many external APIs (LLM providers, SaaS platforms, internal services) authenticate requests using bearer tokens passed in the Authorization header. Traditionally, applications read these tokens from environment variables or mounted files. With Riptides, you can instead:
- Store the API key in a standard Kubernetes Secret.
- Create a CredentialSource that references the Secret.
- Create a CredentialBinding that tells the kernel which outbound traffic should receive the token.
- The Riptides kernel automatically adds the
Authorization: Bearer <token>header to matching requests.
Your application code makes plain HTTP/HTTPS requests to the API. The kernel intercepts them and injects the credential before the request leaves the node.
Prerequisites
Section titled “Prerequisites”- A running Riptides control plane with daemons deployed
kubectlaccess to theriptides-systemnamespace- An API key for the external service you want to call
Step 1: Create a Kubernetes Secret
Section titled “Step 1: Create a Kubernetes Secret”Store your API key in a standard Kubernetes Secret in the riptides-system namespace.
kubectl create secret generic my-api-key \ --from-literal=token='<your-api-key>' \ -n riptides-systemOr as YAML:
apiVersion: v1kind: Secretmetadata: name: my-api-key namespace: riptides-systemtype: OpaquestringData: token: "<your-api-key>"riptides-cli ctl apply -f secret.yamlSecurity note: The Secret lives in the
riptides-systemnamespace, not in your application namespace. Access is mediated through CredentialSource and CredentialBinding resources, so the workload never directly accesses the Secret.
Step 2: Create a CredentialSource
Section titled “Step 2: Create a CredentialSource”The CredentialSource tells Riptides where to find the credential and what type it is.
apiVersion: core.riptides.io/v1alpha1kind: CredentialSourcemetadata: name: my-api-bearer namespace: riptides-systemspec: kubernetes: secretRef: key: token name: my-api-key type: BearerTokenField reference:
| Field | Description |
|---|---|
secretRef.name | Name of the Kubernetes Secret. |
secretRef.key | Key within the Secret that holds the token value. |
type | Credential type. BearerToken means Riptides will inject it as an Authorization: Bearer header. |
riptides-cli ctl apply -f credentialsource.yamlVerify:
riptides-cli ctl get credentialsource my-api-bearer# STATE should show AVAILABLEStep 3: Create an External Service
Section titled “Step 3: Create an External Service”Define a Riptides Service for the external API endpoint. This tells Riptides which destination addresses should be considered part of this service.
apiVersion: core.riptides.io/v1alpha1kind: Servicemetadata: name: my-api-svc namespace: riptides-systemspec: addresses: - address: api.example.com port: 443 tags: - my-api external: true labels: api: my-apiKey fields:
external: true: Marks this as an external service (not running inside the cluster).labels: Used by CredentialBinding injection selectors and WorkloadIdentity egress selectors to match traffic to this service.
riptides-cli ctl apply -f service.yamlStep 4: Create a CredentialBinding
Section titled “Step 4: Create a CredentialBinding”The CredentialBinding connects the CredentialSource to a specific workload and defines how the credential is delivered.
apiVersion: core.riptides.io/v1alpha1kind: CredentialBindingmetadata: name: my-workload-api-access namespace: riptides-systemspec: credentialSource: my-api-bearer propagation: injection: selectors: - api: my-api workloadID: "my-namespace/app/my-workload"How injection works:
- The
selectorsmatch against Service labels. The daemon evaluates the connection context and determines which services should receive the credential. When the workload makes an outbound connection to a Service with labelapi: my-api, the kernel injects theAuthorization: Bearer <token>header. - The workload does not need to know about the token at all. It simply makes requests to
https://api.example.com.
riptides-cli ctl apply -f credentialbinding.yamlVerify:
riptides-cli ctl get credentialbinding my-workload-api-access# STATE should show OKStep 5: Create a WorkloadIdentity with Egress
Section titled “Step 5: Create a WorkloadIdentity with Egress”The WorkloadIdentity assigns a SPIFFE identity to your workload and enables TLS intercept for outbound connections. The CredentialBinding created in the previous step controls which services receive the credential via its propagation.injection.selectors.
apiVersion: core.riptides.io/v1alpha1kind: WorkloadIdentitymetadata: name: my-workload namespace: riptides-systemspec: connection: tls: mode: PERMISSIVE intercept: true scope: daemonGroup: id: "<cluster>/<daemongroup-path>" selectors: - k8s:label:app: my-workload k8s:pod:namespace: my-namespace process:name: node workloadID: "my-namespace/app/my-workload"Key fields:
connection.tls.intercept: true: The kernel intercepts outbound connections and handles TLS + credential injection.selectors: Identifies which processes on which pods receive this identity.
riptides-cli ctl apply -f workloadidentity.yamlComplete Example: Calling an LLM API
Section titled “Complete Example: Calling an LLM API”Here is a full working example for calling an LLM provider’s API.
Secret
Section titled “Secret”apiVersion: v1kind: Secretmetadata: name: llm-api-key namespace: riptides-systemtype: OpaquestringData: token: "<your-llm-api-key>"CredentialSource
Section titled “CredentialSource”apiVersion: core.riptides.io/v1alpha1kind: CredentialSourcemetadata: name: llm-bearer namespace: riptides-systemspec: kubernetes: secretRef: key: token name: llm-api-key type: BearerTokenService
Section titled “Service”apiVersion: core.riptides.io/v1alpha1kind: Servicemetadata: name: llm-api-svc namespace: riptides-systemspec: addresses: - address: api.llm-provider.com port: 443 external: true labels: api: llm-providerCredentialBinding
Section titled “CredentialBinding”apiVersion: core.riptides.io/v1alpha1kind: CredentialBindingmetadata: name: assistant-llm-access namespace: riptides-systemspec: credentialSource: llm-bearer propagation: injection: selectors: - api: llm-provider workloadID: "my-namespace/app/assistant-api"WorkloadIdentity
Section titled “WorkloadIdentity”apiVersion: core.riptides.io/v1alpha1kind: WorkloadIdentitymetadata: name: assistant-api namespace: riptides-systemspec: connection: tls: mode: PERMISSIVE intercept: true scope: daemonGroup: id: "<cluster>/<daemongroup-path>" selectors: - k8s:label:app: assistant k8s:pod:namespace: my-namespace process:name: node workloadID: "my-namespace/app/assistant-api"Deploy
Section titled “Deploy”riptides-cli ctl apply -f secret.yamlriptides-cli ctl apply -f credentialsource.yamlriptides-cli ctl apply -f service.yamlriptides-cli ctl apply -f credentialbinding.yamlriptides-cli ctl apply -f workloadidentity.yamlYour application can now call https://api.llm-provider.com/v1/messages without setting any Authorization header. The Riptides kernel adds it automatically.
Multiple APIs
Section titled “Multiple APIs”A single workload can access multiple external APIs. Create a separate CredentialSource, Service, and CredentialBinding for each API — each CredentialBinding specifies its own propagation.injection.selectors to control which service it targets. The WorkloadIdentity only needs connection.tls.intercept: true set once at the top level.
Verification
Section titled “Verification”-
CredentialSource is
AVAILABLE:Terminal window riptides-cli ctl get credentialsource my-api-bearer -
CredentialBinding is
OK:Terminal window riptides-cli ctl get credentialbinding my-workload-api-access -
WorkloadIdentity is assigned:
Terminal window riptides-cli ctl get workloadidentity my-workload -
Make a request from your workload to the external API. The response should succeed without your application setting any authentication headers.