Policies

Policies define what actions a caller can perform on which resources. This guide explains the JSON structure, the evaluation logic (Allow vs. Deny), wildcard matching, resource formats, and ready-to-use examples.

See also: Permissions Reference

TL;DR (key takeaways)

  • Default is deny; you need an explicit Allow to pass, and any matching Deny wins.
  • Actions and resources match exactly or by trailing wildcard (e.g., compute:*, exc:compute:instance/*).
  • Many list endpoints pass resource=""; scope using Action only in those cases.
  • Managed policies (global) always apply in addition to org policies.
  • Changes take up to ~10s to take effect due to caching.

What is a policy

  • A policy is a JSON document with Version and an array of Statements.
  • Statements decide whether a request is allowed or denied based on Action and Resource.

Policy JSON structure

  • PolicyDocument
    • Version: string (optional)
    • Statements: array of StatementEntry (required)
  • StatementEntry
    • Sid: string (optional label)
    • Effect: “Allow” or “Deny” (required)
    • Action: string or string[] (required)
    • Resource: string or string[] (required; use “*” if not scoping)
    • Condition: any (present but not enforced today)

Example Admin policy (full access):

{
  "Version": "2024-03-05",
  "Statements": [
    { "Sid": "stmt1", "Effect": "Allow", "Action": "*", "Resource": "*" }
  ]
}

Evaluation model

  • Default is deny. Access is only granted if:
    • At least one statement matches AND Effect == Allow, AND
    • No matching statement has Effect == Deny.
  • All policies bound to the caller are merged. Policies apply if:
    • policy.OrgID == request’s orgId, OR
    • policy.IsManaged == true (global managed policies).
  • Matching is case-insensitive.

Matching rules

  • Exact or prefix wildcard matching:
    • compute:instance:list matches exactly that action.
    • compute:* matches all compute actions.
    • exc:compute:instance/* matches any instance with that resource prefix.
    • * matches everything.
  • Wildcards are trailing-only: prefix*.
  • If a handler passes empty resource (""), resource matching is skipped; only Action is checked.

Caching and propagation

  • Policy sets are cached per caller for ~10 seconds.
  • After creating/updating/deleting/binding a policy, allow up to 10s for enforcement to reflect.

Managed vs. Org policies

  • Managed policies (IsManaged=true) are global and always apply.
  • Org policies apply only within that org.

Resource naming conventions

  • Compute
    • Instance lifecycle: exc:compute:instance/<id>
    • Instance connect: compute:instance:connect/<id>
    • Security group: exc:compute:securitygroup/<id>
    • Interface (for SG bindings list): exc:compute:interface/<id>
    • Security group rule: exc:compute:securitygroup:rule/<id>
    • SSH public key: exc:compute:sshpubkey/<id>
    • Many list actions use resource="", so resource scoping doesn’t apply there.
  • DNS
    • Zone: exc:dns:zone/<zoneName>
  • Database
    • Cluster: exc:database:cluster/<id>
    • Node: exc:database:node/<id>
  • IAM, Billing
    • Typically resource="*" (or ""), not resource-scoped in current handlers.

Resource formats cheat sheet

ServiceResource formatExample
Compute (instance)exc:compute:instance/<id>exc:compute:instance/42
Compute (connect)compute:instance:connect/<id>compute:instance:connect/42
Compute (security group)exc:compute:securitygroup/<id>exc:compute:securitygroup/sg-1
Compute (sg rule)exc:compute:securitygroup:rule/<id>exc:compute:securitygroup:rule/r-10
Compute (interface)exc:compute:interface/<id>exc:compute:interface/eni-1
Compute (ssh key)exc:compute:sshpubkey/<id>exc:compute:sshpubkey/k-1
DNS (zone)exc:dns:zone/<zoneName>exc:dns:zone/example.com
Database (cluster)exc:database:cluster/<id>exc:database:cluster/db-1
Database (node)exc:database:node/<id>exc:database:node/n-1

Current action catalog

  • Compute
    • Instances: compute:instance:create, compute:instance:list, compute:instance:start, compute:instance:restart, compute:instance:stop, compute:instance:terminate, compute:instance:connect
    • Security groups: compute:securitygroup:create, compute:securitygroup:list, compute:securitygroup:delete
    • SG bindings: compute:securitygroup:binding:create, compute:securitygroup:binding:list, compute:securitygroup:binding:delete
    • SG rules: compute:securitygroup:rule:create, compute:securitygroup:rule:list, compute:securitygroup:rule:delete
    • SSH keys: compute:sshpubkey:create, compute:sshpubkey:list, compute:sshpubkey:delete
    • Subnets: compute:subnet:list
    • Volumes: compute:volume:create, compute:volume:list, compute:volume:resize, compute:volume:delete
    • Snapshots: compute:snapshot:create, compute:snapshot:list, compute:snapshot:delete
  • DNS
    • dns:record:create, dns:record:update, dns:record:delete, dns:record:list
    • dns:zone:create, dns:zone:delete, dns:zone:list
  • Database
    • database:cluster:create, database:cluster:restart, database:cluster:terminate, database:cluster:resetpassword, database:cluster:list
    • database:node:add, database:node:restart, database:node:terminate
  • IAM
    • iam:account:invite, iam:account:list
    • iam:serviceaccount:create, iam:serviceaccount:list, iam:serviceaccount:update, iam:serviceaccount:delete
    • iam:policy:create, iam:policy:list, iam:policy:update, iam:policy:delete
    • iam:policy:binding:create, iam:policy:binding:list, iam:policy:binding:delete
    • iam:billing:get, iam:billing:update
    • iam:org:rename
  • Billing
    • billing:ca (Cost Explorer)

Note: Some informational endpoints don’t enforce policies (e.g., instance types or images). Those are not listed as actions.

Common policy recipes

Project admin (all within an org):

{
  "Version": "2024-03-05",
  "Statements": [
    { "Sid": "all-compute", "Effect": "Allow", "Action": "compute:*", "Resource": "*" },
    { "Sid": "all-dns", "Effect": "Allow", "Action": "dns:*", "Resource": "*" },
    { "Sid": "all-db", "Effect": "Allow", "Action": "database:*", "Resource": "*" },
    { "Sid": "iam-basic", "Effect": "Allow", "Action": [
      "iam:serviceaccount:create", "iam:serviceaccount:list", "iam:serviceaccount:update", "iam:serviceaccount:delete",
      "iam:policy:create", "iam:policy:list", "iam:policy:update", "iam:policy:delete", "iam:policy:binding:create", "iam:policy:binding:list", "iam:policy:binding:delete",
      "iam:account:list"
    ], "Resource": "*" },
    { "Sid": "billing-read", "Effect": "Allow", "Action": ["iam:billing:get", "billing:ca"], "Resource": "*" }
  ]
}

DNS admin:

{
  "Version": "2024-03-05",
  "Statements": [
    { "Sid": "zones", "Effect": "Allow", "Action": ["dns:zone:create", "dns:zone:delete", "dns:zone:list"], "Resource": "*" },
    { "Sid": "records", "Effect": "Allow", "Action": ["dns:record:create", "dns:record:update", "dns:record:delete", "dns:record:list"], "Resource": "*" }
  ]
}

Compute operator (no deletes):

{
  "Version": "2024-03-05",
  "Statements": [
    { "Sid": "instances", "Effect": "Allow", "Action": [
      "compute:instance:create", "compute:instance:list", "compute:instance:start", "compute:instance:restart", "compute:instance:stop", "compute:instance:connect"
    ], "Resource": "*" },
    { "Sid": "volumes", "Effect": "Allow", "Action": [
      "compute:volume:create", "compute:volume:list", "compute:volume:resize"
    ], "Resource": "*" },
    { "Sid": "securitygroups", "Effect": "Allow", "Action": [
      "compute:securitygroup:create", "compute:securitygroup:list",
      "compute:securitygroup:binding:create", "compute:securitygroup:binding:list",
      "compute:securitygroup:rule:create", "compute:securitygroup:rule:list"
    ], "Resource": "*" }
  ]
}

Billing-only:

{
  "Version": "2024-03-05",
  "Statements": [
    { "Sid": "billing", "Effect": "Allow", "Action": ["iam:billing:get", "billing:ca"], "Resource": "*" }
  ]
}

Ready-to-use policies

Strict ReadOnly (no connect):

{
  "Version": "2024-03-05",
  "Statements": [
    {
      "Sid": "read-only",
      "Effect": "Allow",
      "Action": [
        "compute:instance:list",
        "compute:securitygroup:list",
        "compute:securitygroup:binding:list",
        "compute:securitygroup:rule:list",
        "compute:sshpubkey:list",
        "compute:subnet:list",
        "compute:volume:list",
        "compute:snapshot:list",

        "dns:zone:list",
        "dns:record:list",

        "database:cluster:list",

        "iam:policy:list",
        "iam:policy:binding:list",
        "iam:serviceaccount:list",
        "iam:account:list",
        "iam:billing:get",

        "billing:ca"
      ],
      "Resource": "*"
    }
  ]
}

ReadOnly + Connect (allows ephemeral terminal access):

{
  "Version": "2024-03-05",
  "Statements": [
    {
      "Sid": "read-and-connect",
      "Effect": "Allow",
      "Action": [
        "compute:instance:list",
        "compute:securitygroup:list",
        "compute:securitygroup:binding:list",
        "compute:securitygroup:rule:list",
        "compute:sshpubkey:list",
        "compute:subnet:list",
        "compute:volume:list",
        "compute:snapshot:list",
        "compute:instance:connect",

        "dns:zone:list",
        "dns:record:list",

        "database:cluster:list",

        "iam:policy:list",
        "iam:policy:binding:list",
        "iam:serviceaccount:list",
        "iam:account:list",
        "iam:billing:get",

        "billing:ca"
      ],
      "Resource": "*"
    }
  ]
}

Scoped example: allow viewing SG bindings for one security group:

{
  "Version": "2024-03-05",
  "Statements": [
    {
      "Sid": "view-one-sg",
      "Effect": "Allow",
      "Action": ["compute:securitygroup:binding:list", "compute:securitygroup:rule:list"],
      "Resource": ["exc:compute:securitygroup/123"]
    }
  ]
}

Deny carve-out example (deny SSH key listing everywhere):

{
  "Version": "2024-03-05",
  "Statements": [
    {
      "Sid": "allow-read",
      "Effect": "Allow",
      "Action": ["*"],
      "Resource": ["*"]
    },
    {
      "Sid": "block-ssh-key-list",
      "Effect": "Deny",
      "Action": ["compute:sshpubkey:list"],
      "Resource": ["*"]
    }
  ]
}

Authoring guidelines

  • Start least-privileged; add only needed actions.
  • Use Resource scoping where possible; otherwise keep Resource="*".
  • Apply Deny for must-not rules; Deny always wins.
  • Avoid legacy or non-enforced actions (e.g., iam:policy:bind, compute:instance:describe, database:cluster:describe, compute:subnet:create, orgs:create).

Validation rules

  • Action/Resource strings are lowercase; accept string or array.
  • Wildcards: only trailing * supported.
  • Require at least one statement.
  • Effect must be Allow or Deny.
  • Changes take up to ~10s to take effect (caching).

FAQ

  • Why do my changes not apply immediately?
    • Policies are cached for ~10s per caller.
  • Why am I denied even though an Allow is present?
    • A matching Deny exists; Deny overrides Allow.
  • How do I scope to a specific resource?
    • Use the resource formats above (e.g., exc:compute:instance/42).