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).
Overview
Section titled “Overview”GCP Workload Identity Federation allows external identity providers to impersonate GCP service accounts without long-lived keys. Riptides integrates with this mechanism:
- The Riptides control plane acts as an OIDC identity provider
- A GCP Workload Identity Pool and Provider are configured to trust the Riptides OIDC endpoint
- A
CredentialSourcereferences the GCP federation details - A
CredentialBindingbinds the credential to your workload and controls delivery (wire injection, sysfs, or both) - The workload accesses GCP APIs without handling any credentials
Prerequisites
Section titled “Prerequisites”- 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
kubectlconfigured with access to theriptides-systemnamespacegcloudCLI installed (for GCP configuration steps)
Step 1: Configure GCP Workload Identity Federation
Section titled “Step 1: Configure GCP Workload Identity Federation”Create a Workload Identity Pool
Section titled “Create a Workload Identity Pool”A Workload Identity Pool is a container for external identities. Create one for your Riptides workloads.
gcloud iam workload-identity-pools create riptides-pool \ --project=my-gcp-project \ --location=global \ --display-name="Riptides Workload Pool"Create a Workload Identity Provider
Section titled “Create a Workload Identity Provider”Add an OIDC provider to the pool that trusts the Riptides control plane.
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 yourPROJECT_NUMBER:Terminal window gcloud projects describe my-gcp-project --format="value(projectNumber)" -
--attribute-mapping: Maps the JWTsubclaim (which contains the SPIFFE ID) togoogle.subjectfor use in IAM bindings.
Create a GCP Service Account
Section titled “Create a GCP Service Account”Create a service account that your workload will impersonate.
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:
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.
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:
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.
Step 2: Create the GCP Service
Section titled “Step 2: Create the GCP Service”Define a Riptides Service resource for the GCP API endpoint your workload needs.
apiVersion: core.riptides.io/v1alpha1kind: Servicemetadata: name: gcp-storage-svc namespace: riptides-systemspec: addresses: - address: storage.googleapis.com port: 443 external: true labels: app: my-app service: gcp-storageApply it:
riptides-cli ctl apply -f gcp-storage-svc.yamlIf your workload accesses multiple GCP APIs, create a Service for each endpoint:
apiVersion: core.riptides.io/v1alpha1kind: Servicemetadata: name: gcp-vertexai-svc namespace: riptides-systemspec: addresses: - address: us-central1-aiplatform.googleapis.com port: 443 external: true labels: app: my-app service: gcp-vertexaiStep 3: Create the WorkloadIdentity
Section titled “Step 3: Create the WorkloadIdentity”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/v1alpha1kind: WorkloadIdentitymetadata: name: my-app namespace: riptides-systemspec: 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: trueApply it:
riptides-cli ctl apply -f my-app-workloadidentity.yamlKey fields:
connection.tls.intercept: true: Enables kernel-level TLS interception for outbound connections. Required for credential injection into HTTPS requests to external services.
Step 4: Create the CredentialSource
Section titled “Step 4: Create the CredentialSource”A CredentialSource with the gcp type tells Riptides how to exchange a SPIFFE JWT-SVID for GCP access tokens.
apiVersion: core.riptides.io/v1alpha1kind: CredentialSourcemetadata: name: my-app-gcp-creds namespace: riptides-systemspec: 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: 3600sApply it:
riptides-cli ctl apply -f my-app-gcp-credsource.yamlKey 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. Usehttps://www.googleapis.com/auth/cloud-platformfor broad access, or narrow it to specific API scopes.lifetime: How long each access token is valid. Riptides automatically refreshes tokens before expiration.
Step 5: Create the CredentialBinding
Section titled “Step 5: Create the CredentialBinding”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/v1alpha1kind: CredentialBindingmetadata: name: my-app-gcp-binding namespace: riptides-systemspec: workloadID: my-app/workload credentialSource: my-app-gcp-creds propagation: injection: selectors: - app: my-app service: gcp-storage - app: my-app service: gcp-vertexaiOption 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/v1alpha1kind: CredentialBindingmetadata: name: my-app-gcp-binding namespace: riptides-systemspec: workloadID: my-app/workload credentialSource: my-app-gcp-creds propagation: injection: selectors: - app: my-app service: gcp-storage sysfs: {}Option C: sysfs Only
Section titled “Option C: sysfs Only”If your application uses the GCP SDK exclusively and you do not need wire-level injection:
apiVersion: core.riptides.io/v1alpha1kind: CredentialBindingmetadata: name: my-app-gcp-binding namespace: riptides-systemspec: workloadID: my-app/workload credentialSource: my-app-gcp-creds propagation: sysfs: {}Apply whichever option fits your use case:
riptides-cli ctl apply -f my-app-gcp-binding.yamlKey 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.
Step 6: Access GCP from Your Application
Section titled “Step 6: Access GCP from Your Application”With Wire Injection
Section titled “With Wire Injection”Your application makes plain HTTP requests. The kernel injects the OAuth bearer token automatically.
import requests
# No GCP SDK or credentials neededresponse = requests.get( "https://storage.googleapis.com/storage/v1/b/my-bucket/o", headers={"Content-Type": "application/json"})
print(response.json())With sysfs and GCP Client Libraries
Section titled “With sysfs and GCP Client Libraries”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:
riptides-cli ctl get credentialbinding my-app-gcp-binding -o yamlLook for the status.sysfs.files field:
status: state: OK sysfs: files: - path: /sys/module/riptides/credentials/<hash>/my-app-gcp-binding/token.jwt type: TOKENYour application can then use this path with the GCP client library:
from google.cloud import storagefrom google.auth import credentials
# The GCP SDK reads the token from the sysfs path# configured via GOOGLE_APPLICATION_CREDENTIALS or# programmatic credential loadingclient = storage.Client(project="my-gcp-project")buckets = list(client.list_buckets())How It Works: The OIDC Federation Flow
Section titled “How It Works: The OIDC Federation Flow”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 expirationPhase 2 — Connection-time injection:
Workload (plain HTTP or SDK call) | vRiptides 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 | vGCP API (e.g., Cloud Storage, Vertex AI)Credential Delivery Comparison
Section titled “Credential Delivery Comparison”| Method | How It Works | Best For |
|---|---|---|
| Wire injection | The 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. |
| sysfs | The 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. |
| Both | Combine injection and sysfs for maximum flexibility. | Applications that mix direct HTTP calls and SDK usage. |
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely Cause | Fix |
|---|---|---|
PERMISSION_DENIED from GCP | The SPIFFE ID does not match the principal binding on the service account | Verify the --member in the IAM policy binding matches principal://iam.googleapis.com/projects/<PROJECT_NUMBER>/.../subject/spiffe://example.com/<workloadID> |
CredentialSource stuck in PENDING | OIDC provider not configured correctly in GCP | Verify the Workload Identity Pool provider issuer-uri matches the Riptides control plane URL exactly |
CredentialBinding state is not OK | Mismatched workloadID | Ensure the CredentialBinding workloadID matches the WorkloadIdentity workloadID |
| Token expired errors | Lifetime too short or clock skew | Increase 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 appearing | Missing sysfs: {} in propagation | Ensure the CredentialBinding includes propagation.sysfs: {}. Check status.sysfs.files in the binding resource. |