​Modern Continuous Integration/Continuous Delivery (CI/CD) platforms are vital for automating critical steps in software delivery. They routinely invoke cloud APIs for tasks such as provisioning infrastructure, deploying applications, running tests, scaling resources, and more.

Historically, connecting these pipelines to cloud providers required incorporating static credentials directly into the CI environment. This approach presents a significant security risk. Long-lived credentials can persist far beyond the brief execution window of a pipeline or job. If compromised, they could grant unauthorized access indefinitely. To make things worse, these credentials often provide broad permissions because managing highly scoped, static credentials for each pipeline job is operationally impractical and hinders automation. In addition, static credentials demand greater upkeep as they must be securely stored, regularly monitored, and frequently changed to minimize their inherent security weaknesses.

Top CI platforms, such as GitHub Actions, GitLab CI/CD, and Bitbucket Pipelines, address the issue of static credentials by using OpenID Connect (OIDC).These platforms act as OIDC Identity Providers, generating unique, scope-specific tokens for each pipeline execution. This eliminates the need for static secret storage; instead, pipelines can exchange these OIDC tokens with OIDC-compatible cloud providers. This exchange results in the pipeline receiving temporary, short-lived credentials issued by the cloud provider, often narrowly scope to the requesting pipeline context.

In support of this secure pattern, Oracle Cloud Infrastructure (OCI) recently enhanced its token-exchange feature ( Workload Identity Federation ) to allow the exchange of OIDC tokens . While OCI has long supported OIDC for authenticating users to its console, the new OCI Token Exchange Grant Type lets you exchange any trusted JSON Web Token (JWT), like an OIDC token from your CI platform, for a short-lived OCI Session Token. This session token, also known as a User Principal Session Token (UPST), provides the necessary credentials to authenticate and authorize calls directly to OCI APIs. This enables pipelines to securely access OCI resources without ever needing to store static OCI API keys.

Overview: GitHub OIDC and OCI Token Exchange 

To take advantage of this improved, secure authentication model in your CI/CD pipelines, there are several key steps you need to implement. In the following sections, we’ll walk through the essential components required to set up OIDC-based token exchange between GitHub Action Workflows and OCI, and explain how each part contributes to a secure, secretless automation pipeline.

GitHub OIDC

GitHub’s native support for OIDC enables GitHub Actions workflows to securely request short-lived identity tokens from GitHub’s OIDC provider. When a workflow requests an OIDC token, GitHub generates a signed JWT containing claims about the workflow run, including the repository information (such as name and owner), workflow information (such as name and ID), branch or tag reference, commit SHA, and the actor (the user who triggered the workflow). Additional claims may include environment, run details, event name, and more. This token can be presented to trusted third-party services, such as OCI, to obtain temporary credentials.

Workflows must explicitly request the id-token: write permission to access GitHub OIDC tokens. This permission is configured within your workflow file using the permissions key, either at the top level of the workflow or within specific jobs. By setting id-token to write, you grant a specific workflow or job the necessary authorization to fetch the OIDC JWT issued by GitHub’s OIDC provider. This explicit requirement ensures that only workflows or jobs you have specifically authorized can access this sensitive token, preventing unauthorized access even within the same repository.

OCI Token Exchange Grant Type

To use the OCI JWT-to-UPST token exchange flow with third-party JWTs, we must first establish trust between the external OIDC provider (e.g., GitHub, GitLab, or Bitbucket) and OCI. This is achieved by creating an Identity Propagation Trust Configuration within an OCI Identity Domain, which instructs OCI on how to validate incoming JWTs and map their assertions to an OCI principal.

For automation workloads like CI/CD pipelines, the recommended approach is to configure this trust for impersonation. This ensures the resulting UPST’s subject (i.e., its sub claim) identifies an OCI Service User.

A Service User is a specialized OCI account designed for non-interactive use cases such as automation, applications, and API integrations. They can be assigned OCI group memberships and service roles for precise permission control but cannot log in interactively as the Service User. This makes them ideal for secure, automated access.

With impersonation enabled, the OCI identity domain uses predefined rules to map claims from the incoming third-party JWT (e.g., the GitHub repository and branch) to a specific Service User. The issued UPST, identifying this Service User as its subject, then allows operations to be performed with a dedicated, auditable identity tailored specifically for automation.

Why use Service Users?

Enhanced Security: Automation runs as a dedicated, non-interactive account rather than a standard user.

Fine-Grained Access Control: Permissions are managed by assigning the Service User to specific OCI groups and roles, supporting the principle of least privilege.

Dynamic Principal Mapping: The specific Service User associated with the exchanged UPST can be determined dynamically based on rules defined in the Identity Propagation Trust configuration. This allows for flexible, context-aware principal mapping based on claims in the incoming JWT (e.g., repository name, branch, or environment).

Clear Audit Trails: Actions performed using the UPST are logged and attributed directly to the Service User, simplifying auditing and compliance. For enhanced traceability, the OCI Audit log captures the Service User associated with the UPST as well as the original subject (`sub` claim) from the GitHub OIDC token that was exchanged. This provides an audit trail that links actions back to the specific GitHub workflow context.

With the trust configuration in place, the workflow is ready to perform the token exchange. During the pipeline run, a workflow step leverages the OIDC JWT obtained through GitHub Actions’ support and sends it to OCI via a POST request to the OCI token exchange endpoint, located at /oauth2/v1/token within the IAM domain configured for trust.

 

Just Add Some Details

 

The following sections detail the implementation of this token exchange using a custom GitHub Action. This action encapsulates the logic to exchange a GitHub OIDC token for an OCI UPST and prepares the GitHub runner environment. This allows subsequent workflow steps to use the short-lived UPST with OCI tools like the CLI, SDKs, or the Terraform provider.

Configure a Service User in an OCI IAM domain

For this article, we’ll assume the use of the Default Identity Domain in an OCI account.A Service User can be configured in several ways, including using the OCI SDK, Terraform provider, or the Identity Domain’s SCIM (System for Cross-domain Identity Management) endpoints. This article will focus on using the domain’s SCIM API.

Get an OAuth Access Token to authorize calls to the domain SCIM API

To authorize calls to the OCI Identity Domain’s SCIM endpoint, you will use an OCI Personal Access Token (PAT). Users logged into the OCI console can generate these PATs, which are specific to an OCI Identity Domain. These tokens serve as OAuth 2.0 access tokens, intended for programmatic access to APIs protected by the Identity Domain’s OAuth Authorization Server, including domain-specific APIs like SCIM. Follow the instructions in the OCI Documentation: Generate Personal Access Tokens to obtain this token.

Important considerations when using Personal Access Tokens for SCIM operations:

Domain Context: You must be logged in to the OCI Console within the specific OCI Identity Domain where you intend to add the Service User and configure the OCI Token Exchange trust.

Required Domain Role: You must be entitled to assign the  Identity Domain Administrator domain role to the PAT you are creating 

SCIM API Authorization: The PAT functions as a bearer token that grants your programmatic client the permissions associated with the specific domain role(s) you assign to the PAT during its creation in the console.

Alternatively, you can register a confidential OAuth Client application in the OCI Identity Domain and obtain an OAuth access token with the required domain role using one of the supported OAuth grant types you configured for the application. However, this is not an approach I recommend. From a security perspective, using the Client Credentials grant type specifically for this purpose creates long-lived credentials (the confidential app’s client credentials) that are associated with powerful domain administration roles. In the case of the Identity Domain Administrator role in the Default Identity Domain, any person in possession of those client credentials could assume full control over your OCI account. 

Create the Service User

With your personal OAuth Access Token in hand, you can now create the Service User. This is achieved by authorizing a standard SCIM `POST` request to the OCI Identity Domain’s `/Users` endpoint. The key distinction that designates this user as a “Service User” is the inclusion of the "serviceUser": true attribute within the urn:ietf:params:scim:schemas:oracle:idcs:extension:user:User schema extension. Here’s an example curl command to create a Service User:
 

curl -X POST
  "https://<your_identity_domain_url>/admin/v1/Users"
  -H "Content-Type: application/json"
  -H "Authorization: Bearer YOUR_OAUTH_ACCESS_TOKEN"
  -d '{
"schemas": [
  "urn:ietf:params:scim:schemas:core:2.0:User",
  "urn:ietf:params:scim:schemas:oracle:idcs:extension:user:User"
],
"urn:ietf:params:scim:schemas:oracle:idcs:extension:user:User": {
  "serviceUser": true
},
"userName": "your_service_username"
}'

 

Grant the Service User OCI permissions 

Once the Service User is created within the OCI Identity Domain, the next step is to grant it the appropriate permissions to interact with OCI resources as required by your CI/CD pipeline. This is typically achieved through a two-step process:

1.  Add the Service User to a Group in the Identity Domain: Within the same OCI Identity Domain where the Service User was provisioned, create a new group (e.g., `GitHubProductionWorkflows`) or use an existing one. Then, add your newly created Service User as a member of this group. 

2.  Associate the Domain Group with OCI IAM Policies: The permissions themselves are defined at the OCI tenancy or compartment level using standard OCI IAM policies. You will create policies that grant the necessary permissions (e.g., to manage Compute instances, deploy to Kubernetes, upload to Object Storage, etc.) to the group you created or used in the IAM domain.

By following this approach, you adhere to the principle of least privilege, ensuring that the Service User (and by extension, your GitHub Actions workflow) has only the permissions necessary to perform its intended tasks. The specific permissions will vary based on the use cases your GitHub pipeline intends to implement.

Configure the trust relationship between GitHub and the OCI Identity Domain

Configure a confidential OAuth client Application in the OCI Identity Domain

This OAuth client represents the GitHub custom action that will invoke the token exchange endpoint in the domain to exchange the GitHub OIDC ID token. When you set up this confidential client app in your OCI Identity Domain, make sure it is configured to support the OAuth Client Credentials grant type (IMPORTANT : It is very important NOT to associate any Identity Domain roles with this confidential OAuth client application). After the application is registered, the OCI Identity Domain will create a Client ID and a Client Secret (the OAuth client credentials). Make sure to save both of these values. You will need them later when you set up the oidc_client_identifier input for the GitHub Action. The client ID will also be required in the next section, which describes the one-time task of setting up the trust between GitHub and the OCI Identity Domain. For detailed guidance on configuring a confidential client application, including setting up the necessary OAuth Client Credentials grant type, refer to the OCI Documentation: Adding a Confidential Application. This article will not delve further into these specific configuration steps.

Configure the chosen domain to trust the GitHub OIDC provider

This trust configuration is critical as it dictates how OCI will validate and process incoming JWTs from the trusted provider. It maps the assertions in the external JWT to an OCI principal, either directly or through impersonation of a Service User.

Key aspects to configure in this trust policy include:

Issuer (`issuer`): Must exactly match the `iss` claim of the incoming JWT (e.g., `https://token.actions.githubusercontent.com` for GitHub Actions).

Active Status (`active`): A boolean to enable or disable the trust policy.

OAuth Clients (`oauthClients`): An array of OAuth Client ID(s) registered in the OCI Identity Domain that are permitted to use this trust policy.

Public Key Configuration:

publicKeyEndpoint The JWKS (JSON Web Key Set) URI of the identity provider (e.g., `https://token.actions.githubusercontent.com/.well-known/jwks` for GitHub Actions). OCI will fetch public keys from this endpoint to verify the JWT signature.

– publicCertificate: Alternatively, if the JWKS endpoint is not accessible, you can directly embed the x.509 public signing certificate of the token issuer. (This is generally not required for GitHub Actions, which provides a JWKS endpoint).

– Client Claim Validation (Optional):

clientClaimName: The name of an additional claim within the JWT that OCI should check.

clientClaimValues: A list of acceptable values for the `clientClaimName`. If the specified claim is not present or its value doesn’t match, the token exchange will be denied.

Subject Mapping:

subjectClaimName: The claim in the JWT that identifies the subject (e.g., `sub`, which for GitHub Actions often includes repository and ref information). Defaults to `sub`.

subjectMappingAttribute: The OCI Identity user attribute to map the `subjectClaimName` to (e.g., `userName`).

subjectType: Typically “User”.

Impersonation (`allowImpersonation`):

– Set to `true` if you want the exchanged OCI UPST to represent a specific Service User in OCI. This is highly recommended for CI/CD automation.

– If `false`, the UPST is issued for the user principal whose identity (after mapping via `subjectMappingAttribute`) matches the value of the JWT claim specified in `subjectClaimName`.

Impersonation Rules (`impersonationServiceUsers`): (Required if `allowImpersonation` is `true`)

– Each rule consists of a `rule` (a condition based on JWT claims, e.g., `sub eq ‘repo:your-org/your-repo:ref:refs/heads/main’`) and a `value` (the OCID of the Service User to impersonate if the rule matches).

– Multiple rules can be defined; OCI evaluates them in order, and the first matching rule determines the Service User to be impersonated.

– In addition to the eq( equals) comparator rules also support co ( contains)

– Wildcard (*) are supported in rules that use the eq comparator

For example, a rule like `”rule”: “sub eq ‘repo:your-org/your-repo:ref:refs/heads/main'”` with a specific Service User OCID as the `”value“` would map workflows from the `main` branch of `your-org/your-repo` to that Service User. More complex rules can inspect other standard claims (like `actor`, `repository_owner`, etc) .

When impersonation is used, the resulting OCI UPST will also contain a claim (`source_authn_prin`) indicating the original identity from the JWT for auditing purposes.

 

For the precise API request structure, rule syntax, and all available configuration options for creating Identity Propagation Trust Policies (e.g., via OCI CLI, SDK, or direct API calls), please consult the official OCI Documentation: Managing Identity Propagation Trust Policies

 

With these configurations in place, your OCI Identity Domain is ready to securely accept and validate OIDC tokens from your GitHub Actions workflows.

Below is an illustrative cURL command for creating an Identity Propagation Trust policy using the OCI Identity Domain SCIM API, tailored for GitHub Actions. 

 

curl --request POST \

--url https://YOUR_DOMAIN_URL/admin/v1/IdentityPropagationTrusts \

--header 'authorization: Bearer YOUR_OAUTH_ACCESS_TOKEN' \

--header 'content-type: application/json' \

--data '{

"active": true,

"allowImpersonation": true,

"issuer": "https://token.actions.githubusercontent.com",

"name": "github-actions-trust",

"oauthClients": ["OAUTH_CLIENT_ID"],

"publicKeyEndpoint": "https://token.actions.githubusercontent.com/.well-known/jwks",

"subjectClaimName": "sub",

"subjectMappingAttribute": "userName",

"subjectType": "User",

"type": "JWT",

"schemas": ["urn:ietf:params:scim:schemas:oracle:idcs:IdentityPropagationTrust"],

"impersonationServiceUsers": [

{

"rule": "sub eq '\''repo:YOUR_GITHUB_ORG/YOUR_REPO_NAME:ref:refs/heads/main'\''",

"value": "ocid1.user.oc1..YOUR_SERVICE_USER_OCID_FOR_MAIN"

},

{

"rule": "sub eq '\''repo:YOUR_GITHUB_ORG/YOUR_REPO_NAME:environment:production'\''",

"value": "ocid1.user.oc1..YOUR_SERVICE_USER_OCID"

}

]

}'

*Remember to replace placeholders (YOUR_DOMAIN_URL, YOUR_OAUTH_ACCESS_TOKEN, OAUTH_CLIENT_ID , YOUR_GITHUB_ORG/YOUR_REPO_NAME and YOUR_SERVICE_USER_OCID)with your actual values.

Configure the GitHub Custom Action

To streamline the OIDC-to-OCI token exchange process in your CI/CD pipelines, this blog introduces a ready-to-use GitHub Action. The action encapsulates the logic to fetch the GitHub OIDC token, exchange it for a short-lived OCI UPST, and then configure the GitHub runner environment for secure OCI API access. You don’t have to worry about the underlying mechanics .

Inputs Supported by the Action

The `gtrevorrow/oci-token-exchange-action` is designed to be flexible and configurable for a variety of OCI authentication scenarios. Here are the key inputs you can provide to the action:

– `oidc_client_identifier` (required):

The `client_id:client_secret` string for your confidential OAuth client application. This string is the content used for HTTP Basic Authentication (prior to Base64 encoding), as typically used with the OAuth 2.0 client credentials grant type. The action handles the Base64 encoding. This client application must be registered in the OCI IAM domain and listed in the `oauthClients` attribute of your Identity Propagation Trust policy. This identifies the application making the token exchange request. The client credentials associated with this identifier is used to validate the client performing the token exchange. It’s important to understand that this is not analogous to a static, long-lived API key; you cannot authenticate and authorize calls to OCI APIs using these client credentials directly. Instead, it serves as an additional layer of protection (in addition to the impersonation rules defined in the trust policy) to ensure that only authorized clients can perform a token exchange. This is especially relevant in contexts like GitHub Actions, where all repositories share a single OCI OIDC provider that signs the tokens it issues with the same private key (i.e., the signing key is not unique per repository or workflow).

– `domain_base_url` (required):

The base URL of your OCI IAM domain, which has been configured to trust GitHub OIDC tokens (e.g., `https://idcs-xxxx.identity.oraclecloud.com`). This specifies the endpoint for the token exchange request.

– `oci_tenancy` (required):

The OCID of the OCI tenancy you want to target. This is used to scope the session token and configure the OCI CLI.

– `oci_region` (required):

The OCI region identifier (e.g., `us-phoenix-1`) that should be associated with  the current OCI profile being configured by the action  .

– `platform` (optional):

Specifies the CI/CD platform. For GitHub Actions, this defaults to `github` and typically does not need to be set explicitly. Other values can be `gitlab`, `bitbucket`, or `local`.

– `retry_count` (optional):

The number of times to retry the token exchange request in case of transient failures. Defaults to `0` (no retries).

– `oci_profile` (optional):

The name of the OCI CLI profile to create or update in the OCI configuration file. Defaults to `DEFAULT` if not specified.

– `oci_home` (optional):

The directory where the OCI CLI configuration file (e.g., `~/.oci/config`) and associated token files will be written. Defaults to the standard OCI home directory (e.g., `~/.oci`). This is especially useful when targeting multiple accounts in the same workflow.

– `debug` (optional):

Set to `true` to enable verbose logging for troubleshooting the action’s execution.

 For a full list of supported inputs and advanced configuration options, refer to the README in the repository.

Configuring the Action for Multiple OCI Accounts

A common scenario is deploying to or managing resources across multiple OCI tenancies, or compartments within the same workflow, where each compartment’s associated IAM policies grant access to different groups, thereby necessitating distinct Service Users for your automation. The action supports this by allowing multiple invocations. Instead of using separate oci_home directories for each account, you can configure distinct profiles within the default OCI CLI configuration file by using the oci_profile input.

Below is an example workflow that exchanges tokens and sets up two different OCI profiles, ACCOUNT_1_PROFILE and ACCOUNT_2_PROFILE. Subsequent steps can then target a specific account by setting the OCI_CLI_PROFILE environment variable or by using the --profile option with OCI CLI commands. (Refer to the “Inputs Supported by the Action” section for details on each parameter.)

 

name: Run OCI Multi-Account Token Exchange

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      id-token: 'write' # Required to fetch GitHub OIDC tokens
    steps:
      - name: Set up Node.js
        uses: actions/setup-node@v4.0.3
        with:
          node-version: '20'

      - name: Install OCI CLI
        run: |
          curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh | bash -s -- --accept-all-defaults
          echo "$HOME/bin" >> $GITHUB_PATH # Add OCI CLI to PATH
        shell: bash

      - name: Run OCI Token Exchange Action (Account 1)
        uses: gtrevorrow/oci-token-exchange-action@v1
        with:
          oidc_client_identifier: ${{secrets.OIDC_CLIENT_IDENTIFIER_ACCT1}} # Secret for Account 1's OAuth Client ID:Secret
          domain_base_url: ${{vars.DOMAIN_BASE_URL_ACCT1}} # Variable for Account 1's Identity Domain URL
          oci_tenancy: ${{vars.OCI_TENANCY_ACCT1}} # Variable for Account 1's Tenancy OCID
          oci_region: ${{vars.OCI_REGION_ACCT1}} # Variable for Account 1's OCI Region
          oci_profile: 'ACCOUNT_1_PROFILE' # Creates/updates profile 'ACCOUNT_1_PROFILE'

      - name: Run OCI CLI Command (Account 1)
        shell: bash
        # Using --profile to target the specific account configuration
        run: |
          oci --auth security_token --profile ACCOUNT_1_PROFILE os ns get

      - name: Run OCI Token Exchange Action (Account 2)
        uses: gtrevorrow/oci-token-exchange-action@v1
        with:
          oidc_client_identifier: ${{secrets.OIDC_CLIENT_IDENTIFIER_ACCT2}} # Secret for Account 2's OAuth Client ID:Secret
          domain_base_url: ${{vars.DOMAIN_BASE_URL_ACCT2}} # Variable for Account 2's Identity Domain URL
          oci_tenancy: ${{vars.OCI_TENANCY_ACCT2}} # Variable for Account 2's Tenancy OCID
          oci_region: ${{vars.OCI_REGION_ACCT2}} # Variable for Account 2's OCI Region
          oci_profile: 'ACCOUNT_2_PROFILE' # Creates/updates profile 'ACCOUNT_2_PROFILE'

      - name: Run OCI CLI Command (Account 2)
        shell: bash
        # Using --profile to target the specific account configuration
        run: |
          oci --auth security_token --profile ACCOUNT_2_PROFILE os ns get

 

In this example, each invocation of the action demonstrates how to add or update a distinct profile (e.g., ACCOUNT_1_PROFILE, ACCOUNT_2_PROFILE) in the OCI CLI configuration file (typically ~/.oci/config). This approach consolidates credentials into a single configuration file while still allowing easy targeting of multiple tenancies or compartments. If complete isolation of configuration files is desired, the oci_home input can be used to specify different directories for each.

Conclusion

Securing your CI/CD pipelines is a fundamental requirement in today’s cloud-native landscape. By transitioning from static, long-lived credentials to dynamic, short-lived tokens via OIDC, you significantly reduce the attack surface and enhance your overall security posture when interacting with Oracle Cloud Infrastructure.

This post walked through the main steps for understanding how GitHub Actions provides OIDC tokens, the process of configuring your OCI Identity Domain to trust these tokens, and how to use Service Users for fine-grained, auditable access. We’ve introduced the `gtrevorrow/oci-token-exchange-action`, a custom GitHub Action designed to simplify and automate this secure token exchange.

With this action, your workflows can obtain temporary OCI User Principal Session Tokens (UPSTs), scoped appropriately and, if desired, tied to specific Service Users. This allows your CI/CD pipelines to perform necessary operations within OCI securely without the risks associated with embedded credentials.

Adopting this OIDC-based authentication model is the right step towards more secure and manageable cloud automation. I encourage you to implement this pattern in your GitHub workflows, utilizing the provided action to streamline the process and bolster the security of your OCI integrations.

Resources

Here are some helpful resources related to the topics discussed in this article:
  OCI Product Management feature annouchment blog OCI simplifies multi-cloud workloads with OCI IAM Workload Identity Federation

–  OCI Token Exchange Action Repository: gtrevorrow/oci-token-exchange-action on GitHub

–  GitHub OIDC DocumentationAbout security hardening with OpenID Connect

Oracle OCI Token Exchange DocumentationToken Exchange Grant Type

OCI Documentation: Managing Identity Propagation Trust PoliciesManaging Identity Propagation Trust Policies

OCI Documentation: Adding a Confidential ApplicationAdding a Confidential Application

OCI Documentation: Generate Personal Access TokensGenerate Personal Access Tokens

OCI Documentation: Assign Users to Domain RolesAssign Users to Domain Roles