Skip to content

Secretless GCP Access

This guide shows how to access Google Cloud services from your workloads without distributing service account keys. Riptides federates workload identity to GCP via OIDC using Workload Identity Federation, and delivers credentials transparently — either injected on the wire or via sysfs for Application Default Credentials (ADC).

GCP Workload Identity Federation allows external identity providers to impersonate GCP service accounts without long-lived keys. Riptides integrates with this mechanism:

  1. The Riptides control plane acts as an OIDC identity provider
  2. A GCP Workload Identity Pool and Provider are configured to trust the Riptides OIDC endpoint
  3. A CredentialSource references the GCP federation details
  4. A CredentialBinding binds the credential to your workload and controls delivery (wire injection, sysfs, or both)
  5. The workload accesses GCP APIs without handling any credentials
  • A Riptides control plane with an OIDC endpoint (e.g., https://<your-env-id>.console.riptides.io/oidc)
  • A GCP project with permissions to create Workload Identity Pools, providers, and service accounts
  • A workload deployed on a Kubernetes cluster with the Riptides daemon installed
  • kubectl configured with access to the riptides-system namespace
  • gcloud CLI installed (for GCP configuration steps)

Step 1: Configure GCP Workload Identity Federation

Section titled “Step 1: Configure GCP Workload Identity Federation”

A Workload Identity Pool is a container for external identities. Create one for your Riptides workloads.

Terminal window
gcloud iam workload-identity-pools create riptides-pool \
--project=my-gcp-project \
--location=global \
--display-name="Riptides Workload Pool"

Add an OIDC provider to the pool that trusts the Riptides control plane.

Terminal window
gcloud iam workload-identity-pools providers create-oidc riptides-provider \
--project=my-gcp-project \
--location=global \
--workload-identity-pool=riptides-pool \
--issuer-uri=https://<your-env-id>.console.riptides.io/oidc \
--allowed-audiences="//iam.googleapis.com/projects/<PROJECT_NUMBER>/locations/global/workloadIdentityPools/riptides-pool/providers/riptides-provider" \
--attribute-mapping="google.subject=assertion.sub"

Key parameters:

  • --issuer-uri: The Riptides control plane OIDC endpoint.

  • --allowed-audiences: The full resource name of the Workload Identity Provider. It follows the fixed format //iam.googleapis.com/projects/<PROJECT_NUMBER>/locations/global/workloadIdentityPools/<POOL_ID>/providers/<PROVIDER_ID>, where all components are known at this point. To get your PROJECT_NUMBER:

    Terminal window
    gcloud projects describe my-gcp-project --format="value(projectNumber)"
  • --attribute-mapping: Maps the JWT sub claim (which contains the SPIFFE ID) to google.subject for use in IAM bindings.

Create a service account that your workload will impersonate.

Terminal window
gcloud iam service-accounts create my-app-sa \
--project=my-gcp-project \
--display-name="My App Service Account"

Grant the service account the permissions your workload needs. For example, to access Cloud Storage and Vertex AI:

Terminal window
gcloud projects add-iam-policy-binding my-gcp-project \
--member="serviceAccount:my-app-sa@my-gcp-project.iam.gserviceaccount.com" \
--role="roles/storage.objectViewer"
gcloud projects add-iam-policy-binding my-gcp-project \
--member="serviceAccount:my-app-sa@my-gcp-project.iam.gserviceaccount.com" \
--role="roles/aiplatform.user"

Bind the External Identity to the Service Account

Section titled “Bind the External Identity to the Service Account”

Allow the specific SPIFFE ID from Riptides to impersonate this service account.

Terminal window
gcloud iam service-accounts add-iam-policy-binding \
my-app-sa@my-gcp-project.iam.gserviceaccount.com \
--project=my-gcp-project \
--role="roles/iam.workloadIdentityUser" \
--member="principal://iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/riptides-pool/subject/spiffe://example.com/my-app/workload"

The --member field uses the format principal://iam.googleapis.com/projects/<PROJECT_NUMBER>/locations/global/workloadIdentityPools/<POOL>/subject/<SPIFFE_ID>. This ensures only the workload with the matching SPIFFE ID can impersonate the service account.

To find your PROJECT_NUMBER, you can retrieve it from the pool’s resource name:

Terminal window
gcloud iam workload-identity-pools describe riptides-pool \
--location=global --project=my-gcp-project \
--format="value(name)"

The output will be in the form projects/<PROJECT_NUMBER>/locations/global/workloadIdentityPools/riptides-pool.

Define a Riptides Service resource for the GCP API endpoint your workload needs.

apiVersion: core.riptides.io/v1alpha1
kind: Service
metadata:
name: gcp-storage-svc
namespace: riptides-system
spec:
addresses:
- address: storage.googleapis.com
port: 443
external: true
labels:
app: my-app
service: gcp-storage

Apply it:

Terminal window
riptides-cli ctl apply -f gcp-storage-svc.yaml

If your workload accesses multiple GCP APIs, create a Service for each endpoint:

apiVersion: core.riptides.io/v1alpha1
kind: Service
metadata:
name: gcp-vertexai-svc
namespace: riptides-system
spec:
addresses:
- address: us-central1-aiplatform.googleapis.com
port: 443
external: true
labels:
app: my-app
service: gcp-vertexai

Create (or update) the WorkloadIdentity for your application with TLS intercept enabled. The CredentialBinding you will create in the next steps handles which services receive the credentials via its propagation.injection.selectors.

apiVersion: core.riptides.io/v1alpha1
kind: WorkloadIdentity
metadata:
name: my-app
namespace: riptides-system
spec:
workloadID: my-app/workload
selectors:
- k8s:label:app: my-app
k8s:pod:namespace: my-app
process:name: python3
scope:
daemonGroup:
id: riptides/daemongroup/dev-eu-west-1/on-demand-workers
connection:
tls:
mode: PERMISSIVE
intercept: true

Apply it:

Terminal window
riptides-cli ctl apply -f my-app-workloadidentity.yaml

Key fields:

  • connection.tls.intercept: true: Enables kernel-level TLS interception for outbound connections. Required for credential injection into HTTPS requests to external services.

A CredentialSource with the gcp type tells Riptides how to exchange a SPIFFE JWT-SVID for GCP access tokens.

apiVersion: core.riptides.io/v1alpha1
kind: CredentialSource
metadata:
name: my-app-gcp-creds
namespace: riptides-system
spec:
gcp:
oidcProviderId: >-
//iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/riptides-pool/providers/riptides-provider
serviceAccount: my-app-sa@my-gcp-project.iam.gserviceaccount.com
scopes:
- https://www.googleapis.com/auth/cloud-platform
lifetime: 3600s

Apply it:

Terminal window
riptides-cli ctl apply -f my-app-gcp-credsource.yaml

Key fields:

  • oidcProviderId: The full resource path to the Workload Identity Provider in GCP. Format: //iam.googleapis.com/projects/<PROJECT_NUMBER>/locations/global/workloadIdentityPools/<POOL_ID>/providers/<PROVIDER_ID>
  • serviceAccount: The GCP service account email to impersonate.
  • scopes: OAuth scopes for the generated access tokens. Use https://www.googleapis.com/auth/cloud-platform for broad access, or narrow it to specific API scopes.
  • lifetime: How long each access token is valid. Riptides automatically refreshes tokens before expiration.

A CredentialBinding connects the CredentialSource to your workload identity and defines how credentials are delivered.

Option A: Wire Injection (for HTTP API calls)

Section titled “Option A: Wire Injection (for HTTP API calls)”

The kernel injects OAuth bearer tokens into outbound HTTP requests. Your application makes plain calls to GCP APIs.

apiVersion: core.riptides.io/v1alpha1
kind: CredentialBinding
metadata:
name: my-app-gcp-binding
namespace: riptides-system
spec:
workloadID: my-app/workload
credentialSource: my-app-gcp-creds
propagation:
injection:
selectors:
- app: my-app
service: gcp-storage
- app: my-app
service: gcp-vertexai

Option B: sysfs Delivery (for Application Default Credentials)

Section titled “Option B: sysfs Delivery (for Application Default Credentials)”

Riptides can also expose credentials via the sysfs filesystem, allowing GCP client libraries that use Application Default Credentials (ADC) to pick them up automatically.

apiVersion: core.riptides.io/v1alpha1
kind: CredentialBinding
metadata:
name: my-app-gcp-binding
namespace: riptides-system
spec:
workloadID: my-app/workload
credentialSource: my-app-gcp-creds
propagation:
injection:
selectors:
- app: my-app
service: gcp-storage
sysfs: {}

If your application uses the GCP SDK exclusively and you do not need wire-level injection:

apiVersion: core.riptides.io/v1alpha1
kind: CredentialBinding
metadata:
name: my-app-gcp-binding
namespace: riptides-system
spec:
workloadID: my-app/workload
credentialSource: my-app-gcp-creds
propagation:
sysfs: {}

Apply whichever option fits your use case:

Terminal window
riptides-cli ctl apply -f my-app-gcp-binding.yaml

Key fields:

  • propagation.injection.selectors: Controls which egress destinations receive injected credentials. Selectors match Service labels.
  • propagation.sysfs: When present (even as an empty object {}), Riptides exposes the credential via sysfs. The token becomes available at a path like /sys/module/riptides/credentials/<hash>/<binding-name>/token.jwt. GCP client libraries can be configured to read from this path.

Your application makes plain HTTP requests. The kernel injects the OAuth bearer token automatically.

import requests
# No GCP SDK or credentials needed
response = requests.get(
"https://storage.googleapis.com/storage/v1/b/my-bucket/o",
headers={"Content-Type": "application/json"}
)
print(response.json())

When using sysfs delivery, configure the GCP SDK to use the token from the sysfs path. The CredentialBinding status will show the exact file path after the binding is created:

Terminal window
riptides-cli ctl get credentialbinding my-app-gcp-binding -o yaml

Look for the status.sysfs.files field:

status:
state: OK
sysfs:
files:
- path: /sys/module/riptides/credentials/<hash>/my-app-gcp-binding/token.jwt
type: TOKEN

Your application can then use this path with the GCP client library:

from google.cloud import storage
from google.auth import credentials
# The GCP SDK reads the token from the sysfs path
# configured via GOOGLE_APPLICATION_CREDENTIALS or
# programmatic credential loading
client = storage.Client(project="my-gcp-project")
buckets = list(client.list_buckets())

Phase 1 — Credential provisioning (triggered by CredentialBinding creation):

Riptides Control Plane
|
|-- (1) Workload has SPIFFE ID: spiffe://example.com/my-app/workload
|-- (2) Control plane presents workload's JWT-SVID to GCP STS for federation
|-- (3) GCP validates the JWT against the Riptides OIDC endpoint
|-- (4) GCP returns an access token scoped to the service account
|-- (5) Control plane pushes credentials to daemon → daemon loads them into kernel module
|-- (6) Credentials are automatically refreshed before expiration

Phase 2 — Connection-time injection:

Workload (plain HTTP or SDK call)
|
v
Riptides Kernel Module (intercepts outbound connection)
|
|-- (1) Daemon evaluates connection context
|-- (2) Daemon determines credential to inject; sets reference in eval context
|-- (3) Kernel checks eval context for credential reference
|-- (4) Kernel injects the bearer token into the outbound request
|
v
GCP API (e.g., Cloud Storage, Vertex AI)
MethodHow It WorksBest For
Wire injectionThe kernel intercepts outbound HTTP requests and adds the Authorization: Bearer <token> header. The application uses plain HTTP.Applications that make direct REST API calls without using the GCP SDK. Zero application changes required.
sysfsThe credential is written to a file under /sys/module/riptides/credentials/. GCP client libraries can read it as Application Default Credentials.Applications that use the GCP SDK and expect ADC-style credential discovery.
BothCombine injection and sysfs for maximum flexibility.Applications that mix direct HTTP calls and SDK usage.
SymptomLikely CauseFix
PERMISSION_DENIED from GCPThe SPIFFE ID does not match the principal binding on the service accountVerify the --member in the IAM policy binding matches principal://iam.googleapis.com/projects/<PROJECT_NUMBER>/.../subject/spiffe://example.com/<workloadID>
CredentialSource stuck in PENDINGOIDC provider not configured correctly in GCPVerify the Workload Identity Pool provider issuer-uri matches the Riptides control plane URL exactly
CredentialBinding state is not OKMismatched workloadIDEnsure the CredentialBinding workloadID matches the WorkloadIdentity workloadID
Token expired errorsLifetime too short or clock skewIncrease lifetime in the CredentialSource spec. Riptides refreshes tokens before expiration, but large clock differences between the cluster and GCP can cause issues.
sysfs path not appearingMissing sysfs: {} in propagationEnsure the CredentialBinding includes propagation.sysfs: {}. Check status.sysfs.files in the binding resource.