When a workload in one cloud needs to call an API in another, the usual answer is to copy a static access key onto the machine and use it forever. That key does not expire on its own, can be copied elsewhere, and says nothing about which workload is using it at runtime or whether that workload should still be trusted. For cross-cloud service access, that is operationally simple but security-poor.

A better approach is to let the workload prove its identity at runtime and receive short-lived credentials only when it needs them. In this model, the OCI instance authenticates with Instance Principals, retrieves an X.509 certificate issued by OCI Certificates Service, and presents that certificate to AWS IAM Roles Anywhere. AWS validates the certificate against a trusted CA and returns temporary STS credentials. Nothing long-lived is stored on disk, and the private key can be removed immediately after the exchange.

Solution Overview

At a high level, the trust flow is straightforward. OCI establishes workload identity, OCI Certificates Service issues the certificate material, AWS Roles Anywhere validates the certificate, and AWS STS returns a short-lived session.

OCI Instance          OCI Certificates Service      AWS IAM Role Anywhere          AWS STS
| | | |
| Authenticate with | | |
| Instance Principal | | |
|------------------------->| | |
| Request certificate | | |
|------------------------->| | |
|<-------------------------| Return certificate/key | |
| | | |
| Present certificate | | |
|----------------------------------------------------->| |
| | | Validate trust |
| | | and assume role |
| | |---------------------->|
|<-----------------------------------------------------| Return temp creds |
| | | |
| Call AWS APIs with temporary credentials | |

Part 1 — OCI Setup

This part creates the certificate infrastructure on the OCI side. You will create a Private Root CA that AWS will trust, issue a leaf certificate from that CA, and allow only the target instance to read that certificate.

1. Create a Root Certificate Authority

Navigate to Identity & Security → Certificates → Certificate Authorities and create a new CA.

  • Name — any name of your choice
  • Certificate Authority Type — select Root CA
  • Signature AlgorithmSHA256_WITH_RSA or stronger

Once the CA becomes Active, download the Certificate PEM. AWS will use this as the trust anchor certificate.

Optionally, for additional security, you can create a Subordinate CA signed by this Root CA and issue leaf certificates from the Subordinate instead. Upload the Subordinate CA certificate as the AWS Trust Anchor. This keeps the Root CA offline and unexposed — the Root only signs the Subordinate, and the Subordinate handles day-to-day certificate issuance.

2. Issue a Leaf Certificate

Navigate to Identity & Security → Certificates → Certificates and create a new certificate.

  • Name — any name of your choice
  • Certificate Type — select Issued by Internal CA and choose the Root CA you created
  • Certificate Profile — select TLS Client Authentication
  • Common Name (CN) — a value that uniquely identifies this OCI instance, within the 64-character X.509 CN limit. OCI instance OCIDs are 88 characters and cannot be used directly. Use one of the following approaches: the last 64 characters of the instance OCID, a UUID derived from the OCID, or any short unique identifier you choose to map to this instance. This value is what AWS checks in the role trust policy to identify which instance is presenting the certificate.

Under Certificate Rules, set the renewal interval to 60 days before expiry. OCI will create a new certificate version automatically on that schedule.

Record the Certificate OCID. The instance script uses it to fetch the certificate bundle at runtime.

3. Create the OCI IAM Policy

Create a policy that allows only the target instance to read the certificate.

Allow any-user to read leaf-certificate-family in compartment <your-compartment>
where all {
  request.principal.type = 'instance',
  request.principal.id = '<your-instance-ocid>',
  target.leaf-certificate.id = '<your-certificate-ocid>'
}

Part 2 — AWS Setup

This part registers the OCI Root CA in AWS and creates the IAM role that the OCI instance will assume through Roles Anywhere.

1. Create a Trust Anchor

Navigate to IAM → Roles Anywhere → Trust Anchors and create a new trust anchor.

  • Name — any name of your choice
  • Trust anchor type — select External certificate bundle
  • Certificate — paste the Root CA PEM downloaded from OCI

Save the Trust Anchor ARN.

2. Create an IAM Role

Navigate to IAM → Roles → Create role and choose Custom trust policy. The trust policy below allows assumption only when the certificate subject CN and issuer CN match the values you expect.

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Service": "rolesanywhere.amazonaws.com"
    },
    "Action": [
      "sts:AssumeRole",
      "sts:TagSession",
      "sts:SetSourceIdentity"
    ],
    "Condition": {
      "StringEquals": {
        "aws:PrincipalTag/x509Subject/CN": "<your-uuid>",
        "aws:PrincipalTag/x509Issuer/CN": "<your-ca-common-name>"
      }
    }
  }]
}

Attach the permissions policy required by your workload. For initial testing, AmazonS3ReadOnlyAccess is a simple choice. Save the Role ARN.

3. Create a Profile

Navigate to IAM → Roles Anywhere → Profiles and create a profile.

  • Name — any name of your choice
  • Roles — select the role you created
  • Session duration — 1 hour

Save the Profile ARN. You now have the Trust Anchor ARN, Profile ARN, and Role ARN required by the script.

Part 3 — Sample Script

The following sample script walks through the full exchange. The instance authenticates to OCI using Instance Principals, fetches the certificate and private key from OCI Certificates Service, hands both to the AWS signing helper which signs the request and calls Roles Anywhere, receives temporary STS credentials back, and removes the private key from disk. Each step is labeled so you can follow the flow. Run this script on the OCI instance.

Replace the four placeholder values before running the script.

#!/bin/bash
set -e

echo "=== OCI Certificates to AWS Roles Anywhere Credential Exchange ==="

CERT_OCID="<your-certificate-ocid>"
TRUST_ANCHOR_ARN="<your-trust-anchor-arn>"
PROFILE_ARN="<your-profile-arn>"
ROLE_ARN="<your-role-arn>"
REGION="us-east-1"
WORK_DIR="/tmp/oci-aws-auth"
mkdir -p $WORK_DIR && chmod 700 $WORK_DIR

echo ""
echo "[1/4] Fetching cert from OCI Certificates Service..."
python3 << PYEOF
import oci, os, sys

try:
    signer = oci.auth.signers.InstancePrincipalsSecurityTokenSigner()
    print("  Instance Principal auth: OK")

    certs_client = oci.certificates.CertificatesClient(config={}, signer=signer)
    bundle = certs_client.get_certificate_bundle(
        certificate_id="$CERT_OCID",
        certificate_bundle_type="CERTIFICATE_CONTENT_WITH_PRIVATE_KEY"
    ).data

    with open("$WORK_DIR/leaf.pem", "w") as f:
        f.write(bundle.certificate_pem)
    key_path = "$WORK_DIR/key.pem"
    with open(key_path, "w") as f:
        f.write(bundle.private_key_pem)
    os.chmod(key_path, 0o600)
    if bundle.cert_chain_pem:
        with open("$WORK_DIR/chain.pem", "w") as f:
            f.write(bundle.cert_chain_pem)
    print(f"  Cert fetched — version {bundle.version_number}")
except Exception as e:
    print(f"ERROR: {e}", file=sys.stderr)
    sys.exit(1)
PYEOF

openssl x509 -in $WORK_DIR/leaf.pem -noout -subject -issuer -startdate -enddate

echo ""
echo "[2/4] Checking aws_signing_helper..."
HELPER="$WORK_DIR/aws_signing_helper"
if [ ! -f "$HELPER" ]; then
    curl -sf -L \
      https://rolesanywhere.amazonaws.com/releases/1.1.1/X86_64/Linux/aws_signing_helper \
      -o $HELPER && chmod +x $HELPER
    echo "  Downloaded."
else
    echo "  Already present."
fi

echo ""
echo "[3/4] Calling AWS Roles Anywhere..."
CREDS_JSON=$($HELPER credential-process \
  --certificate $WORK_DIR/leaf.pem \
  --private-key $WORK_DIR/key.pem \
  --trust-anchor-arn $TRUST_ANCHOR_ARN \
  --profile-arn $PROFILE_ARN \
  --role-arn $ROLE_ARN \
  --region $REGION)

echo ""
echo "[4/4] AWS Temporary Credentials:"
python3 << PYEOF
import json, os
c = json.loads('''$CREDS_JSON''')
print(f"  Access Key ID : {c['AccessKeyId']}")
print(f"  Secret Key    : {c['SecretAccessKey'][:20]}...")
print(f"  Session Token : {c['SessionToken'][:40]}...")
print(f"  Expiration    : {c['Expiration']}")
with open("$WORK_DIR/aws_env", "w") as f:
    f.write(f"export AWS_ACCESS_KEY_ID={c['AccessKeyId']}\n")
    f.write(f"export AWS_SECRET_ACCESS_KEY={c['SecretAccessKey']}\n")
    f.write(f"export AWS_SESSION_TOKEN={c['SessionToken']}\n")
PYEOF

source $WORK_DIR/aws_env
echo ""
echo "=== Verifying with AWS STS ==="
aws sts get-caller-identity --region $REGION \
  && echo "AWS auth successful!" \
  || echo "STS call failed — check role trust policy"

rm -f $WORK_DIR/key.pem $WORK_DIR/aws_env
echo "Private key removed from disk. Done."

Make the script executable and run it:

chmod +x ~/oci_aws_creds.sh
bash ~/oci_aws_creds.sh

If the AWS CLI is not installed, use the following commands. The CLI is only required for the final sts get-caller-identity verification step.

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip
unzip /tmp/awscliv2.zip -d /tmp
sudo /tmp/aws/install

What Could Go Wrong

1. The certificate CN or Issuer CN does not match the IAM role condition

If the values in the role trust policy do not exactly match the values in the certificate, AWS will deny the role assumption. Verify the subject and issuer from the leaf certificate:

openssl x509 -in /tmp/oci-aws-auth/leaf.pem -noout -subject -issuer

The output must match aws:PrincipalTag/x509Subject/CN and aws:PrincipalTag/x509Issuer/CN character for character.

2. The AWS CLI is not installed

The certificate exchange itself can still succeed. Only the final verification command fails if aws is missing. Install the AWS CLI and re-run the verification step.

Adding More Instances

The Root CA and trust anchor can support any number of OCI instances. For each new instance, repeat the same pattern:

  1. Issue a new leaf certificate from the same Root CA.
  2. Use a new UUID in the certificate CN.
  3. Create an IAM role with conditions matching that certificate identity.
  4. Create a Roles Anywhere profile for that role.
  5. Run the script on the new instance with the new values.

This keeps permissions isolated by default, because one instance certificate cannot assume another instance role unless you intentionally design the trust policy to allow that.

If you want multiple instances to share a role, you can replace the CN check with a pattern match such as StringLike on worker-*, while keeping the issuer constraint unchanged so the certificates still have to come from your CA.

Closing

At the end of this setup, an OCI instance can call AWS APIs without a single long-lived AWS credential stored anywhere in the system. The instance authenticates to OCI using Instance Principals, retrieves a certificate from OCI Certificates Service, exchanges that certificate for a one-hour AWS session through Roles Anywhere, and removes the private key from disk. The resulting trust model is simpler to audit, easier to rotate, and safer than distributing static keys.

References