Skip to content

Commit 66e2d75

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

File tree

6 files changed

+516
-0
lines changed

6 files changed

+516
-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: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
variables {
2+
install_policy_json = <<-EOF
3+
{
4+
"Version": "2012-10-17",
5+
"Statement": [
6+
{
7+
"Effect": "Allow",
8+
"Action": [
9+
"cloudformation:CreateChangeSet",
10+
"cloudformation:CreateStack",
11+
"cloudformation:DeleteChangeSet",
12+
"cloudformation:DeleteStack",
13+
"cloudformation:DescribeStacks",
14+
"cloudwatch:DeleteMetricStream",
15+
"cloudwatch:GetMetricStream",
16+
"cloudwatch:PutMetricStream",
17+
"cloudwatch:TagResource",
18+
"config:DeleteConfigurationRecorder",
19+
"config:DeleteDeliveryChannel",
20+
"config:DescribeConfigurationRecorderStatus",
21+
"config:DescribeConfigurationRecorders",
22+
"config:DescribeDeliveryChannelStatus",
23+
"config:DescribeDeliveryChannels",
24+
"config:PutConfigurationRecorder",
25+
"config:PutDeliveryChannel",
26+
"config:StartConfigurationRecorder",
27+
"config:StopConfigurationRecorder",
28+
"ec2:DescribeNetworkInterfaces",
29+
"events:DeleteRule",
30+
"events:DescribeRule",
31+
"events:PutRule",
32+
"events:PutTargets",
33+
"events:RemoveTargets",
34+
"firehose:CreateDeliveryStream",
35+
"firehose:DeleteDeliveryStream",
36+
"firehose:DescribeDeliveryStream",
37+
"firehose:ListTagsForDeliveryStream",
38+
"firehose:TagDeliveryStream",
39+
"firehose:UpdateDestination",
40+
"iam:AttachRolePolicy",
41+
"iam:CreateRole",
42+
"iam:DeleteRole",
43+
"iam:DeleteRolePolicy",
44+
"iam:DetachRolePolicy",
45+
"iam:GetRole",
46+
"iam:GetRolePolicy",
47+
"iam:ListAttachedRolePolicies",
48+
"iam:ListRolePolicies",
49+
"iam:PassRole",
50+
"iam:PutRolePolicy",
51+
"iam:TagRole",
52+
"iam:UpdateRole",
53+
"kms:CreateGrant",
54+
"kms:Decrypt",
55+
"kms:DescribeKey",
56+
"kms:Encrypt",
57+
"kms:ListGrants",
58+
"kms:RevokeGrant",
59+
"lambda:CreateEventSourceMapping",
60+
"lambda:CreateFunction",
61+
"lambda:DeleteEventSourceMapping",
62+
"lambda:DeleteFunction",
63+
"lambda:GetEventSourceMapping",
64+
"lambda:GetFunction",
65+
"lambda:GetFunctionCodeSigningConfig",
66+
"lambda:GetRuntimeManagementConfig",
67+
"lambda:InvokeFunction",
68+
"lambda:ListEventSourceMappings",
69+
"lambda:ListTags",
70+
"lambda:TagResource",
71+
"lambda:UntagResource",
72+
"lambda:UpdateEventSourceMapping",
73+
"lambda:UpdateFunctionCode",
74+
"lambda:UpdateFunctionConfiguration",
75+
"logs:CreateLogGroup",
76+
"logs:CreateLogStream",
77+
"logs:DeleteLogGroup",
78+
"logs:DeleteLogStream",
79+
"logs:DeleteSubscriptionFilter",
80+
"logs:DescribeLogGroups",
81+
"logs:DescribeSubscriptionFilters",
82+
"logs:ListTagsForResource",
83+
"logs:PutRetentionPolicy",
84+
"logs:PutSubscriptionFilter",
85+
"logs:TagResource",
86+
"logs:UntagResource",
87+
"s3:CreateBucket",
88+
"s3:DeleteBucket",
89+
"s3:GetBucketNotification",
90+
"s3:GetLifecycleConfiguration",
91+
"s3:GetObject",
92+
"s3:ListBucket",
93+
"s3:PutBucketNotification",
94+
"s3:PutBucketTagging",
95+
"s3:PutLifecycleConfiguration",
96+
"scheduler:CreateSchedule",
97+
"scheduler:DeleteSchedule",
98+
"scheduler:GetSchedule",
99+
"scheduler:UpdateSchedule",
100+
"sns:CreateTopic",
101+
"sns:DeleteTopic",
102+
"sns:GetTopicAttributes",
103+
"sns:ListTopics",
104+
"sns:Publish",
105+
"sns:SetTopicAttributes",
106+
"sns:Subscribe",
107+
"sns:TagResource",
108+
"sns:Unsubscribe",
109+
"sqs:CreateQueue",
110+
"sqs:DeleteQueue",
111+
"sqs:GetQueueAttributes",
112+
"sqs:GetQueueUrl",
113+
"sqs:PurgeQueue",
114+
"sqs:SetQueueAttributes",
115+
"sqs:TagQueue",
116+
"sqs:UntagQueue"
117+
],
118+
"Resource": "*"
119+
},
120+
{
121+
"Effect": "Allow",
122+
"Action": [
123+
"s3:GetObject"
124+
],
125+
"Resource": [
126+
"arn:aws:s3:::observeinc/cloudwatchmetrics/filters/*"
127+
]
128+
}
129+
]
130+
}
131+
EOF
132+
}
133+
134+
run "setup" {
135+
module {
136+
source = "observeinc/collection/aws//modules/testing/setup"
137+
version = "2.9.0"
138+
}
139+
variables {
140+
id_length = 51
141+
}
142+
}
143+
144+
run "create_bucket" {
145+
module {
146+
source = "observeinc/collection/aws//modules/testing/s3_bucket"
147+
version = "2.9.0"
148+
}
149+
variables {
150+
setup = run.setup
151+
}
152+
}
153+
154+
# Get the organization ID for testing
155+
run "get_org_id" {
156+
module {
157+
source = "./modules/get_org_id"
158+
}
159+
}
160+
161+
run "install_with_org_id" {
162+
variables {
163+
setup = run.setup
164+
app = "stack"
165+
parameters = {
166+
DataAccessPointArn = run.create_bucket.access_point.arn
167+
DestinationUri = "s3://${run.create_bucket.access_point.alias}/"
168+
ConfigDeliveryBucketName = "example-bucket"
169+
SourceBucketNames = "*"
170+
LogGroupNamePatterns = "*"
171+
OrgId = run.get_org_id.org_id
172+
NameOverride = run.setup.id
173+
}
174+
capabilities = [
175+
"CAPABILITY_NAMED_IAM",
176+
"CAPABILITY_AUTO_EXPAND",
177+
]
178+
}
179+
}
180+
181+
run "verify_org_id_policy" {
182+
module {
183+
source = "observeinc/collection/aws//modules/testing/exec"
184+
version = "2.9.0"
185+
}
186+
187+
variables {
188+
command = "./scripts/check_sns_topic_policy"
189+
env_vars = {
190+
TOPIC_ARN = run.install_with_org_id.stack.outputs["TopicArn"]
191+
EXPECTED_ORG_ID = run.get_org_id.org_id
192+
CHECK_TYPE = "org_id"
193+
}
194+
}
195+
196+
assert {
197+
condition = output.exitcode == 0
198+
error_message = "SNS topic policy does not contain expected OrgId condition"
199+
}
200+
}
201+
202+
run "check_sqs" {
203+
module {
204+
source = "observeinc/collection/aws//modules/testing/exec"
205+
version = "2.9.0"
206+
}
207+
208+
variables {
209+
command = "./scripts/check_object_diff"
210+
env_vars = {
211+
SOURCE = run.install_with_org_id.stack.outputs["BucketName"]
212+
DESTINATION = run.create_bucket.id
213+
}
214+
}
215+
216+
assert {
217+
condition = output.exitcode == 0
218+
error_message = "Failed to copy object using SQS"
219+
}
220+
}
221+

0 commit comments

Comments
 (0)