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,EndorseandAdmitmust 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-tenancyallows 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
- Record the owner, the expiry date and a ticket number in each policy's description.
- Keep shared data in a dedicated compartment, so the admit is easy to reason about.
- Turn on Object Storage write and read logging, so that you have evidence of who read what.
- Related: IAM policy conditions, PAR errors and the bucket security baseline.