In our view, every workload must have a verifiable identity, and only workloads with a trusted, cryptographically provable identity should be allowed to access protected resources, without relying on secrets.
This approach is essential to defend against modern threats, where increasingly sophisticated attacks routinely target static credentials, configuration files, and long-lived keys. Secrets eventually leak; identities can be continuously verified.
Riptides is built around this principle: SPIFFE-based workload identities, enforced at runtime, with access decisions tied to who the workload is, not what secrets it happens to possess.
For a deeper exploration of the problem space and why the industry must move beyond credentials, see the following posts:
You can also learn how Riptides assigns trusted identities to processes at the kernel level here:
Workload Attestation and Metadata Gathering: Building Trust from the Ground Up
In this post, we show how to securely access OCI resources without creating or managing secrets, using the OCI CLI as client/example.
This pattern applies to any client application accessing OCI.
Riptides integrates transparently with the OCI SDK, enabling this capability without requiring any application code changes.
If you’re interested in how Riptides supports other cloud providers, see:
In this walkthrough, we configure secretless access for the OCI CLI using Riptides. Under the hood, this leverages:
For background, see:
To access OCI resources, the OCI CLI traditionally authenticates as an IAM user with appropriate permissions. This is the conventional model used by most cloud SDKs and CLIs today.
For this demo:
IAMUserViewersAllow group IAMUserViewers to inspect users in tenancy
The OCI CLI requires a configuration file that specifies which user it authenticates as. This file typically contains sensitive material such as:
key_file (private signing key)pass_phrase protecting the private keysecurity_token_file for session-based authIn this model, the client application is directly responsible for handling credentials. Even when carefully secured, these files become high-value targets: they must be stored somewhere, protected at rest, rotated regularly, and kept out of logs, backups, and build artifacts. This credential-centric approach is functional, but it tightly couples application execution with secret management, increasing both operational overhead and security risk.
Even if all best practices are followed, this model does not protect against supply‑chain attacks, as discussed in the posts linked earlier.
Listing users using this approach:
oci iam user list | jq '.data[].name'
"testuser1"
"testuser2"
Now let’s look at the same operation in a Riptides managed environment.
Riptides eliminates stored secrets by injecting credentials dynamically at runtime. The client never stores, reads, or manages OCI credentials directly.
To enable secretless access, OCI must be able to trust identities issued by Riptides and map them to an OCI IAM principal. This is done in two steps:
OCI requires a concrete IAM principal to authorize API calls. Instead of authenticating as a human user, we create a service user that will be impersonated using short-lived credentials.
The following service user definition creates a service user in OCI Identity Domains:
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"urn:ietf:params:scim:schemas:oracle:idcs:extension:user:User": {
"serviceUser": true
},
"userName": "testserviceuser1"
}
What this does:
Add this service user to the IAMUserViewers group so it inherits the required permissions.
At this point, we have an OCI principal that is allowed to access the Identity API, but no credentials have been issued or stored yet.
Next, register the Riptides Control Plane as an OIDC identity provider in OCI. This enables OCI to trust tokens issued by Riptides and exchange them for User Principal Session Tokens (UPSTs).
{
"active": true,
"allowImpersonation": true,
"issuer": "https://<riptides-control-plane>/oidc",
"name": "Token Trust JWT to UPST",
"oauthClients": ["1d92..."],
"publicKeyEndpoint": "https://<riptides-control-plane>/oidc/keys",
"impersonationServiceUsers": [
{
"rule": "sub eq spiffe://acme.org/oci-cli",
"value": "2a86..."
}
],
"subjectType": "User",
"type": "JWT",
"schemas": [
"urn:ietf:params:scim:schemas:oracle:idcs:IdentityPropagationTrust"
]
}
What this configuration enables:
issuer: Identifies the Riptides Control Plane as the trusted OIDC issueroauthClients: Restricts trust to a specific OAuth 2.0 client registered for the Riptides Control PlanepublicKeyEndpoint: Allows OCI to fetch the public keys used to verify ID tokens signed by Riptides Control PlaneimpersonationServiceUsers: Defines how a workload identity maps to an OCI IAM service userIn this example:
sub claim equal to
spiffe://acme.org/oci-cli2a86...type: JWT indicates that the trust relationship is based on JWT-formatted ID tokensHow this fits into the secretless flow:
At no point does the application receive, store, or manage credentials. Secrets exist, but they are managed by the platform, not the application.
From Riptides’ perspective, the OCI IAM service API is an external dependency. It is not discovered automatically.
To enable credential injection, Riptides must first be told which external service the workload will communicate with.
This is done by registering the OCI Identity API as an external service in the Riptides Control Plane using a Kubernetes custom resource:
apiVersion: core.riptides.io/v1alpha1
kind: Service
metadata:
name: oci-identity-api
namespace: riptides-system
spec:
addresses:
- address: identity.eu-frankfurt-1.oci.oraclecloud.com
# OCI Identity REST API service endpoint
port: 443 # Port the client connects to
labels:
app: oci-identity-api # Label for matching this service
external: true # Indicates this is an external service, not managed by Riptides
What this configuration does:
At this point, Riptides knows where the workload will connect (OCI Identity API). Next, we define how Riptides should obtain credentials for that workload.
This is done in two steps:
Step 1: Define a credential source:
A CredentialSource describes how Riptides exchanges identity for temporary OCI credentials. In this case, the source is OCI itself, using OCI IAM Workload Identity Federation.
apiVersion: core.riptides.io/v1alpha1
kind: CredentialSource
metadata:
name: oci-cred-1
namespace: riptides-system
spec:
oci: # Temporary credentials sourced from OCI
region: eu-frankfurt-1
clientId: 1d92... # the OAUth 2.0 client id defined in OCI for the Riptides Control Plane
clientSecret: idcscs-.... # the client secret defined in OCI for the Riptides Control Plane
identityDomainUrl: https://idcs-......identity.oraclecloud.com # the URL of the identity domain where the impersonated OCI service user is defined
tenancyOcid: ocid1.tenancy.oc1........
What this configuration defines:
region: The OCI region where credentials will be issued.clientId / clientSecret: OAuth 2.0 credentials used by the Riptides Control Plane, not the application, to interact with OCI IAM.identityDomainUrl: The OCI Identity Domain where the impersonated service user is defined.tenancyOcid: Identifies the OCI tenancy where authentication and authorization occur.This resource does not issue credentials on its own. It simply defines how credentials can be obtained when needed.
Step 2: Bind credentials to a workload identity:
Next, we associate the credential source with a specific workload identity using a WorkloadCredential:
apiVersion: core.riptides.io/v1alpha1
kind: WorkloadCredential
metadata:
name: oci-cli-cred-1
namespace: riptides-system
spec:
credentialSource: oci-cred-1 # Source of temporary credentials
workloadID: oci-cli # Workload ID to get OCI temporary credentials for
What this does:
oci-cli workload identity to the OCI credential sourceAt this point, no credentials are issued yet; the configuration merely defines the relationship.
How it works at runtime:
When a workload with the oci-cli identity needs to call OCI:
sub claim follows the SPIFFE format: spiffe://<TRUST_DOMAIN>/<WORKLOAD_ID>spiffe://acme.org/oci-cliAt no point does the application:
Secrets exist, but they are **entirely managed by the platform, not the workload.
We define which processes can be assigned the oci-cli workload ID, and under what conditions:
apiVersion: core.riptides.io/v1alpha1
kind: WorkloadIdentity
metadata:
name: oci-cli-wid
namespace: riptides-system
spec:
scope:
agent:
id: <AGENT_WORKLOAD_ID> # Scope: node(s) where this workload identity can be assigned
workloadID: oci-cli # The workload ID assigned to matching processes
selectors:
- process:name: [oci] # Runtime process attribute that must match to get this identity
egress: # Egress rules for credential injection
- selectors:
- app: oci-identity-api # Service endpoint(s) targeted by this rule
credentialName: oci-cli-cred-1 # Credentials to inject, referenced from WorkloadCredential
connection:
tls:
intercept: true # Intercept traffic and inject credentials into HTTP requests
How it works:
In this example, any process named oci will receive OCI temporary credentials automatically when sending requests to the OCI Identity service endpoint.
The original HTTP request sent by OCI CLI to OCI Identity service endpoint is modified by Riptides’ Linux kernel module as it injects the temporary credentials on the fly. The modified HTTP request requires resigning with the correct OCI signature. Our Linux kernel module accomplishes this using our oci-req-signer-c library, implemented in portable C with Linux kernel compatibility. This enables credentials to be injected and signed at the kernel level just before the request is sent, ensuring full OCI authentication without exposing keys to the client application.
Why is this matters:
Unlike sidecars, environment variables, or SDK hooks:
From the application’s point of view, authentication “just works.” From a security perspective, access is tightly constrained, observable, and auditable.
The OCI SDK still expects a configuration file, so we provide dummy credentials that satisfy the SDK’s syntax requirements but are never used for authentication. Riptides intercepts the request, injects valid temporary credentials, and re-signs the request transparently at runtime.
[DEFAULT]
user=ocid1.user.oc1..dummy
fingerprint=00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
key_file=/var/tmp/dummy_priv_key.pem
tenancy=ocid1.tenancy.oc1......
region=eu-frankfurt-1
The private key referenced here must be syntactically valid, but it has no permissions and no security value. At runtime, Riptides replaces it with workload-bound private key derived from the workload identity.
oci --cert-bundle /sys/module/riptides/certs/ca-certificates.crt iam user list | jq '.data.[].name'
"testuser1"
"testuser2"
"testserviceuser1
What actually happened:
There may be situations where you cannot or do not want to use on-the-wire credential injection.
In these cases, Riptides still provides credentials to the OCI SDK without exposing them on the filesystem:
sysfs.To disable on-the-wire injection, set connection.tls.intercept: false in the WorkloadIdentity custom resource.
Running OCI CLI with sysfs-based credentials:
You can point the OCI CLI to the Riptides managed configuration files as follows:
oci oci --auth security_token --cert-bundle /sys/module/riptides/certs/ca-certificates.crt --config-file /sys/module/riptides/credentials/4e1ef9dd-fa21-513d-8505-7e9ef13b9be0/oci-cli-cred-1/oci_config iam user list | jq '.data.[].name'
"testuser1"
"testuser2"
"testserviceuser1
The exact path of oci_config can be retrieved from the WorkloadCredential custom resource status fields.
Why is this approach safe:
This provides a fallback option when on-the-wire injection is not feasible, without compromising the security guarantees of the platform.
At Riptides, we are strong advocates of SPIFFE-based workload identities as the foundation for secure, scalable non‑human authentication.
The benefits are concrete and measurable:
This is more than an implementation detail; it’s a security philosophy: no static secrets, no blind trust, only verifiable workload identities at runtime.