Secure an S3 Bucket with IAM
Control S3 access using IAM users, custom policies, and roles with STS.
Introduction
30 Second Summary
Every app deployed to AWS needs permissions to access databases, storage, and other services. Get it wrong, and either your app breaks with "Access Denied" errors or your data is exposed to anyone who asks.
In this project, you will create a restricted IAM user, write custom JSON policies, and assume a role with temporary credentials to control exactly who can read and write to an S3 bucket.
What You'll Build
You will switch between CLI profiles, watch "Access Denied" errors appear when permissions are missing, then grant precise access and prove it works by downloading and uploading files to your bucket.
By the end of this project, you'll have:
- A restricted IAM user with zero permissions that visibly fails to access your S3 bucket, proving what "no access" looks like before you fix it.
- A custom JSON policy that grants read-only access following the least-privilege principle, so your user can download files but gets blocked when trying to upload.
- An IAM role with a trust policy that grants temporary write access through STS AssumeRole, mirroring how production backend apps authenticate.
- Secret Mission: Use the IAM Policy Simulator to test your policies without making live API calls.
Are there any prerequisites?
You need an AWS account (free tier works fine). No prior IAM experience is required.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
Install AWS CLI and Create an S3 Bucket
To secure AWS resources, you need a way to interact with your account from the command line.
In this step, you will install the AWS CLI (Command Line Interface), connect it to your AWS account, and create the S3 bucket that the rest of this project will protect.
In this step, get ready to:
- Install AWS CLI v2 and verify it works.
- Configure the CLI with your AWS admin credentials.
- Create an S3 bucket and upload a test file.
Install AWS CLI v2
The AWS CLI lets you manage AWS services from your terminal instead of clicking through the Console.
- Open a terminal. Press the Windows key, type cmd, and press Enter to open Command Prompt.
- Check whether you already have the AWS CLI installed by running this command:
aws --version
✔️ I see aws-cli/2.x.x
Your output starts with aws-cli/2. followed by a version number. You already have AWS CLI v2 installed.
- Skip ahead to the next substep, "Configure the CLI with your admin credentials."
ⓧ I see aws-cli/1.x.x
Your output starts with aws-cli/1. which means you have the older version 1 installed. You need to uninstall it and install version 2.
- Open Programs and Features by pressing the Windows key, typing appwiz.cpl, and pressing Enter.
- Select AWS Command Line Interface and click Uninstall.
- Confirm the uninstall and close the window.
- Close your current Command Prompt and open a new one.
- Download the AWS CLI v2 installer by visiting this URL in your browser:
https://awscli.amazonaws.com/AWSCLIV2.msi
- Run the downloaded AWSCLIV2.msi file and follow the on-screen prompts to complete the installation.
- Close your Command Prompt and open a new one for the PATH changes to take effect.
- Verify the installation by running:
aws --version
You should now see output starting with aws-cli/2. confirming version 2 is installed.
ⓧ Command not found
The AWS CLI is not installed on your machine yet. Follow these steps to install it.
Windows
- Download the AWS CLI v2 installer by visiting this URL in your browser:
https://awscli.amazonaws.com/AWSCLIV2.msi
- Run the downloaded AWSCLIV2.msi file and follow the on-screen prompts to complete the installation.
- Close your Command Prompt and open a new one for the PATH changes to take effect.
- Verify the installation by running:
aws --version
You should see output starting with aws-cli/2. confirming version 2 is installed.
macOS
- Download and install AWS CLI v2 by running these commands in Terminal:
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg AWSCLIV2.pkg -target /
What do these commands do?
The first command downloads the official AWS CLI installer package. The second command runs the macOS installer, placing files in /usr/local/aws-cli and creating a symlink at /usr/local/bin/aws.
- Verify the installation by running:
aws --version
You should see output starting with aws-cli/2. confirming version 2 is installed.
Still seeing 'command not found' after installing?
- Make sure you opened a brand new Command Prompt or Terminal window after installation. Existing windows do not pick up PATH changes.
- On Windows, try restarting your computer if a new Command Prompt still does not find the command.
Still stuck? Help me troubleshoot why aws --version is not working after I installed AWS CLI v2.
Configure the CLI with your admin credentials
The CLI needs credentials to prove who you are when it talks to AWS. You will connect it to the admin account you use to manage your AWS resources.
- Run the configuration command:
aws configure
What will aws configure ask me?
This command prompts you for four values, one at a time:
- AWS Access Key ID and AWS Secret Access Key from the IAM user you use to manage AWS. You can find or create these in the AWS Console under IAM > Users > your user > Security credentials.
- Default region name controls which AWS data centre your commands target.
- Default output format controls how results are displayed in your terminal.
- When prompted for AWS Access Key ID, paste your Access Key ID and press Enter.
- When prompted for AWS Secret Access Key, paste your Secret Access Key and press Enter.
- When prompted for Default region name, type us-east-1 and press Enter.
- When prompted for Default output format, type json and press Enter.
Where do my credentials come from?
These credentials come from the IAM user or root account you use to manage AWS. If you do not have an Access Key yet, go to the AWS Console, navigate to IAM > Users, select your admin user, open the Security credentials tab, and click Create access key.
Choose Command Line Interface (CLI) as the use case and save both keys securely. You will not be able to see the Secret Access Key again after closing the creation dialog.
Getting an 'invalid credentials' error later?
- Double-check that you pasted the full Access Key ID and Secret Access Key without leading or trailing spaces.
- Confirm your credentials belong to a user with admin permissions (or at minimum, the IAM and S3 permissions needed for this project).
Need more help? Help me fix my AWS CLI credentials configuration.
Create an S3 bucket and upload a test file
Now you have a working CLI. Time to create the S3 bucket that the rest of this project will protect with IAM policies.
- Create your S3 bucket by running this command (replace the name with something unique to you):
aws s3 mb s3://iam-demo-[[YOURNAME="your unique name"]]-2026
Why does the bucket name need to be unique?
S3 bucket names are globally unique across all AWS accounts. Adding your name and the year ensures no one else has already claimed this bucket name.
You should see a line saying make_bucket followed by your bucket name. This confirms the bucket was created.
- Create a local test file by running:
echo This is a test file for the IAM demo > testfile.txt
- Upload the test file to your new bucket by running:
aws s3 cp testfile.txt s3://iam-demo-[[YOURNAME="your unique name"]]-2026/
You should see an upload line confirming the file was copied to S3.
Before you verify, what do you think aws s3 ls will show you?
- Confirm the bucket exists by running:
aws s3 ls
You should see your bucket iam-demo-YOURNAME-2026 listed in the output alongside any other buckets in your account.
Bucket not showing up?
- If you see an access denied error, your credentials may not have S3 permissions. Confirm you used admin credentials during aws configure.
- If you see a "bucket already exists" error when creating, try a different unique name. Someone else may already own that name globally.
Still stuck? Help me troubleshoot my S3 bucket creation.
Your CLI is connected and your bucket is live. Next up, you will create a restricted IAM user and discover what happens when someone has zero permissions.
Create a Restricted IAM User and Hit Access Denied
Your S3 bucket is live and your admin credentials can do anything to it. In a real deployment, you would never hand out admin-level access to every person or service that touches your infrastructure.
In this step, you will create a brand new IAM user with zero permissions attached and attempt to access your bucket. Experiencing "Access Denied" firsthand makes the policies you write in the next step meaningful.
In this step, get ready to:
- Create an IAM user in the Console with no permissions attached.
- Generate access keys and configure a named CLI profile for the restricted user.
- Attempt to access your S3 bucket and confirm you see "Access Denied".
Create an IAM user in the Console
Every AWS account starts with a root user or an admin user that can do everything. To demonstrate least-privilege access, you need a second user that starts with nothing.
- In the AWS Management Console, type IAM in the top search bar and select IAM from the results.
- In the left navigation pane, click Users.
- Click Create user in the top right.
- In the User name field, enter demo-restricted-user.
- Leave the Provide user access to the AWS Management Console checkbox unchecked.
- Click Next.
The next screen asks you to set permissions. This is the critical part.
- Do NOT attach any policies or add the user to any group. Leave everything empty.
- Click Next to skip the permissions page.
- On the review page, confirm the user name is demo-restricted-user and the permissions summary shows nothing attached.
- Click Create user.
You should see a success banner confirming the user was created. The user now appears in your Users list.
Why zero permissions?
IAM follows a deny-by-default model. A new user with no policies attached cannot access any AWS service or resource. This is the starting point for least-privilege: you grant only the specific permissions needed, rather than starting open and restricting later.
Don't see the Create user button?
Make sure you are signed in with an account that has IAM admin permissions. If you are using a limited IAM user yourself, you may not have permission to create other users.
help me troubleshoot why I can't create an IAM user in the Console
Generate access keys for the new user
To use this user from the CLI, you need access keys. Access keys are a pair of credentials (an ID and a secret) that authenticate API requests.
- In the Users list, click demo-restricted-user to open the user details page.
- Click the Security credentials tab.
- Scroll down to the Access keys section and click Create access key.
The Console shows the Access key best practices & alternatives page with a list of use cases.
- Select Command Line Interface (CLI) as the use case.
- Check the confirmation checkbox at the bottom acknowledging the recommendation.
- Click Next.
- Skip the optional description tag and click Create access key.
You now see the Retrieve access keys page with your Access Key ID and Secret Access Key.
- Click Download .csv file and save the file to a secure location on your computer.
- Click Done.
This is your only chance to see the secret key
Once you close this page, you cannot retrieve the Secret Access Key again. If you lose it, you must delete the access key and create a new one. Make sure you have the .csv saved before clicking Done.
Configure a named CLI profile and test access
Named profiles let you switch between different sets of credentials in the CLI without overwriting your default admin configuration.
- Set up a named CLI profile called restricted by running this command:
aws configure --profile restricted
The CLI prompts you for four values. Enter them one at a time:
- For AWS Access Key ID, paste the Access Key ID from your downloaded .csv file.
- For AWS Secret Access Key, paste the Secret Access Key from the .csv file.
- For Default region name, enter us-east-1 (the same region as your bucket).
- For Default output format, enter json.
What is a named profile?
A named profile stores a separate set of credentials in your %USERPROFILE%\.aws\credentials file on Windows. When you add --profile restricted to any CLI command, the CLI authenticates as demo-restricted-user instead of your admin user.
Now for the moment of truth. Before you run the next command, think about what you expect to happen. The restricted user has zero policies attached. What do you think AWS will say when that user tries to list objects in your bucket?
- Test the restricted user's access to your bucket by running:
aws s3 ls s3://iam-demo-[[YOURNAME="your-unique-name"]]-2026 --profile restricted
You see an error containing "Access Denied". This confirms your restricted user has no permissions to access the bucket.
What does this error mean?
IAM evaluated the request against demo-restricted-user's attached policies and found zero statements granting s3:ListBucket permission. With no Allow statement, IAM implicitly denies the request. This is least-privilege in action.
This is exactly the gap you will close in the next step by writing a custom policy.
Not seeing Access Denied?
If the command succeeds instead of failing, you may have accidentally attached a policy to the user. Return to the IAM Console, click on demo-restricted-user, check the Permissions tab, and remove any attached policies.
If you see an "InvalidAccessKeyId" error instead, double-check that you pasted the correct Access Key ID from the .csv file when configuring the profile.
help me troubleshoot why my restricted user command is not returning the expected Access Denied error
That wall of denial is exactly what you wanted. You have proven that a user with no permissions cannot touch your bucket. Next up, you will write a custom JSON policy that grants this user read-only access and attach it via the CLI.
Write and Attach a Read-Only Policy
In the last step, you proved that demo-restricted-user has zero access to your S3 bucket. Every command returned "Access Denied."
Now you will write a custom JSON policy that grants exactly two permissions: listing the bucket contents and downloading objects. Nothing more. This is the least-privilege principle in action.
In this step, get ready to:
- Write a policy JSON file that grants read-only S3 access.
- Create the policy in AWS using the CLI.
- Attach the policy and verify read works but write is denied.
Write the policy JSON file
IAM policies are JSON documents that define what actions are allowed (or denied) on which AWS resources. You will create a policy that allows reading from your bucket but blocks any writes.
- Create a new file called s3-read-policy.json in the same folder where you have been running your AWS CLI commands. On Windows, you can open Notepad from your terminal by running:
notepad s3-read-policy.json
- When Notepad asks if you want to create a new file, click Yes.
- Paste the following JSON into the file:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::[[BUCKET_NAME="iam-demo-yourname-2026"]]"
},
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::[[BUCKET_NAME="iam-demo-yourname-2026"]]/*"
}
]
}
What does this policy do?
- "Version": "2012-10-17" is the policy language version. This is the current and recommended version for all IAM policies.
- The first statement allows s3:ListBucket on the bucket itself. This lets the user see what objects exist inside.
- The second statement allows s3:GetObject on all objects (/*) inside the bucket. This lets the user download files.
- Notice what is NOT here: there is no s3:PutObject, so the user cannot upload anything.
- Save s3-read-policy.json and close Notepad.
- Confirm the file exists by running:
dir s3-read-policy.json
You should see s3-read-policy.json listed with its file size in the output.
File not found?
- Make sure you saved the file in the same directory where your terminal is currently open. Run cd to check your current directory.
- On Windows, Notepad sometimes adds a hidden .txt extension. In the Save dialog, set Save as type to All Files (*.*) before saving.
- help me find my s3-read-policy.json file
Create the policy in AWS via CLI
The JSON file on your computer is just a document. To make AWS enforce it, you need to upload it as a managed policy in IAM.
- Create the policy in your AWS account by running:
aws iam create-policy --policy-name S3ReadOnlyDemo --policy-document file://s3-read-policy.json
You should see a JSON response containing a Policy object. Look for the Arn field. It looks like this:
"Arn": "arn:aws:iam::123456789012:policy/S3ReadOnlyDemo"
- Copy your Arn value and save it somewhere (a sticky note, a text file, or just keep the terminal open). You will need it in the next command.
What is an ARN?
An ARN (Amazon Resource Name) is a unique address for any resource in AWS. Every policy, user, role, bucket, and function has one. You use ARNs to tell AWS exactly which resource a command targets.
Seeing an error about an existing policy?
- If you see an error mentioning the policy already exists, it means a policy named S3ReadOnlyDemo is already in your account. Delete it first with aws iam delete-policy --policy-arn <your-existing-arn> and try again.
- If you see a malformed JSON error, double check your s3-read-policy.json file for missing commas, extra commas, or mismatched braces.
- help me fix my create-policy error
Attach the policy and test access
The policy exists in AWS, but it is not connected to anyone yet. You need to attach it to demo-restricted-user so that user gains the permissions it defines.
- Attach the policy to your restricted user by running:
aws iam attach-user-policy --user-name demo-restricted-user --policy-arn [[POLICY_ARN="arn:aws:iam::123456789012:policy/S3ReadOnlyDemo"]]
What does attach-user-policy do?
This command links the S3ReadOnlyDemo policy to demo-restricted-user. From now on, AWS evaluates this policy every time that user makes an API call. The command produces no output on success.
IAM changes can take a few minutes to propagate across AWS. Wait about one minute before testing.
- Test that the restricted user can now download files by running:
aws s3 cp s3://[[BUCKET_NAME="iam-demo-yourname-2026"]]/testfile.txt ./downloaded.txt --profile restricted
You should see a download progress line followed by a success message. The file downloaded.txt now exists in your current folder.
Now for the real test. Before you run the next command, predict: will the restricted user be able to upload a file to the bucket?
- Try to upload a file as the restricted user by running:
aws s3 cp testfile.txt s3://[[BUCKET_NAME="iam-demo-yourname-2026"]]/blocked.txt --profile restricted
You should see an "Access Denied" error. The upload fails because your policy only grants s3:GetObject and s3:ListBucket. There is no s3:PutObject permission, so writes are blocked.
Why does this matter?
You just proved the least-privilege principle works. The user can read exactly what they need and nothing else. If this user's credentials were leaked, an attacker could only download files. They could not modify, delete, or upload anything.
Still seeing Access Denied on the download?
- IAM changes use eventual consistency. Wait a few more minutes and try the download command again.
- Verify the policy is attached by running aws iam list-attached-user-policies --user-name demo-restricted-user. You should see S3ReadOnlyDemo in the output.
- Check that the bucket name in your s3-read-policy.json matches your actual bucket name exactly. Even one extra character breaks the match.
- help me debug why my restricted user still gets Access Denied
✔️ Awesome, I've got everything!
Great. Double check you saved s3-read-policy.json and that both test results match expectations (download succeeds, upload denied).
ⓧ I'd like to double check the full policy
Open s3-read-policy.json and confirm it matches this exactly (with your own bucket name in place of the placeholder):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::[[BUCKET_NAME="iam-demo-yourname-2026"]]"
},
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::[[BUCKET_NAME="iam-demo-yourname-2026"]]/*"
}
]
}
Your restricted user can now read from the bucket but cannot write to it. In the next step, you will create an IAM role that grants temporary write access through STS. This is how production applications get elevated permissions without permanent credentials.
Create an IAM Role and Assume It for Write Access
Your restricted user can now read from the bucket, but it cannot write. In production, write access is not granted through permanent user policies.
Instead, services and users get write access by assuming an IAM role with temporary credentials. This is the same pattern your backend applications will use when they need to write to S3 or any other AWS resource.
In this step, get ready to:
- Create a trust policy that allows your account to assume a new role.
- Create the IAM role and attach a write policy to it.
- Assume the role from the CLI and use the temporary credentials to upload a file.
Create a trust policy JSON file
Before you can create a role, AWS needs to know who is allowed to assume it. You define this in a trust policy JSON file.
- Find your AWS account ID by running this command:
aws sts get-caller-identity --query Account --output text
You should see a 12-digit number printed in the output. This is your AWS account ID.
- Copy the 12-digit account ID. You will use it in the trust policy file.
- Create a new file called trust-policy.json in the same folder where you have been running commands. Paste the following JSON into it, replacing the account ID placeholder with the number you just copied:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[[ACCOUNT_ID="your 12-digit AWS account ID"]]:root"
},
"Action": "sts:AssumeRole"
}
]
}
What does this trust policy do?
The Principal field specifies WHO can assume the role. Setting it to your account root means any IAM entity in your account (including your admin user and your restricted user) is allowed to request this role's credentials.
The Action field is sts:AssumeRole. This is the specific API call that trades your current identity for the role's temporary credentials.
- Confirm the file exists by running:
dir trust-policy.json
You should see trust-policy.json listed in the output.
Don't see the file?
Make sure you saved the file in the directory your Command Prompt is currently pointing to. Run cd by itself to see your current directory, and confirm that is where you saved trust-policy.json.
help me find where my trust-policy.json file was saved
Create the role and attach a write policy
Now you will create the IAM role itself, then write a separate policy granting s3:PutObject on your bucket and attach it to the role.
- Create the IAM role by running this command:
aws iam create-role --role-name S3WriteDemoRole --assume-role-policy-document file://trust-policy.json
You should see JSON output containing the role's ARN and the trust policy you defined.
What does this command do?
- --role-name S3WriteDemoRole sets the name for your new role.
- --assume-role-policy-document file://trust-policy.json tells AWS to use your trust policy file to define who can assume this role.
The role exists now, but it has no permissions yet. You need to create a write policy and attach it.
- Create a new file called s3-write-policy.json with the following JSON content (use the same bucket name you created earlier):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::[[BUCKET_NAME="iam-demo-YOURNAME-2026"]]/*"
}
]
}
What does this policy allow?
This policy grants only s3:PutObject on objects inside your bucket. Anyone using this role can upload files, but cannot read, list, or delete them. This is the least-privilege principle in action.
- Create the policy in AWS by running this command:
aws iam create-policy --policy-name S3WriteDemo --policy-document file://s3-write-policy.json
You should see JSON output containing an Arn field. It looks like arn:aws:iam::123456789012:policy/S3WriteDemo. Copy this value.
- Attach the write policy to the role by running this command (replace the ARN with the one you just copied):
aws iam attach-role-policy --role-name S3WriteDemoRole --policy-arn [[WRITE_POLICY_ARN="arn:aws:iam::YOUR_ACCOUNT_ID:policy/S3WriteDemo"]]
This command produces no output on success. Your role now has write access to the bucket.
Seeing a NoSuchEntity error?
Double-check that the Policy ARN you pasted matches the one from the create-policy output exactly. A typo or missing character in the ARN causes this error.
help me fix a NoSuchEntity error when attaching a policy to my IAM role
Assume the role and use temporary credentials
With the role ready, you will now use STS (Security Token Service) to assume it. This gives you back a set of temporary credentials that carry the role's write permissions.
- Assume the role by running this command (replace the account ID with your 12-digit number):
aws sts assume-role --role-arn arn:aws:iam::[[ACCOUNT_ID="your 12-digit AWS account ID"]]:role/S3WriteDemoRole --role-session-name demo-session
You should see a JSON response containing a Credentials block with three values: AccessKeyId, SecretAccessKey, and SessionToken.
What is happening here?
AWS STS trades your current identity (admin credentials) for a short-lived set of credentials that belong to the role. These temporary credentials expire automatically after a set duration.
This is far safer than giving a user permanent write access. In production, your backend application would call AssumeRole on startup, use the temporary credentials, and refresh them before they expire.
- Copy the three credential values from the JSON output. Set them as environment variables by running each of these commands (paste your actual values from the output in place of the placeholders):
set AWS_ACCESS_KEY_ID=your-access-key-id-from-output
set AWS_SECRET_ACCESS_KEY=your-secret-access-key-from-output
set AWS_SESSION_TOKEN=your-session-token-from-output
Why environment variables instead of a profile?
Environment variables override CLI profiles. By setting these three variables, every aws command you run in this terminal session authenticates as the assumed role, without needing a --profile flag.
Before you upload, think about this: do you expect the upload to succeed or fail? Your environment variables carry the role's write credentials, and the role has s3:PutObject permission on your bucket.
- Test the temporary write credentials by uploading a file to your bucket:
aws s3 cp testfile.txt s3://[[BUCKET_NAME="iam-demo-YOURNAME-2026"]]/role-upload.txt
You should see a success message confirming the upload completed. The role's s3:PutObject permission allowed the write operation using temporary credentials.
Seeing Access Denied on the upload?
Make sure all three environment variables are set correctly. Run echo %AWS_ACCESS_KEY_ID% to confirm the value is not empty. If it shows the variable name with percent signs literally, the variable was not set.
Also verify the Resource field in s3-write-policy.json matches your actual bucket name exactly. A mismatch between the policy and the bucket path causes Access Denied.
help me debug Access Denied when using assumed role credentials to upload to S3
- Clear the temporary credentials from your terminal so they do not interfere with later commands:
set AWS_ACCESS_KEY_ID=
set AWS_SECRET_ACCESS_KEY=
set AWS_SESSION_TOKEN=
You have just demonstrated the production access pattern: assume a role, use temporary credentials, and let them expire. Next up, you will clean up all the resources you created so nothing lingers in your AWS account.
Secret mission
Test Policies with the IAM Policy Simulator
Want to validate IAM policies before deploying them to production? The IAM Policy Simulator lets you test whether specific actions would be allowed or denied, without making any live API calls. Open the simulator, point it at your restricted user, and discover exactly which S3 actions pass and which are blocked.
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. This project uses IAM (which is free) and S3 (within free tier), so there are no ongoing costs. However, leaving active IAM users with access keys is a security risk.
Resources you used:
- IAM User: demo-restricted-user (with access keys and attached policy).
- IAM Role: S3WriteDemoRole (with attached write policy).
- Customer-managed IAM policies: S3ReadOnlyDemo and the write policy attached to the role.
- S3 Bucket: iam-demo-YOURNAME-2026 (with uploaded objects).
- Local files: s3-read-policy.json, trust-policy.json, testfile.txt, and downloaded.txt.
- AWS CLI named profile: restricted.
- AWS CLI v2 (installed via MSI).
Keep everything running
No action needed. Choose this if you plan to continue experimenting with IAM policies or building more AWS projects that use this bucket.
- Your IAM user and role remain active with their current permissions.
- Your S3 bucket and objects remain in place.
- Your AWS CLI profiles stay configured for future use.
- Note: the demo-restricted-user has active access keys. If you keep this user, monitor it for unexpected usage.
Pause - I'll come back to this later
Deactivate the restricted user's access keys so they cannot be used, but keep everything in place for when you return.
- In the AWS Console, go to IAM > Users > demo-restricted-user > Security credentials.
- Find the access key and click Actions > Deactivate.
- Your IAM user, role, policies, and S3 bucket all remain intact for later use.
- When you come back, reactivate the key from the same page to resume.
Delete - I don't want to use this again
Remove all project resources and start fresh. Run these commands in your Command Prompt using your default (admin) profile.
Detach the read policy from the user and delete the access key:
aws iam detach-user-policy --user-name demo-restricted-user --policy-arn [[READ_POLICY_ARN="arn:aws:iam::123456789012:policy/S3ReadOnlyDemo"]]
aws iam delete-access-key --user-name demo-restricted-user --access-key-id [[ACCESS_KEY_ID="the access key ID from your CSV"]]
Delete the IAM user:
aws iam delete-user --user-name demo-restricted-user
Detach the write policy from the role and delete the role:
aws iam detach-role-policy --role-name S3WriteDemoRole --policy-arn [[WRITE_POLICY_ARN="arn:aws:iam::123456789012:policy/S3WriteDemo"]]
aws iam delete-role --role-name S3WriteDemoRole
Delete both customer-managed policies:
aws iam delete-policy --policy-arn [[READ_POLICY_ARN="arn:aws:iam::123456789012:policy/S3ReadOnlyDemo"]]
aws iam delete-policy --policy-arn [[WRITE_POLICY_ARN="arn:aws:iam::123456789012:policy/S3WriteDemo"]]
Empty and delete the S3 bucket:
aws s3 rm s3://[[BUCKET_NAME="iam-demo-YOURNAME-2026"]] --recursive
aws s3 rb s3://[[BUCKET_NAME="iam-demo-YOURNAME-2026"]]
Delete local project files:
del s3-read-policy.json
del trust-policy.json
del testfile.txt
del downloaded.txt
Remove the restricted CLI profile:
Open the file at %USERPROFILE%\.aws\credentials in a text editor. Find the [restricted] section and delete it along with its aws_access_key_id and aws_secret_access_key lines. Do the same in %USERPROFILE%\.aws\config for the [profile restricted] section.
Uninstall AWS CLI (optional):
- Open Programs and Features (run appwiz.cpl from a Command Prompt).
- Select AWS Command Line Interface and click Uninstall.
- Optionally delete the %USERPROFILE%\.aws folder to remove all saved credentials and config.
That's a Wrap!
That's a Wrap!
Nice work. You just built a working IAM security setup that controls access to an S3 bucket using both user-based policies and role-based temporary credentials.
You've learned how to:
- Write a custom JSON policy enforcing least-privilege access to a specific S3 bucket, proving that a restricted user can read but not write.
- Create an IAM role with a trust policy and assume it via STS to gain temporary write credentials from the CLI.
- Configure multiple named CLI profiles for different IAM identities and experience the full progression from "Access Denied" to precisely scoped access.
- (Secret Mission) Used the IAM Policy Simulator to validate policies against simulated API calls without touching real resources.
Ready to quiz yourself?