Oracle Cloud Infrastructure's identity model looks deceptively simple. There are no JSON documents, nothing is attached to a user, and a grant is one English-like sentence. The simplicity is real, but it relocates the difficulty: everything now hangs on where that sentence is stored, which principal form its subject names, and which bundle of low-level permissions its verb expands into. The tenancy and compartment tree those sentences address is the subject of the OCI overview and is assumed here. This article is the layer below it: statement anatomy, verbs as permission bundles, the where clause, dynamic groups and matching rules, instance and resource principals, cross-tenancy Endorse/Admit, and the identity-domain layer that decides how a human becomes a principal at all.

Policies are objects in a compartment, not attachments on an identity

The structural difference from the other two major clouds is the first thing to internalise, because every other surprise follows from it. In AWS a policy document hangs off a user, a role or the resource itself. In GCP an allow policy is a set of bindings hanging off a node in the resource tree. In OCI a policy is its own first-class resource, created inside some compartment, and it names both its subject and its target from the outside.

A group therefore has no attached policies. There is no console page that answers "what can this group do", because the answer is not stored anywhere near the group. It is distributed across however many policy objects, in however many compartments, happen to contain a statement mentioning that group by name. Auditing an OCI tenancy means enumerating policies and reading statements, not walking identities. The search-based workflow this forces on you is the single biggest day-one adjustment for anyone arriving from AWS.

The evaluation rule is correspondingly plain: every statement that applies is unioned, and the union is the principal's authority. That contrasts sharply with AWS, where five distinct ceiling types subtract in a fixed order, and with GCP, where bindings on an ancestor flow downward onto descendants. The vendor-neutral framing of all three is in cloud IAM architecture; what follows is only the OCI instantiation.

OCI IAM primitivesUsers + groupsidentitiesDynamic groupsinstances match by rulePoliciesattached to compartmentCompartment-scoped policies grant group permissions; dynamic groups enable instance-based auth
IAM entities in OCI.
Advertisement

Anatomy of a policy statement

Every statement in the language has the same shape, and only the pieces vary.

Allow <subject> to <verb> <resource-type> in <location> where <conditions>

The subject is where most of the expressive power lives. It can be group Analysts, or group id ocid1.group.oc1..aaaaexample when the name is ambiguous, or group 'HR'/'Analysts' once identity domains are in play. It can be dynamic-group LogShippers for workloads rather than people, service objectstorage-us-ashburn-1 for an Oracle-operated service acting on your behalf, or any-user / any-group when the narrowing is meant to come entirely from a where clause.

The location is either tenancy or a compartment. Compartment references can be by name, by nested colon-delimited path, or by OCID:

Allow group Analysts to read buckets in tenancy
Allow group Analysts to read buckets in compartment Analytics
Allow group Analysts to read buckets in compartment Platform:Analytics:Raw
Allow group Analysts to read buckets in compartment id ocid1.compartment.oc1..aaaaexample

Statements are grouped into policy objects, and the policy object itself is a manageable resource type: Allow group PolicyAdmins to manage policies in compartment Shared is the statement that delegates policy authorship. Because a policy is a resource, it also has an OCID, a compartment, tags and a lifecycle — and it shows up in audit events like anything else.

Verbs are bundles of permissions, and that is why conditions can see them

The four verbs — inspect, read, use, manage — form a ladder in which each rung strictly contains the one below. What makes the ladder more than a convenience is that a verb is not the unit of authorization. It expands into a set of named permission strings, and those strings are what the service actually checks: INSTANCE_INSPECT, INSTANCE_READ, INSTANCE_UPDATE, INSTANCE_POWER_ACTIONS, INSTANCE_CREATE, INSTANCE_DELETE and so on. Because permissions are first-class, request.permission is available as a condition variable, which is how you write a grant that sits between two rungs of the ladder.

What a verb covers is type-specific, and this is where intuition misleads. use means the operations that consume a resource without altering its lifecycle: on a Vault key that is encrypt and decrypt, on a subnet it is attaching a VNIC to it, on an instance it is power actions and volume attachment. Reading use as "harmless" is wrong — use keys is the authority to decrypt everything that key protects.

Resource types come in individual and aggregate forms. buckets and objects are separate types; object-family covers both. instance-family, volume-family, virtual-network-family, database-family and file-family bundle the several types that any real operation on that service touches, which is why aggregate forms are usually correct and individual forms are how you tighten. The distinction bites hardest in Object Storage: inspect buckets lets someone list bucket names, read buckets adds their metadata, and neither returns a single byte of content — that needs read objects. The same split exists for secrets, where listing a vault's secrets and retrieving their contents are deliberately different authorities.

Where a policy is attached is a hard ceiling on what it can grant

A policy created in compartment Platform can only write statements affecting Platform and its descendants. It cannot grant anything in a sibling, and it cannot say in tenancy. Statements that reach across the whole tenancy, or across sibling subtrees, must live in the root compartment — which is the tenancy itself.

Compartment names inside a statement are resolved relative to the attachment point. From a policy in Platform, in compartment Analytics means Platform:Analytics. From a policy at the root, the same target is in compartment Platform:Analytics. in compartment id <ocid> is unambiguous from anywhere, at the cost of a statement no human can read at review time. Pick one convention per tenancy and hold it; mixing relative names and OCIDs is how review stops catching things.

The attachment rule is also OCI's answer to delegated administration, and it is a good one. Grant a team manage policies in compartment TeamA and they can author whatever they like inside their own subtree, self-serving every grant their projects need, with a structural guarantee that nothing they write can reach outside it. There is no separate boundary object to maintain, because the tree already is the boundary.

The corresponding trap is mundane and extremely common: the console creates the policy in whatever compartment you happen to be browsing. A statement written while scoped to a child compartment will either resolve to a narrower target than intended or refuse to save, and the resulting "the policy exists but nothing works" state sends people off writing a second, broader statement instead of moving the first one.

There is no Deny

The OCI policy language has no Deny statement. Nothing subtracts. Every applicable statement adds authority to the union, and the only reason a request fails is that no statement authorised it.

Three consequences follow, and they shape how you have to design. First, revoking access is a search-and-edit problem: you must find every statement that grants the thing and narrow or delete it, and there is no single object you can add to shut a door quickly. Second, you cannot carve an exception out of a broad grant. If a group has manage object-family in compartment Data, there is no way to exclude one bucket from it — the broad statement has to be replaced by narrower ones. The design discipline this enforces is to never write the broad grant in the first place, which is inconvenient early and correct later. Third, the closest thing to a subtractive control is the where clause, which narrows a single statement rather than overriding others, plus the compartment tree itself.

Set against AWS's ordered stack of service control policies, resource control policies, permission boundaries and session policies, this is a genuine trade. The OCI reviewer's job is easier: the statements naming a subject are the whole answer, with no ceiling hiding in an organizational unit two levels up and no simulator that fails to model it. The price is that there is no org-level brake to catch a mistake after the fact. The default Allow group Administrators to manage all-resources in tenancy that every tenancy ships with is exactly as absolute as it reads, and nothing above it can be configured to clip it.

The where clause, and the variables it can see

A statement may end with where plus a single condition, or a set of them combined with any { ... } or all { ... }. The variables fall into three families: request.* describes the call and the caller, target.* describes the resource being acted on, and tag variables reach into defined tags on either side.

Allow group Auditors to read object-family in compartment Data
 where target.bucket.name = 'audit-logs'

Allow group Operators to manage instance-family in compartment Prod
 where any {request.permission = 'INSTANCE_POWER_ACTIONS',
            request.permission = 'INSTANCE_INSPECT'}

Allow group TenancyAdmins to manage all-resources in tenancy
 where request.networkSource.name = 'corpnet'

Allow group Developers to manage all-resources in compartment Sandbox
 where target.resource.tag.Ops.Environment = 'dev'

The second example is the useful shape: a grant sitting between read and manage, expressed by naming the exact permissions rather than pretending a coarser verb is close enough. request.operation does the same job at API-name granularity, and request.region, request.user.id, request.groups.id and request.principal.type cover the rest of the caller context.

A network source is its own IAM object listing public CIDR blocks and VCN or subnet OCIDs, and request.networkSource.name is the closest OCI gets to an IP allow-list on the control plane. It is the standard hardening applied to administrator groups.

Tag conditions give you attribute-based access control without restructuring compartments, and the caveat is important: support for the tag variables is not uniform across every resource type, so a condition that is silently unevaluable is a real failure mode. Test a tag-based grant by confirming that it denies the case it is supposed to deny, not only that it permits the case you want.

Advertisement

Dynamic groups and matching rules

A dynamic group has no membership list. It carries a matching rule, evaluated against resource attributes when the request is made, and whatever matches is a member at that moment.

ALL {instance.compartment.id = 'ocid1.compartment.oc1..aaaaexample'}

ANY {instance.id = 'ocid1.instance.oc1.phx.aaaaexample',
     instance.compartment.id = 'ocid1.compartment.oc1..aaaaexample'}

ALL {resource.type = 'fnfunc',
     resource.compartment.id = 'ocid1.compartment.oc1..aaaaexample'}

ANY {tag.Ops.Role.value = 'log-shipper'}

Compartment-scoped rules are the common shape, and they make identity a property of placement: an instance launched into that compartment silently acquires whatever the dynamic group has been granted, and moving an instance between compartments changes who it is. That is either elegant or alarming depending on how disciplined your compartment layout is, and it is a strong argument for the layout being an access-control decision rather than a tidiness one.

Tag-based rules decouple identity from placement, which is more flexible and strictly more dangerous: anyone who can write that tag on a resource can mint a member of the dynamic group. If you use tag matching, the tag namespace needs to be protected as carefully as the policies themselves — manage tag-namespaces becomes a privilege-escalation path.

The rule on its own grants nothing whatsoever. Two independent objects must both exist: a matching rule that matches the resource, and a policy statement naming the dynamic group as a subject. Most of the time an instance-principal call fails, exactly one of the two is present, and the error text tells you nothing about which.

Instance principals, resource principals, service principals

Instance principals let compute call OCI APIs with no credential on disk. The instance retrieves a short-lived certificate through the instance metadata service at 169.254.169.254; the SDKs and the CLI refresh it transparently, and oci os ns get --auth instance_principal is the one-line proof it is wired up. The intent matches an EC2 instance profile, but the binding is inverted. AWS attaches a role to the instance, so the instance's identity is a property of the instance. OCI has the instance match a rule, so its identity is a property of the fleet definition — which is why you can change what a thousand instances can do without touching any of them.

Resource principals serve platform services where there is no instance to attach anything to: Functions, Data Science, Data Flow, and others. The runtime injects a resource principal session token; inside a function container that surfaces as the OCI_RESOURCE_PRINCIPAL_VERSION, OCI_RESOURCE_PRINCIPAL_RPST and OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM environment variables, from which the SDK's resource-principal provider assembles a signer. Authorization still routes through a dynamic group whose rule matches on resource.type.

Service principals are Oracle-operated services acting on your resources, and they are the statements everyone forgets. Allow service blockstorage to use keys in compartment Security is what permits a volume to be encrypted with your own Vault key. Allow service objectstorage-us-ashburn-1 to manage object-family in compartment Backups is what permits Object Storage to write into a bucket on your behalf. The failure mode is nasty: the missing grant surfaces as an error inside the consuming service, minutes later, nowhere near the API call you made. Where a service principal is too broad, request.principal.type narrows an any-user grant to exactly one calling service.

Endorse, Admit, Define — policy across tenancy boundaries

Cross-tenancy access is a two-sided handshake, and OCI gives it three dedicated statement keywords rather than reusing the ordinary Allow. Both sides attach their statements at the tenancy root.

# in the requestor tenancy
Define tenancy Acceptor as ocid1.tenancy.oc1..aaaaexampleacceptor
Endorse group ReplicationAgents to manage objects in tenancy Acceptor

# in the acceptor tenancy
Define tenancy Requestor as ocid1.tenancy.oc1..aaaaexamplerequestor
Define group ReplicationAgents as ocid1.group.oc1..aaaaexamplegroup
Admit group ReplicationAgents of tenancy Requestor to manage objects in compartment Landing

Define binds a local alias to an OCID belonging to the other tenancy, which is necessary because group and compartment names are only unique within a tenancy. Endorse is the requestor saying "my group may act over there". Admit is the acceptor saying "that group from over there may act here", and it takes the extra of tenancy qualifier so the acceptor is naming a specific foreign group and not any group that happens to share the name.

Neither side can grant unilaterally. Both halves must be present, which is the same veto-on-both-ends property that makes AWS cross-account sharing reviewable, expressed with different machinery. The practical advantage of dedicated keywords is discoverability: every external grant into your tenancy is an Admit statement, so one search over your root-compartment policies enumerates the complete list of foreign principals with access. There is no equivalent single query in a cloud where cross-account grants hide inside ordinary resource policies scattered across a dozen services. This machinery is what sits underneath cross-tenancy Object Storage replication and several Oracle-managed service integrations.

Identity domains, federation, and how a human becomes a principal

Users and groups do not live directly in the tenancy any more; they live in an identity domain. A domain is a container for users, groups and applications together with its own security posture — password policy, MFA factors, sign-on rules, self-registration settings and its own federation configuration. Every tenancy has a Default domain holding whatever already existed. Additional domains exist so that separate populations (employees, contractors, the end users of a customer-facing application) do not have to share a password policy or an administrator.

The moment a second domain exists, the policy subject has to be qualified:

Allow group 'Corp'/'DataEngineers' to read object-family in compartment Analytics

An unqualified group DataEngineers resolves in the default domain, so the first grant written for a group in a new domain silently targets the wrong — usually nonexistent — group. This is the most common syntax error after a domain is introduced, and because a statement that names a nonexistent group is not a syntax error, nothing complains.

Federation is configured per domain. A domain can trust an external SAML 2.0 identity provider — Entra ID, Okta, ADFS — or act as an OIDC/OAuth provider for applications itself. Federated users are not hand-created: SCIM provisioning or just-in-time creation materialises them on first sign-in, and identity-provider group membership maps onto domain groups. The property that matters architecturally is that the policy language never changes. A federated user is a member of a group and the statements are byte-identical to the ones written for local users, which is why moving a tenancy from local accounts to enterprise SSO is a provisioning project rather than a policy rewrite. The vendor-neutral treatment of federation and short-lived credentials is in cloud IAM architecture.

The failure modes that produce NotAuthorizedOrNotFound

OCI deliberately conflates "you are not allowed" with "it does not exist", so the error text never tells you which. Everything below produces the same message.

The cross-compartment launch. The most common one by a wide margin. Compute lives in App, the VCN lives in Network, and launching an instance touches both. manage instance-family in compartment App alone is not enough; you also need use virtual-network-family in compartment Network. Any operation that spans compartments needs a statement per compartment, and there is no inheritance sideways.

The policy in the wrong compartment. Attached below the target, so it cannot reach it, or attached at a child so the relative compartment name resolves somewhere unexpected.

A dynamic group rule pinned to a stale OCID. Compartment OCIDs in matching rules do not follow resources that get moved, and the resource simply stops being a member.

Home-region writes. Users, groups, policies, dynamic groups and compartments are created only in the tenancy's home region and replicate outward. Infrastructure-as-code pinned to a non-home region fails on the identity resources specifically while every other resource in the same plan applies cleanly.

Propagation. A corrected policy is usually effective within moments, but authorization decisions can be cached briefly. Re-test the original call before concluding the fix was wrong — the reflex of adding a second, broader statement while the first was already correct is how tenancies accumulate over-grants nobody dares remove.

The shortcuts. manage all-resources in tenancy handed out to unblock a deadline, and read secret-family given to auditors who needed to enumerate secrets rather than read their contents. Both are grants that, in a language with no Deny, cannot be walked back with an exception — only rewritten.

OCI policy is one sentence per grant, stored as a resource in a compartment, and the compartment it is stored in is a hard ceiling on what it can reach. Everything else follows: the language is allow-only with no Deny, so authority is the union of every statement naming your principal and revocation is a search-and-edit job rather than a switch; verbs are bundles of permission strings, which is why request.permission can slice between rungs of the inspect/read/use/manage ladder; and workloads get identity by matching a dynamic group rule rather than by having a role attached, so a matching rule and a policy statement must both exist before an instance principal can do anything at all.