Skip to content

Commit 9177186

Browse files
feat: Added OrgId and SourceAccounts to SNS topic policy
Added OrgId and SourceAccounts to SNS topic policy
1 parent 246ebde commit 9177186

File tree

8 files changed

+608
-0
lines changed

8 files changed

+608
-0
lines changed

DEVELOPER.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,8 @@ make test-integration-metricstream
278278
make test-integration-simple
279279
make test-integration-stack
280280
make test-integration-stack_including_metricspollerrole
281+
make test-integration-stack_with_org_id
282+
make test-integration-stack_with_source_accounts
281283
```
282284

283285
### Debugging

apps/stack/template.yaml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ Metadata:
3131
- ConfigDeliveryBucketName
3232
- IncludeResourceTypes
3333
- ExcludeResourceTypes
34+
- OrgId
35+
- SourceAccounts
3436
- Label:
3537
default: CloudWatch Logs
3638
Parameters:
@@ -215,6 +217,23 @@ Parameters:
215217
OpenTelemetry endpoint to send additional telemetry to.
216218
Default: ''
217219
AllowedPattern: "^(http(s)?:\/\/.*)?$"
220+
OrgId:
221+
Type: String
222+
Description: >-
223+
Optional AWS Organizations ID. If set, adds an AllowAWSConfigFromOrg
224+
statement on the SNS topic that allows publishes by aws:PrincipalOrgID.
225+
Useful for AWS Control Tower integrations.
226+
Default: ''
227+
AllowedPattern: '^(o-[a-z0-9]{10,32})?$'
228+
SourceAccounts:
229+
Type: CommaDelimitedList
230+
Description: >-
231+
List of AWS account IDs allowed to publish to the SNS topic via
232+
AWS Config. Useful for sub-accounts in AWS Organizations and
233+
Control Tower integrations. The current account ID is automatically
234+
included when this list is non-empty.
235+
Default: ''
236+
AllowedPattern: '^\d*$'
218237

219238
Conditions:
220239
EmptyConfigDeliveryBucketName: !Equals
@@ -269,6 +288,16 @@ Conditions:
269288
- !Equals
270289
- !Ref ObserveAwsAccountId
271290
- ""
291+
HasOrgId: !Not
292+
- !Equals
293+
- !Ref OrgId
294+
- ""
295+
HasSourceAccounts: !Not
296+
- !Equals
297+
- !Join
298+
- ','
299+
- !Ref SourceAccounts
300+
- ''
272301
Resources:
273302
Topic:
274303
Type: "AWS::SNS::Topic"
@@ -307,6 +336,40 @@ Resources:
307336
- "sns:Publish"
308337
Resource:
309338
- !Ref Topic
339+
- !If
340+
- HasOrgId
341+
- Sid: "AllowAWSConfigFromOrg"
342+
Effect: "Allow"
343+
Principal:
344+
Service:
345+
- "config.amazonaws.com"
346+
Action:
347+
- "SNS:Publish"
348+
Resource:
349+
- !Ref Topic
350+
Condition:
351+
StringEquals:
352+
"aws:PrincipalOrgID": !Ref OrgId
353+
- !Ref AWS::NoValue
354+
- !If
355+
- HasSourceAccounts
356+
- Sid: "AllowAWSConfigFromSourceAccounts"
357+
Effect: "Allow"
358+
Principal:
359+
Service:
360+
- "config.amazonaws.com"
361+
Action:
362+
- "SNS:Publish"
363+
Resource:
364+
- !Ref Topic
365+
Condition:
366+
StringEquals:
367+
"AWS:SourceAccount": !Split
368+
- ','
369+
- !Sub
370+
- '${Accounts},${AWS::AccountId}'
371+
- Accounts: !Join [',', !Ref SourceAccounts]
372+
- !Ref AWS::NoValue
310373
Topics:
311374
- !Ref Topic
312375
Bucket:

docs/stack.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ The Observe stack provisions the following components:
3232
| `ConfigDeliveryBucketName` | String | If AWS Config is already enabled in this account and region, provide the S3 bucket snapshots are written to. |
3333
| `IncludeResourceTypes` | CommaDelimitedList | If AWS Config is not enabled in this account and region, provide a list of resource types to collect. Use a wildcard to collect all supported resource types. |
3434
| `ExcludeResourceTypes` | CommaDelimitedList | Exclude a subset of resource types from configuration collection. This parameter can only be set if IncludeResourceTypes is wildcarded. |
35+
| `OrgId` | String | Optional AWS Organizations ID. If set, adds an AllowAWSConfigFromOrg statement on the SNS topic that allows publishes by aws:PrincipalOrgID. Useful for AWS Control Tower integrations. |
36+
| `SourceAccounts` | CommaDelimitedList | List of AWS account IDs allowed to publish to the SNS topic via AWS Config. Useful for sub-accounts in AWS Organizations and Control Tower integrations. The current account ID is automatically included when this list is non-empty. |
3537
| `LogGroupNamePatterns` | CommaDelimitedList | Comma separated list of patterns. If not empty, the lambda function will only apply to log groups that have names that match one of the provided strings based on a case-sensitive substring search. |
3638
| `LogGroupNamePrefixes` | CommaDelimitedList | Comma separated list of prefixes. If not empty, the lambda function will only apply to log groups that start with a provided string. |
3739
| `ExcludeLogGroupNamePatterns` | CommaDelimitedList | Comma separated list of patterns. This paramter is used to filter out log groups from subscription, and supports the use of regular expressions. |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
data "aws_organizations_organization" "current" {}
2+
3+
data "aws_caller_identity" "current" {}
4+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
output "org_id" {
2+
description = "AWS Organizations ID"
3+
value = data.aws_organizations_organization.current.id
4+
}
5+
6+
output "account_id" {
7+
description = "AWS Account ID"
8+
value = data.aws_caller_identity.current.account_id
9+
}
10+
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
DIE() { echo "$*" 1>&2; exit 1; }
5+
6+
[[ ! -z "${TOPIC_ARN:-}" ]] || DIE "TOPIC_ARN not set"
7+
[[ ! -z "${CHECK_TYPE:-}" ]] || DIE "CHECK_TYPE not set"
8+
9+
echo "Fetching SNS topic policy for ${TOPIC_ARN}"
10+
POLICY=$(aws sns get-topic-attributes \
11+
--topic-arn "${TOPIC_ARN}" \
12+
--query 'Attributes.Policy' \
13+
--output text ${OPTS:-})
14+
15+
[[ ! -z "${POLICY}" ]] || DIE "Failed to fetch topic policy"
16+
17+
echo "Policy retrieved successfully"
18+
19+
case "${CHECK_TYPE}" in
20+
org_id)
21+
[[ ! -z "${EXPECTED_ORG_ID:-}" ]] || DIE "EXPECTED_ORG_ID not set for org_id check"
22+
echo "Checking for aws:PrincipalOrgID condition with value: ${EXPECTED_ORG_ID}"
23+
24+
# Check if the policy contains the expected org ID
25+
if echo "${POLICY}" | jq -e --arg org_id "${EXPECTED_ORG_ID}" \
26+
'.Statement[] | select(.Sid == "AllowAWSConfigFromOrg") | .Condition.StringEquals."aws:PrincipalOrgID" == $org_id' > /dev/null; then
27+
echo "✓ Found correct aws:PrincipalOrgID condition"
28+
exit 0
29+
else
30+
echo "✗ aws:PrincipalOrgID condition not found or incorrect"
31+
echo "Policy:"
32+
echo "${POLICY}" | jq '.'
33+
exit 1
34+
fi
35+
;;
36+
37+
source_accounts)
38+
[[ ! -z "${EXPECTED_ACCOUNT_IDS:-}" ]] || DIE "EXPECTED_ACCOUNT_IDS not set for source_accounts check"
39+
40+
# Build the full list of expected accounts (including current account if provided)
41+
EXPECTED_ACCOUNTS="${EXPECTED_ACCOUNT_IDS}"
42+
if [[ ! -z "${CURRENT_ACCOUNT_ID:-}" ]]; then
43+
EXPECTED_ACCOUNTS="${EXPECTED_ACCOUNTS},${CURRENT_ACCOUNT_ID}"
44+
fi
45+
46+
echo "Checking for AWS:SourceAccount condition with accounts: ${EXPECTED_ACCOUNTS}"
47+
48+
# Check if the policy contains the SourceAccount condition
49+
if echo "${POLICY}" | jq -e '.Statement[] | select(.Sid == "AllowAWSConfigFromSourceAccounts")' > /dev/null; then
50+
echo "✓ Found AllowAWSConfigFromSourceAccounts statement"
51+
52+
# Verify the condition exists
53+
SOURCE_ACCOUNTS=$(echo "${POLICY}" | jq -r '.Statement[] | select(.Sid == "AllowAWSConfigFromSourceAccounts") | .Condition.StringEquals."AWS:SourceAccount" | if type == "array" then .[] else . end' | sort | tr '\n' ',' | sed 's/,$//')
54+
55+
echo "Found source accounts: ${SOURCE_ACCOUNTS}"
56+
echo "Expected accounts: ${EXPECTED_ACCOUNTS}"
57+
58+
# Split expected accounts and check each one is present
59+
IFS=',' read -ra EXPECTED_ARRAY <<< "${EXPECTED_ACCOUNTS}"
60+
for account in "${EXPECTED_ARRAY[@]}"; do
61+
if echo "${SOURCE_ACCOUNTS}" | grep -q "${account}"; then
62+
echo "✓ Found account ${account}"
63+
else
64+
echo "✗ Account ${account} not found in policy"
65+
exit 1
66+
fi
67+
done
68+
69+
exit 0
70+
else
71+
echo "✗ AllowAWSConfigFromSourceAccounts statement not found"
72+
echo "Policy:"
73+
echo "${POLICY}" | jq '.'
74+
exit 1
75+
fi
76+
;;
77+
78+
*)
79+
DIE "Unknown CHECK_TYPE: ${CHECK_TYPE}. Must be 'org_id' or 'source_accounts'"
80+
;;
81+
esac
82+

0 commit comments

Comments
 (0)