Home / OCI

OCIHow-toAdvanced

OCI cross-tenancy Object Storage access: Define, Endorse and Admit policies that work

Grant a group in one OCI tenancy access to a bucket in another. Write matching Define, Endorse and Admit statements, restrict them with conditions, call the right namespace and debug 404 errors.

Deepak Bhardwaj6 min read

Applies toOCI IAM policies, OCI Object Storage, OCI CLI 3.x, identity domains

The goal

A team in tenancy A (the requestor) needs to read objects from a bucket in tenancy B (the acceptor). Pre-authenticated requests would work for one-off file sharing, but they're bearer URLs with no user identity. Cross-tenancy policies keep access tied to real users, groups and audit logs.

Both tenancies must opt in. One side's policy alone does nothing.

Tenancy Statement Meaning
Requestor (A) Define tenancy Gives the other tenancy's OCID a short alias
Requestor (A) Endorse "My group may do X in that tenancy"
Acceptor (B) Define tenancy, Define group Gives aliases to the other side's OCIDs
Acceptor (B) Admit "That group may do X here"

Access is only what both sides allow. If the endorse says read and the admit says manage, the group only gets read.

Step 1: collect the OCIDs

In each tenancy:

# Requestor: its tenancy and the group
oci iam tenancy get --tenancy-id "$A_TENANCY" --query 'data.{name:name,id:id,home:"home-region-key"}'
oci iam group list --compartment-id "$A_TENANCY" --all \
  --query "data[?name=='DataReaders'].id"

# Acceptor: its tenancy, and the namespace for later
oci iam tenancy get --tenancy-id "$B_TENANCY" --query 'data.{name:name,id:id}'
oci os ns get

If you use identity domains, take the group's OCID from the domain's group details page. The alias must use the real group OCID, not its name.

Step 2: write the requestor policy (tenancy A)

Create this in tenancy A's root compartment:

Define tenancy PartnerB as ocid1.tenancy.oc1..aaaabbbb
Endorse group DataReaders to read buckets in tenancy PartnerB
Endorse group DataReaders to read objects in tenancy PartnerB

For a group in a non-default identity domain:

Endorse group 'finance-domain'/'DataReaders' to read objects in tenancy PartnerB
oci iam policy create \
  --compartment-id "$A_TENANCY" \
  --name xt-endorse-partnerb \
  --description "Allow DataReaders to read PartnerB storage" \
  --statements file://endorse.json

endorse.json is a JSON array holding the statement strings.

Step 3: write the acceptor policy (tenancy B)

Create this in tenancy B's root compartment:

Define tenancy SourceA as ocid1.tenancy.oc1..aaaaaaaa
Define group DataReaders as ocid1.group.oc1..aaaagggg
Admit group DataReaders of tenancy SourceA to read buckets in compartment shared-data
Admit group DataReaders of tenancy SourceA to read objects in compartment shared-data where target.bucket.name = 'partner-exports'
oci iam policy create \
  --compartment-id "$B_TENANCY" \
  --name xt-admit-sourcea \
  --description "Admit SourceA DataReaders to partner-exports" \
  --statements file://admit.json

Warning

Define, Endorse and Admit must be in the root compartment policy, even if the resource is in a child compartment. Put these statements elsewhere, and they're rejected or won't take effect.

The Admit statement can name a compartment and use conditions, so scope it there. Use the narrowest resource type you can. manage object-family also allows changing bucket settings, which is rarely needed for sharing.

Step 4: test it from the requestor

The key point: use the acceptor's namespace and the bucket's region. Your CLI still signs in with your own tenancy.

oci os object list \
  --namespace-name acceptor-namespace \
  --bucket-name partner-exports \
  --region ap-mumbai-1

oci os object get \
  --namespace-name acceptor-namespace \
  --bucket-name partner-exports \
  --name 2026-11/export.csv \
  --file ./export.csv

Python:

import oci

config = oci.config.from_file()
client = oci.object_storage.ObjectStorageClient(config)
objects = client.list_objects("acceptor-namespace", "partner-exports").data.objects
print([o.name for o in objects][:10])

Avoid calling get_namespace for this; it returns your own namespace, not the acceptor's.

Step 5: debug the 404

Failures usually show up as BucketNotFound or NotAuthorizedOrNotFound, status 404. Work through this list:

Check Common mistake
Namespace Using your own, not the acceptor's
Region Bucket is in another region from your CLI profile
User in group Your user isn't in the endorsed group
Group OCID Define group holds a different or recreated group's OCID
Tenancy OCIDs Swapped between the two policies
Both verbs read objects alone, when the call also needs read buckets or inspect access
Compartment The bucket isn't in the admitted compartment
Condition Bucket name has a typo, or isn't an exact match
Delay IAM changes can take a little time to apply

Check who you are:

oci iam user get --user-id "$MY_USER" --query 'data.name'
oci iam user list-groups --user-id "$MY_USER" --compartment-id "$A_TENANCY" \
  --query 'data[].name'

Look for the failing request in Audit in the acceptor tenancy; that's where it's evaluated. Filter by the bucket's compartment and time. Ask the other tenancy's admin to run it if you don't have access there.

Customer-managed keys

If the bucket is encrypted with a Vault key, reading objects also needs access to that key in the acceptor tenancy. Object Storage itself must already have permission to use the key:

Allow service objectstorage-ap-mumbai-1 to use keys in compartment shared-data

The admit may need a matching key permission too, depending on the operation.

Machines rather than people

To let an instance in tenancy A get in, endorse a dynamic group on the requestor side, and admit it on the acceptor side:

# Requestor (A)
Define tenancy PartnerB as ocid1.tenancy.oc1..aaaabbbb
Endorse dynamic-group export-workers to read objects in tenancy PartnerB

# Acceptor (B)
Define tenancy SourceA as ocid1.tenancy.oc1..aaaaaaaa
Define dynamic-group export-workers as ocid1.dynamicgroup.oc1..aaaadddd
Admit dynamic-group export-workers of tenancy SourceA to read objects in compartment shared-data

Then use an instance principal signer in the code, so no key sits on the server.

Caution

Endorse ... in any-tenancy allows the group into every tenancy that admits it. Name the tenancy instead. When the partnership ends, remove both policies, and review the acceptor's admits once a quarter.

Prevent it next time