Skip to content
Get Started for Free

IAM Policy Enforcement

IAM Policy Enforcement feature can be used to test your security policies and create a more realistic environment that more closely resembles real AWS. The environment configuration ENFORCE_IAM=1 is required while starting LocalStack to enable this feature. Per default, IAM enforcement is disabled, and all APIs can be accessed without authentication.

When enabled, LocalStack evaluates identity-based policies, resource-based policies, permissions boundaries, and service control policies together to decide whether a request is allowed. When a request is denied, LocalStack returns a descriptive error that identifies the denied action and the policy responsible for the denial. See Explainable IAM for a detailed look at these messages.

This guide is designed for users new to IAM Policy Enforcement and assumes basic knowledge of the AWS CLI and our awslocal wrapper script.

Start your LocalStack container with the DEBUG=1 and ENFORCE_IAM=1 environment variables set:

Terminal window
DEBUG=1 ENFORCE_IAM=1 localstack start

We will demonstrate IAM Policy Enforcement, by creating a user and obtaining the access/secret keys. We will make an attempt to create a bucket using the user’s credentials, which inevitably fails due to insufficient permissions.

Lastly, a policy is attached to the user, granting the necessary s3:CreateBucket permission, thereby enabling the successful creation of the bucket.

To follow this guide, open two separate terminal sessions: Terminal 1 for the administrative IAM commands, which will utilize the default root IAM user, and Terminal 2 for executing the commands under the test IAM user you are about to create. This way, we can demonstrate the differentiation in access permissions between the administrative and test users in real-time.

In Terminal 1, execute the following commands to create a test user and obtain the access/secret keys:

Terminal window
awslocal iam create-user --user-name test
Terminal window
{
"User": {
"Path": "/",
"UserName": "test",
"UserId": "d7ryukg7bls4rq1ihq1d",
"Arn": "arn:aws:iam::000000000000:user/test",
"CreateDate": "2023-11-03T12:20:12.332000Z"
}
}
Terminal window
awslocal iam create-access-key --user-name test
Terminal window
{
"AccessKey": {
"UserName": "test",
"AccessKeyId": "LKIAQAAAAAAAHFR7QTN3",
"Status": "Active",
"SecretAccessKey": "EYUHpIol7bRJpKd/28c/LI2C4bbEnp82LJCRwXRV",
"CreateDate": "2023-11-03T12:20:27Z"
}
}

Navigate to Terminal 2, where we will configure the access keys for the user test in the environment. Once the access keys are set, you will attempt to create an S3 bucket using these credentials.

Terminal window
export AWS_ACCESS_KEY_ID=LKIAQAAAAAAAHFR7QTN3 AWS_SECRET_ACCESS_KEY=EYUHpIol7bRJpKd/28c/LI2C4bbEnp82LJCRwXRV
awslocal s3 mb s3://mybucket
Terminal window
make_bucket failed: s3://mybucket An error occurred (AccessDeniedException) when calling the CreateBucket operation: User: arn:aws:iam::000000000000:user/test is not authorized to perform: s3:CreateBucket on resource: arn:aws:s3:::mybucket because no identity-based policy allows the s3:CreateBucket action

As anticipated, the attempt to create the bucket fails with an AccessDeniedException error, confirming that user test lacks the necessary permissions for this action. The error message names the denied action (s3:CreateBucket) and explains that no identity-based policy allows it. You can view the LocalStack logs to validate the policy enforcement:

Terminal window
2023-11-03T12:21:10.971 INFO --- [ asgi_gw_1] localstack.pro.core.services.iam.policy_engine.handler : User: arn:aws:iam::000000000000:user/test is not authorized to perform: s3:CreateBucket on resource: arn:aws:s3:::mybucket because no identity-based policy allows the s3:CreateBucket action
2023-11-03T12:21:10.972 INFO --- [ asgi_gw_1] localstack.request.aws : AWS s3.CreateBucket => 403 (AccessDenied)

Let’s now return to Terminal 1 and execute the following commands to attach a policy to the user test:

Terminal window
awslocal iam create-policy --policy-name p1 --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:CreateBucket","Resource":"*"}]}'
awslocal iam attach-user-policy --user-name test --policy-arn arn:aws:iam::000000000000:policy/p1

Now, let’s switch back to Terminal 2 and observe how the bucket creation succeeds with the test IAM user:

Terminal window
awslocal s3 mb s3://mybucket
Terminal window
make_bucket: mybucket

The bucket creation succeeds, confirming that the user test now has the necessary permissions to perform this action. You can view the LocalStack logs to validate the policy enforcement:

Terminal window
2023-11-03T12:23:11.469 INFO --- [ asgi_gw_1] localstack.request.aws : AWS iam.CreatePolicy => 200
2023-11-03T12:23:15.753 INFO --- [ asgi_gw_1] localstack.request.aws : AWS iam.AttachUserPolicy => 200
2023-11-03T12:23:22.795 INFO --- [ asgi_gw_2] localstack.request.aws : AWS s3.CreateBucket => 200

You can further use the IAM Policy Enforcement feature to test your Infrastructure as Code (IaC) deployments and ensure that your policies are correctly enforced. If the IAM policies are not correctly enforced, you will get an unsuccessful response from the API call, and the LocalStack logs will provide you with the necessary information to debug the issue.

Service Control Policies (SCPs) are a policy type managed by AWS Organizations. Unlike identity-based and resource-based policies, SCPs do not grant permissions on their own — they act as guardrails that define the maximum permissions available to the accounts they apply to. If an SCP does not allow an action (or explicitly denies it), no Allow in an identity-based policy can override that result.

With ENFORCE_IAM=1, LocalStack evaluates SCPs alongside identity-based policies, resource-based policies, and permissions boundaries. This applies both to single-account access and to cross-account access, where a principal in one account acts on a resource owned by another.

The steps below extend the walkthrough above: user test already has an identity-based policy that allows s3:CreateBucket. We will add an SCP that denies the action and confirm that the request is blocked despite the identity-based Allow.

In Terminal 1, create an organization and a service control policy that denies s3:CreateBucket:

Terminal window
awslocal organizations create-organization --feature-set ALL
Terminal window
awslocal organizations create-policy \
--name deny-create-bucket \
--type SERVICE_CONTROL_POLICY \
--description "Deny S3 bucket creation" \
--content '{"Version":"2012-10-17","Statement":[{"Effect":"Deny","Action":"s3:CreateBucket","Resource":"*"}]}'

Attach the SCP to the target account:

Terminal window
awslocal organizations attach-policy \
--policy-id <POLICY_ID> \
--target-id <TARGET_ID>

Back in Terminal 2, attempt to create the bucket again as user test:

Terminal window
awslocal s3 mb s3://mybucket

Even though the user’s identity-based policy allows s3:CreateBucket, the SCP guardrail blocks the request, and the denial message names the responsible SCP:

Terminal window
make_bucket failed: s3://mybucket An error occurred (AccessDeniedException) when calling the CreateBucket operation: User: arn:aws:iam::000000000000:user/test is not authorized to perform: s3:CreateBucket on resource: arn:aws:s3:::mybucket with an explicit deny in a service control policy: arn:aws:organizations::000000000000:policy/<POLICY_ID>

The LocalStack logs record the same information:

Terminal window
2023-11-03T12:30:44.512 INFO --- [ asgi_gw_1] localstack.pro.core.services.iam.policy_engine.handler : User: arn:aws:iam::000000000000:user/test is not authorized to perform: s3:CreateBucket on resource: arn:aws:s3:::mybucket with an explicit deny in a service control policy: arn:aws:organizations::000000000000:policy/<POLICY_ID>
2023-11-03T12:30:44.513 INFO --- [ asgi_gw_1] localstack.request.aws : AWS s3.CreateBucket => 403 (AccessDenied)

This confirms that the SCP overrides the identity-based Allow, matching the AWS evaluation order in which an SCP guardrail takes precedence.

The feature coverage is documented in the IAM coverage documentation.

Was this page helpful?