Audit and Harden an AWS IAM Policy
Simulate, harden, and audit an AWS IAM policy with a GitHub security report.
Introduction
30 Second Summary
Permission rules often sit quietly in the background until one gives somebody power they never needed. A single broad rule can turn a read-only task into permission to delete data.
In this project, you will turn a deliberately dangerous policy in AWS Identity and Access Management (IAM) into scoped read-only access. A browser simulation plus a sanitized GitHub report will prove the result.
What You'll Build
Your finished demo pairs an IAM policy simulator result where object reading remains allowed while deletion becomes implicitly denied with a public GitHub report that traces the safer policy to its audit event.
By the end of this project, you'll have:
- Reveal the hidden risk through a safe simulation where both reading and deleting a fictional Amazon S3 object are allowed.
- Prove least privilege by keeping object reads allowed while deletion becomes implicitly denied.
- Present audit evidence by tracing the CreatePolicy event in AWS CloudTrail Event history for a public report containing only sanitized facts.
- Secret Mission: Add a targeted explicit deny that overrides a matching allow in the simulator.
Are there any prerequisites?
You need AWS and GitHub accounts that you can access from a current desktop browser. Your AWS session must use a non-root identity with administrator permissions.
Before We Start
A clear fictional S3 access boundary keeps this lab focused on authorization without involving production data or real identifiers. The workload may read evidence objects but must not delete them.
Set Up a Safe Browser Lab
Security testing needs a safe boundary before any authorization logic enters the picture. A non-root session in the AWS Management Console keeps everyday work away from the account's most powerful credentials.
In this step, you will confirm access to the IAM policy simulator and AWS CloudTrail Event history. You will then create a sanitized public workspace with GitHub and github.dev.
In this step, get ready to:
- Verify your non-root AWS identity.
- Confirm access to the required AWS security pages and policy JSON editor.
- Create a sanitized public GitHub workspace in github.dev.
Verify safe AWS access
AWS recommends avoiding the root user for everyday tasks. This identity check happens before the lab makes any account change.
Signing in can feel sensitive. This check uses your existing account without creating permissions or resources.
- Use your browser to go to the AWS Management Console sign-in page.
- Sign in with your configured non-root administrative identity.
- Open the account menu in the console header.
The account menu identifies your current session. Choose the outcome that matches what you see.
✔️ I do not see Root user
Your account menu does not identify the session as Root user. Your non-root administrative session is ready for the access checks.
ⓧ I see Root user
The root user holds unrestricted account-level access. Stop the lab path until you can use a configured non-root administrative identity.
- Sign out of the root user session.
- Sign in with your configured non-root administrative identity.
- Open the account menu again.
The menu should no longer identify the session as Root user.
ⓧ I cannot use a non-root identity
A non-root administrative identity is required for this lab. Your account administrator controls whether one is available.
- Ask your AWS account administrator for a suitable non-root administrative identity.
- Return to this step after that identity is available.
The required AWS pages prove that your identity can inspect policies and management events. Opening the policy editor also confirms that the lab can validate policy documents later.
- Use the AWS console search to open AWS Identity and Access Management (IAM).
- Choose Policy simulator in the IAM navigation pane.
- Confirm that Custom mode appears without an access error.
- Open Policies in a new browser tab from the IAM navigation pane.
- Duplicate the IAM Policies browser tab.
- Choose Create policy in the duplicated tab.
- Choose JSON in the policy editor.
- Confirm that the policy editor loads without an access error.
Why Check Access First?
These views support the policy tests and audit checks later in the project. Finding a permissions gap now prevents an interrupted security workflow.
You stop at the JSON editor in this step. You do not complete the policy creation flow.
- Use the AWS console search to open AWS CloudTrail in a new browser tab.
- Choose Event history in the CloudTrail navigation pane.
- Confirm that the recent management event list loads without an access error.
Blocked by an AWS Access Error?
- Confirm that you signed in with the intended non-root administrative identity.
- Ask your AWS account administrator for access if an IAM or CloudTrail page remains blocked.
- Help me diagnose the blocked AWS access.
Create the sanitized repository
A public repository is visible to anyone. Fictional identifiers keep the lab useful without exposing production data or account details.
Publishing a repository can feel exposed. This workspace starts with one generated README and contains no credentials or real resource identifiers.
- Return to GitHub in your browser.
- Sign in to your GitHub account if prompted.
- Choose New repository.
- Enter aws-iam-least-privilege-lab as the repository name.
- Select Public for the repository visibility.
- Turn Add README on.
- Choose Create repository.
Good. The repository front page now shows the committed README.md file with the heading aws-iam-least-privilege-lab.
Repository Not Created?
- Confirm that GitHub shows you as signed in.
- Check that the repository name matches aws-iam-least-privilege-lab exactly.
- Make sure Add README was turned on before repository creation.
- Help me create the public lab repository safely.
Open the repository in github.dev
The browser editor lets you manage this repository without installing desktop software. Its keyboard shortcut replaces the repository view in the same browser tab.
- Press the . key while viewing the repository front page.
- Wait for the github.dev browser editor to load.
You should see the repository files in the sidebar. The committed README.md file remains unchanged.
- Select README.md in the file sidebar.
The editor should show the heading # aws-iam-least-privilege-lab.
github.dev Not Opening?
- Confirm that you pressed the period key from the repository front page.
- Reload the repository in a current desktop browser.
- Press the period key again after the repository finishes loading.
- Help me open this repository in github.dev.
Before the final check, do you expect every browser view to load without exposing or creating a real resource?
- Switch to the IAM Policy simulator browser tab.
- Switch to the IAM Policies browser tab.
- Switch to the policy JSON editor browser tab.
- Switch to the CloudTrail Event history browser tab.
- Return to the github.dev browser tab.
All four AWS views should load without access errors. github.dev should show README.md with the committed lab heading.
✔️ Awesome, I've got everything!
Your non-root AWS access and sanitized browser workspace are ready. Keep README.md unchanged for the next step.
ⓧ I'd like to double check the full code
The committed README.md file contains one heading and a blank line.
# aws-iam-least-privilege-lab
Your non-root AWS access is confirmed. Next up, you will expose the deletion risk hidden inside a wildcard S3 policy.
Expose the Over-Permission
Your sanitized repository is ready. Your non-root AWS session can reach the lab pages.
Now you'll use the IAM policy simulator to expose the permission hidden inside a compact wildcard identity-based policy. The test makes a destructive action visible without touching a real resource.
In this step, get ready to:
- Create the over-permissive policy in your GitHub repository.
- Simulate two S3 actions against a fictional object.
- Record the authorization decision for each action.
Create the unsafe policy baseline
An identity-based policy defines which requests match an authorization statement. This baseline combines an allowing effect with wildcard action and resource scopes.
- In github.dev from earlier, use the new-file control above the repository file tree to start a file.
- Enter policies/over-permissive.json as the file path.
You should see a new policies folder in the file tree. The empty over-permissive.json file opens in the editor.
- Paste the unsafe baseline into policies/over-permissive.json using this policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAllS3Actions",
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
]
}
What Does This Policy Allow?
- The Version value 2012-10-17 selects the current IAM policy language.
- The Effect value Allow grants requests that match this statement.
- The s3:* wildcard matches every Amazon S3 action.
- The Resource value * applies those actions to every resource.
- Save policies/over-permissive.json by pressing Cmd+S (macOS) or Ctrl+S (Windows).
You should see policies/over-permissive.json listed beneath the new policies folder.
- Select Source Control in the github.dev sidebar.
- Stage policies/over-permissive.json from the Source Control view.
The file moves into the staged changes area. This marks it for the next commit.
- Enter Add over-permissive baseline as the commit message.
- Choose Commit & Push.
The Source Control view should stop listing the file as an uncommitted change. Your unsafe baseline is now preserved for the audit comparison.
File Path or Commit Missing?
- Check that the file appears inside policies with the name over-permissive.json.
- Stage the file if Commit & Push remains unavailable.
- Add the commit message if the staged file is visible but the commit cannot start.
- Help me fix my github.dev file path or commit.
✔️ Awesome, I've got everything!
Your committed baseline now captures the deliberately unsafe policy. The existing README.md remains unchanged.
ⓧ I'd like to double check the full code
The full committed policies/over-permissive.json file should match this reference.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAllS3Actions",
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
]
}
What Should Match?
The statement uses AllowAllS3Actions as its identifier. Its allowing effect is paired with wildcard action and resource values.
Your README.md should still contain only the original repository heading.
Configure the Custom mode simulation
The IAM policy simulator evaluates authorization without sending a request to an AWS service. Custom mode lets you test the policy you just committed without saving it to your AWS account.
Why Simulate Instead of Use a Real Bucket?
The simulator evaluates the policy against a fictional object ARN. The bucket does not need to exist.
This keeps the lab focused on authorization logic. It keeps live data and credentials out of the test.
- In github.dev, copy the complete contents of policies/over-permissive.json.
- Switch back to the IAM Policy Simulator page from earlier.
The simulator page should load without an access error under your existing non-root AWS session.
- Switch the simulator to Custom mode.
- Paste the copied policy into the custom policy editor.
The custom policy editor should now show the AllowAllS3Actions statement from your repository.
- Choose Amazon S3 in the service selector.
- Add s3:GetObject to the simulation.
The selected-action list should now contain the object-reading action.
- Add s3:DeleteObject to the simulation.
- Enter arn:aws:s3:::amzn-s3-demo-bucket/report.txt in the resource field.
The simulation is now configured with two actions against one fictional object ARN.
Actions or Resource Missing?
- Confirm that Custom mode is selected. Principal mode tests policies for an existing identity.
- Choose Amazon S3 before searching for the two S3 actions.
- Check that the object ARN ends with /report.txt.
- Help me configure the IAM Policy Simulator test.
Run the simulation
The policy and test inputs are ready. The next check reveals how far the two wildcards reach.
Before you run the simulation, do you expect the read action and delete action to receive the same decision?
- Start the test with the simulator's run control beneath the selected actions.
You'll see s3:GetObject marked allowed. You'll also see s3:DeleteObject marked allowed.
Why Are Both Actions Allowed?
- The s3:* wildcard matches both simulated actions.
- The Resource wildcard matches the fictional object ARN.
- The Allow effect grants each matching request.
- The simulator sends no request to Amazon S3. No real bucket exists for this test.
Not Seeing Both Decisions as Allowed?
- Compare the custom policy with policies/over-permissive.json to confirm that both wildcard values remain present.
- Confirm that both s3:GetObject and s3:DeleteObject are included in the same simulation.
- Check that both actions use arn:aws:s3:::amzn-s3-demo-bucket/report.txt as the simulated resource.
- Help me diagnose my unexpected IAM Policy Simulator decisions.
- Capture only the simulation result table in your screenshot so that AWS account details stay outside the image.
Strong work: you reproduced a destructive permission without touching a real S3 bucket.
Next, you'll validate a narrower policy. You'll keep only the read permission the fictional workload needs.
Validate and Harden the Policy
Your IAM policy simulator test made the risk visible. The wildcard identity-based policy allowed both an S3 object read and deletion.
Now you will apply least privilege by narrowing the allowed action plus the resource scope. IAM Access Analyzer policy validation checks the replacement before it can become an account policy.
In this step, get ready to:
- Review the broad policy with IAM Access Analyzer policy validation.
- Commit a hardened policy to the public lab repository.
- Confirm the hardened policy has zero validation errors.
Inspect the broad policy safely
Policy validation checks JSON grammar plus AWS security best practices. The simulator result remains your proof that the broad policy grants the destructive action.
- Return to the IAM Policies page from earlier.
- Select Create policy.
- Select JSON in the policy editor.
- Switch back to github.dev from earlier.
- Select policies/over-permissive.json in the file sidebar.
- Copy the full policy document.
- Return to the IAM JSON editor.
- Replace the editor contents with the copied policy.
The policy validation pane now groups its review under Security, Errors, Warnings, plus Suggestions.
Why validate without creating?
Policy validation reviews grammar plus AWS best practices at no additional charge. It does not grant the permissions described by the policy.
The paid Check for new access check is outside this lab. Your earlier simulation already demonstrated the broad policy's risk.
- Review the findings shown across the four validation tabs.
- Leave Check for new access unused.
- Keep the broad policy uncreated.
The broad document has now been inspected without becoming a customer managed policy. Your AWS account has not gained any permission from it.
Create the least-privilege policy file
The hardened policy replaces both wildcards with the exact access the fictional workload needs. One action permits object reads within one fictional bucket.
- Switch back to github.dev.
- Expand the policies folder in the file sidebar.
- Use the new-file control to create policies/least-privilege.json.
- Add the hardened policy by pasting this code:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowReadOnlyEvidenceObjects",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*"
}
]
}
What Does This Policy Do?
- The Version value uses the current IAM policy language version of 2012-10-17.
- The AllowReadOnlyEvidenceObjects statement gives the permission a descriptive identifier.
- The s3:GetObject action permits object retrieval.
- The arn:aws:s3:::amzn-s3-demo-bucket/* resource limits that permission to objects under the fictional bucket.
- Save policies/least-privilege.json.
- Confirm the file sidebar lists least-privilege.json beside over-permissive.json.
- Select the Source Control view.
- Stage policies/least-privilege.json.
- Enter Add least privilege S3 policy as the commit message.
- Choose Commit & Push.
The Source Control view stops listing the policy as a pending change. The hardened file is now committed to your repository.
Policy File Still Pending?
- Check that least-privilege.json sits inside the existing policies folder.
- Confirm the file contains every opening plus closing brace from the code block.
- Stage the file again before choosing Commit & Push.
- help me fix my least-privilege policy file or github.dev commit.
✔️ Awesome, I've got everything!
Good progress. Your repository now has the committed hardened policy beside the unchanged baseline.
ⓧ I'd like to double check the full code
Compare your committed policies/least-privilege.json file with this complete version.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowReadOnlyEvidenceObjects",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*"
}
]
}
Validate the hardened policy
A narrower permission still needs valid policy syntax. This final check confirms the replacement can move forward without creating the broad policy.
Before you paste the replacement, do you expect its narrower scope to pass the syntax check?
- Switch back to policies/least-privilege.json in github.dev.
- Copy the full hardened policy.
- Return to the IAM JSON editor from earlier.
- Replace the broad policy with the hardened policy.
- Select the Errors tab in the validation pane.
- Confirm the error count is zero.
- Confirm the policy contains only s3:GetObject as its action.
- Confirm the resource is arn:aws:s3:::amzn-s3-demo-bucket/*.
You will see zero errors. The policy now permits object reads only within the fictional bucket's object path.
Seeing a Validation Error?
- Compare every quotation mark plus comma with the full-file reference above.
- Check that the resource ARN ends with /*.
- Confirm the action is spelled exactly as s3:GetObject.
- help me fix my IAM policy validation error.
- Choose Next after confirming the error count is zero.
- Leave the policy uncreated on the Review and create page.
You have closed the wildcard gap in the policy document. Next, you will prove the authorization change in the simulator before creating one safe audit event.
Prove the Fix and Create an Audit Event
Your least-privilege policy has passed IAM Access Analyzer policy validation. That confirms the document has zero syntax errors.
A valid document can still authorize the wrong behavior. The IAM policy simulator lets you test the decision without sending a request to Amazon S3.
You will prove that reading still works while deletion is denied. You will then create an unattached customer managed policy that produces a traceable CreatePolicy management event.
In this step, get ready to:
- Simulate the hardened policy against the fictional object.
- Explain why object deletion no longer has permission.
- Create the hardened policy without attaching it to an identity.
Test the hardened policy
The simulator previously showed that the wildcard policy allowed both tested actions. Replacing that document with the hardened policy reveals whether your remediation changes the authorization result.
- Return to the IAM Policy simulator page from earlier.
- Choose Custom mode.
- Replace the broad policy text in the Custom mode editor with this hardened policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowReadOnlyEvidenceObjects",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*"
}
]
}
What does this policy allow?
- The Version value 2012-10-17 identifies the current IAM policy language.
- The Action value permits only s3:GetObject.
- The Resource value limits that permission to objects under arn:aws:s3:::amzn-s3-demo-bucket/*.
- No statement allows s3:DeleteObject.
- Confirm s3:GetObject remains in the simulated action list.
- Confirm s3:DeleteObject remains in the simulated action list.
- Confirm the simulated resource remains arn:aws:s3:::amzn-s3-demo-bucket/report.txt.
Before you run the simulation, pause to predict the decision for each action.
- Run the simulation against the fictional object.
You will see s3:GetObject as allowed. You will see s3:DeleteObject as implicitly denied.
That is the key security win. The required read survives while deletion loses its permission.
The simulator reaches these decisions without sending an S3 request. The fictional bucket does not need to exist.
Still Seeing Deletion Allowed?
- Check whether the Custom mode editor still contains s3:*. Replace the broad policy with the hardened document above.
- Check that you reran the simulation after replacing the policy text.
- Ask for help with the simulator results.
✔️ Awesome, I've got everything!
The hardened simulation now preserves object reads while leaving object deletion implicitly denied.
ⓧ I'd like to double check the full code
Here is the complete policies/least-privilege.json file used in the simulation.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowReadOnlyEvidenceObjects",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*"
}
]
}
Explain the changed decision
The original policy used s3:*. That wildcard matched both tested actions.
The hardened policy contains a matching Allow for s3:GetObject only. Deletion therefore has no permission that can authorize it.
- Switch back to github.dev from earlier.
- Find the AllowReadOnlyEvidenceObjects statement in policies/least-privilege.json.
- Compare its single action with the two actions tested in the simulator.
Why Is This an Implicit Deny?
An implicit deny applies when no matching Allow authorizes a request. The hardened policy never grants s3:DeleteObject.
This default protects actions that the policy leaves out. The result proves the wildcard removal changed real authorization logic.
Create the audit event safely
The validated policy currently exists as a file and as temporary simulator input. Creating it in IAM produces the account change that can be traced in the next step.
- Switch back to the IAM policy JSON editor from earlier.
- Confirm the Errors tab still shows 0.
- Choose Next.
- Enter CloudSecurityEvidenceReadOnly in the Policy Name field on Review and create.
- Review the permission summary for s3:GetObject on arn:aws:s3:::amzn-s3-demo-bucket/*.
Keep the Policy Unattached
Creating a customer managed policy changes your AWS account. This is the careful part.
The policy stays unattached to every user, group, and role. It creates audit evidence without granting permissions.
- Choose Create policy.
You will see CloudSecurityEvidenceReadOnly among the customer managed policies. The creation also produces a CreatePolicy management event.
You handled the account change cleanly. The policy exists without granting its permissions to an identity.
Before you inspect the attachment state, pause to predict whether any identity gained access.
- Locate CloudSecurityEvidenceReadOnly in Policies.
- Select CloudSecurityEvidenceReadOnly.
- Confirm that no user, group, or role is attached.
You will see that the policy is unattached. No identity receives its s3:GetObject permission.
The hardened authorization result is proven. Next, you will trace the CreatePolicy event in AWS CloudTrail Event history.
Trace and Publish the Evidence
Your hardened IAM policy now produces the right authorization decisions. Its creation also left a management event that connects the account change to a point in time.
In this step, you will trace that event in CloudTrail. You will publish the safe facts as a GitHub security report without exposing account details.
In this step, get ready to:
- Find the customer managed policy creation event in CloudTrail Event history.
- Separate safe audit facts from sensitive account details.
- Publish the sanitized security report from github.dev.
Find the CreatePolicy event
IAM is a global service. Its events are recorded in us-east-1. CloudTrail Event history remains Region-scoped.
- Use the Region selector in the AWS console from earlier to switch to US East (N. Virginia).
- Return to the Event history page in CloudTrail from earlier.
- Set the time range to include the policy creation from Step 4.
Before you filter the history, do you think the policy creation event has arrived yet?
- Choose Event name as the filter.
- Enter CreatePolicy as the filter value.
You should see a CreatePolicy event for the customer managed policy you created.
A short wait here is normal because CloudTrail delivery averages about five minutes with no guaranteed timing.
- Wait several minutes if the event is not visible.
- Refresh Event history after the wait.
The filtered history should now include the policy creation event.
Still Missing the Event?
- Confirm that the AWS Region is us-east-1.
- Check that the Event name filter contains CreatePolicy.
- Expand the time range to include the moment when you created the policy.
- Ask your account administrator to confirm that your identity can view CloudTrail Event history.
- help me find a missing CreatePolicy event in CloudTrail Event history
Inspect and sanitize the event record
A CloudTrail event record can contain useful proof alongside account-specific identity data. Evidence sanitization preserves the audit result while keeping those identity details out of a public repository.
- Select the CreatePolicy event for CloudSecurityEvidenceReadOnly.
- Open its Event record.
- Confirm that eventName is CreatePolicy.
- Confirm that eventSource is iam.amazonaws.com.
- Confirm that the Region is us-east-1.
- Confirm that the policy name is CloudSecurityEvidenceReadOnly.
What Belongs in Public Evidence?
The event name proves which management action occurred. The event source identifies the AWS service that handled it.
The Region makes the lookup reproducible. The test policy name connects the event to this lab.
- Keep the account ID out of the public report.
- Keep the principal ARN out of the public report.
- Keep the access key ID out of the public report.
- Keep the source IP address out of the public report.
- Keep all other identity details out of the public report.
Build and publish the security report
A Markdown report turns your policy tests into evidence that another person can review. The first half documents the fictional scope through the least-privilege remediation.
- Switch back to github.dev from earlier.
- Select README.md in the Explorer sidebar.
- Replace the current heading with the first part of the security report below:
# AWS IAM Least-Privilege Policy Audit
## Scenario
A draft identity policy granted every Amazon S3 action on every resource. The fictional workload only needed to read evidence objects, so deletion and access to unrelated resources were unnecessary.
## Safe test scope
- Simulated resource: `arn:aws:s3:::amzn-s3-demo-bucket/report.txt`
- No Amazon S3 bucket or object was created.
- Neither policy was attached to a user, group, or role during testing.
## Finding
The original policy used `s3:*` with `Resource` set to `*`. The IAM policy simulator showed that both `s3:GetObject` and `s3:DeleteObject` were allowed against the test object.
Risk: A principal with this policy could read, modify, or delete S3 data far beyond the stated read-only requirement.
## Remediation
The replacement policy allows only `s3:GetObject` and scopes access to objects under `arn:aws:s3:::amzn-s3-demo-bucket/*`.
| Policy | `s3:GetObject` | `s3:DeleteObject` |
| --- | --- | --- |
| Over-permissive baseline | allowed | allowed |
| Least-privilege replacement | allowed | implicitly denied |
IAM Access Analyzer policy validation completed with no remaining syntax errors in the replacement policy.
What Does This Report Show?
- The scenario states the fictional workload requirement.
- The safe test scope confirms that no Amazon S3 resource was created.
- The finding records both actions as allowed under the wildcard policy.
- The remediation table shows deletion changing to implicitly denied.
- The validation statement records the zero-error policy check.
- Save README.md by pressing Cmd+S on macOS or Ctrl+S on Windows.
- Open the Markdown preview from the github.dev editor toolbar.
You should see the Scenario section rendered beneath the report title. You should also see the Safe test scope through Remediation sections.
Preview Not Rendering Correctly?
- Check that each heading begins with the same number of hash characters shown in the code.
- Confirm that every table row starts and ends with a vertical bar.
- Check that you replaced the original heading instead of pasting the report beneath it.
- help me fix the Markdown preview for my IAM audit report
The final sections connect the report to its CloudTrail evidence. They also explain the lab's cost boundaries.
- Return to README.md in the editor.
- Place the cursor on the empty line at the bottom of the file.
- Append the audit evidence through cleanup sections by copying the code below:
## Audit evidence
- Event name: `CreatePolicy`
- Event source: `iam.amazonaws.com`
- Region inspected: `us-east-1`
- Customer managed policy: `CloudSecurityEvidenceReadOnly`
The account ID, principal ARN, access key ID, source IP address, and other identity details were intentionally excluded from this public report.
## Cost and cleanup
This lab used no real S3 resources, no CloudTrail trail, no CloudTrail Lake event data store, and no paid IAM Access Analyzer custom policy checks. The customer managed policy was created unattached and can be deleted after the audit evidence is reviewed.
Why Include These Sections?
- The audit evidence section records the four safe CloudTrail facts.
- The exclusion statement documents how identity details were protected.
- The cleanup section confirms that the lab avoided real S3 resources.
- The cleanup section also records that the customer managed policy remains unattached.
- Save README.md by pressing Cmd+S on macOS or Ctrl+S on Windows.
Before you preview the finished report, do you expect any account-specific identity values to appear?
- Return to the Markdown preview.
You should see the Audit evidence section with four safe facts. The paragraph below it should explicitly confirm that sensitive identity details were excluded.
Seeing Sensitive Values in the Report?
- Remove any account ID copied from the CloudTrail event.
- Remove any principal ARN copied from the CloudTrail event.
- Remove any access key ID copied from the CloudTrail event.
- Remove any source IP address copied from the CloudTrail event.
- help me sanitize my public CloudTrail evidence
✔️ Awesome, I've got everything!
Your README.md now contains the complete sanitized policy audit report.
ⓧ I'd like to double check the full code
Compare your README.md with this complete report before publishing it.
# AWS IAM Least-Privilege Policy Audit
## Scenario
A draft identity policy granted every Amazon S3 action on every resource. The fictional workload only needed to read evidence objects, so deletion and access to unrelated resources were unnecessary.
## Safe test scope
- Simulated resource: `arn:aws:s3:::amzn-s3-demo-bucket/report.txt`
- No Amazon S3 bucket or object was created.
- Neither policy was attached to a user, group, or role during testing.
## Finding
The original policy used `s3:*` with `Resource` set to `*`. The IAM policy simulator showed that both `s3:GetObject` and `s3:DeleteObject` were allowed against the test object.
Risk: A principal with this policy could read, modify, or delete S3 data far beyond the stated read-only requirement.
## Remediation
The replacement policy allows only `s3:GetObject` and scopes access to objects under `arn:aws:s3:::amzn-s3-demo-bucket/*`.
| Policy | `s3:GetObject` | `s3:DeleteObject` |
| --- | --- | --- |
| Over-permissive baseline | allowed | allowed |
| Least-privilege replacement | allowed | implicitly denied |
IAM Access Analyzer policy validation completed with no remaining syntax errors in the replacement policy.
## Audit evidence
- Event name: `CreatePolicy`
- Event source: `iam.amazonaws.com`
- Region inspected: `us-east-1`
- Customer managed policy: `CloudSecurityEvidenceReadOnly`
The account ID, principal ARN, access key ID, source IP address, and other identity details were intentionally excluded from this public report.
## Cost and cleanup
This lab used no real S3 resources, no CloudTrail trail, no CloudTrail Lake event data store, and no paid IAM Access Analyzer custom policy checks. The customer managed policy was created unattached and can be deleted after the audit evidence is reviewed.
- Select the Source Control view in github.dev.
- Stage the change to README.md.
- Enter a commit message that describes the sanitized audit report.
- Choose Commit & Push.
Before you check the repository front page, do you expect any account-specific identity values to appear in the published report?
- Return to the public repository front page after the push finishes.
You should see the rendered before-and-after result table. You should also see the sanitized CreatePolicy audit evidence with an explicit exclusion statement.
Report Missing From the Front Page?
- Return to github.dev to confirm that README.md has no unsaved changes.
- Check the Source Control view for an uncommitted README.md change.
- Choose Commit & Push if the commit exists only in the browser editor.
- help me publish my README.md changes from github.dev
That is a strong finish. Your repository now tells the complete security story without exposing the identity behind the audit.
Secret mission
Add an Explicit Deny Guardrail
A least-privilege policy leaves unwanted actions implicitly denied. Add a targeted explicit Deny to prove that it overrides a matching Allow without attaching the teaching policy to an identity.
Clean Up Your Resources
Clean Up Your Resources
Your lab has no running compute or paid IAM Access Analyzer checks. Leaving the public GitHub repository and unattached AWS Identity and Access Management (IAM) policy in place costs $0.
Decide whether to keep your resources available, pause your browser work, or delete the resources entirely.
Resources you used:
- The public GitHub repository aws-iam-least-privilege-lab.
- The unattached customer managed IAM policy CloudSecurityEvidenceReadOnly.
The CreatePolicy record remains in AWS CloudTrail Event history until it leaves the regional 90-day history automatically. Event history records cannot be manually deleted.
Keep everything running
No action is needed. Choose this if you want to keep your sanitized security report available for demonstrations.
- Keep the public aws-iam-least-privilege-lab repository available as your security report.
- Leave CloudSecurityEvidenceReadOnly unattached for later least-privilege demonstrations.
Pause - I'll come back to this later
Pausing closes your browser work while leaving the two persistent resources intact. There is no running compute to stop.
- Sign out of the AWS Management Console.
- Close the github.dev browser tab.
Delete - I don't want to use this again
This permanently removes the public repository and customer managed policy. Your regional CloudTrail Event history record remains until it expires automatically.
Delete the IAM Policy
- Return to the IAM console from earlier.
- Choose Policies in the navigation pane.
- Select CloudSecurityEvidenceReadOnly.
- Choose Actions.
- Choose Delete.
- Follow the confirmation instructions shown for the policy.
- Choose Delete to permanently remove the policy.
Delete the GitHub Repository
- Return to the aws-iam-least-privilege-lab repository on GitHub.
- Choose Settings.
- Scroll to Danger Zone.
- Choose Delete this repository.
- Choose I want to delete this repository.
- Choose I have read and understand these effects.
- Type aws-iam-least-privilege-lab when GitHub asks for the repository name.
- Choose Delete this repository to complete the deletion.
Nice Work!
Nice Work!
Outstanding work! You completed a least-privilege audit from risky policy discovery through safe evidence publishing.
You've learned how to:
- Audited a deliberately over-permissive policy for Amazon S3. Applied least privilege by limiting access to s3:GetObject on arn:aws:s3:::amzn-s3-demo-bucket/*.
- Validated the hardened document with IAM Access Analyzer policy validation. Used the IAM policy simulator to compare the same two object actions. Confirmed s3:GetObject remained allowed. Observed an implicit deny for s3:DeleteObject after its matching Allow was removed.
- Created an unattached customer managed policy named CloudSecurityEvidenceReadOnly. Traced its CreatePolicy management event in AWS CloudTrail Event history. Published a sanitized security report through GitHub without account or identity details.
- Secret Mission: Added a targeted explicit deny for s3:DeleteObject. Proved that a matching Deny overrides a matching Allow. The teaching policy stayed unattached.
Ready to quiz yourself?