Simulate Cross-Account Access with IAM

Use IAM roles and STS to simulate cross-team S3 access with temporary credentials.

Introduction

30 Second Summary

Imagine two teams at a company. The operations team manages a storage bucket, and a developer on another team needs temporary access to debug an issue. Giving the developer permanent access is risky. The smarter approach is granting access that automatically expires.

In this project, you will simulate a cross-account access pattern using AWS IAM roles and STS. You'll create a Developer user with no permissions, build a role with a trust policy, and use temporary credentials to prove the access pattern works.

What You'll Build

You'll build a working role-assumption flow where an IAM user assumes a cross-team role to gain temporary, scoped S3 access via STS.

By the end of this project, you'll have:

  • An IAM user representing a Developer with zero direct S3 permissions.
  • An IAM role with a trust policy and a permissions policy granting scoped read-only S3 access.
  • A working STS AssumeRole flow that produces temporary credentials, proving access is properly gated and expires automatically.
  • Secret Mission: Defend against the confused deputy problem by adding an external ID condition to your trust policy.

Are there any prerequisites?

You'll need an AWS account (Free Tier or paid). No prior IAM experience is required. This project walks you through every command from scratch using the AWS CLI.

Set Up Your Environment

In this project, you'll simulate how organizations grant cross-team access to AWS resources using IAM roles and STS.

Before you dive into creating users and roles, you need a target resource. In this step, you'll create an S3 bucket that represents a resource managed by an "Operations" team. This is the bucket your Developer identity will eventually need temporary access to.

In this step, get ready to:
  • Confirm your AWS CLI is configured and working.
  • Create an S3 bucket that the Ops team manages.
Confirm your AWS CLI is ready

Your AWS CLI should already be installed and configured with your credentials. Confirm everything is working by checking your identity.

  • Verify your CLI identity by running this command:
aws sts get-caller-identity

✔️ I see my account ID and ARN

Your CLI is configured and authenticated. You're ready to create your S3 bucket.

ⓧ I see an error

If you see a credentials error, you need to configure your CLI first.

  • Run the following command and enter your access key, secret key, and default region when prompted:
aws configure

After configuring, run aws sts get-caller-identity again to confirm it works.

Create an S3 bucket for the simulation

Every cross-account access scenario needs a target resource. You'll create an S3 bucket that represents a resource owned by the Operations team. Later, you'll prove that your Developer identity can only reach this bucket by assuming the Ops role.

Why an S3 bucket?

S3 buckets are free when empty and make a clear demonstration target. You can test both successful access (listing contents) and denied access (attempting to list without permission) in seconds.

  • Create your Ops team bucket by running this command:
aws s3 mb s3://[[BUCKET_NAME="nextwork-sts-sim-your-account-id"]]

What does this command do?

The aws s3 mb command creates ("makes") a new S3 bucket. Bucket names must be globally unique across all of AWS, so include something unique like your AWS account ID in the name.

You can find your account ID in the output of aws sts get-caller-identity that you ran earlier.

Bucket naming rules

Bucket names must be 3-63 characters, use only lowercase letters, numbers, and hyphens, and start and end with a letter or number.

You should see a confirmation message like:

make_bucket: s3://nextwork-sts-sim-123456789012
  • Verify the bucket exists by listing your S3 buckets:
aws s3 ls

You should see your new bucket in the list alongside its creation timestamp.

Seeing a BucketAlreadyExists error?

This means your chosen bucket name is already taken by someone else on AWS. Try a different name that includes your account ID or another unique string.

help me fix my S3 bucket creation error

Your Ops team resource is ready. Next up, you'll create the Developer user who currently has zero access to this bucket.

Create the Developer User

Your S3 bucket is ready and represents a resource managed by an Operations team. Now you need someone who wants access to it.

In this step, you'll create an IAM user called "Developer" that represents someone on a different team. This user will start with zero permissions, proving that AWS denies access by default.

In this step, get ready to:
  • Create an IAM user with no permissions.
  • Generate access keys for the Developer user.
  • Prove the Developer cannot access S3.
Create an IAM user called Developer

Every identity in AWS starts with zero permissions. By creating a fresh IAM user with no policies attached, you establish a baseline that proves access must be explicitly granted.

  • Create the Developer user by running this command:
aws iam create-user --user-name Developer

What does this command do?

`aws iam create-user` calls the IAM API to register a new user in your AWS account. The `--user-name Developer` flag sets the user's name to "Developer".

This user has no policies, no group memberships, and no permissions. It exists purely as an identity that AWS can recognize.

  • You should see a JSON response containing the new user's details, including a `UserName` of `Developer` and an `Arn` field.

Seeing an error?

  • If you see an "EntityAlreadyExists" error, a user named Developer already exists in your account. Delete it first with `aws iam delete-user --user-name Developer` and try again.
  • If you see "AccessDenied", your default profile does not have IAM admin permissions. Make sure your default profile uses credentials with IAM access.
  • Help me troubleshoot this error.
Create access keys for the Developer

Access keys let you authenticate as the Developer via the CLI. Without them, you have no way to prove that this user lacks S3 access.

  • Generate access keys for the Developer user by running:
aws iam create-access-key --user-name Developer

What does this command do?

`aws iam create-access-key` generates a pair of credentials: an Access Key ID and a Secret Access Key. Together, these act like a username and password for programmatic access.

The secret key is only shown once at creation time. Copy both values now because you'll need them in the next step.

  • You should see a JSON response with an `AccessKey` object containing `AccessKeyId` and `SecretAccessKey` fields.
  • Copy the `AccessKeyId` value and the `SecretAccessKey` value somewhere safe (like a text file). You'll need them in a moment.

Don't see an access key?

  • If you see "NoSuchEntity", the Developer user doesn't exist yet. Go back and run the `create-user` command first.
  • If you see "LimitExceeded", the user already has two access keys. Delete one with `aws iam delete-access-key --user-name Developer --access-key-id YOUR_KEY_ID`.
  • Help me figure out what went wrong.
Prove the Developer has no S3 access

Now you'll configure a CLI profile for the Developer and try to access the S3 bucket. This should fail, proving that the Developer has no permissions.

  • Set up a new CLI profile called `developer` by running:
aws configure --profile developer

What prompts will I see?

The CLI will ask for four values:

  • `AWS Access Key ID`: paste the AccessKeyId you copied from the previous command.
  • `AWS Secret Access Key`: paste the SecretAccessKey you copied.
  • `Default region name`: enter the same region as your default profile (e.g., `us-east-1`).
  • `Default output format`: press Enter to accept the default (or type `json`).
  • Enter the `AccessKeyId` you copied when prompted for **AWS Access Key ID**.
  • Enter the `SecretAccessKey` you copied when prompted for **AWS Secret Access Key**.
  • Enter the same region as your default profile (e.g., `us-east-1`) for **Default region name**.
  • Press **Enter** to accept the default for **Default output format**.

Now attempt to list the contents of your S3 bucket using the Developer's profile.

  • Try to access your S3 bucket as the Developer by running:
aws s3 ls s3://[[BUCKET_NAME="nextwork-sts-sim-YOUR-ACCOUNT-ID"]] --profile developer
  • You should see an "Access Denied" error. This confirms the Developer has no S3 permissions.

Why does this fail?

AWS follows a "deny by default" model. Unless a policy explicitly grants a user permission to perform an action, that action is denied.

The Developer user has no policies attached, so every API call it makes is rejected. This is the principle of least privilege in action.

Not seeing Access Denied?

  • If the command succeeds and lists bucket contents, you may have accidentally used your admin profile. Make sure you included `--profile developer` at the end of the command.
  • If you see "InvalidAccessKeyId", double-check that you pasted the correct AccessKeyId when running `aws configure --profile developer`.
  • Help me figure out why my Developer user might have unintended permissions.

You now have a Developer identity that is completely locked out of S3. Next, you'll build the role and trust policy that will unlock temporary access for this user.

Create the Cross-Account Role with Trust Policy

In the previous step, you proved the Developer user has zero S3 access. Now you'll build the IAM role that grants it.

Every IAM role has two critical components: a trust policy that controls WHO can assume the role, and a permissions policy that controls WHAT the role can do. You'll create both from scratch using the AWS CLI.

In this step, get ready to:
  • Write a trust policy JSON document specifying the Developer as the trusted principal.
  • Create the Ops-S3-ReadOnly IAM role with the trust policy attached.
  • Write and attach a scoped permissions policy granting read-only S3 access.
Write the trust policy JSON

The trust policy is a JSON document that answers one question: who is allowed to assume this role? In your case, the answer is the Developer user.

  • Create the trust policy file by running this command:
cat > trust-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::[[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]]:user/Developer"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

What does this policy do?

This trust policy has one statement that allows the Developer user (identified by their full ARN) to perform the sts:AssumeRole action on this role.

The Principal field is what makes this a trust policy. It declares which identity is trusted to "become" this role temporarily.

  • Verify the file was created correctly by running:
cat trust-policy.json

You should see the JSON document printed out with your account ID in the Principal ARN.

File looks empty or malformed?

Make sure you copied the entire command including the EOF on its own line at the end. The heredoc syntax requires the closing EOF to appear alone on a new line with no leading spaces.

Also confirm you replaced the account ID placeholder with your actual 12-digit AWS account ID (the one you saw when you ran aws sts get-caller-identity earlier).

Help me troubleshoot my trust-policy.json file

Create the IAM role with the trust policy

Now you'll create the actual IAM role and attach the trust policy to it in one command. This tells AWS "create a role called Ops-S3-ReadOnly, and only the Developer can assume it."

  • Create the role by running this command:
aws iam create-role \
  --role-name Ops-S3-ReadOnly \
  --assume-role-policy-document file://trust-policy.json

What does this command do?

  • --role-name Ops-S3-ReadOnly names your new role. This represents an Operations team role for read-only S3 access.
  • --assume-role-policy-document file://trust-policy.json attaches the trust policy you just wrote. The file:// prefix tells the CLI to read from a local file.

You should see JSON output containing the new role's details, including its Arn and RoleName.

Seeing a MalformedPolicyDocument error?

Double-check that your trust-policy.json contains valid JSON. Run cat trust-policy.json and look for missing commas, mismatched braces, or an incorrect account ID format. The account ID must be exactly 12 digits with no dashes.

Help me debug my trust policy

Write and attach the permissions policy

The role exists, but it can't do anything yet. You need a permissions policy that defines what actions the role is allowed to perform. You'll create a managed policy granting read-only access to your specific S3 bucket.

  • Create the permissions policy file by running this command:
cat > permissions-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::nextwork-sts-sim-[[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]]",
        "arn:aws:s3:::nextwork-sts-sim-[[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]]/*"
      ]
    }
  ]
}
EOF

What does this policy do?

This permissions policy grants exactly two actions on your bucket:

  • s3:ListBucket lets the role list objects in the bucket (like viewing a folder's contents).
  • s3:GetObject lets the role download individual objects from the bucket.

Notice the two Resource ARNs. The first (without /*) targets the bucket itself for ListBucket. The second (with /*) targets all objects inside the bucket for GetObject. This scoping follows the principle of least privilege.

  • Verify the file looks correct by running:
cat permissions-policy.json

You should see the JSON with your bucket name in both Resource ARNs.

  • Create the managed policy in AWS by running:
aws iam create-policy \
  --policy-name Ops-S3-ReadOnly-Policy \
  --policy-document file://permissions-policy.json

You should see JSON output containing the new policy's Arn field. It follows the pattern arn:aws:iam::<account-id>:policy/Ops-S3-ReadOnly-Policy.

  • Attach the permissions policy to your role by running:
aws iam attach-role-policy \
  --role-name Ops-S3-ReadOnly \
  --policy-arn arn:aws:iam::[[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]]:policy/Ops-S3-ReadOnly-Policy

Why two separate steps?

Creating a policy and attaching it are distinct operations in AWS. First you create the policy as a standalone reusable object. Then you attach it to any role, user, or group that needs those permissions. This makes policies reusable across your organization.

  • Confirm the policy is attached by running:
aws iam list-attached-role-policies \
  --role-name Ops-S3-ReadOnly

You should see output listing Ops-S3-ReadOnly-Policy with its full ARN under AttachedPolicies.

Policy not showing up?

If AttachedPolicies is empty, double-check that the --policy-arn in your attach command uses your actual account ID and the exact policy name Ops-S3-ReadOnly-Policy. A typo in the ARN causes a silent failure.

Help me debug my attached policy

Your role is fully configured with both its trust policy and permissions policy. Next up, you'll give the Developer permission to actually call AssumeRole on this role.

Grant the Developer Permission to Assume the Role

You've built the role and its trust policy, which says who can assume it. But in AWS, that's only half the picture.

The Developer user also needs an identity-based policy granting them permission to call sts:AssumeRole. Without both sides in place, the assume-role request fails. In this step you'll create that bridge policy, attach it, and confirm the two-sided relationship is complete.

In this step, get ready to:
  • Write a policy granting the Developer permission to assume the Ops-S3-ReadOnly role.
  • Attach the policy to the Developer user.
  • Verify that both sides of the trust relationship are configured.
Write the AssumeRole policy

Right now the trust policy on the Ops-S3-ReadOnly role says "I trust the Developer user." But the Developer user has no permission to initiate an AssumeRole call.

You need a policy on the Developer side that grants sts:AssumeRole on that specific role ARN.

  • Create a new policy document file called assume-role-policy.json by running this command:
cat > assume-role-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "arn:aws:iam::[[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]]:role/Ops-S3-ReadOnly"
    }
  ]
}
EOF

What does this policy do?

This is an identity-based policy. It grants whoever it's attached to the ability to call sts:AssumeRole on one specific role ARN.

By scoping the Resource to the exact role ARN, you follow the principle of least privilege. The Developer can only assume this one role, not any other role in the account.

  • Verify the file was created by running:
cat assume-role-policy.json

You should see the JSON policy document printed to the terminal with sts:AssumeRole as the action and your role ARN as the resource.

  • Now create this as a managed policy in IAM by running:
aws iam create-policy --policy-name Developer-AssumeRole-Policy --policy-document file://assume-role-policy.json

What does this command do?

The aws iam create-policy command registers a new customer-managed policy in your account. The --policy-name flag gives it a human-readable name. The --policy-document flag points to your local JSON file.

After creation, the policy exists in IAM but is not yet attached to anyone. You'll attach it to the Developer user next.

You should see JSON output that includes the policy's ARN. It looks like arn:aws:iam::<account-id>:policy/Developer-AssumeRole-Policy. Copy this ARN for the next command.

Seeing a MalformedPolicyDocument error?

Make sure your JSON uses "Version": "2012-10-17" exactly. This is the IAM policy language version, not a date you choose. Also check that your Resource ARN matches the format arn:aws:iam::<account-id>:role/Ops-S3-ReadOnly with your actual 12-digit account ID.

Help me fix my IAM policy JSON document

Attach the policy to the Developer user

The policy exists in IAM, but it is floating unattached. You need to explicitly link it to the Developer user so that user gains the sts:AssumeRole permission.

  • Attach the policy to the Developer user by running:
aws iam attach-user-policy --user-name Developer --policy-arn arn:aws:iam::[[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]]:policy/Developer-AssumeRole-Policy

What does this command do?

The aws iam attach-user-policy command links a managed policy to a specific IAM user. The --user-name identifies the target user. The --policy-arn identifies the policy to attach.

This command produces no output on success. If you see no error message, the policy was attached correctly.

If the terminal returns to your prompt without an error message, the attach succeeded.

Seeing a NoSuchEntity error?

Double-check that the policy ARN matches exactly what was returned when you created the policy. The account ID and policy name are case-sensitive. Also confirm the user name Developer is spelled with a capital D.

Help me fix my attach-user-policy command

Verify the two-sided relationship

Cross-account access in AWS requires two sides to be in place. The trust policy on the role says who is allowed. The identity-based policy on the user grants permission to call AssumeRole.

  • Confirm the Developer now has the policy attached by running:
aws iam list-attached-user-policies --user-name Developer

You should see output listing Developer-AssumeRole-Policy in the AttachedPolicies array, along with its full ARN.

Why does cross-account access need both sides?

AWS documentation states: "adding an account to the trust policy of a role is only half of establishing the cross-account trust relationship. By default, no users in the trusted accounts can assume the role. The administrator for the newly trusted account must grant the users the permission to assume the role."

You have now set up both sides. The trust policy on the Ops-S3-ReadOnly role allows the Developer. The Developer-AssumeRole-Policy grants the Developer permission to call sts:AssumeRole. The bridge is complete.

Both sides of the relationship are now locked in. Next up, you'll put this to the test by assuming the role as the Developer and accessing S3 with temporary credentials.

Assume the Role and Access S3

Both sides of your trust relationship are in place. The role trusts the Developer, and the Developer has permission to call AssumeRole. Now it's time to bring them together.

In this step, you'll call STS AssumeRole as the Developer to get temporary credentials. Then you'll use those credentials to access the S3 bucket and prove that the permissions are scoped and time-limited.

In this step, get ready to:
  • Call STS AssumeRole as the Developer to receive temporary credentials.
  • Configure and use temporary credentials to access S3.
  • Prove that the credentials are scoped and temporary.
Call STS AssumeRole as the Developer

The aws sts assume-role command asks AWS to hand back temporary credentials tied to a specific role. If both the trust policy and the identity-based policy check out, AWS returns an access key ID, a secret access key, and a session token.

  • Call AssumeRole as the Developer by running this command:
aws sts assume-role --role-arn arn:aws:iam::[[AWS_ACCOUNT_ID="your-12-digit-account-id"]]:role/Ops-S3-ReadOnly --role-session-name DevSession --profile developer

What does this command do?

This command asks AWS to issue temporary credentials for the Ops-S3-ReadOnly role.

  • --role-arn tells STS which role you want to assume.
  • --role-session-name is a label for this session that shows up in CloudTrail logs.
  • --profile developer makes the request using the Developer's long-term credentials (not your admin credentials).

You should see a JSON response containing a Credentials object with four fields: AccessKeyId, SecretAccessKey, SessionToken, and Expiration.

What is the Expiration field?

The Expiration timestamp tells you when these credentials stop working. By default, STS sessions last 1 hour. After that, the credentials are invalid and you'd need to call AssumeRole again.

This is the core security benefit of STS. Even if temporary credentials are leaked, they're useless after they expire.

Seeing Access Denied?

  • Check that your --role-arn includes your actual 12-digit account ID (no angle brackets).
  • Confirm the Developer-AssumeRole-Policy is attached to the Developer user. Run aws iam list-attached-user-policies --user-name Developer to check.
  • Verify the trust policy on the role lists the Developer user ARN as the Principal. Run aws iam get-role --role-name Ops-S3-ReadOnly to check.

Help me troubleshoot why my Developer user cannot assume the Ops-S3-ReadOnly role. I've set up a trust policy and an identity-based policy but I'm getting Access Denied.

Configure temporary credentials and access S3

The JSON output from the previous command contains your temporary credentials. To use them, you need to export them as environment variables that the AWS CLI reads automatically.

  • Export the three credential values by running these commands, replacing each placeholder with the corresponding value from your JSON output:
export AWS_ACCESS_KEY_ID=your-access-key-id-from-output
export AWS_SECRET_ACCESS_KEY=your-secret-access-key-from-output
export AWS_SESSION_TOKEN=your-session-token-from-output

Why environment variables?

When the AWS CLI finds AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN in your environment, it uses them instead of any profile credentials. This is how real automation pipelines inject temporary credentials into build processes and containers.

  • Now prove the temporary credentials grant S3 access by listing your bucket:
aws s3 ls s3://nextwork-sts-sim-[[AWS_ACCOUNT_ID="your-12-digit-account-id"]]

The command should complete without an error. Since the bucket is empty, you'll see no listed objects. The important thing is you did NOT get an Access Denied response.

Why is no output a success?

Remember in Step 2, the Developer got Access Denied when trying the exact same aws s3 ls command. Now that you've assumed the role, the same operation succeeds. The lack of an error IS the proof that your temporary credentials carry the role's S3 permissions.

Still getting Access Denied on the S3 command?

  • Make sure you exported all three environment variables. Missing the AWS_SESSION_TOKEN is the most common mistake.
  • Check for typos or missing characters. The session token is very long. Copy-paste it carefully from the JSON output.
  • Verify your bucket name is correct. It should match the name you created in Step 1.

Help me debug why my temporary STS credentials aren't granting S3 access after assuming the Ops-S3-ReadOnly role.

Prove credentials are scoped and temporary

Your temporary credentials only carry the permissions defined in the role's policy. The Ops-S3-ReadOnly-Policy grants s3:GetObject and s3:ListBucket. Any other S3 action should be denied.

  • Attempt to delete an object from the bucket by running this command:
aws s3 rm s3://nextwork-sts-sim-[[AWS_ACCOUNT_ID="your-12-digit-account-id"]]/test.txt

You should see an error message: An error occurred (AccessDenied) when calling the DeleteObject operation: Access Denied.

What does this prove?

Even though you successfully assumed the role, you can only do what the role's permissions policy allows. The policy grants read-only access, not delete. This is least privilege in action. The Developer gets exactly what they need (read) and nothing more.

Combine this with the Expiration timestamp you saw earlier, and you have two layers of protection. The credentials are both permission-scoped AND time-limited.

  • Clean up the environment variables so your terminal returns to using your default profile by running:
unset AWS_ACCESS_KEY_ID
unset AWS_SECRET_ACCESS_KEY
unset AWS_SESSION_TOKEN
  • Verify you're back to your admin identity by running:
aws sts get-caller-identity

You should see your original admin user ARN, not the assumed role ARN.

Still seeing the assumed role identity?

  • Make sure you ran all three unset commands. Even one leftover environment variable can confuse the CLI.
  • If you opened a new terminal tab, the variables won't exist there. Run aws sts get-caller-identity in the new tab to confirm.

Help me understand why get-caller-identity still shows the assumed role after I unset the environment variables.

You just proved the complete IAM role-assumption flow end to end. A user with zero direct S3 permissions assumed a role and gained temporary, scoped access. This is the exact pattern that powers every multi-account AWS organization in the real world.

Secret mission

Defend Against the Confused Deputy

The confused deputy problem is one of the most subtle security vulnerabilities in cloud computing. A trusted third party can be tricked into acting on a malicious actor's behalf. In this bonus challenge, you'll add an external ID condition to your trust policy to prevent this attack.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. IAM is offered at no additional charge, and your S3 bucket is free while empty or within the free tier. There are no ongoing costs for leaving these resources in place.

Resources you used:

  • IAM user: Developer (with access keys).
  • IAM role: Ops-S3-ReadOnly.
  • IAM managed policy: Developer-AssumeRole-Policy.
  • IAM managed policy: Ops-S3-ReadOnly-Policy.
  • S3 bucket: nextwork-sts-sim-<account-id>.
  • Local file: trust-policy.json.
  • AWS CLI profile: developer.

Keep everything running

No action needed. Choose this if you're still actively experimenting with IAM roles, trust policies, or STS.

  • IAM resources are free regardless of how long they exist.
  • The S3 bucket is free while empty or within the AWS Free Tier.
  • The developer CLI profile and trust-policy.json remain on your machine for future reference.

Pause - I'll come back to this later

Deactivate the Developer's access keys so nobody can use them while you're away, but keep all resources intact for easy resumption.

  • List the Developer's access keys by running:
aws iam list-access-keys --user-name Developer
  • Note the AccessKeyId in the output. Deactivate it by running:
aws iam update-access-key --user-name Developer --access-key-id [[DEV_ACCESS_KEY_ID="your Developer access key ID"]] --status Inactive
  • When you return, reactivate it by changing Inactive to Active in the same command.

Delete - I don't want to use this again

Remove all project resources and start fresh. Run the following commands in order from your default (admin) CLI profile.

  • Delete the S3 bucket by running:
aws s3 rb s3://[[BUCKET_NAME="nextwork-sts-sim-your-account-id"]] --force
  • Detach the managed policy from the Developer user by running:
aws iam detach-user-policy --user-name Developer --policy-arn arn:aws:iam::[[AWS_ACCOUNT_ID="your-account-id"]]:policy/Developer-AssumeRole-Policy
  • List and delete the Developer's access keys by running:
aws iam list-access-keys --user-name Developer
  • Copy the AccessKeyId from the output. Delete it by running:
aws iam delete-access-key --user-name Developer --access-key-id [[DEV_ACCESS_KEY_ID="your Developer access key ID"]]
  • Delete the Developer user by running:
aws iam delete-user --user-name Developer
  • Detach the managed policy from the role by running:
aws iam detach-role-policy --role-name Ops-S3-ReadOnly --policy-arn arn:aws:iam::[[AWS_ACCOUNT_ID="your-account-id"]]:policy/Ops-S3-ReadOnly-Policy
  • Delete the role by running:
aws iam delete-role --role-name Ops-S3-ReadOnly
  • Delete both managed policies by running:
aws iam delete-policy --policy-arn arn:aws:iam::[[AWS_ACCOUNT_ID="your-account-id"]]:policy/Developer-AssumeRole-Policy
aws iam delete-policy --policy-arn arn:aws:iam::[[AWS_ACCOUNT_ID="your-account-id"]]:policy/Ops-S3-ReadOnly-Policy
  • Remove the local trust policy file by running:
rm trust-policy.json
  • Remove the developer CLI profile by opening ~/.aws/credentials and ~/.aws/config in a text editor. Delete the [developer] section from each file, then save.

Nice Work!

Nice Work!

You just built the exact access pattern that powers every multi-account AWS organization. You now have hands-on experience with the trust policy, permissions policy, and STS trifecta that underpins AWS security architecture.

Here's what you learned:

  • Built a cross-account-style IAM role with a trust policy controlling who can assume it and a permissions policy controlling what it can do.
  • Used STS AssumeRole to obtain temporary credentials and proved that a Developer user can only access S3 through the role, demonstrating least privilege in action.
  • Configured the two-sided access relationship that AWS requires. The trust policy on the role allows the principal, and an identity-based policy on the user grants permission to call AssumeRole.
  • Defended against the confused deputy problem by adding an external ID condition to the trust policy (Secret Mission).

Ready to quiz yourself?