Skip to content

Commit 4e2a71f

Browse files
HarshCasperquetzalliwritesjbeemsterviren-nadkarni
authored
DOC-265: Organizations: document SCP syntax validation (#758)
Co-authored-by: Quetzalli <hola@quetzalliwrites.com> Co-authored-by: Josh <joshua.a.beemster@gmail.com> Co-authored-by: Viren Nadkarni <viren.nadkarni@gmail.com>
1 parent e3aee10 commit 4e2a71f

2 files changed

Lines changed: 111 additions & 0 deletions

File tree

src/content/docs/aws/developer-tools/security-testing/iam-coverage.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ It only includes operations performed with a principal, not as root, so test set
123123
| **Permission Boundaries** | |
124124
| | - Roles |
125125
| | - Users |
126+
| **Service Control Policies (SCPs)** | |
127+
| | - Enforced across the organization hierarchy (root, OU, account) |
128+
| | - Enforced for cross-account access |
129+
| | - Evaluated by the IAM Policy Simulator |
126130

127131
## Supported Policy Features
128132

src/content/docs/aws/services/organizations.mdx

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,113 @@ To get started, start your LocalStack instance using your preferred method:
8888
awslocal organizations delete-organization
8989
```
9090

91+
## Service Control Policy enforcement
92+
93+
[Service Control Policies (SCPs)](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html) set the maximum permissions for accounts in your organization.
94+
When IAM enforcement is enabled, LocalStack checks SCPs together with other applicable policies.
95+
A request goes through only if both the principal's policies, resource's policies and the SCPs covering its account allow the action on the resource.
96+
97+
To turn on SCP enforcement, start LocalStack with [`ENFORCE_IAM=1`](/aws/developer-tools/security-testing/iam-policy-enforcement) and enable the `SERVICE_CONTROL_POLICY` policy type on your organization root (see the [getting started](#getting-started) steps above).
98+
99+
LocalStack evaluates SCPs at each level of the organization hierarchy: root, organizational unit, and account.
100+
An action must be allowed by an SCP at every level between the root and the account.
101+
If any level lacks an `Allow`, the result is an implicit deny, and an explicit `Deny` overrides any `Allow`.
102+
103+
:::note
104+
The organization's management (master) account is exempt from SCPs.
105+
Principals in the management account are never restricted by SCPs, even with an explicit `Deny` SCP attached.
106+
:::
107+
108+
### Cross-account access
109+
110+
LocalStack enforces SCPs for [cross-account access](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic-cross-account.html), where a principal in one account uses a resource owned by another account.
111+
112+
For a cross-account request, LocalStack checks the SCPs of the source account (the account making the request).
113+
A deny in those SCPs blocks the request even when the target resource's policy grants access.
114+
115+
Consider a member account that lists the objects of an S3 bucket in another account of the same organization, with a bucket policy that grants the member account access:
116+
117+
```bash
118+
# Run as the member (source) account
119+
awslocal s3api list-objects-v2 --bucket cross-account-bucket
120+
```
121+
122+
The default `FullAWSAccess` SCP lets the request succeed on the bucket policy.
123+
Attach an SCP that denies `s3:ListBucket` to the member account, and the request fails:
124+
125+
```bash title="Output"
126+
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: User: arn:aws:iam::111111111111:user/test is not authorized to perform: s3:ListBucket on resource: "arn:aws:s3:::cross-account-bucket" with an explicit deny in a service control policy
127+
```
128+
129+
An SCP that allows only unrelated actions (for example, an `ec2:*`-only SCP) produces an implicit deny, since no SCP allows `s3:ListBucket`:
130+
131+
```bash title="Output"
132+
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: User: arn:aws:iam::111111111111:user/test is not authorized to perform: s3:ListBucket on resource: "arn:aws:s3:::cross-account-bucket" because no service control policy allows the s3:ListBucket action
133+
```
134+
135+
The management account stays exempt from SCPs for cross-account requests too.
136+
137+
### Testing SCPs with the IAM Policy Simulator
138+
139+
You can use the [IAM Policy Simulator](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_testing-policies.html) to check whether a principal's request would be allowed or denied without running it against your resources.
140+
LocalStack evaluates SCPs during policy simulation, so you can validate SCP behavior with [`SimulatePrincipalPolicy`](https://docs.aws.amazon.com/IAM/latest/APIReference/API_SimulatePrincipalPolicy.html) before making live requests.
141+
142+
```bash
143+
awslocal iam simulate-principal-policy \
144+
--policy-source-arn arn:aws:iam::111111111111:user/test \
145+
--action-names s3:ListBucket \
146+
--resource-arns arn:aws:s3:::cross-account-bucket
147+
```
148+
149+
When SCPs affect the decision, LocalStack populates the `OrganizationsDecisionDetail` field of the [`EvaluationResult`](https://docs.aws.amazon.com/IAM/latest/APIReference/API_EvaluationResult.html), so you can see whether an SCP allowed the action.
150+
151+
:::note
152+
LocalStack's Policy Simulator and its IAM enforcement engine share the same underlying evaluation logic.
153+
As a result, the simulator reflects the real AWS IAM behavior rather than the behavior of the AWS Policy Simulator, which differs in a few ways:
154+
155+
- AWS ignores SCPs that contain conditions during simulation. LocalStack evaluates SCPs with conditions.
156+
- AWS applies SCPs to the organization's management account during simulation. LocalStack does not apply SCPs to the management account, matching the real behavior of AWS Organizations.
157+
- AWS reports an explicit `Deny` from an SCP as an implicit deny. LocalStack reports it as an explicit deny, which is the expected outcome.
158+
:::
159+
160+
## Service Control Policy validation
161+
162+
When you create or update a Service Control Policy (SCP) with `CreatePolicy` or `UpdatePolicy`, LocalStack validates the policy document against the syntax rules that SCPs must follow.
163+
A policy that violates any of these rules is rejected with a `MalformedPolicyDocumentException` or a `ConstraintViolationException`, matching the behavior you would see on AWS.
164+
165+
The following constraints are enforced for SCPs:
166+
167+
- **No `Principal` or `NotPrincipal` elements**: unlike identity-based or resource-based IAM policies, SCPs cannot specify a `Principal` or `NotPrincipal` key inside a `Statement`. A policy that includes either key is rejected with a `MalformedPolicyDocumentException`.
168+
- **Maximum policy size of 10,240 characters**: a policy document larger than 10,240 characters is rejected with a `ConstraintViolationException` (reason `POLICY_CONTENT_LIMIT_EXCEEDED`). This matches the limit enforced by AWS in practice.
169+
- **A single policy object**: the document must be a single JSON object. Passing a JSON array of policy objects is rejected.
170+
- **A single `Statement` key**: the document may contain only one `Statement` key. Duplicate `Statement` keys (or any other duplicate keys) cause the policy to be rejected as malformed.
171+
- **Resources must be present**: each statement must contain a `Resource` element. Specific resource ARNs (not just the `*` wildcard) are accepted.
172+
173+
:::note
174+
The AWS documentation states that the [maximum SCP size is 5,120 characters](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_reference_limits.html) and that SCPs [only support the `*` wildcard for resources](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps_syntax.html#scp-syntax-resource).
175+
LocalStack instead mirrors the behavior observed against real AWS: the enforced size limit is 10,240 characters, and specific resource ARNs are accepted.
176+
:::
177+
178+
For example, the following policy is rejected because it includes a `Principal` element, which is not permitted in an SCP:
179+
180+
```bash
181+
awslocal organizations create-policy \
182+
--name "InvalidSCP" \
183+
--description "SCP with a Principal element" \
184+
--type SERVICE_CONTROL_POLICY \
185+
--content '{
186+
"Version": "2012-10-17",
187+
"Statement": [
188+
{
189+
"Effect": "Deny",
190+
"Principal": "*",
191+
"Action": "s3:*",
192+
"Resource": "*"
193+
}
194+
]
195+
}'
196+
```
197+
91198
## API Coverage
92199

93200
<FeatureCoverage service="organizations" client:load />

0 commit comments

Comments
 (0)