OneLinersCommand workbench
Guides
Security / Networking & DNS / System Administration

Configure AWS IAM roles and least-privilege policies

Design exact trust, identity, boundary, S3-prefix, and KMS-key permissions; validate with Access Analyzer, simulate candidates/live role, test temporary credentials, audit CloudTrail, reduce unused access, rotate trust, revoke sessions, and roll back safely.

210 min14 stepsChanges system stateRevision 2
Save or explore
Save to collectionCreate a collection in the sidebar first.
0 of 14 steps completed
Goal

Create an AWS role whose principal, session, actions, resources, context, maximum permissions, runtime behavior, audit, and retirement are all explicit and regression-tested.

Supported environments
  • AWS IAM current regional/global APIs
  • AWS CLI v2
  • IAM policy language 2012-10-17
  • CloudFormation 2010-09-09 template format
Prerequisites
  • Governed AWS organization Federation/MFA, SCPs, organization CloudTrail with required data events, IAM Access Analyzer, Config/security monitoring, and protected IAM change role.
  • Owned principal and resources Exact trusted caller role, S3 bucket/prefix owner, KMS key owner/policy, region, endpoint/bucket policies, and workload lifecycle are documented.
  • Executable access matrix Required and denied action/resource/context tuples, synthetic test data, policy assertions, and rollback owner exist before policy creation.
  • Protected infrastructure source Reviewed CloudFormation/policy documents, no literal secrets/account IDs, exact change set approval, dependency inventory, and policy-version retention.
Operating boundary

OneLiners never runs these steps or stores secrets. Review placeholders, versions, current state, and change-control requirements before using a command.

Full guide

What you will build

System
  • A workload-oriented AWS IAM role whose trust policy names one exact principal and External ID condition, whose identity policy permits one documented S3 release prefix and one KMS key, and whose permissions boundary caps the role at the same approved service envelope.
  • A policy engineering workflow that begins with an action/resource/condition matrix, runs IAM Access Analyzer validation and custom-policy simulation before creation, reviews a CloudFormation change set, tests allowed and denied calls with temporary credentials, and evaluates organization guardrails.
  • An operating lifecycle with CloudTrail, external-access findings, service-last-accessed data, role session limits, controlled policy versioning, emergency session revocation, periodic reduction, and dependency-aware rollback.
Observable outcome
  • Only the exact trusted principal can call sts:AssumeRole with the required context; another principal, missing/wrong External ID, and excessive session duration are denied.
  • The role can list only the approved S3 prefix, read/write only objects below that prefix, and use the exact KMS key for the required encryption operations; neighboring buckets, prefixes, keys, IAM actions, and administrative APIs are denied.
  • Access Analyzer returns no unresolved ERROR or SECURITY_WARNING, simulations cover positive and negative action/resource/context tuples, and CloudTrail records assumption and data/control calls according to trail configuration.
  • Role removal or policy rollback follows a dependency inventory and active-session plan instead of deleting policy versions blindly or broadening permissions to restore service.

Architecture

How the parts fit together

A protected CloudFormation stack owns a customer-managed permissions-boundary policy, a customer-managed workload policy, one IAM role, and their attachments. The role trust policy controls who may request temporary credentials; the role's identity policy controls what those credentials may call; the boundary caps the maximum identity-policy permissions but grants nothing by itself. Organization SCPs, resource policies, KMS key policy, VPC endpoint policy, and session policy can further restrict the effective decision. The example release publisher uses an exact trusted role ARN, a required External ID, a maximum one-hour session, an exact S3 bucket and releases/production prefix, and an exact KMS key. No Action or Principal wildcard exists. A trailing wildcard appears only in the object resource ARN to represent objects under the one approved prefix; it is not an account/service wildcard. Access Analyzer validates policy structure and security guidance. IAM simulators evaluate documented tuples but do not model every service, resource policy, SCP, key grant, or runtime context. Real tests use short-lived role credentials and synthetic objects.

Trust policyAuthorizes one exact AWS principal to assume the role under required External ID and session constraints. It does not grant access to S3 or KMS.
Identity policyEnumerates allowed S3 and KMS actions, resource ARNs, prefixes, encryption context, and transport conditions.
Permissions boundarySets the maximum permissions identity policies can grant to the role. It must be attached at role creation and protected from removal.
Resource and organization controlsS3 bucket policy, KMS key policy, VPC endpoint policy, SCPs, and explicit denies participate in effective authorization.
Analyzer and simulatorFind policy syntax/security issues and preview action/resource/context decisions before and after creation, with documented modeling limits.
CloudTrail and access reviewAttribute role assumptions and API calls, detect anomalous use, and supply last-accessed evidence for permission reduction.
  1. Identify the calling workload, data owner, exact API operations, resources, conditions, session duration, failure mode, and reviewer before writing JSON.
  2. Write trust, identity, boundary, S3 bucket, and KMS key policy together because effective access is their intersection with organization controls.
  3. Validate every policy with Access Analyzer and simulate explicit allow and deny tuples using realistic context.
  4. Create a CloudFormation change set, inspect every IAM resource and attachment, then execute only after named-IAM approval.
  5. Assume the role with the approved principal and test synthetic allowed calls plus neighboring-resource, IAM, and wrong-context denials.
  6. Correlate CloudTrail and Access Analyzer, record policy versions and dependencies, review last-accessed data over an adequate period, and remove unused permissions.
  7. Rotate the trusted principal/External ID and policy versions with overlap only when necessary; revoke sessions under incident control and roll back through the stack.

Assumptions

  • The organization has AWS Organizations/SCP governance, a multi-region organization CloudTrail, IAM Access Analyzer, federated administrator identities with MFA, change review, and protected CloudFormation execution.
  • The example role publishes encrypted release objects to an existing S3 bucket/prefix with an existing customer-managed KMS key. Bucket and key owners approve their resource policies separately.
  • The trusted caller is one workload role, not an IAM user or broad account principal. The caller itself has sts:AssumeRole permission for only this target role.
  • The External ID is a confused-deputy control rather than a password. It is unique and protected as operational metadata but not relied upon as the sole authentication factor.
  • No literal AWS account IDs, access keys, secret keys, session tokens, object data, or internal role identifiers are committed to the tutorial. CloudFormation parameters/pseudo parameters and redacted evidence are used.
  • A synthetic non-sensitive object and isolated production-like prefix are available for positive/negative tests; testing never overwrites a real release.

Key concepts

Trust policy
A role resource policy that names principals allowed to call role-assumption APIs under conditions. It is evaluated separately from the role's identity permissions.
Identity policy
A policy attached to an identity that grants actions on resources when conditions match, subject to boundaries, SCPs, resource policies, and explicit denies.
Permissions boundary
A maximum-permissions filter for identity policies. It grants no permission and does not constrain resource-based grants in every scenario the same way.
Least privilege
Only actions, resources, conditions, principals, and duration needed for the current workload, refined with tests and observed use rather than a broad service role.
Policy validation versus simulation
Validation finds syntax and security findings; simulation predicts decisions for supplied tuples. Neither proves all runtime policy layers or service behavior.
Explicit deny
A matching deny overrides an allow. SCP, boundary, session, identity, resource, endpoint, and key policies can all influence a request.
Role session
Temporary credentials issued by STS with a principal identity, duration, optional source identity/tags, and policies. Updating a role does not necessarily invalidate already issued sessions immediately.

Before you copy

Values used in this guide

{{awsRegion}}

Approved region for S3/KMS and testing.

Example: eu-central-1
{{iamStackName}}

CloudFormation stack owning the role and policies.

Example: production-release-publisher-role
{{roleName}}

Stable role name without embedded account ID.

Example: ProductionReleasePublisher
{{trustedPrincipalArn}}

Exact approved caller role ARN injected through protected deployment parameters.

Example: arn:aws:iam::REDACTED:role/ApprovedCiPublisher
{{externalId}}secret

Unique confused-deputy context value delivered to both trust owner and caller; not treated as a standalone secret.

Example: organization-issued-value-redacted
{{releaseBucketName}}

Existing S3 bucket approved for release objects.

Example: organization-production-releases
{{releasePrefix}}

Exact object prefix owned by this workload.

Example: releases/production/
{{kmsKeyArn}}

Exact customer-managed KMS key ARN approved by the key owner.

Example: arn:aws:kms:eu-central-1:REDACTED:key/REDACTED
{{policyVersionToRestore}}

Reviewed non-default managed-policy version for rollback.

Example: v3
{{releaseVersion}}

Non-secret IAM policy change identifier used for change-set correlation.

Example: 2026.07.28-1

Security and production boundaries

  • Never use Principal:'*', Action:'*', service:* action families, Resource:'*' for resource-capable APIs, AdministratorAccess, PowerUserAccess, or an account-root principal as a shortcut.
  • An object-prefix ARN necessarily ends with '*' to represent objects below that exact prefix. Pair it with an exact bucket ARN and s3:prefix conditions; do not broaden it to the bucket's entire object namespace.
  • A role name or External ID is not authentication. The exact trusted principal, its own identity policy, organization membership, source identity/session tags, duration, and monitoring form the assumption boundary.
  • KMS authorization is the intersection of IAM policy, key policy, grants, encryption context, and explicit denies. Do not add kms:* or all-key resources when a decrypt/encrypt test fails.
  • Policy simulators do not evaluate every SCP, resource policy, permissions boundary, key grant, service control, or runtime condition in the same way as a real request. Keep negative live tests.
  • CloudTrail management-event history does not automatically include all S3 object data events. Configure a retained organization trail and data-event selectors for the protected prefix as required.

Stop before continuing if

  • Stop if the calling principal, resource owner, exact actions, ARNs, prefix, key, region, session duration, or rollback owner is unknown.
  • Stop on any Action/Principal wildcard, account-wide trust, broad object wildcard, unbounded pass-role, IAM mutation, or policy-attachment authority.
  • Stop if Access Analyzer returns an unresolved ERROR or SECURITY_WARNING, or simulations lack neighboring-resource and administrative-action denials.
  • Stop if the change set replaces/deletes an existing shared role/policy, removes the boundary, or exposes literal identifiers/secrets.
  • Stop if real negative tests succeed, CloudTrail is missing, active sessions cannot be inventoried, or resource policies/SCP/key policy are outside review.
01

verification

Inventory caller, resources, organization controls, and existing grants

read-only

Confirm the federated administrator role and alias, identify the exact trusted workload role, bucket, prefix, KMS key, region, SCPs, resource policies, endpoint policies, existing IAM policies, active sessions, and CloudTrail trail before creating another grant.

Why this step matters

IAM decisions are the intersection of more than the role policy. Existing bucket/key policies, SCPs, boundaries, endpoint policies, grants, and sessions can permit or deny independently. Inventory prevents duplicate roles and hidden grants. Numeric account IDs and policy contents can be sensitive topology, so evidence remains protected/redacted.

What to understand

Name resource and principal owners separately.

Record current role/policy attachments and policy versions.

Confirm organization trail and data-event scope.

System changes

  • Queries identity and authorization inventory only.

Syntax explained

path-prefix
Narrows IAM inventory to the owned workload namespace.
effective SCP
Shows organization maximum/deny layer for the target.
Command
aws sts get-caller-identity --query Arn --output text && aws iam list-account-aliases --query 'AccountAliases[0]' --output text && aws organizations describe-effective-policy --policy-type SERVICE_CONTROL_POLICY --target-id REDACTED_TARGET 2>/dev/null || true && aws iam list-roles --path-prefix /workloads/release/ --query 'Roles[].RoleName' --output text
Example output / evidence
arn:aws:sts::REDACTED:assumed-role/IamPolicyOperator/session-redacted
production-platform
Effective organization policy reviewed in protected evidence
No existing role named ProductionReleasePublisher

Checkpoint: All effective authorization owners are known

aws iam get-role --role-name {{roleName}} 2>&1 || true

Continue whenNo conflicting role exists, and principal/resource/organization owners plus policies are recorded.

Stop whenExisting role/grant, account/region, resource owner, active session, or policy layer is unexplained.

If this step fails

Organization policy cannot be read.

Likely causeOperator lacks Organizations read or is in member account.

Safe checks
  • Request protected SCP evidence from organization owner.

ResolutionDo not finalize policy until the effective organization constraints are reviewed.

Security notes

  • Do not paste full sensitive policies or account IDs into public tickets.

Alternatives

  • Use a centrally generated authorization graph from organization tooling.

Stop conditions

  • Stop on incomplete policy-layer inventory.
02

instruction

Write an action, resource, condition, and denial test matrix

read-only

For every workload operation, name the API action, exact resource ARN pattern, required request/resource context, expected allow, neighboring-resource deny, administrative-action deny, owner, and evidence. Exclude permissions not exercised by the workflow.

Why this step matters

Least privilege starts from operations, not JSON. The matrix forces positive and negative cases before policy syntax. Neighboring resources catch prefix/ARN mistakes; administrative actions catch escalation. Conditions such as TLS, encryption key, prefix, region, source endpoint, and tags must be supplied in simulations or real tests.

What to understand

Separate bucket-level and object-level S3 actions/resources.

Distinguish encrypt, decrypt, data-key, and key administration.

Include session assumption failures and maximum duration.

System changes

  • Creates local review/test data only.

Syntax explained

expected deny
First-class acceptance criteria, not merely absence from allow list.
owner
Person/team authorized to approve that resource operation.
Command
column -t -s, policy-tests.csv | sed -n '1,20p'
Example output / evidence
case                  action             resource                                      expected
list-release-prefix   s3:ListBucket      arn:aws:s3:::organization-production-releases   allow
put-release-object    s3:PutObject       arn:aws:s3:::.../releases/production/test.bin   allow
put-other-prefix      s3:PutObject       arn:aws:s3:::.../releases/staging/test.bin      deny
change-bucket-policy  s3:PutBucketPolicy arn:aws:s3:::organization-production-releases   deny
use-other-key         kms:Encrypt        arn:aws:kms:...:key/OTHER                        deny

Checkpoint: Every permission has a business operation and a negative boundary

awk -F, 'NR>1 {count[$4]++} END {print "allow=" count["allow"], "deny=" count["deny"]}' policy-tests.csv

Continue whenAll required operations have allow tests and every resource/action boundary has denial tests; deny cases outnumber broad assumptions.

Stop whenAny requested action lacks exact resource/context/owner or an administrative capability is justified only as convenience.

If this step fails

Team cannot name exact API actions.

Likely causeWorkflow/tool behavior is undocumented or permissions were copied from a managed policy.

Safe checks
  • Capture calls in a sandbox through CloudTrail and service docs.

ResolutionPrototype in an isolated account and enumerate operations before production policy.

Security notes

  • The matrix can reveal resource names and controls; protect it.

Alternatives

  • Start with Access Analyzer generated policy from sandbox activity, then reduce manually.

Stop conditions

  • Stop before JSON until the matrix is complete.
03

config

Trust one exact workload principal with bounded sessions

read-only

Name the exact caller role ARN, require the organization-issued External ID, and cap session duration at one hour. Do not trust an account root, wildcard, IAM user collection, or mutable principal tag without separate controls.

Why this step matters

Trust determines who receives credentials; identity policy determines what credentials can do. Exact role ARN avoids granting an entire account. External ID helps prevent confused-deputy use when a third party operates the caller but is not a secret or substitute for exact trust. MaxSessionDuration belongs on the role resource. For human access, require federation/MFA in the caller chain rather than adding users to this trust.

What to understand

Verify caller role ownership and its own sts:AssumeRole permission.

Plan role recreation because principal IDs can affect stored trust relationships.

Use source identity/session tags only with exact allowed keys/values.

System changes

  • Validates local trust policy only.

Syntax explained

Principal exact role ARN
Only the named workload role can satisfy principal matching.
sts:ExternalId
Required assumption context mitigating confused-deputy scenarios.
File iam/trust-policy.json
Configuration
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "TrustedPublisherOnly",
    "Effect": "Allow",
    "Principal": {"AWS": "{{trustedPrincipalArn}}"},
    "Action": "sts:AssumeRole",
    "Condition": {
      "StringEquals": {"sts:ExternalId": "{{externalId}}"}
    }
  }]
}
Command
aws accessanalyzer validate-policy --policy-type RESOURCE_POLICY --validate-policy-resource-type AWS::IAM::AssumeRolePolicyDocument --policy-document file://trust-policy.json --query 'findings[].[findingType,issueCode,learnMoreLink]' --output table
Example output / evidence
--------------------
|  ValidatePolicy  |
+------------------+
| No findings      |
+------------------+

Checkpoint: Trust has one principal and one assumption path

jq -e '.Statement | length==1 and .[0].Principal.AWS!="*" and .[0].Action=="sts:AssumeRole"' iam/trust-policy.json

Continue whenOne exact approved principal, no wildcard/account-root, required context, one-hour role maximum, and analyzer no security finding.

Stop whenPrincipal broadens, External ID is sole control, federation context is unclear, or analyzer finds external access.

If this step fails

Exact role ARN breaks after caller role recreation.

Likely causeIAM transformed the principal to an internal ID tied to deleted role.

Safe checks
  • Inspect trust policy principal representation and caller lifecycle.

ResolutionReapprove/reapply the exact new role ARN; do not switch to account-wide trust.

Security notes

  • Treat External ID as protected metadata, not a password.

Alternatives

  • Use exact OIDC provider and subject/audience claims for CI federation.

Stop conditions

  • Stop if exact principal lifecycle cannot be governed.
04

config

Scope S3 objects, prefix listing, TLS, and exact KMS use

read-only

Separate bucket ListBucket from object access, constrain prefix and TLS, require the approved KMS key on writes, and allow only exact KMS data-plane actions on that key. Do not grant bucket policy, ACL, delete, IAM, key administration, or all-service operations.

Why this step matters

S3 uses different ARNs for bucket and objects. Prefix listing requires bucket ARN plus s3:prefix condition; object operations require the prefix object ARN. The trailing object wildcard is deliberately bounded by bucket and prefix. Read and write are separate because GetObject does not carry PutObject encryption headers. TLS and KMS headers prevent unencrypted writes, while the exact KMS ARN, region, and S3 encryption context prevent use with another key or namespace.

What to understand

Consider omitting Decrypt for write-only publishing.

Use bucket policy to deny insecure transport and wrong encryption independently.

Avoid s3:DeleteObject unless lifecycle/rollback explicitly requires it.

System changes

  • Validates candidate identity policy only.

Syntax explained

s3:prefix
Constrains bucket listing to one logical object namespace.
object ARN prefix*
Represents objects beneath one approved prefix, not all bucket objects.
exact KMS ARN
Limits cryptographic data-plane calls to one customer-managed key.
File iam/publisher-policy.json
Configuration
Fill variables0/4 ready

Values stay on this page and are never sent or saved.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListOnlyReleasePrefix",
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::{{releaseBucketName}}"],
      "Condition": {"StringLike": {"s3:prefix": ["{{releasePrefix}}*"]}}
    },
    {
      "Sid": "ReadReleaseObjectsOverTls",
      "Effect": "Allow",
      "Action": ["s3:GetObject"],
      "Resource": ["arn:aws:s3:::{{releaseBucketName}}/{{releasePrefix}}*"],
      "Condition": {"Bool": {"aws:SecureTransport": "true"}}
    },
    {
      "Sid": "WriteReleaseObjectsWithApprovedKms",
      "Effect": "Allow",
      "Action": ["s3:PutObject"],
      "Resource": ["arn:aws:s3:::{{releaseBucketName}}/{{releasePrefix}}*"],
      "Condition": {
        "Bool": {"aws:SecureTransport": "true"},
        "StringEquals": {
          "s3:x-amz-server-side-encryption": "aws:kms",
          "s3:x-amz-server-side-encryption-aws-kms-key-id": "{{kmsKeyArn}}"
        }
      }
    },
    {
      "Sid": "UseExactReleaseKey",
      "Effect": "Allow",
      "Action": ["kms:Encrypt", "kms:Decrypt", "kms:GenerateDataKey", "kms:DescribeKey"],
      "Resource": ["{{kmsKeyArn}}"],
      "Condition": {
        "StringEquals": {
          "aws:RequestedRegion": "{{awsRegion}}",
          "kms:EncryptionContext:aws:s3:arn": "arn:aws:s3:::{{releaseBucketName}}/{{releasePrefix}}*"
        }
      }
    }
  ]
}
Command
aws accessanalyzer validate-policy --policy-type IDENTITY_POLICY --policy-document file://publisher-policy.json --query 'findings[].[findingType,issueCode]' --output table
Example output / evidence
--------------------
|  ValidatePolicy  |
+------------------+
| No findings      |
+------------------+

Checkpoint: Policy grants only matrix operations and resources

jq -e '[.Statement[].Action[]] | all(. != "*" and (endswith(":*")|not))' iam/publisher-policy.json

Continue whenNo wildcard action, exact bucket/prefix/key resources, required transport/encryption/region context, and analyzer no errors/security warnings.

Stop whenAny action family, broader bucket/key, delete/admin action, absent encryption, or unresolved analyzer finding appears.

If this step fails

GetObject denied because PutObject-only headers are required.

Likely causeOne statement applies request-header conditions to both read and write.

Safe checks
  • Simulate and inspect actual context keys per action.

ResolutionSplit read and write into separate exact statements with applicable conditions.

Security notes

  • Do not include real object names or KMS identifiers in public examples.

Alternatives

  • Create a write-only publisher and separate read/verification role.

Stop conditions

  • Stop until every action-condition combination is valid.
05

config

Attach a permissions boundary at role creation

read-only

Create a customer-managed boundary that permits no more than the approved S3 prefix and KMS key envelope, then require its ARN on the role resource. Protect boundary modification/removal through the deployment operator policy and organization controls.

Why this step matters

A boundary is a maximum, not a grant. Effective identity permission is the intersection of identity policy and boundary, subject to other denies. The boundary example names exact resources but combines actions/resources; service-inapplicable pairs do not create access. A more verbose service-separated boundary improves clarity. Its main value is preventing a later attached identity policy from escaping the approved service/resource envelope.

What to understand

Attach the boundary in the same CloudFormation role creation.

Deny deployment operators iam:DeleteRolePermissionsBoundary except controlled stack execution.

Simulate with and without candidate identity-policy expansion.

System changes

  • Validates candidate boundary only.

Syntax explained

PermissionsBoundary
Role property pointing to the maximum-permissions policy ARN.
boundary allow
Defines possible maximum; still requires an identity-policy allow.
File iam/publisher-boundary.json
Configuration
Fill variables0/3 ready

Values stay on this page and are never sent or saved.

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "MaximumReleasePublishingEnvelope",
    "Effect": "Allow",
    "Action": [
      "s3:ListBucket",
      "s3:GetObject",
      "s3:PutObject",
      "kms:Encrypt",
      "kms:Decrypt",
      "kms:GenerateDataKey",
      "kms:DescribeKey"
    ],
    "Resource": [
      "arn:aws:s3:::{{releaseBucketName}}",
      "arn:aws:s3:::{{releaseBucketName}}/{{releasePrefix}}*",
      "{{kmsKeyArn}}"
    ]
  }]
}
Command
aws accessanalyzer validate-policy --policy-type IDENTITY_POLICY --policy-document file://publisher-boundary.json --query 'findings[].[findingType,issueCode]' --output table
Example output / evidence
--------------------
|  ValidatePolicy  |
+------------------+
| No findings      |
+------------------+

Checkpoint: Boundary blocks every out-of-envelope candidate

aws iam simulate-custom-policy --policy-input-list file://publisher-boundary.json --action-names iam:CreateUser s3:PutObject --resource-arns arn:aws:s3:::{{releaseBucketName}}/outside/test --query 'EvaluationResults[].[EvalActionName,EvalDecision]' --output table

Continue whenIAM creation and outside-prefix object write are implicitDeny.

Stop whenBoundary uses broad resources/actions or can be removed by workload/deployment role.

If this step fails

Boundary unexpectedly permits another S3 prefix.

Likely causeResource list/pattern is broader or test ARN falls under wildcard.

Safe checks
  • Expand and compare ARN path characters and simulator result.

ResolutionNarrow resource pattern and add explicit negative regression tests.

Security notes

  • Boundary administration is a privilege-escalation control and needs independent review.

Alternatives

  • Use a centrally mandated organization boundary template plus workload-specific narrower policy.

Stop conditions

  • Stop if boundary can be bypassed by ordinary operators.
06

verification

Validate JSON and simulate every allow and deny tuple

read-only

Run Access Analyzer validation on trust, identity, and boundary documents. Use simulate-custom-policy with exact action/resource/context cases from the matrix. Store structured results and fail the pipeline if an expected deny becomes allowed or expected allow lacks required context.

Why this step matters

Validation detects malformed elements, unsupported actions/conditions, wildcard risk, and best-practice findings. Simulation turns the access matrix into executable policy tests. Context values must mirror the request; omitting encryption or transport should produce deny. The simulator does not replace live tests or every policy layer, but a candidate that fails its own matrix cannot proceed.

What to understand

Test wrong bucket, adjacent prefix, other KMS key, HTTP transport, absent encryption, administrative actions, and excessive session duration.

Use machine-readable assertions rather than visual tables in CI.

Review WARNING/SUGGESTION, not only ERROR.

System changes

  • Queries validation/simulation APIs without creating IAM resources.

Syntax explained

simulate-custom-policy
Evaluates supplied policy documents independent of an existing principal.
context-entries
Supplies request/environment keys used by policy conditions.
Command
Fill variables0/3 ready

Values stay on this page and are never sent or saved.

aws accessanalyzer validate-policy --policy-type IDENTITY_POLICY --policy-document file://publisher-policy.json > /secure-staging/validate.json && aws iam simulate-custom-policy --policy-input-list file://publisher-policy.json --action-names s3:PutObject s3:DeleteObject iam:CreateAccessKey --resource-arns arn:aws:s3:::{{releaseBucketName}}/{{releasePrefix}}synthetic.bin arn:aws:s3:::{{releaseBucketName}}/neighbor/synthetic.bin --context-entries ContextKeyName=aws:SecureTransport,ContextKeyValues=true,ContextKeyType=boolean ContextKeyName=s3:x-amz-server-side-encryption,ContextKeyValues=aws:kms,ContextKeyType=string ContextKeyName=s3:x-amz-server-side-encryption-aws-kms-key-id,ContextKeyValues={{kmsKeyArn}},ContextKeyType=string --output table
Example output / evidence
-------------------------------------------------
|              SimulateCustomPolicy             |
+---------------------+--------------------------+
| s3:PutObject        | allowed                  |
| s3:PutObject neighbor| implicitDeny            |
| s3:DeleteObject     | implicitDeny             |
| iam:CreateAccessKey | implicitDeny             |
+---------------------+--------------------------+

Checkpoint: Policy matrix is executable and passes both directions

jq -e '.findings | map(select(.findingType=="ERROR" or .findingType=="SECURITY_WARNING")) | length==0' /secure-staging/validate.json

Continue whenNo unresolved high-severity finding; all intended allows allowed only with context; every negative tuple denied.

Stop whenAny negative case allowed, positive case works without context, or finding lacks written disposition.

If this step fails

Simulator says missing context values.

Likely causePolicy condition keys were not included or wrong types supplied.

Safe checks
  • Inspect MissingContextValues and service condition-key docs.

ResolutionAdd realistic typed context to tests; do not remove the production condition.

Security notes

  • Simulation inputs can reveal exact resources and controls; protect artifacts.

Alternatives

  • Use local policy-as-code tests in addition to AWS simulation.

Stop conditions

  • Stop until all matrix assertions pass.
07

config

Preview role, managed policies, boundary, and attachments as one stack

read-only

Use CloudFormation parameters and pseudo parameters to avoid literal account IDs. Create a change set with named-IAM capability, then inspect trust, policy bodies, role path, session duration, boundary attachment, tags, and every create/update/delete/replace.

Why this step matters

Role, policies, and boundary should change atomically under one owner. NoEcho masks parameter display but is not a secret store; protect parameters and avoid placing credentials in the template. CAPABILITY_NAMED_IAM highlights stable IAM names, which can be referenced externally and conflict. The inline documents deliberately match the separately validated trust, identity, and boundary artifacts; compare digests in CI so the preview cannot silently drift from the reviewed policies.

What to understand

Protect the stack execution role from changing unrelated IAM paths.

Review replacement impact: replacing a role can break trust principal IDs and active workloads.

Apply stack policy/tag governance where appropriate.

System changes

  • Creates change-set preview only.

Syntax explained

NoEcho
Masks parameter display in common views; does not make template metadata a secret vault.
MaxSessionDuration: 3600
Caps role sessions at one hour.
Path
Places role in an owned IAM namespace for inventory/governance.
File iam/release-publisher-stack.yml
Configuration
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
  TrustedPrincipalArn: {Type: String}
  ExternalId: {Type: String, NoEcho: true}
  ReleaseBucketName: {Type: String}
  ReleasePrefix: {Type: String}
  KmsKeyArn: {Type: String}
Resources:
  PublisherBoundary:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      Description: Maximum release publishing envelope
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: ListExactReleasePrefix
            Effect: Allow
            Action: s3:ListBucket
            Resource: !Sub arn:${AWS::Partition}:s3:::${ReleaseBucketName}
            Condition:
              StringLike:
                s3:prefix: [!Sub "${ReleasePrefix}*"]
          - Sid: ReadWriteExactReleaseObjects
            Effect: Allow
            Action: [s3:GetObject, s3:PutObject]
            Resource: !Sub arn:${AWS::Partition}:s3:::${ReleaseBucketName}/${ReleasePrefix}*
          - Sid: UseExactReleaseKey
            Effect: Allow
            Action: [kms:Encrypt, kms:Decrypt, kms:GenerateDataKey, kms:DescribeKey]
            Resource: !Ref KmsKeyArn
  PublisherPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      Description: Exact release publishing operations
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: ListExactReleasePrefix
            Effect: Allow
            Action: s3:ListBucket
            Resource: !Sub arn:${AWS::Partition}:s3:::${ReleaseBucketName}
            Condition:
              StringLike:
                s3:prefix: [!Sub "${ReleasePrefix}*"]
          - Sid: ReadReleaseObjectsOverTls
            Effect: Allow
            Action: s3:GetObject
            Resource: !Sub arn:${AWS::Partition}:s3:::${ReleaseBucketName}/${ReleasePrefix}*
            Condition:
              Bool: {aws:SecureTransport: "true"}
          - Sid: WriteReleaseObjectsWithApprovedKms
            Effect: Allow
            Action: s3:PutObject
            Resource: !Sub arn:${AWS::Partition}:s3:::${ReleaseBucketName}/${ReleasePrefix}*
            Condition:
              Bool: {aws:SecureTransport: "true"}
              StringEquals:
                s3:x-amz-server-side-encryption: aws:kms
                s3:x-amz-server-side-encryption-aws-kms-key-id: !Ref KmsKeyArn
          - Sid: UseExactReleaseKey
            Effect: Allow
            Action: [kms:Encrypt, kms:Decrypt, kms:GenerateDataKey, kms:DescribeKey]
            Resource: !Ref KmsKeyArn
            Condition:
              StringEquals:
                aws:RequestedRegion: !Ref AWS::Region
                kms:EncryptionContext:aws:s3:arn: !Sub arn:${AWS::Partition}:s3:::${ReleaseBucketName}/${ReleasePrefix}*
  PublisherRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: ProductionReleasePublisher
      Path: /workloads/release/
      MaxSessionDuration: 3600
      PermissionsBoundary: !Ref PublisherBoundary
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: TrustedPublisherOnly
            Effect: Allow
            Principal: {AWS: !Ref TrustedPrincipalArn}
            Action: sts:AssumeRole
            Condition:
              StringEquals: {sts:ExternalId: !Ref ExternalId}
      ManagedPolicyArns: [!Ref PublisherPolicy]
      Tags:
        - {Key: Owner, Value: ReleaseEngineering}
        - {Key: ManagedBy, Value: CloudFormation}
Command
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

aws cloudformation create-change-set --stack-name {{iamStackName}} --change-set-name reviewed-{{releaseVersion}} --change-set-type CREATE --template-body file://iam/release-publisher-stack.yml --parameters file://iam/parameters.json --capabilities CAPABILITY_NAMED_IAM --query Id --output text && aws cloudformation wait change-set-create-complete --stack-name {{iamStackName}} --change-set-name reviewed-{{releaseVersion}} && aws cloudformation describe-change-set --stack-name {{iamStackName}} --change-set-name reviewed-{{releaseVersion}} --query 'Changes[].ResourceChange.[Action,LogicalResourceId,ResourceType,Replacement]' --output table
Example output / evidence
arn:aws:cloudformation:eu-central-1:REDACTED:changeSet/reviewed/REDACTED
| Add | PublisherBoundary | AWS::IAM::ManagedPolicy | False |
| Add | PublisherPolicy   | AWS::IAM::ManagedPolicy | False |
| Add | PublisherRole     | AWS::IAM::Role          | False |

Checkpoint: Change set creates exactly three governed IAM resources

aws cloudformation describe-change-set --stack-name {{iamStackName}} --change-set-name reviewed-{{releaseVersion}} --output json > /secure-staging/iam-change-set.json && sha256sum /secure-staging/iam-change-set.json

Continue whenOnly boundary, identity policy, and role/attachments appear; no replacement/delete, literal account/secret, broad trust, or boundary omission.

Stop whenUnexpected IAM resource, attachment, role replacement, broad path/name collision, or policy differs from validated artifact.

If this step fails

Change set proposes replacing the role.

Likely causeImmutable name/path/property or stack ownership changed.

Safe checks
  • Inspect replacement details, active sessions, trust dependents, and role ID references.

ResolutionPlan a parallel role migration; do not replace an active shared role in place.

Security notes

  • Change set and parameters expose authorization architecture; restrict access.

Alternatives

  • Use a centrally controlled IAM deployment pipeline with equivalent named-IAM preview.

Stop conditions

  • Stop if template cannot faithfully embed validated policies.
08

command

Execute the reviewed IAM change set and inspect effective attachments

caution

Execute only the approved change set, wait for stack completion, then retrieve role metadata, trust, boundary, attached policies, default policy versions, and tags. Do not edit the role in the console after stack creation.

Why this step matters

Stack completion means IAM accepted resources, not that effective permissions match the matrix. Reading the live role catches parameter substitution, URL-decoded trust, wrong path, missing boundary, session duration, and attachments. Managed policies have versions; record default version IDs and document digests. Console edits create drift and can bypass validated source.

What to understand

Run stack drift and compare live policy documents with validated artifacts.

Record role unique ID for dependency/recreation analysis.

Do not output External ID or deployment parameters.

System changes

  • Creates the role, boundary, policy, and attachments.

Syntax explained

get-role
Returns live trust, path, duration, boundary, and role identifiers.
list-attached-role-policies
Enumerates managed policy attachments; inspect inline policies separately.
Command
Fill variables0/3 ready

Values stay on this page and are never sent or saved.

aws cloudformation execute-change-set --stack-name {{iamStackName}} --change-set-name reviewed-{{releaseVersion}} && aws cloudformation wait stack-create-complete --stack-name {{iamStackName}} && aws iam get-role --role-name {{roleName}} --query 'Role.[Arn,Path,MaxSessionDuration,PermissionsBoundary.PermissionsBoundaryArn,AssumeRolePolicyDocument]' --output json && aws iam list-attached-role-policies --role-name {{roleName}} --output table
Example output / evidence
[
  "arn:aws:iam::REDACTED:role/workloads/release/ProductionReleasePublisher",
  "/workloads/release/",
  3600,
  "arn:aws:iam::REDACTED:policy/ProductionReleasePublisherBoundary",
  {"Version":"2012-10-17","Statement":[{"Principal":{"AWS":"arn:aws:iam::REDACTED:role/ApprovedCiPublisher"}}]}
]
Attached: ProductionReleasePublisherPolicy

Checkpoint: Live IAM matches validated source exactly

aws iam list-role-policies --role-name {{roleName}} && aws iam list-attached-role-policies --role-name {{roleName}} && aws cloudformation detect-stack-drift --stack-name {{iamStackName}} --query StackDriftDetectionId --output text

Continue whenNo unexpected inline policy, one identity attachment, required boundary, exact trust/path/duration/tags, and stack later IN_SYNC.

Stop whenBoundary missing, extra attachment/inline policy, trust changed, duration broad, or stack drift appears.

If this step fails

Role exists but boundary is missing.

Likely causeTemplate property omitted, console-created conflicting role, or stack failed partially.

Safe checks
  • Inspect stack events, role metadata, and physical resource ownership.

ResolutionDo not test/use role; correct stack and attach boundary through reviewed change.

Security notes

  • Restrict iam:Get* output because it reveals trust and control design.

Alternatives

  • Use an organization IAM provisioning service returning equivalent evidence.

Stop conditions

  • Stop before assumption if live state diverges.
09

verification

Simulate the created principal with boundary and context

read-only

Run simulate-principal-policy against the live role ARN for every matrix tuple, including exact and neighboring resources, missing encryption context, administrative APIs, and other KMS keys. Record permissions-boundary decision details.

Why this step matters

Live-principal simulation includes attached identity policies and boundary details, making it stronger than candidate-only simulation. It still may not include every resource policy/SCP/key/endpoint/session layer. Missing context can turn intended allows into denies; unexpected allows are higher severity. Store structured results and assert every matrix row.

What to understand

Test secure transport false, wrong KMS key, wrong prefix, delete, policy mutation, pass-role, and another region.

Inspect OrganizationsDecisionDetail where available.

Do not use simulator output as permission to skip live negative tests.

System changes

  • Calls IAM simulation only.

Syntax explained

policy-source-arn
Selects live principal policies for simulation.
PermissionsBoundaryDecisionDetail
Shows whether the boundary permits the evaluated action/resource.
Command
Fill variables0/5 ready

Values stay on this page and are never sent or saved.

ROLE_ARN=$(aws iam get-role --role-name {{roleName}} --query Role.Arn --output text) && aws iam simulate-principal-policy --policy-source-arn "$ROLE_ARN" --action-names s3:PutObject s3:DeleteObject iam:AttachRolePolicy kms:Encrypt --resource-arns arn:aws:s3:::{{releaseBucketName}}/{{releasePrefix}}synthetic.bin arn:aws:s3:::{{releaseBucketName}}/neighbor/synthetic.bin {{kmsKeyArn}} --context-entries ContextKeyName=aws:SecureTransport,ContextKeyValues=true,ContextKeyType=boolean ContextKeyName=aws:RequestedRegion,ContextKeyValues={{awsRegion}},ContextKeyType=string --output json > /secure-staging/effective-simulation.json && jq -r '.EvaluationResults[] | [.EvalActionName,.EvalResourceName,.EvalDecision,.PermissionsBoundaryDecisionDetail.AllowedByPermissionsBoundary] | @tsv' /secure-staging/effective-simulation.json
Example output / evidence
s3:PutObject  arn:aws:s3:::.../releases/production/synthetic.bin  allowed       true
s3:PutObject  arn:aws:s3:::.../neighbor/synthetic.bin             implicitDeny  false
s3:DeleteObject arn:aws:s3:::.../releases/production/synthetic.bin implicitDeny  false
iam:AttachRolePolicy *                                             implicitDeny  false
kms:Encrypt    arn:aws:kms:eu-central-1:...:key/...                 allowed       true

Checkpoint: Effective role passes the complete policy matrix

jq -e '[.EvaluationResults[] | select((.EvalActionName|test("Delete|Attach|Create")) and .EvalDecision=="allowed")] | length==0' /secure-staging/effective-simulation.json

Continue whenOnly explicit required tuples allowed; neighboring/admin/missing-context cases denied; boundary blocks all out-of-envelope cases.

Stop whenAny unexpected allow, boundary bypass, missing context, or unexplained difference from custom simulation appears.

If this step fails

Live simulation differs from candidate simulation.

Likely causeBoundary, attachment, inline policy, SCP, or live policy version differs.

Safe checks
  • Compare attachments/default versions and decision details.

ResolutionResolve live policy source/drift before real credentials are issued.

Security notes

  • Simulation artifact contains sensitive authorization map.

Alternatives

  • Use automated policy regression tests on every stack change.

Stop conditions

  • Stop until both candidate/live simulations agree with matrix.
10

verification

Assume the role and run synthetic positive and negative calls

caution

From the exact trusted caller, assume the role for a short named session with the required External ID. Inject temporary credentials only into an isolated subprocess, upload one non-sensitive synthetic object with the approved KMS key, verify it, then test neighboring prefix, delete, IAM, and wrong-key denials. Remove the test object through a separate approved cleanup identity if the role intentionally lacks delete.

Why this step matters

Real calls evaluate STS trust, caller permission, role identity policy, boundary, SCP, bucket policy, key policy, endpoint policy, and service behavior together. The session is short and stored only in a mode-0600 ephemeral file. Credentials must not appear in shell tracing, process arguments, output, or test reports. Synthetic content prevents production overwrite. A cleanup identity is separate because adding DeleteObject solely for tests would violate the role's business need.

What to understand

Disable shell xtrace/history and ensure the test harness never prints credentials.

Use unique synthetic key and encryption headers.

Test missing/wrong External ID and excessive duration from controlled callers.

System changes

  • Issues a 15-minute role session and writes/reads one synthetic encrypted object.
  • Negative API calls generate denied CloudTrail events where logged.

Syntax explained

--duration-seconds 900
Limits verification credential lifetime to 15 minutes.
isolated subprocess
Prevents credentials from contaminating the operator shell/environment.
Command
aws sts assume-role --role-arn "$ROLE_ARN" --role-session-name policy-verification-redacted --external-id "$EXTERNAL_ID_FROM_SECRET_SOURCE" --duration-seconds 900 --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken,Expiration]' --output json > /run/user/1000/role-session.json && chmod 600 /run/user/1000/role-session.json && ./tests/run-role-policy-matrix.sh /run/user/1000/role-session.json && rm -f /run/user/1000/role-session.json
Example output / evidence
PASS allow  s3:ListBucket releases/production/
PASS allow  s3:PutObject releases/production/policy-test.bin (SSE-KMS exact key)
PASS allow  s3:GetObject releases/production/policy-test.bin
PASS deny   s3:PutObject releases/staging/policy-test.bin
PASS deny   s3:DeleteObject releases/production/policy-test.bin
PASS deny   iam:ListUsers
PASS deny   kms:Encrypt other-key
Temporary session file removed

Checkpoint: Runtime permissions equal matrix and credentials are gone

test ! -e /run/user/1000/role-session.json && aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRole --max-results 5 --output table

Continue whenEvery allow/deny passes, synthetic object cleaned by owner, session file removed, and assumption attributed in CloudTrail.

Stop whenCredential leaks, unapproved principal assumes, negative call succeeds, positive call needs broader policy, or audit event absent.

If this step fails

Negative API unexpectedly succeeds.

Likely causeResource policy, SCP exception, another session policy, or role policy grants access.

Safe checks
  • Stop testing, preserve CloudTrail request, inspect every effective layer.

ResolutionTreat as security incident and remove the unintended grant before role use.

Security notes

  • STS credentials are secrets; rotate/revoke incident path if exposed.

Alternatives

  • Run the matrix in a protected CI job with ephemeral OIDC federation.

Stop conditions

  • Stop immediately on any unexpected allow or credential exposure.
11

verification

Verify CloudTrail attribution and Access Analyzer findings

read-only

Confirm the organization trail captures role assumption and required S3/KMS data events, inspect source identity/session name, and query Access Analyzer for external-access and unused-access findings associated with the role and resources.

Why this step matters

CloudTrail answers who requested credentials and what APIs were called, but lookup event history is limited and S3 object/KMS data visibility depends on trail selectors. Access Analyzer external findings identify resource-based access outside the zone of trust; unused-access analyzers can help reduce permissions after enough observation. No finding does not prove safe policy—the trust zone and analyzer coverage matter.

What to understand

Verify trail encryption, log validation, region coverage, destination, retention, and delivery alarms.

Alert on assumption by unexpected principal/source, denied bursts, policy/boundary changes, and session duration anomalies.

Archive findings only with documented reason and owner.

System changes

  • Queries audit and analyzer services only.

Syntax explained

list-findings-v2
Queries current Access Analyzer findings with filters.
data events
Trail selectors required for object-level S3 operations beyond management history.
Command
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRole --max-results 20 --query 'Events[].{Time:EventTime,User:Username,Event:EventName}' --output table && aws accessanalyzer list-analyzers --type ACCOUNT --query 'analyzers[?status==`ACTIVE`].[name,arn]' --output table && aws accessanalyzer list-findings-v2 --analyzer-arn "$ANALYZER_ARN" --filter '{"resource":{"eq":["'$ROLE_ARN'"]}}' --output table
Example output / evidence
-------------------------------------------------
|                 LookupEvents                  |
+---------------------------+-------------------+
| 2026-07-28T11:42:18+00:00 | policy-verification-redacted | AssumeRole |
Active analyzer: production-account-access
No ACTIVE external-access findings for role

Checkpoint: Assumption and resource use are attributable and monitored

aws accessanalyzer list-findings-v2 --analyzer-arn "$ANALYZER_ARN" --filter '{"status":{"eq":["ACTIVE"]}}' --query 'findings[?resource==`'$ROLE_ARN'`]' --output json

Continue whenExpected assumption and test activity attributed, required data events retained, no unexplained active finding, and alerts route to an owner.

Stop whenTrail gap, unrecognized principal/session, missing data events, inactive analyzer, or external finding remains.

If this step fails

AssumeRole event has ambiguous session name.

Likely causeCaller does not set controlled role-session-name/source identity.

Safe checks
  • Inspect userIdentity sessionIssuer, principalId, and request parameters in retained event.

ResolutionRequire a structured non-secret session name/source identity in the caller workflow and alert on deviations.

Security notes

  • CloudTrail logs are security records containing principal/resource metadata.

Alternatives

  • Stream CloudTrail to a central SIEM with identity enrichment.

Stop conditions

  • Stop production enablement without attributable audit.
12

verification

Review service last accessed and remove unused permissions

read-only

After a representative operational period, generate service-last-accessed details, compare CloudTrail and workload requirements, and remove permissions that are unused or no longer required through a new policy version and change set. Lack of observed access is evidence, not automatic proof of safety.

Why this step matters

Last-accessed data helps find unused services/actions but has reporting lag, tracking-period limits, service coverage gaps, and cannot know future recovery paths. Compare it with the access matrix, application code, release schedules, and CloudTrail. Removal follows the same validation, simulation, change-set, live negative tests, and rollback gates as addition.

What to understand

Observe long enough to include rare but valid release/recovery operations.

Separate read/write/admin usage and identify permissions granted but never expected.

Keep policy versions within IAM limits and retention.

System changes

  • Starts and reads an IAM access report only.

Syntax explained

ACTION_LEVEL
Requests per-action usage where service supports it.
with-entities
Correlates service action access with identities.
Command
JOB_ID=$(aws iam generate-service-last-accessed-details --arn "$ROLE_ARN" --granularity ACTION_LEVEL --query JobId --output text) && aws iam get-service-last-accessed-details-with-entities --job-id "$JOB_ID" --service-namespace s3 --max-items 100 --output table
Example output / evidence
ActionName       LastAccessedEntity                     LastAccessedTime
ListBucket       ProductionReleasePublisher             2026-07-28T11:43:02Z
PutObject        ProductionReleasePublisher             2026-07-28T11:43:05Z
GetObject        ProductionReleasePublisher             2026-07-28T11:43:09Z
DeleteObject     -                                      -

Checkpoint: Permissions remain minimal over observed lifecycle

aws iam list-policy-versions --policy-arn "$PUBLISHER_POLICY_ARN" --output table

Continue whenEvery retained action remains tied to matrix need or documented rare recovery; obsolete permissions scheduled for tested removal.

Stop whenObservation window incomplete, data unsupported, recovery use unmodeled, or removal would break approved function.

If this step fails

Last-accessed report remains IN_PROGRESS.

Likely causeAsynchronous job has not completed or account data is large.

Safe checks
  • Poll job status with bounded interval.

ResolutionWait; do not infer no use from an incomplete report.

Security notes

  • Usage reports reveal operational schedules and resource relationships.

Alternatives

  • Use Access Analyzer unused-access findings with owner review.

Stop conditions

  • Stop automated permission removal without human/context review.
13

warning

Rotate trusted caller/context and rehearse session revocation

danger

Rotate the trusted caller role or External ID through a dual-control plan, minimize overlap, test the new path, then remove the old path. For compromise, block new assumptions and revoke older sessions with a time-based explicit deny under incident control while preserving CloudTrail evidence.

Why this step matters

Updating trust blocks future assumptions but existing STS credentials remain valid until expiry unless an explicit revocation deny is added. AWS console 'revoke sessions' creates a deny based on token issue time. That deny can interrupt legitimate workloads and must be applied with exact timestamp and incident approval. Normal rotation can overlap old/new caller briefly, but overlap expands trust and needs monitoring.

What to understand

Inventory active session duration and workload schedules before rotation.

Test new principal with complete matrix and audit before removing old.

After compromise, preserve events and rotate any resource credentials/data affected.

System changes

  • Shown commands are read-only; later trust/revocation changes affect session authorization.

Syntax explained

RoleLastUsed
Coarse service/region usage hint, not complete active-session inventory.
TokenIssueTime deny
Emergency policy condition denying sessions issued before a cutoff.
Command
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

aws iam get-role --role-name {{roleName}} --query 'Role.[Arn,MaxSessionDuration,RoleLastUsed]' --output json && aws cloudtrail lookup-events --lookup-attributes AttributeKey=ResourceName,AttributeValue={{roleName}} --max-results 50 --output table
Example output / evidence
[
  "arn:aws:iam::REDACTED:role/workloads/release/ProductionReleasePublisher",
  3600,
  {"LastUsedDate":"2026-07-28T11:43:09Z","Region":"eu-central-1"}
]
Recent events reviewed: AssumeRole, UpdateAssumeRolePolicy (none unexpected)

Checkpoint: New path works and old sessions/principal are retired deliberately

aws iam get-role --role-name {{roleName}} --query 'Role.AssumeRolePolicyDocument' --output json

Continue whenOnly current exact principal/context remains, old sessions expired/revoked as planned, negative old-path test denies, CloudTrail/alerts clean.

Stop whenActive workload ownership unknown, overlap unmonitored, cutoff uncertain, or revocation would create unsafe outage.

If this step fails

Old session continues after trust policy removal.

Likely causeTrust affects new AssumeRole, not credentials already issued.

Safe checks
  • Check token issue/expiration and CloudTrail session principal.

ResolutionWait for bounded expiry or apply reviewed token-issue-time revoke deny under incident process.

Security notes

  • Revocation policy details and incident timestamps are sensitive.

Alternatives

  • Use shorter default sessions and workload retry/failover to reduce revocation impact.

Stop conditions

  • Stop normal rotation and invoke incident response on suspected compromise.
14

warning

Roll back policy versions or retire the role through its stack

danger

For a bad policy update, restore the reviewed managed-policy version through a CloudFormation change set, retest trust/effective/live cases, and keep the boundary attached. To retire, block new assumptions, wait/revoke sessions, detach workload dependencies, retain audit, and delete through the stack in reverse dependency order.

Why this step matters

Managed-policy versions allow fast default-version restoration, but CloudFormation must remain the source of truth or the next deployment will undo the emergency change. A prior policy may still be unsafe with current resources/workflow, so revalidate and simulate it. Role retirement requires dependency and session handling because temporary credentials survive detachment/trust changes until expiry and resource policies may reference the role.

What to understand

Preserve bad policy, findings, CloudTrail, and affected requests before rollback.

Never remove the boundary as a rollback technique.

Delete non-default versions only after retention and recovery review.

System changes

  • Reads/validates prior policy; a later approved change may switch policy and eventually delete IAM resources.

Syntax explained

policy version
Immutable document version under a managed policy; one version is default.
stack retirement
Removes attachments, role, and policies under the declared owner and event history.
Command
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

aws iam list-policy-versions --policy-arn "$PUBLISHER_POLICY_ARN" --output table && aws iam get-policy-version --policy-arn "$PUBLISHER_POLICY_ARN" --version-id {{policyVersionToRestore}} --query 'PolicyVersion.Document' --output json > /secure-staging/policy-to-restore.json && aws accessanalyzer validate-policy --policy-type IDENTITY_POLICY --policy-document file:///secure-staging/policy-to-restore.json
Example output / evidence
VERSION  DEFAULT  CREATED
v4       True     2026-07-28T14:00:00Z
v3       False    2026-07-21T09:00:00Z
Policy validation: no ERROR or SECURITY_WARNING

Checkpoint: Restored policy passes current matrix and role has no orphan authority

aws iam list-entities-for-policy --policy-arn "$PUBLISHER_POLICY_ARN" --output table && aws iam list-instance-profiles-for-role --role-name {{roleName}} --output table

Continue whenRestored policy validated/simulated/live-tested, stack in sync, or retirement dependencies empty and sessions expired/revoked.

Stop whenPrior version fails current controls, boundary removed, active dependency/session remains, or resource policy still trusts deleted role.

If this step fails

CloudFormation restores the newer bad policy after manual default switch.

Likely causeTemplate/source still declares newer document.

Safe checks
  • Compare stack template, Git revision, policy default, and drift.

ResolutionCommit reviewed rollback/forward fix and deploy through a change set immediately.

Security notes

  • Policy rollback can reintroduce removed permissions; run full current test matrix.

Alternatives

  • Create a new parallel role and migrate caller/resources before retiring the old role.

Stop conditions

  • Stop deletion while any active session or resource-policy dependency is unresolved.

Finish line

Verification checklist

Trust boundaryaws iam get-role --role-name {{roleName}} --query 'Role.[AssumeRolePolicyDocument,MaxSessionDuration,PermissionsBoundary]' --output jsonOne exact trusted role/context, one-hour maximum, and required boundary.
Policy qualityaws accessanalyzer validate-policy --policy-type IDENTITY_POLICY --policy-document file://publisher-policy.jsonNo unresolved ERROR/SECURITY_WARNING and no wildcard action/principal or broad resource.
Effective simulationaws iam simulate-principal-policy --policy-source-arn "$ROLE_ARN" --action-names s3:PutObject s3:DeleteObject iam:AttachRolePolicy --resource-arns arn:aws:s3:::{{releaseBucketName}}/{{releasePrefix}}synthetic.binOnly required exact tuple allows; delete/admin/neighbor/wrong-context cases deny and boundary details match.
Runtime matrix./tests/run-role-policy-matrix.sh /run/user/1000/role-session.jsonSynthetic allowed calls succeed, every negative call denies, credentials/logs remain protected, and test object is cleaned by its owner.
Audit and ownershipaws cloudformation describe-stacks --stack-name {{iamStackName}} --query 'Stacks[0].StackStatus' --output text && aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRole --max-results 5Stack in sync, assumptions attributed, required data events retained, analyzer/alerts active, and no unexplained finding.

Recovery guidance

Common problems and safe checks

AssumeRole is denied for the approved caller.

Likely causeCaller identity policy, trust principal, External ID, source identity, session duration, SCP, or boundary condition differs.

Safe checks
  • Inspect caller ARN, trust policy, encoded denial, and simulate sts:AssumeRole where supported.
  • Compare External ID through approved metadata handling without logging it.

ResolutionCorrect the exact mismatched principal/context or caller permission; do not broaden trust to account root or wildcard.

An unapproved principal can assume the role.

Likely causeTrust uses broad account/root/wildcard principal, a permissive condition, or shared caller role.

Safe checks
  • Inspect role trust and CloudTrail AssumeRole principal ARNs.

ResolutionTreat as incident, tighten trust to exact principal, revoke older sessions, and review activity.

S3 ListBucket is denied but object writes work.

Likely causeBucket-level ListBucket requires bucket ARN and matching s3:prefix condition separate from object ARN.

Safe checks
  • Simulate s3:ListBucket on exact bucket with s3:prefix context.

ResolutionAdd only the required bucket action/resource/condition; do not grant s3:*.

Object PutObject is denied under the approved prefix.

Likely causeObject ARN slash/prefix mismatch, required encryption headers absent, bucket policy, endpoint policy, or KMS authorization denies.

Safe checks
  • Compare exact object ARN and request encryption headers.
  • Inspect bucket/key/endpoint policy and CloudTrail denial.

ResolutionCorrect the exact prefix/context intersection rather than widening bucket or KMS resources.

KMS Encrypt succeeds but Decrypt fails.

Likely causeIAM/key policy grants differ, encryption context does not match, key state/region differs, or grant is absent.

Safe checks
  • Inspect key ARN/state/policy and encryption context without decrypting production data.

ResolutionAdd only the required key operation/context for the approved workflow or redesign one-way publishing.

Access Analyzer reports wildcard or pass-role finding.

Likely causePolicy is broader than resource capabilities require or contains privilege-escalation path.

Safe checks
  • Read finding location and AWS service authorization resource types.

ResolutionEnumerate actions/resources/conditions and remove pass-role/IAM mutation unless separately designed.

Simulator allows a call that real AWS denies.

Likely causeSCP, resource policy, boundary, session policy, endpoint policy, key policy, or unsupported condition is absent from simulation.

Safe checks
  • Inspect all effective policy layers and CloudTrail authorization context.

ResolutionPreserve the deny and correct the intended restrictive layer only; do not broaden identity policy blindly.

Simulator denies a call that real AWS allows.

Likely causeA resource-based policy or session context grants access not modeled in the selected simulation.

Safe checks
  • Inspect bucket/key/resource policies and actual principal/session ARN.

ResolutionRemove unintended resource-based grant or update the test model; treat unexpected allow as security finding.

Permissions boundary blocks a newly attached policy.

Likely causeThe requested permission is outside the approved maximum or boundary condition/resource is narrower.

Safe checks
  • Simulate identity policy intersected with the boundary and review requested business need.

ResolutionKeep it blocked unless a separate boundary-version change is approved and tested; boundaries are not debugging obstacles.

CloudTrail shows AssumeRole but not S3 object calls.

Likely causeTrail data events are not enabled for the bucket/prefix or delivery is delayed.

Safe checks
  • Inspect organization trail event selectors, region, destination, and delivery health.

ResolutionEnable scoped S3 data events and alarms; do not claim complete usage evidence from management history alone.

Deleting an old policy version fails.

Likely causeVersion is default or still referenced by recovery/change process.

Safe checks
  • List policy versions/default and attachment/dependency inventory.

ResolutionSet the reviewed replacement default first, verify behavior, then delete only a non-default version outside retention.

Role deletion fails due to dependencies.

Likely causePolicies, instance profiles, or service integrations remain attached.

Safe checks
  • List attached/inline policies, instance profiles, and CloudFormation resources.

ResolutionRemove dependencies through the owning stack in reverse order after active-session and service checks.

After the procedure

Alternatives and next steps

Consider these alternatives

  • Use service-native resource policies and workload identity rather than a cross-account role when the workload and resource share a controlled account.
  • Use IAM Roles Anywhere, OIDC federation, or service-specific roles instead of long-lived IAM user keys for workloads outside AWS.
  • Generate a candidate policy from CloudTrail through Access Analyzer, then manually reduce and test it; generated policy is evidence, not an automatic approval.

Operate it safely

  • Add CI policy tests for every allowed action plus adjacent resource, administrative action, missing context, wrong region, and wrong principal denials.
  • Enable organization Access Analyzer unused-access findings and review permissions over a representative operational period.
  • Add source identity and session tags with tag-key/value controls where the trusted workflow supports them.
  • Move role/policy stacks into delegated infrastructure pipelines with signed templates and two-person named-IAM approval.
  • Review S3 bucket policy, KMS key policy, VPC endpoint policy, SCP, and role policy together as one authorization graph.

Recovery

Rollback

Restore a previously reviewed managed-policy version only after validating it against current resources and the complete matrix, and update the CloudFormation source immediately so ownership converges. Never remove the permissions boundary to restore service. For role retirement, block new assumptions, inventory/wait/revoke active sessions, remove caller/resource-policy dependencies, preserve audit evidence, and delete attachments, role, and policies through the owning stack.

  1. Freeze IAM changes and capture stack/change-set IDs, role unique ID/trust/boundary, attached/inline policies, default/non-default versions, resource policies, active use, findings, and CloudTrail.
  2. Identify whether failure is trust, identity, boundary, SCP, bucket/key/endpoint/resource policy, session context, or service behavior.
  3. Retrieve the prior policy version and validate/simulate it against the current complete matrix.
  4. Create a CloudFormation change set restoring the exact prior document; reject boundary removal, trust broadening, role replacement, or new attachment.
  5. Execute and run candidate, live-principal, and temporary-session positive/negative tests.
  6. Confirm CloudTrail, Access Analyzer, workload health, and stack drift/in-sync state.
  7. If compromise is suspected, block new assumptions and apply reviewed older-session revocation while preserving evidence.
  8. For retirement, wait for or revoke sessions, remove caller and resource-policy references, ensure no instance profile/service dependency, then delete through the stack.
  9. Retain policy/audit evidence and delete obsolete non-default versions only after recovery retention expires.

Evidence

Sources and review

Verified 2026-07-24Review due 2026-10-22
AWS IAM rolesofficialAWS IAM role trust policiesofficialAWS IAM policies and permissionsofficialAWS IAM permissions boundariesofficialAWS IAM policy evaluation logicofficialAWS IAM least privilegeofficialAWS IAM Access Analyzer policy validationofficialAWS IAM policy simulatorofficialAWS IAM Access Analyzer external accessofficialAWS IAM last accessed informationofficialAWS IAM role session revocationofficialAWS STS AssumeRoleofficialAWS CloudTrail IAM eventsofficialAWS CloudFormation change setsofficial