Roles and bindings
Custom roles built from permission lists or policy documents, role inheritance, bindings to people and groups, and delegated grant authorities.
Listing permissions person by person does not scale: when a new editor joins, someone has to remember every action an editor needs, and when the editor job changes, every editor has to be updated. Roles solve this. A is a named set of permissions for a job function, such as Editor or Approver, defined once and given to everyone who does that job.
A is what gives a role to someone: it links one role to one (a person or service account) or to one . A person receives every role bound to them directly or through a group they belong to. Change the role, and everyone who holds it changes with it.
Roles are built from policy documents, so a role can be a plain list of or carry . Every role, policy, and binding is also issued under a : the delegated right to hand out access, with a ceiling on what it may ever grant. That is how you let a team lead manage their own team's roles without making them an administrator.
Create a role
Create one role per job function in your product. roles.create (requires iam:roles:create) stores a role and
returns it, with its ID for bindings. A role grants the union of its attached policies and its optional inline
document, and you describe its permissions in one of three ways:
A permissions list becomes an inline allow statement over every resource of the tenant (resources: ['*']). It is
the usual shape for application roles.
const editor = await iam.api.roles.create(credential, {
tenantId,
name: 'Editor',
description: 'Reads and writes documents',
permissions: ['documents:read', 'documents:write'],
});
// editor.document:
// { version: 1, statements: [{ sid: 'RolePermissions', effect: 'allow',
// actions: ['documents:read', 'documents:write'], resources: ['*'] }] }Passing both permissions and document fails with INVALID_INPUT. Inline documents are validated against the
catalog and bounded by the role's grant-authority ceilings exactly like
attached policies.
To change what a job function may do, call roles.update. It accepts the same fields as roles.create:
document: null removes the inline document, and policyIds replaces the attached set. Everyone who holds the
role sees the change at their next request. roles.get and roles.list read roles back (iam:roles:read).
When a job function no longer exists, roles.delete removes the role together with its bindings and their
activations. It is refused with RESOURCE_IN_USE while another role inherits it or an
includes it. To see who would lose what before you delete or edit
a role, use the
(Change safety).
Role inheritance
Job functions often build on each other: a Manager does everything an Editor does, plus approvals. Copying
the editor's permissions into the manager role works until someone changes one and forgets the other.
Inheritance avoids the copy. A role lists the roles it builds on in inherits, and grants the union of its own
policies and document and, recursively, everything the roles it inherits grant.
const manager = await iam.api.roles.create(credential, {
tenantId,
name: 'Manager',
permissions: ['reports:export'],
inherits: [editor.id, approver.id],
});
// Replace the parents; an empty list clears inheritance.
await iam.api.roles.update(credential, { tenantId, roleId: manager.id, inherits: [editor.id] });- A role may inherit at most 20 direct parents, cannot inherit itself, and cannot form a cycle.
- Protected roles, such as Owner, cannot be inherited (
PROTECTED_RESOURCE). - Inherited grants are evaluated under the inheriting role's own authority ceilings as well as the inherited role's. A delegated administrator who makes their role inherit a broader one gets no more than their ceiling allows.
- A role that others inherit cannot be deleted (
RESOURCE_IN_USE) until they stop inheriting it. - Configuration sync carries
inheritsby name and applies it once every role of the document exists, so a parent and its child can be introduced together.
Bindings
A role grants nothing until it is bound to someone. A binding gives one role to one subject: an identity
(subjectType: 'identity') or a group (subjectType: 'group', which applies to every live member). Binding roles
to groups is usually the better habit: people then gain and lose the role as they join and leave the group,
without anyone editing bindings.
bindings.create creates a binding. It requires iam:bindings:create on the role (iam/{roleId}), so you can
let someone hand out some roles and not others, and it records the binding under the caller's grant authority:
// A person
await iam.api.bindings.create(credential, {
tenantId,
roleId: editor.id,
subjectType: 'identity',
subjectId: alice.id,
});
// A group: every current and future member receives the role
await iam.api.bindings.create(credential, {
tenantId,
roleId: auditor.id,
subjectType: 'group',
subjectId: finance.id,
});By default a binding applies at all times until it is removed. For access that should not last forever, a binding can be narrower than "always":
| Option | Effect | Details |
|---|---|---|
expiresAt | Stops granting at that time. | Temporary access |
startsAt | Grants nothing until then. | Temporary access |
window | An : applies only inside recurring hours in a time zone. | Temporary access |
eligible | An : grants nothing until the subject activates it, for a bounded time. | Just-in-time elevation |
bindings.delete (iam:bindings:delete) takes a role away, and bindings.update changes a binding's dates,
window, or eligibility. Both are allowed only to the administrator whose authority issued the binding, or to
root; anyone else gets ACCESS_DENIED ("Cannot mutate a higher authority binding"). That keeps a junior
administrator from undoing a senior one's grants. Protected roles are never bound this way
(PROTECTED_RESOURCE); ownership has its own API.
Who holds what
Reviews and support questions come down to "what can this person do?" and "who has this role?". These
administrative reads answer them, and all require iam:bindings:read:
identities.listBindings({ tenantId, identityId })returns the effective role set of one person, including roles that reach them through groups. Each entry carriesvia('identity'or the group), therole, and, where they apply, the liveactivation, apendingActivation, andinWindow.roles.listBindings({ tenantId, roleId })shows who holds a role, with a summary of each person or group.bindings.listsearches bindings across the tenant, filtered byroleId,subjectType,subjectId,eligible, andexpiresBefore(for "what ends this month?").
Expired bindings are omitted unless you pass includeExpired: true to bindings.list. To see what a person can
actually do on a specific resource, after conditions and boundaries, use
policies.effectiveActions.
Groups and deny statements
A group binding reaches every live member, including deny statements in the role. Adding or removing a member can
therefore grant or remove denies, so group membership changes require iam:groups:update on the group and
authority over the group's bindings, not just the right to edit the group's name.
Grant authorities
In a growing organization, not every administrator should be able to grant everything. A support lead should be able to give support roles to their team, but never make someone a tenant administrator. Grant authorities are how Better IAM delegates the right to grant, with a cap.
Every role, policy, and binding records the grant authority it was created under, and each authority has a ceiling: a policy document that bounds everything issued under it. Whatever a role says, a binding issued under a narrow authority grants no more than that authority's ceiling allows. Ceilings chain: an authority delegated from another is bounded by its own ceiling and every ceiling above it.
authorities.create delegates a new authority to a person:
// Give the support lead authority to grant support roles, and nothing else.
await iam.api.authorities.create(credential, {
tenantId,
identityId: supportLead.id,
ceiling: {
version: 1,
statements: [{ effect: 'allow', actions: ['tickets:*', 'customers:read'], resources: ['*'] }],
},
});The authority caps what the support lead's grants can reach; it does not give them the right to grant. They also
need a role that allows iam:bindings:create on the support roles. The two together mean: "may bind these roles,
and whatever those roles say, the result never exceeds tickets and customer reads".
authorities.createrequiresiam:authorities:createon the identity and a recently authenticated session. Nobody but root can issue authority to themselves. The new authority is a child of one of the caller's own (parentAuthorityIdpicks which), so delegation creates a narrower child authority while retaining its parent chain.authorities.revokewithdraws an authority, for example when the support lead changes teams. It requires recent authentication and is allowed only to the holder of the parent authority (or root).- A caller with no active authority cannot create roles, policies, or bindings (
GRANT_AUTHORITY_REQUIRED). Root administrators receive a root-issued, unrestricted authority automatically.
Grant authorities are retained as references, including the creator's authority for policies and roles. That has three consequences:
- Edits stay with their authority. A role or policy can be edited only by the holder of the authority that created it, or by root. A lower-authority editor cannot broaden a policy after a superior attaches it, and a higher authority attaching a lower authority's policy keeps the limits under which that policy was created.
- Revocation cascades. Removing a person's delegation authority disables every that depends on it at the next request. An API key also retains its issuing authority's ceiling, and every check it makes is denied once that authority is revoked.
- Membership is not authority. Removing an administrator's membership alone does not delete access they provisioned earlier; revoke their authority to disable it.
A ceiling works like any other : it constrains and never grants access. There is no arbitrary policy-containment solver, so a ceiling is not proved to contain a role when the role is created. Instead, it is applied as a boundary whenever a request is evaluated. See Boundaries.
The Owner role
Every tenant needs someone who can always fix its configuration, even after a bad policy edit. That is the
Owner: a protected role backed by the protected Owner policy, which allows everything. So that nobody can lock
the tenant out by accident or on purpose, owner role definitions are protected: they cannot be updated, deleted,
inherited, bound with bindings.create, requested, packaged, assumed, or named in a separation-of-duties rule.
Ownership changes go through its dedicated API instead. identities.setOwner({ tenantId, identityId, owner })
makes a person an owner (owner: true) or removes their ownership (owner: false). It requires
iam:identities:update and recent authentication, only an owner (or root) may call it, and it refuses to remove
the last active owner (LAST_OWNER).
Assuming a role
Sometimes a person or service needs a role only for one task, or needs to act inside another tenant. Instead of
binding the role, a trusted identity can take it on temporarily through
, in a role session whose permissions are exactly the role's.
Trusts, roles.assume, and their limits are covered in
Temporary access.
Next steps
Better IAM is created by Sean Filimon
Last updated
Permission catalog
The actions and resource types policies may name, from built-in iam:* actions to product, plugin, and tenant-defined types and their resources.
Policy documents
Versioned JSON policies, from statement fields and limits to how grants and boundaries combine, policy variables, versions, testing, and lint.