This repository has been archived by the owner on Nov 1, 2023. It is now read-only.
/
control-tower.yaml
226 lines (196 loc) · 7.46 KB
/
control-tower.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: Sets up AWS ControlTower. (qs-1s3rsr7lh)
Parameters:
AuditAWSAccountEmail:
Type: String
LogArchiveAWSAccountEmail:
Type: String
Resources:
SetupControlTower:
Type: AWS::CloudFormation::CustomResource
Properties:
ServiceToken: !GetAtt SetupControlTowerCustomResource.Arn
SetupControlTowerCustomResource:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: python3.7
MemorySize: 2048
Timeout: 900 # give it more time since it installs awsapilib and tries to deploy control tower with retries
Role: !GetAtt SetupControlTowerCustomResourceRole.Arn # provide explicit role to avoid circular dependency with AwsApiLibRole
Policies:
- Version: 2012-10-17
Statement:
- Effect: Allow
Action: sts:AssumeRole
Resource: !GetAtt AwsApiLibRole.Arn
Environment:
Variables:
AWSAPILIB_CONTROL_TOWER_ROLE_ARN: !GetAtt AwsApiLibRole.Arn
LOG_ARCHIVE_AWS_ACCOUNT_EMAIL: !Ref LogArchiveAWSAccountEmail
AUDIT_AWS_ACCOUNT_EMAIL: !Ref AuditAWSAccountEmail
InlineCode: |
import boto3
import os
import cfnresponse
import sys
import subprocess
# load awsapilib in-process as long as we have no strategy for bundling assets
sys.path.insert(1, '/tmp/packages')
subprocess.check_call([sys.executable, "-m", "pip", "install", '--target', '/tmp/packages', 'https://github.com/superwerker/awsapilib/archive/198a3269e324455dc3cc499b61bf61e5ec095779.zip'])
# workaround for install awsapilib via zip (remove me once back to official awsapilib version)
with open('/tmp/packages/awsapilib/.VERSION', 'w') as version_file:
version_file.write("2.3.1-ctapifix\n")
import awsapilib
from awsapilib import ControlTower
CREATE = 'Create'
DELETE = 'Delete'
UPDATE = 'Update'
def exception_handling(function):
def catch(event, context):
try:
function(event, context)
except Exception as e:
print(e)
print(event)
cfnresponse.send(event, context, cfnresponse.FAILED, {})
return catch
@exception_handling
def handler(event, context):
RequestType = event["RequestType"]
Properties = event["ResourceProperties"]
LogicalResourceId = event["LogicalResourceId"]
PhysicalResourceId = event.get("PhysicalResourceId")
print('RequestType: {}'.format(RequestType))
print('PhysicalResourceId: {}'.format(PhysicalResourceId))
print('LogicalResourceId: {}'.format(LogicalResourceId))
id = PhysicalResourceId
data = {}
tower = ControlTower(os.environ['AWSAPILIB_CONTROL_TOWER_ROLE_ARN'])
if RequestType == CREATE:
tower.deploy(logging_account_email=os.environ['LOG_ARCHIVE_AWS_ACCOUNT_EMAIL'], security_account_email=os.environ['AUDIT_AWS_ACCOUNT_EMAIL'], retries=50, wait=5)
cfnresponse.send(event, context, cfnresponse.SUCCESS, data, id)
SetupControlTowerCustomResourceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
SetupControlTowerCustomResourceRolePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyDocument:
Statement:
- Effect: Allow
Action: sts:AssumeRole
Resource: !GetAtt AwsApiLibRole.Arn
Version: 2012-10-17
PolicyName: !Sub ${SetupControlTowerCustomResourceRole}Policy
Roles:
- !Ref SetupControlTowerCustomResourceRole
AwsApiLibRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
AWS: !GetAtt SetupControlTowerCustomResourceRole.Arn
Action: sts:AssumeRole
ManagedPolicyArns:
- !Sub arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess
ControlTowerReadyHandle:
Type: AWS::CloudFormation::WaitConditionHandle
ControlTowerReadyHandleWaitCondition:
Type: AWS::CloudFormation::WaitCondition
Properties:
Handle: !Ref ControlTowerReadyHandle
Timeout: "7200"
SuperwerkerBootstrapFunction:
Type: AWS::Serverless::Function
Properties:
Events:
SetupLandingZone: # event from entirely fresh landing zone
Type: CloudWatchEvent
Properties:
InputPath: $.detail.serviceEventDetails.setupLandingZoneStatus
Pattern:
detail-type:
- AWS Service Event via CloudTrail
source:
- aws.controltower
detail:
serviceEventDetails:
setupLandingZoneStatus:
state:
- SUCCEEDED
eventName:
- SetupLandingZone
Handler: index.handler
Runtime: python3.7
Environment:
Variables:
SIGNAL_URL: !Ref ControlTowerReadyHandle
InlineCode: |-
import boto3
import json
ssm = boto3.client('ssm')
events = boto3.client('events')
import urllib3
import os
def handler(event, context):
for account in event['accounts']:
ssm.put_parameter(
Name='/superwerker/account_id_{}'.format(account['accountName'].lower().replace(' ', '')),
Value=account['accountId'],
Overwrite=True,
Type='String',
)
# signal cloudformation stack that control tower setup is complete
encoded_body = json.dumps({
"Status": "SUCCESS",
"Reason": "Control Tower Setup completed",
"UniqueId": "doesthisreallyhavetobeunique",
"Data": "Control Tower Setup completed"
})
http = urllib3.PoolManager()
http.request('PUT', os.environ['SIGNAL_URL'], body=encoded_body)
# signal Control Tower Landing ZOne Setup/Update has finished
events.put_events(
Entries=[
{
'DetailType': 'superwerker-event',
'Detail': json.dumps(
{
'eventName': 'LandingZoneSetupOrUpdateFinished',
}
),
'Source': 'superwerker'
}
]
)
Policies:
- Version: 2012-10-17
Statement:
- Action:
- ssm:PutParameter
Effect: Allow
Resource:
- !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/superwerker*
- Action: events:PutEvents
Effect: Allow
Resource: !Sub 'arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/default'
Metadata:
SuperwerkerVersion: 0.13.2
cfn-lint:
config:
ignore_checks:
- E9007