├── LICENSE.txt
├── README.md
├── aws-cross-account-manager-master.template
├── aws-cross-account-manager-sub.template
├── code
└── cross-account-handler
│ ├── LICENSE
│ ├── NOTICE.txt
│ ├── index.js
│ ├── lib
│ ├── accessLinks_handler.js
│ ├── account_init.js
│ ├── ddb_helper.js
│ ├── event_handler.js
│ ├── file_handler.js
│ ├── helper.js
│ ├── iam_helper.js
│ ├── s3_helper.js
│ └── sns_helper.js
│ └── package.json
└── samples
├── Administrator.json
├── Read-Only.json
├── accounts.yaml
└── roles.yaml
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Amazon Software License
2 |
3 | This Amazon Software License (“License”) governs your use, reproduction, and distribution of the accompanying software
4 | as specified below.
5 |
6 | 1. Definitions
7 |
8 | “Licensor” means any person or entity that distributes its Work.
9 |
10 | “Software” means the original work of authorship made available under this License.
11 |
12 | “Work” means the Software and any additions to or derivative works of the Software that are made available under
13 | this License.
14 |
15 | The terms “reproduce,” “reproduction,” “derivative works,” and “distribution” have the meaning as provided under
16 | U.S. copyright law; provided, however, that for the purposes of this License, derivative works shall not include
17 | works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work.
18 |
19 | Works, including the Software, are “made available” under this License by including in or with the Work either
20 | (a) a copyright notice referencing the applicability of this License to the Work, or (b) a copy of this License.
21 |
22 | 2. License Grants
23 |
24 | 2.1 Copyright Grant. Subject to the terms and conditions of this License, each Licensor grants to you a perpetual,
25 | worldwide, non-exclusive, royalty-free, copyright license to reproduce, prepare derivative works of, publicly
26 | display, publicly perform, sublicense and distribute its Work and any resulting derivative works in any form.
27 |
28 | 2.2 Patent Grant. Subject to the terms and conditions of this License, each Licensor grants to you a perpetual,
29 | worldwide, non-exclusive, royalty-free patent license to make, have made, use, sell, offer for sale, import, and
30 | otherwise transfer its Work, in whole or in part. The foregoing license applies only to the patent claims licensable
31 | by Licensor that would be infringed by Licensor’s Work (or portion thereof) individually and excluding any
32 | combinations with any other materials or technology.
33 |
34 | 3. Limitations
35 |
36 | 3.1 Redistribution. You may reproduce or distribute the Work only if (a) you do so under this License, (b) you
37 | include a complete copy of this License with your distribution, and (c) you retain without modification any
38 | copyright, patent, trademark, or attribution notices that are present in the Work.
39 |
40 | 3.2 Derivative Works. You may specify that additional or different terms apply to the use, reproduction, and
41 | distribution of your derivative works of the Work (“Your Terms”) only if (a) Your Terms provide that the use
42 | limitation in Section 3.3 applies to your derivative works, and (b) you identify the specific derivative works that
43 | are subject to Your Terms. Notwithstanding Your Terms, this License (including the redistribution requirements in
44 | Section 3.1) will continue to apply to the Work itself.
45 |
46 | 3.3 Use Limitation. The Work and any derivative works thereof only may be used or intended for use with the web
47 | services, computing platforms or applications provided by Amazon.com, Inc. or its affiliates, including Amazon Web
48 | Services, Inc.
49 |
50 | 3.4 Patent Claims. If you bring or threaten to bring a patent claim against any Licensor (including any claim,
51 | cross-claim or counterclaim in a lawsuit) to enforce any patents that you allege are infringed by any Work, then
52 | your rights under this License from such Licensor (including the grants in Sections 2.1 and 2.2) will terminate
53 | immediately.
54 |
55 | 3.5 Trademarks. This License does not grant any rights to use any Licensor’s or its affiliates’ names, logos, or
56 | trademarks, except as necessary to reproduce the notices described in this License.
57 |
58 | 3.6 Termination. If you violate any term of this License, then your rights under this License (including the grants
59 | in Sections 2.1 and 2.2) will terminate immediately.
60 |
61 | 4. Disclaimer of Warranty.
62 |
63 | THE WORK IS PROVIDED “AS IS” WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
64 | WARRANTIES OR CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT. YOU BEAR
65 | THE RISK OF UNDERTAKING ANY ACTIVITIES UNDER THIS LICENSE. SOME STATES’ CONSUMER LAWS DO NOT ALLOW EXCLUSION OF AN
66 | IMPLIED WARRANTY, SO THIS DISCLAIMER MAY NOT APPLY TO YOU.
67 |
68 | 5. Limitation of Liability.
69 |
70 | EXCEPT AS PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL THEORY, WHETHER IN TORT (INCLUDING
71 | NEGLIGENCE), CONTRACT, OR OTHERWISE SHALL ANY LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, INDIRECT,
72 | SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATED TO THIS LICENSE, THE USE OR INABILITY TO USE
73 | THE WORK (INCLUDING BUT NOT LIMITED TO LOSS OF GOODWILL, BUSINESS INTERRUPTION, LOST PROFITS OR DATA, COMPUTER
74 | FAILURE OR MALFUNCTION, OR ANY OTHER COMMERCIAL DAMAGES OR LOSSES), EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE
75 | POSSIBILITY OF SUCH DAMAGES.
76 |
77 | Effective Date – April 18, 2008 © 2008 Amazon.com, Inc. or its affiliates. All rights reserved.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # aws-cross-account-manager
2 |
3 | Source code for the AWS solution [Cross Account Manager](https://aws.amazon.com/answers/account-management/aws-multi-account-security-strategy/).
4 |
5 | ## CloudFormation templates
6 |
7 | - aws-cross-account-manager-master.template
8 | - aws-cross-account-manager-sub.template
9 |
10 | ## Lambda source code
11 |
12 | - code/cross-account-handler/index.js
13 | - code/cross-account-handler/lib/accessLinks_handler.js
14 | - code/cross-account-handler/lib/ddb_helper.js
15 | - code/cross-account-handler/lib/file_handler.js
16 | - code/cross-account-handler/lib/s3_helper.js
17 | - code/cross-account-handler/lib/account_init.js
18 | - code/cross-account-handler/lib/event_handler.js
19 | - code/cross-account-handler/lib/helper.js
20 | - code/cross-account-handler/lib/iam_helper.js
21 | - code/cross-account-handler/lib/sns_helper.js
22 |
23 | ## Sample input files
24 |
25 | - accounts.yaml
26 | - roles.yaml
27 | - Administrator.json
28 | - Read-Only.json
29 |
30 | ***
31 |
32 | Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
33 |
34 | Licensed under the Amazon Software License (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at
35 |
36 | http://aws.amazon.com/asl/
37 |
38 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and limitations under the License.
39 |
--------------------------------------------------------------------------------
/aws-cross-account-manager-master.template:
--------------------------------------------------------------------------------
1 | {
2 | "AWSTemplateFormatVersion" : "2010-09-09",
3 | "Description" : "(SO0015) - Cross-Account Manager Solution: Master Account template ",
4 | "Parameters" : {
5 | "ConfigBucket" : {
6 | "Description" : "Name of the Bucket to input files for the solution",
7 | "Type" : "String",
8 | "Default" : ""
9 | },
10 | "AccessLinksBucket" : {
11 | "Description" : "Name of the Bucket to store the Access Links page with shortucts to all the Sub-Accounts/Roles managed by the solution",
12 | "Type" : "String",
13 | "Default" : ""
14 | },
15 | "SendAnonymousData" : {
16 | "Description" : "Send anonymous data to AWS",
17 | "Type" : "String",
18 | "Default" : "Yes",
19 | "AllowedValues" : [ "Yes", "No" ]
20 | }
21 | },
22 | "Metadata" : {
23 | "AWS::CloudFormation::Interface" : {
24 | "ParameterGroups" : [ {
25 | "Label" : {
26 | "default" : "Bucket Configuration"
27 | },
28 | "Parameters" : [ "ConfigBucket", "AccessLinksBucket" ]
29 | }, {
30 | "Label" : {
31 | "default" : "Anonymous Metrics Request"
32 | },
33 | "Parameters" : [ "SendAnonymousData" ]
34 | } ]
35 | }
36 | },
37 | "Conditions" : {
38 | "SendData" : {
39 | "Fn::Equals" : [ {
40 | "Ref" : "SendAnonymousData"
41 | }, "Yes" ]
42 | }
43 | },
44 | "Resources" : {
45 | "AccountFileHandlerExecRole" : {
46 | "Type" : "AWS::IAM::Role",
47 | "Properties" : {
48 | "AssumeRolePolicyDocument" : {
49 | "Version" : "2012-10-17",
50 | "Statement" : [ {
51 | "Effect" : "Allow",
52 | "Principal" : {
53 | "Service" : [ "lambda.amazonaws.com" ]
54 | },
55 | "Action" : [ "sts:AssumeRole" ]
56 | } ]
57 | }
58 | }
59 | },
60 | "AccountFileHandler" : {
61 | "Type" : "AWS::Lambda::Function",
62 | "Properties" : {
63 | "Code" : {
64 | "S3Bucket" : {
65 | "Fn::Join" : [ "", [ "solutions-", {
66 | "Ref" : "AWS::Region"
67 | } ] ]
68 | },
69 | "S3Key" : "cross-account-manager/v1/cross-account-handler.zip"
70 | },
71 | "Description" : "This event-triggered Lambda function monitors S3 bucket and SNS topic for Account related activities e.g. onboarding, offboarding.",
72 | "Handler" : "index.handleAccountS3File",
73 | "Role" : {
74 | "Fn::GetAtt" : [ "AccountFileHandlerExecRole", "Arn" ]
75 | },
76 | "Runtime" : "nodejs4.3",
77 | "Timeout" : "300"
78 | }
79 | },
80 | "AccountEventHandlerExecRole" : {
81 | "Type" : "AWS::IAM::Role",
82 | "Properties" : {
83 | "AssumeRolePolicyDocument" : {
84 | "Version" : "2012-10-17",
85 | "Statement" : [ {
86 | "Effect" : "Allow",
87 | "Principal" : {
88 | "Service" : [ "lambda.amazonaws.com" ]
89 | },
90 | "Action" : [ "sts:AssumeRole" ]
91 | } ]
92 | }
93 | }
94 | },
95 | "AccountEventHandler" : {
96 | "Type" : "AWS::Lambda::Function",
97 | "Properties" : {
98 | "Code" : {
99 | "S3Bucket" : {
100 | "Fn::Join" : [ "", [ "solutions-", {
101 | "Ref" : "AWS::Region"
102 | } ] ]
103 | },
104 | "S3Key" : "cross-account-manager/v1/cross-account-handler.zip"
105 | },
106 | "Description" : "This event-triggered Lambda function monitors S3 bucket and SNS topic for Account related activities e.g. onboarding, offboarding.",
107 | "Handler" : "index.handleAccountEvent",
108 | "Role" : {
109 | "Fn::GetAtt" : [ "AccountEventHandlerExecRole", "Arn" ]
110 | },
111 | "Runtime" : "nodejs4.3",
112 | "Timeout" : "300"
113 | }
114 | },
115 | "RoleFileHandlerExecRole" : {
116 | "Type" : "AWS::IAM::Role",
117 | "Properties" : {
118 | "AssumeRolePolicyDocument" : {
119 | "Version" : "2012-10-17",
120 | "Statement" : [ {
121 | "Effect" : "Allow",
122 | "Principal" : {
123 | "Service" : [ "lambda.amazonaws.com" ]
124 | },
125 | "Action" : [ "sts:AssumeRole" ]
126 | } ]
127 | }
128 | }
129 | },
130 | "RoleFileHandler" : {
131 | "Type" : "AWS::Lambda::Function",
132 | "Properties" : {
133 | "Code" : {
134 | "S3Bucket" : {
135 | "Fn::Join" : [ "", [ "solutions-", {
136 | "Ref" : "AWS::Region"
137 | } ] ]
138 | },
139 | "S3Key" : "cross-account-manager/v1/cross-account-handler.zip"
140 | },
141 | "Description" : "This event-triggered Lambda function monitors S3 bucket for Role related activities e.g. onboarding, offboarding.",
142 | "Handler" : "index.handleRoleS3File",
143 | "Role" : {
144 | "Fn::GetAtt" : [ "RoleFileHandlerExecRole", "Arn" ]
145 | },
146 | "Runtime" : "nodejs4.3",
147 | "Timeout" : "300"
148 | }
149 | },
150 | "RoleEventHandlerExecRole" : {
151 | "Type" : "AWS::IAM::Role",
152 | "Properties" : {
153 | "RoleName" : "CrossAccountManager-Admin-DO-NOT-DELETE",
154 | "AssumeRolePolicyDocument" : {
155 | "Version" : "2012-10-17",
156 | "Statement" : [ {
157 | "Effect" : "Allow",
158 | "Principal" : {
159 | "Service" : [ "lambda.amazonaws.com" ]
160 | },
161 | "Action" : [ "sts:AssumeRole" ]
162 | } ]
163 | },
164 | "Policies" : [ {
165 | "PolicyName" : "CrossAccountAccessPolicy",
166 | "PolicyDocument" : {
167 | "Version" : "2012-10-17",
168 | "Statement" : [ {
169 | "Effect" : "Allow",
170 | "Resource" : [ "arn:aws:iam::*:role/CrossAccountManager-*" ],
171 | "Action" : [ "sts:*" ]
172 | } ]
173 | }
174 | } ]
175 | }
176 | },
177 | "RoleEventHandler" : {
178 | "Type" : "AWS::Lambda::Function",
179 | "Properties" : {
180 | "Code" : {
181 | "S3Bucket" : {
182 | "Fn::Join" : [ "", [ "solutions-", {
183 | "Ref" : "AWS::Region"
184 | } ] ]
185 | },
186 | "S3Key" : "cross-account-manager/v1/cross-account-handler.zip"
187 | },
188 | "Description" : "This event-triggered Lambda function monitors Role Topic for Role related activities e.g. onboarding, offboarding.",
189 | "Handler" : "index.handleRoleEvent",
190 | "Role" : {
191 | "Fn::GetAtt" : [ "RoleEventHandlerExecRole", "Arn" ]
192 | },
193 | "Runtime" : "nodejs4.3",
194 | "Timeout" : "300"
195 | }
196 | },
197 | "SolutionHelperRole" : {
198 | "Type" : "AWS::IAM::Role",
199 | "Properties" : {
200 | "AssumeRolePolicyDocument" : {
201 | "Version" : "2012-10-17",
202 | "Statement" : [ {
203 | "Effect" : "Allow",
204 | "Principal" : {
205 | "Service" : "lambda.amazonaws.com"
206 | },
207 | "Action" : "sts:AssumeRole"
208 | } ]
209 | },
210 | "Policies" : [ {
211 | "PolicyName" : "S3_Permission",
212 | "PolicyDocument" : {
213 | "Version" : "2012-10-17",
214 | "Statement" : [ {
215 | "Effect" : "Allow",
216 | "Action" : [ "s3:PutObject", "s3:PutBucketNotification" ],
217 | "Resource" : {
218 | "Fn::Join" : [ "", [ "arn:aws:s3:::", {
219 | "Ref" : "CAMConfigBucket"
220 | }, "/*" ] ]
221 | }
222 | } ]
223 | }
224 | } ]
225 | }
226 | },
227 | "SolutionHelper" : {
228 | "Type" : "AWS::Lambda::Function",
229 | "DependsOn" : "CloudwatchLogsCloudformationPolicy",
230 | "Properties" : {
231 | "Handler" : "solution-helper.lambda_handler",
232 | "Role" : {
233 | "Fn::GetAtt" : [ "SolutionHelperRole", "Arn" ]
234 | },
235 | "Description" : "This function creates a CloudFormation custom lambda resource that creates custom lambda functions by finding and replacing specific values from existing lambda function code.",
236 | "Code" : {
237 | "S3Bucket" : {
238 | "Fn::Join" : [ "", [ "solutions-", {
239 | "Ref" : "AWS::Region"
240 | } ] ]
241 | },
242 | "S3Key" : "library/solution-helper/v3/solution-helper.zip"
243 | },
244 | "Runtime" : "python2.7",
245 | "Timeout" : "300"
246 | }
247 | },
248 | "InitMasterAccountExecRole" : {
249 | "Type" : "AWS::IAM::Role",
250 | "Properties" : {
251 | "AssumeRolePolicyDocument" : {
252 | "Version" : "2012-10-17",
253 | "Statement" : [ {
254 | "Effect" : "Allow",
255 | "Principal" : {
256 | "Service" : [ "lambda.amazonaws.com" ]
257 | },
258 | "Action" : [ "sts:AssumeRole" ]
259 | } ]
260 | }
261 | }
262 | },
263 | "InitMasterAccount" : {
264 | "Type" : "AWS::Lambda::Function",
265 | "Properties" : {
266 | "Code" : {
267 | "S3Bucket" : {
268 | "Fn::Join" : [ "", [ "solutions-", {
269 | "Ref" : "AWS::Region"
270 | } ] ]
271 | },
272 | "S3Key" : "cross-account-manager/v1/cross-account-handler.zip"
273 | },
274 | "Description" : "This Lambda function handles the Master Account initialization and destruction logic",
275 | "Handler" : "index.handleMasterAccountInit",
276 | "Role" : {
277 | "Fn::GetAtt" : [ "InitMasterAccountExecRole", "Arn" ]
278 | },
279 | "Runtime" : "nodejs4.3",
280 | "Timeout" : "60"
281 | }
282 | },
283 | "AccessLinksHandlerExecRole" : {
284 | "Type" : "AWS::IAM::Role",
285 | "Properties" : {
286 | "AssumeRolePolicyDocument" : {
287 | "Version" : "2012-10-17",
288 | "Statement" : [ {
289 | "Effect" : "Allow",
290 | "Principal" : {
291 | "Service" : [ "lambda.amazonaws.com" ]
292 | },
293 | "Action" : [ "sts:AssumeRole" ]
294 | } ]
295 | }
296 | }
297 | },
298 | "AccessLinksHandler" : {
299 | "Type" : "AWS::Lambda::Function",
300 | "Properties" : {
301 | "Code" : {
302 | "S3Bucket" : {
303 | "Fn::Join" : [ "", [ "solutions-", {
304 | "Ref" : "AWS::Region"
305 | } ] ]
306 | },
307 | "S3Key" : "cross-account-manager/v1/cross-account-handler.zip"
308 | },
309 | "Description" : "This event-triggered Lambda function monitors DynamoDB table Account-Roles and updates the static access links page",
310 | "Handler" : "index.handleAccessLinksEvent",
311 | "Role" : {
312 | "Fn::GetAtt" : [ "AccessLinksHandlerExecRole", "Arn" ]
313 | },
314 | "Runtime" : "nodejs4.3",
315 | "Timeout" : "300"
316 | }
317 | },
318 | "CloudwatchLogsCloudformationPolicy" : {
319 | "Type" : "AWS::IAM::Policy",
320 | "Properties" : {
321 | "Roles" : [ {
322 | "Ref" : "SolutionHelperRole"
323 | }, {
324 | "Ref" : "AccountFileHandlerExecRole"
325 | }, {
326 | "Ref" : "RoleFileHandlerExecRole"
327 | }, {
328 | "Ref" : "AccessLinksHandlerExecRole"
329 | }, {
330 | "Ref" : "AccountEventHandlerExecRole"
331 | }, {
332 | "Ref" : "RoleEventHandlerExecRole"
333 | }, {
334 | "Ref" : "InitMasterAccountExecRole"
335 | } ],
336 | "PolicyName" : "Cloudwatch_Logs_Permissions",
337 | "PolicyDocument" : {
338 | "Version" : "2012-10-17",
339 | "Statement" : [ {
340 | "Effect" : "Allow",
341 | "Action" : [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ],
342 | "Resource" : {
343 | "Fn::Join" : [ "", [ "arn:aws:logs:", {
344 | "Ref" : "AWS::Region"
345 | }, ":", {
346 | "Ref" : "AWS::AccountId"
347 | }, ":log-group:/aws/lambda/*" ] ]
348 | }
349 | }, {
350 | "Effect" : "Allow",
351 | "Action" : [ "cloudformation:DescribeStacks" ],
352 | "Resource" : "*"
353 | } ]
354 | }
355 | }
356 | },
357 | "AccessLinksHandlerExecRolePolicy" : {
358 | "Type" : "AWS::IAM::Policy",
359 | "Properties" : {
360 | "Roles" : [ {
361 | "Ref" : "AccessLinksHandlerExecRole"
362 | } ],
363 | "PolicyName" : "AccessLink_Permissions",
364 | "PolicyDocument" : {
365 | "Version" : "2012-10-17",
366 | "Statement" : [ {
367 | "Effect" : "Allow",
368 | "Action" : [ "s3:GetObject", "s3:PutObject" ],
369 | "Resource" : {
370 | "Fn::Join" : [ "", [ "arn:aws:s3:::", {
371 | "Ref" : "AccessLinkBucket"
372 | }, "/", "*" ] ]
373 | }
374 | }, {
375 | "Effect" : "Allow",
376 | "Action" : [ "s3:ListAllMyBuckets", "s3:GetBucketTagging" ],
377 | "Resource" : "arn:aws:s3:::*"
378 | }, {
379 | "Effect" : "Allow",
380 | "Action" : [ "dynamodb:GetItem", "dynamodb:Scan" ],
381 | "Resource" : {
382 | "Fn::Join" : [ "", [ "arn:aws:dynamodb:", {
383 | "Ref" : "AWS::Region"
384 | }, ":", {
385 | "Ref" : "AWS::AccountId"
386 | }, ":table/CrossAccountManager-*" ] ]
387 | }
388 | } ]
389 | }
390 | }
391 | },
392 | "S3DynamoDBSNSPolicy" : {
393 | "Type" : "AWS::IAM::Policy",
394 | "Properties" : {
395 | "Roles" : [ {
396 | "Ref" : "AccountFileHandlerExecRole"
397 | }, {
398 | "Ref" : "RoleFileHandlerExecRole"
399 | }, {
400 | "Ref" : "AccountEventHandlerExecRole"
401 | }, {
402 | "Ref" : "RoleEventHandlerExecRole"
403 | } ],
404 | "PolicyName" : "S3_DynamoDB_SNS_Permissions",
405 | "PolicyDocument" : {
406 | "Version" : "2012-10-17",
407 | "Statement" : [ {
408 | "Effect" : "Allow",
409 | "Action" : [ "s3:GetObject", "s3:DeleteObject" ],
410 | "Resource" : {
411 | "Fn::Join" : [ "", [ "arn:aws:s3:::", {
412 | "Ref" : "CAMConfigBucket"
413 | }, "/", "*" ] ]
414 | }
415 | }, {
416 | "Effect" : "Allow",
417 | "Action" : [ "dynamodb:PutItem", "dynamodb:GetItem", "dynamodb:Scan" ],
418 | "Resource" : [ {
419 | "Fn::Join" : [ "", [ "arn:aws:dynamodb:", {
420 | "Ref" : "AWS::Region"
421 | }, ":", {
422 | "Ref" : "AWS::AccountId"
423 | }, ":table/CrossAccountManager-*" ] ]
424 | } ]
425 | }, {
426 | "Effect" : "Allow",
427 | "Action" : [ "sns:AddPermission", "sns:RemovePermission" ],
428 | "Resource" : {
429 | "Fn::Join" : [ "", [ "arn:aws:sns:", {
430 | "Ref" : "AWS::Region"
431 | }, ":", {
432 | "Ref" : "AWS::AccountId"
433 | }, ":CrossAccountManager-AccountTopic" ] ]
434 | }
435 | }, {
436 | "Effect" : "Allow",
437 | "Action" : [ "sns:Publish" ],
438 | "Resource" : [ {
439 | "Fn::Join" : [ "", [ "arn:aws:sns:", {
440 | "Ref" : "AWS::Region"
441 | }, ":", {
442 | "Ref" : "AWS::AccountId"
443 | }, ":CrossAccountManager-RoleTopic" ] ]
444 | } ,{
445 | "Fn::Join" : [ "", [ "arn:aws:sns:", {
446 | "Ref" : "AWS::Region"
447 | }, ":", {
448 | "Ref" : "AWS::AccountId"
449 | }, ":CrossAccountManager-AccessLinksTopic" ] ]
450 | } ]
451 | } ]
452 | }
453 | }
454 | },
455 | "IAMPolicy" : {
456 | "Type" : "AWS::IAM::Policy",
457 | "Properties" : {
458 | "Roles" : [ {
459 | "Ref" : "AccountEventHandlerExecRole"
460 | }, {
461 | "Ref" : "RoleEventHandlerExecRole"
462 | }, {
463 | "Ref" : "RoleFileHandlerExecRole"
464 | }, {
465 | "Ref" : "InitMasterAccountExecRole"
466 | } ],
467 | "PolicyName" : "IAM_Permissions",
468 | "PolicyDocument" : {
469 | "Version" : "2012-10-17",
470 | "Statement" : [ {
471 | "Effect" : "Allow",
472 | "Action" : [ "iam:CreateRole", "iam:DeleteRole", "iam:GetRole", "iam:PutRolePolicy", "iam:DeleteRolePolicy", "iam:GetRolePolicy" ],
473 | "Resource" : {
474 | "Fn::Join" : [ "", [ "arn:aws:iam:", ":", {
475 | "Ref" : "AWS::AccountId"
476 | }, ":role/CrossAccountManager-*" ] ]
477 | }
478 | },{
479 | "Effect" : "Allow",
480 | "Action" : [ "iam:ListRoles" ],
481 | "Resource" : {
482 | "Fn::Join" : [ "", [ "arn:aws:iam:", ":", {
483 | "Ref" : "AWS::AccountId"
484 | }, ":role/" ] ]
485 | }
486 | } ]
487 | }
488 | }
489 | },
490 | "AccountTopic" : {
491 | "Type" : "AWS::SNS::Topic",
492 | "Properties" : {
493 | "TopicName" : "CrossAccountManager-AccountTopic",
494 | "DisplayName" : "CrossAccountManager-AccountTopic",
495 | "Subscription" : [ {
496 | "Endpoint" : {
497 | "Fn::GetAtt" : [ "AccountEventHandler", "Arn" ]
498 | },
499 | "Protocol" : "lambda"
500 | } ]
501 | }
502 | },
503 | "RoleTopic" : {
504 | "Type" : "AWS::SNS::Topic",
505 | "Properties" : {
506 | "TopicName" : "CrossAccountManager-RoleTopic",
507 | "DisplayName" : "CrossAccountManager-RoleTopic",
508 | "Subscription" : [ {
509 | "Endpoint" : {
510 | "Fn::GetAtt" : [ "RoleEventHandler", "Arn" ]
511 | },
512 | "Protocol" : "lambda"
513 | } ]
514 | }
515 | },
516 | "AccessLinksTopic" : {
517 | "Type" : "AWS::SNS::Topic",
518 | "Properties" : {
519 | "TopicName" : "CrossAccountManager-AccessLinksTopic",
520 | "DisplayName" : "CrossAccountManager-AccessLinksTopic",
521 | "Subscription" : [ {
522 | "Endpoint" : {
523 | "Fn::GetAtt" : [ "AccessLinksHandler", "Arn" ]
524 | },
525 | "Protocol" : "lambda"
526 | } ]
527 | }
528 | },
529 | "AccessLinksHandlerInvokePermission" : {
530 | "Type" : "AWS::Lambda::Permission",
531 | "Properties" : {
532 | "FunctionName" : {
533 | "Fn::GetAtt" : [ "AccessLinksHandler", "Arn" ]
534 | },
535 | "Action" : "lambda:InvokeFunction",
536 | "Principal" : "sns.amazonaws.com",
537 | "SourceArn" : {
538 | "Ref" : "AccessLinksTopic"
539 | }
540 | }
541 | },
542 | "AccountEventHandlerInvokePermission" : {
543 | "Type" : "AWS::Lambda::Permission",
544 | "Properties" : {
545 | "FunctionName" : {
546 | "Fn::GetAtt" : [ "AccountEventHandler", "Arn" ]
547 | },
548 | "Action" : "lambda:InvokeFunction",
549 | "Principal" : "sns.amazonaws.com",
550 | "SourceArn" : {
551 | "Ref" : "AccountTopic"
552 | }
553 | }
554 | },
555 | "AccountFileHandlerInvokePermission" : {
556 | "Type" : "AWS::Lambda::Permission",
557 | "Properties" : {
558 | "FunctionName" : {
559 | "Fn::GetAtt" : [ "AccountFileHandler", "Arn" ]
560 | },
561 | "Action" : "lambda:InvokeFunction",
562 | "Principal" : "s3.amazonaws.com",
563 | "SourceArn" : {
564 | "Fn::Join" : [ "", [ "arn:aws:s3:::", {
565 | "Ref" : "ConfigBucket"
566 | } ] ]
567 | }
568 | }
569 | },
570 | "RoleFileHandlerInvokePermission" : {
571 | "Type" : "AWS::Lambda::Permission",
572 | "Properties" : {
573 | "FunctionName" : {
574 | "Fn::GetAtt" : [ "RoleFileHandler", "Arn" ]
575 | },
576 | "Action" : "lambda:InvokeFunction",
577 | "Principal" : "s3.amazonaws.com",
578 | "SourceArn" : {
579 | "Fn::Join" : [ "", [ "arn:aws:s3:::", {
580 | "Ref" : "ConfigBucket"
581 | } ] ]
582 | }
583 | }
584 | },
585 | "RoleEventHandlerInvokePermission" : {
586 | "Type" : "AWS::Lambda::Permission",
587 | "Properties" : {
588 | "FunctionName" : {
589 | "Fn::GetAtt" : [ "RoleEventHandler", "Arn" ]
590 | },
591 | "Action" : "lambda:InvokeFunction",
592 | "Principal" : "sns.amazonaws.com",
593 | "SourceArn" : {
594 | "Ref" : "RoleTopic"
595 | }
596 | }
597 | },
598 | "AccessLinkBucket" : {
599 | "Type" : "AWS::S3::Bucket",
600 | "DeletionPolicy" : "Retain",
601 | "Properties" : {
602 | "BucketName" : {
603 | "Ref" : "AccessLinksBucket"
604 | },
605 | "VersioningConfiguration" : {
606 | "Status" : "Enabled"
607 | }
608 | }
609 | },
610 | "CAMConfigBucket" : {
611 | "Type" : "AWS::S3::Bucket",
612 | "DeletionPolicy" : "Retain",
613 | "DependsOn" : [ "AccountFileHandlerInvokePermission", "RoleFileHandlerInvokePermission" ],
614 | "Properties" : {
615 | "BucketName" : {
616 | "Ref" : "ConfigBucket"
617 | },
618 | "VersioningConfiguration" : {
619 | "Status" : "Enabled"
620 | },
621 | "NotificationConfiguration" : {
622 | "LambdaConfigurations" : [ {
623 | "Event" : "s3:ObjectCreated:*",
624 | "Function" : {
625 | "Fn::GetAtt" : [ "AccountFileHandler", "Arn" ]
626 | },
627 | "Filter" : {
628 | "S3Key" : {
629 | "Rules" : [ {
630 | "Name" : "prefix",
631 | "Value" : "account"
632 | }, {
633 | "Name" : "suffix",
634 | "Value" : ".yml"
635 | } ]
636 | }
637 | }
638 | }, {
639 | "Event" : "s3:ObjectCreated:*",
640 | "Function" : {
641 | "Fn::GetAtt" : [ "AccountFileHandler", "Arn" ]
642 | },
643 | "Filter" : {
644 | "S3Key" : {
645 | "Rules" : [ {
646 | "Name" : "prefix",
647 | "Value" : "account"
648 | }, {
649 | "Name" : "suffix",
650 | "Value" : ".yaml"
651 | } ]
652 | }
653 | }
654 | },{
655 | "Event" : "s3:ObjectCreated:*",
656 | "Function" : {
657 | "Fn::GetAtt" : [ "RoleFileHandler", "Arn" ]
658 | },
659 | "Filter" : {
660 | "S3Key" : {
661 | "Rules" : [ {
662 | "Name" : "prefix",
663 | "Value" : "role"
664 | }, {
665 | "Name" : "suffix",
666 | "Value" : ".yml"
667 | } ]
668 | }
669 | }
670 | },{
671 | "Event" : "s3:ObjectCreated:*",
672 | "Function" : {
673 | "Fn::GetAtt" : [ "RoleFileHandler", "Arn" ]
674 | },
675 | "Filter" : {
676 | "S3Key" : {
677 | "Rules" : [ {
678 | "Name" : "prefix",
679 | "Value" : "role"
680 | }, {
681 | "Name" : "suffix",
682 | "Value" : ".yaml"
683 | } ]
684 | }
685 | }
686 | } ]
687 | }
688 | }
689 | },
690 | "CAMConfigBucketPolicy" : {
691 | "Type" : "AWS::S3::BucketPolicy",
692 | "Properties" : {
693 | "Bucket" : {
694 | "Ref" : "CAMConfigBucket"
695 | },
696 | "PolicyDocument" : {
697 | "Statement" : [ {
698 | "Sid" : "DenyUnEncryptedObjectUploads",
699 | "Effect" : "Deny",
700 | "Principal" : "*",
701 | "Action" : "s3:PutObject",
702 | "Resource" : [ {
703 | "Fn::Join" : [ "", [ "arn:aws:s3:::", {
704 | "Ref" : "CAMConfigBucket"
705 | }, "/account", "*" ] ]
706 | }, {
707 | "Fn::Join" : [ "", [ "arn:aws:s3:::", {
708 | "Ref" : "CAMConfigBucket"
709 | }, "/role", "*" ] ]
710 | }, {
711 | "Fn::Join" : [ "", [ "arn:aws:s3:::", {
712 | "Ref" : "CAMConfigBucket"
713 | }, "/custom_policy", "*" ] ]
714 | } ],
715 | "Condition" : {
716 | "StringNotEquals" : {
717 | "s3:x-amz-server-side-encryption" : "aws:kms"
718 | }
719 | }
720 | }, {
721 | "Action" : [ "s3:GetObject", "s3:PutObject", "s3:PutObjectAcl" ],
722 | "Effect" : "Allow",
723 | "Resource" : [ {
724 | "Fn::Join" : [ "", [ "arn:aws:s3:::", {
725 | "Ref" : "CAMConfigBucket"
726 | }, "/account", "*" ] ]
727 | }, {
728 | "Fn::Join" : [ "", [ "arn:aws:s3:::", {
729 | "Ref" : "CAMConfigBucket"
730 | }, "/role", "*" ] ]
731 | }, {
732 | "Fn::Join" : [ "", [ "arn:aws:s3:::", {
733 | "Ref" : "CAMConfigBucket"
734 | }, "/custom_policy", "*" ] ]
735 | } ],
736 | "Principal" : {
737 | "AWS" : [ {
738 | "Fn::Join" : [ "", [ "arn:aws:iam::", {
739 | "Ref" : "AWS::AccountId"
740 | }, ":root" ] ]
741 | } ]
742 | }
743 | } ]
744 | }
745 | }
746 | },
747 | "CAMKey" : {
748 | "Type" : "AWS::KMS::Key",
749 | "Properties" : {
750 | "Description" : "Account Management CMK for S3 SSE-KMS",
751 | "EnableKeyRotation" : true,
752 | "KeyPolicy" : {
753 | "Version" : "2012-10-17",
754 | "Id" : "CrossAccountManager-key-1",
755 | "Statement" : [ {
756 | "Sid" : "Enable IAM User Permissions",
757 | "Effect" : "Allow",
758 | "Principal" : {
759 | "AWS" : [ {
760 | "Fn::Join" : [ "", [ "arn:aws:iam::", {
761 | "Ref" : "AWS::AccountId"
762 | }, ":root" ] ]
763 | } ]
764 | },
765 | "Action" : [ "kms:*" ],
766 | "Resource" : "*"
767 | }, {
768 | "Sid" : "Allow use of the key",
769 | "Effect" : "Allow",
770 | "Principal" : {
771 | "AWS" : [ {
772 | "Fn::GetAtt" : [ "SolutionHelperRole", "Arn" ]
773 | }, {
774 | "Fn::GetAtt" : [ "AccountFileHandlerExecRole", "Arn" ]
775 | }, {
776 | "Fn::GetAtt" : [ "RoleFileHandlerExecRole", "Arn" ]
777 | }, {
778 | "Fn::GetAtt" : [ "AccountEventHandlerExecRole", "Arn" ]
779 | }, {
780 | "Fn::GetAtt" : [ "AccessLinksHandlerExecRole", "Arn" ]
781 | } ]
782 | },
783 | "Action" : [ "kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:DescribeKey" ],
784 | "Resource" : "*"
785 | } ]
786 | }
787 | }
788 | },
789 | "CAMKeyAlias" : {
790 | "Type" : "AWS::KMS::Alias",
791 | "Properties" : {
792 | "AliasName" : "alias/CrossAccountManager-Key",
793 | "TargetKeyId" : {
794 | "Ref" : "CAMKey"
795 | }
796 | }
797 | },
798 | "CAMConfigBucketS3Folders" : {
799 | "Type" : "Custom::SolutionHelper",
800 | "DependsOn" : [ "CAMConfigBucket" ],
801 | "Properties" : {
802 | "ServiceToken" : {
803 | "Fn::GetAtt" : [ "SolutionHelper", "Arn" ]
804 | },
805 | "StoreInS3KMS" : {
806 | "Fn::Join" : [ "", [ "[{ 'Bucket' : '", {
807 | "Ref" : "CAMConfigBucket"
808 | }, "', ", "'Key' : 'account/', ", "'SSEKMSKeyId' : 'arn:aws:kms:", {
809 | "Ref" : "AWS::Region"
810 | }, ":", {
811 | "Ref" : "AWS::AccountId"
812 | }, ":key/", {
813 | "Ref" : "CAMKey"
814 | }, "', ", "'Body': ''", "},{ 'Bucket' : '", {
815 | "Ref" : "CAMConfigBucket"
816 | }, "', ", "'Key' : 'role/', ", "'SSEKMSKeyId' : 'arn:aws:kms:", {
817 | "Ref" : "AWS::Region"
818 | }, ":", {
819 | "Ref" : "AWS::AccountId"
820 | }, ":key/", {
821 | "Ref" : "CAMKey"
822 | }, "', ", "'Body': ''", "},{ 'Bucket' : '", {
823 | "Ref" : "CAMConfigBucket"
824 | }, "', ", "'Key' : 'custom_policy/', ", "'SSEKMSKeyId' : 'arn:aws:kms:", {
825 | "Ref" : "AWS::Region"
826 | }, ":", {
827 | "Ref" : "AWS::AccountId"
828 | }, ":key/", {
829 | "Ref" : "CAMKey"
830 | }, "', ", "'Body': ''", "}]" ] ]
831 | }
832 | }
833 | },
834 | "CreateAccountsDDBTable" : {
835 | "Type" : "AWS::DynamoDB::Table",
836 | "Properties" : {
837 | "AttributeDefinitions" : [ {
838 | "AttributeName" : "AccountId",
839 | "AttributeType" : "S"
840 | } ],
841 | "KeySchema" : [ {
842 | "AttributeName" : "AccountId",
843 | "KeyType" : "HASH"
844 | } ],
845 | "ProvisionedThroughput" : {
846 | "ReadCapacityUnits" : "5",
847 | "WriteCapacityUnits" : "5"
848 | },
849 | "TableName" : "CrossAccountManager-Accounts"
850 | }
851 | },
852 | "CreateRolesDDBTable" : {
853 | "Properties" : {
854 | "AttributeDefinitions" : [ {
855 | "AttributeName" : "Role",
856 | "AttributeType" : "S"
857 | } ],
858 | "KeySchema" : [ {
859 | "AttributeName" : "Role",
860 | "KeyType" : "HASH"
861 | } ],
862 | "ProvisionedThroughput" : {
863 | "ReadCapacityUnits" : "5",
864 | "WriteCapacityUnits" : "5"
865 | },
866 | "TableName" : "CrossAccountManager-Roles"
867 | },
868 | "Type" : "AWS::DynamoDB::Table"
869 | },
870 | "CreateAccountRolesDDBTable" : {
871 | "Properties" : {
872 | "AttributeDefinitions" : [ {
873 | "AttributeName" : "Role",
874 | "AttributeType" : "S"
875 | }, {
876 | "AttributeName" : "AccountId",
877 | "AttributeType" : "S"
878 | } ],
879 | "KeySchema" : [ {
880 | "AttributeName" : "Role",
881 | "KeyType" : "HASH"
882 | }, {
883 | "AttributeName" : "AccountId",
884 | "KeyType" : "RANGE"
885 | } ],
886 | "ProvisionedThroughput" : {
887 | "ReadCapacityUnits" : "5",
888 | "WriteCapacityUnits" : "5"
889 | },
890 | "TableName" : "CrossAccountManager-Account-Roles"
891 | },
892 | "Type" : "AWS::DynamoDB::Table"
893 | },
894 | "SendingAnonymousData" : {
895 | "Type" : "Custom::LoadLambda",
896 | "Condition" : "SendData",
897 | "Properties" : {
898 | "ServiceToken" : {
899 | "Fn::GetAtt" : [ "SolutionHelper", "Arn" ]
900 | },
901 | "SendAnonymousData" : {
902 | "Fn::Join" : [ "", [ "{ 'Solution' : '", "SO0015", "', ", "'UUID' : '", {
903 | "Fn::GetAtt" : [ "CreateUniqueID", "UUID" ]
904 | }, "', ", "'Data': {", "'Version': '1'}", "}" ] ]
905 | }
906 | }
907 | },
908 | "CreateUniqueID" : {
909 | "Type" : "Custom::LoadLambda",
910 | "Properties" : {
911 | "ServiceToken" : {
912 | "Fn::GetAtt" : [ "SolutionHelper", "Arn" ]
913 | },
914 | "Region" : {
915 | "Ref" : "AWS::Region"
916 | },
917 | "CreateUniqueID" : "true"
918 | }
919 | },
920 | "InitMasterAccountCustomResource" : {
921 | "Type" : "Custom::InitMasterAccountCustomResource",
922 | "DependsOn" : [ "CloudwatchLogsCloudformationPolicy" ],
923 | "Properties" : {
924 | "ServiceToken" : {
925 | "Fn::GetAtt" : [ "InitMasterAccount", "Arn" ]
926 | }
927 | }
928 | }
929 | },
930 | "Outputs" : {
931 | "CAMConfigBucket" : {
932 | "Description" : "S3 bucket to input files for the solution",
933 | "Value" : {
934 | "Ref" : "CAMConfigBucket"
935 | }
936 | },
937 | "AccessLinksBucket" : {
938 | "Description" : "S3 bucket for storing the Access Links page with shortucts to all the Sub-Accounts/Roles managed by the solution",
939 | "Value" : {
940 | "Ref" : "AccessLinkBucket"
941 | }
942 | },
943 | "KMSKeyAlias" : {
944 | "Description" : "KMS Customer Master Key to upload files to S3 Config bucket",
945 | "Value" : {
946 | "Ref" : "CAMKeyAlias"
947 | }
948 | },
949 | "UUID" : {
950 | "Description" : "Newly created random UUID.",
951 | "Value" : {
952 | "Fn::GetAtt" : [ "CreateUniqueID", "UUID" ]
953 | }
954 | },
955 | "AnonymousData" : {
956 | "Description" : "Send Anonymous Data",
957 | "Value" : {
958 | "Ref" : "SendAnonymousData"
959 | }
960 | }
961 | }
962 | }
963 |
--------------------------------------------------------------------------------
/aws-cross-account-manager-sub.template:
--------------------------------------------------------------------------------
1 | {
2 | "AWSTemplateFormatVersion" : "2010-09-09",
3 | "Description" : "(SO0015s) - Cross-Account Manager Solution: Sub Account template ",
4 | "Parameters" : {
5 | "MasterAccountID" : {
6 | "Description" : "Account ID of the master account.",
7 | "Type" : "String",
8 | "ConstraintDescription" : "Must be a valid AWS Account ID without hyphens.",
9 | "AllowedPattern" : "\\d{12}",
10 | "MinLength" : "12",
11 | "MaxLength" : "12"
12 | }
13 | },
14 | "Resources" : {
15 | "InitSubAccountExecRole" : {
16 | "Type" : "AWS::IAM::Role",
17 | "Properties" : {
18 | "AssumeRolePolicyDocument" : {
19 | "Version" : "2012-10-17",
20 | "Statement" : [ {
21 | "Effect" : "Allow",
22 | "Principal" : {
23 | "Service" : [ "lambda.amazonaws.com" ]
24 | },
25 | "Action" : [ "sts:AssumeRole" ]
26 | } ]
27 | },
28 | "Policies" : [ {
29 | "PolicyName" : "Cloudwatch_Logs_SNS_Permissions",
30 | "PolicyDocument" : {
31 | "Version" : "2012-10-17",
32 | "Statement" : [ {
33 | "Effect" : "Allow",
34 | "Action" : [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ],
35 | "Resource" : {
36 | "Fn::Join" : [ "", [ "arn:aws:logs:", {
37 | "Ref" : "AWS::Region"
38 | }, ":", {
39 | "Ref" : "AWS::AccountId"
40 | }, ":log-group:/aws/lambda/*" ] ]
41 | }
42 | }, {
43 | "Effect" : "Allow",
44 | "Action" : [ "sns:Publish" ],
45 | "Resource" : {
46 | "Fn::Join" : [ "", [ "arn:aws:sns:", {
47 | "Ref" : "AWS::Region"
48 | }, ":", {
49 | "Ref" : "MasterAccountID"
50 | }, ":CrossAccountManager-AccountTopic" ] ]
51 | }
52 | } ]
53 | }
54 | } ]
55 | }
56 | },
57 | "InitSubAccount" : {
58 | "Type" : "AWS::Lambda::Function",
59 | "Properties" : {
60 | "Code" : {
61 | "S3Bucket" : {
62 | "Fn::Join" : [ "", [ "solutions-", {
63 | "Ref" : "AWS::Region"
64 | } ] ]
65 | },
66 | "S3Key" : "cross-account-manager/v1/cross-account-handler.zip"
67 | },
68 | "Description" : "This Lambda function subscribes SNS topic from Master Account",
69 | "Handler" : "index.handleSubAccountInit",
70 | "Role" : {
71 | "Fn::GetAtt" : [ "InitSubAccountExecRole", "Arn" ]
72 | },
73 | "Runtime" : "nodejs4.3",
74 | "Timeout" : "60"
75 | }
76 | },
77 | "InitSubAccountCustomResource" : {
78 | "Type" : "Custom::InitSubAccountCustomResource",
79 | "DependsOn" : "CAMAdmin",
80 | "Properties" : {
81 | "ServiceToken" : {
82 | "Fn::GetAtt" : [ "InitSubAccount", "Arn" ]
83 | },
84 | "MasterAccountID" : {
85 | "Ref" : "MasterAccountID"
86 | }
87 | }
88 | },
89 | "CAMAdmin" : {
90 | "Type" : "AWS::IAM::Role",
91 | "Properties" : {
92 | "Path" : "/",
93 | "RoleName" : "CrossAccountManager-Admin-DO-NOT-DELETE",
94 | "AssumeRolePolicyDocument" : {
95 | "Version" : "2012-10-17",
96 | "Statement" : [ {
97 | "Effect" : "Allow",
98 | "Principal" : {
99 | "AWS" : {
100 | "Fn::Join" : [ "", [ "arn:aws:iam::", {
101 | "Ref" : "MasterAccountID"
102 | }, ":role/CrossAccountManager-Admin-DO-NOT-DELETE" ] ]
103 | }
104 | },
105 | "Action" : [ "sts:AssumeRole" ]
106 | } ]
107 | }
108 | }
109 | },
110 | "IAMPermissionsPolicy" : {
111 | "Type" : "AWS::IAM::Policy",
112 | "Properties" : {
113 | "Roles" : [ {
114 | "Ref" : "CAMAdmin"
115 | }, {
116 | "Ref" : "InitSubAccountExecRole"
117 | } ],
118 | "PolicyName" : "IAM_Permissions",
119 | "PolicyDocument" : {
120 | "Version" : "2012-10-17",
121 | "Statement" : [ {
122 | "Effect" : "Allow",
123 | "Action" : [ "iam:CreateRole", "iam:DeleteRole", "iam:GetRole", "iam:PutRolePolicy", "iam:DeleteRolePolicy" ],
124 | "Resource" : {
125 | "Fn::Join" : [ "", [ "arn:aws:iam:", ":", {
126 | "Ref" : "AWS::AccountId"
127 | }, ":role/CrossAccountManager-*" ] ]
128 | }
129 | },{
130 | "Effect" : "Allow",
131 | "Action" : [ "iam:ListRoles" ],
132 | "Resource" : {
133 | "Fn::Join" : [ "", [ "arn:aws:iam:", ":", {
134 | "Ref" : "AWS::AccountId"
135 | }, ":role/" ] ]
136 | }
137 | } ]
138 | }
139 | }
140 | }
141 | },
142 | "Outputs" : {
143 | "CAMConfigBucket" : {
144 | "Description" : "CrossAccountManager Admin role for the solution",
145 | "Value" : {
146 | "Ref" : "CAMAdmin"
147 | }
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/code/cross-account-handler/LICENSE:
--------------------------------------------------------------------------------
1 | Amazon Software License
2 |
3 | 1. Definitions
4 |
5 | "Licensor" means any person or entity that distributes its Work.
6 |
7 | "Software" means the original work of authorship made available under this
8 | License.
9 |
10 | "Work" means the Software and any additions to or derivative works of the
11 | Software that are made available under this License.
12 |
13 | The terms "reproduce," "reproduction," "derivative works," and
14 | "distribution" have the meaning as provided under U.S. copyright law;
15 | provided, however, that for the purposes of this License, derivative works
16 | shall not include works that remain separable from, or merely link (or bind
17 | by name) to the interfaces of, the Work.
18 |
19 | Works, including the Software, are "made available" under this License by
20 | including in or with the Work either (a) a copyright notice referencing the
21 | applicability of this License to the Work, or (b) a copy of this License.
22 |
23 | 2. License Grants
24 |
25 | 2.1 Copyright Grant. Subject to the terms and conditions of this License, each
26 | Licensor grants to you a perpetual, worldwide, non-exclusive, royalty-free,
27 | copyright license to reproduce, prepare derivative works of, publicly
28 | display, publicly perform, sublicense and distribute its Work and any
29 | resulting derivative works in any form.
30 |
31 | 2.2 Patent Grant. Subject to the terms and conditions of this License, each
32 | Licensor grants to you a perpetual, worldwide, non-exclusive, royalty-free
33 | patent license to make, have made, use, sell, offer for sale, import, and
34 | otherwise transfer its Work, in whole or in part. The foregoing license
35 | applies only to the patent claims licensable by Licensor that would be
36 | infringed by Licensor's Work (or portion thereof) individually and
37 | excluding any combinations with any other materials or technology.
38 |
39 | 3. Limitations
40 |
41 | 3.1 Redistribution. You may reproduce or distribute the Work only if (a) you do
42 | so under this License, (b) you include a complete copy of this License with
43 | your distribution, and (c) you retain without modification any copyright,
44 | patent, trademark, or attribution notices that are present in the Work.
45 |
46 | 3.2 Derivative Works. You may specify that additional or different terms apply
47 | to the use, reproduction, and distribution of your derivative works of the
48 | Work ("Your Terms") only if (a) Your Terms provide that the use
49 | limitation in Section 3.3 applies to your derivative works, and (b) you
50 | identify the specific derivative works that are subject to Your Terms.
51 | Notwithstanding Your Terms, this License (including the redistribution
52 | requirements in Section 3.1) will continue to apply to the Work itself.
53 |
54 | 3.3 Use Limitation. The Work and any derivative works thereof only may be used
55 | or intended for use with the web services, computing platforms or
56 | applications provided by Amazon.com, Inc. or its affiliates, including Amazon
57 | Web Services, Inc.
58 |
59 | 3.4 Patent Claims. If you bring or threaten to bring a patent claim
60 | against any Licensor (including any claim, cross-claim or counterclaim in a
61 | lawsuit) to enforce any patents that you allege are infringed by any Work,
62 | then your rights under this License from such Licensor (including the grants
63 | in Sections 2.1 and 2.2) will terminate immediately.
64 |
65 | 3.5 Trademarks. This License does not grant any rights to use any
66 | Licensor's or its affiliates' names, logos, or trademarks, except as
67 | necessary to reproduce the notices described in this License.
68 |
69 | 3.6 Termination. If you violate any term of this License, then your rights
70 | under this License (including the grants in Sections 2.1 and 2.2) will
71 | terminate immediately.
72 |
73 | 4. Disclaimer of Warranty.
74 |
75 | THE WORK IS PROVIDED "AS IS" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
76 | EITHER EXPRESS OR IMPLIED, INCLUDING WARRANTIES OR CONDITIONS OF M
77 | ERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR
78 | NON-INFRINGEMENT. YOU BEAR THE RISK OF UNDERTAKING ANY ACTIVITIES UNDER THIS
79 | LICENSE. SOME STATES' CONSUMER LAWS DO NOT ALLOW EXCLUSION OF AN IMPLIED
80 | WARRANTY, SO THIS DISCLAIMER MAY NOT APPLY TO YOU.
81 |
82 | 5. Limitation of Liability.
83 |
84 | EXCEPT AS PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL
85 | THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE SHALL
86 | ANY LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT,
87 | INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR
88 | RELATED TO THIS LICENSE, THE USE OR INABILITY TO USE THE WORK (INCLUDING BUT
89 | NOT LIMITED TO LOSS OF GOODWILL, BUSINESS INTERRUPTION, LOST PROFITS OR DATA,
90 | COMPUTER FAILURE OR MALFUNCTION, OR ANY OTHER COMM ERCIAL DAMAGES OR
91 | LOSSES), EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
92 | DAMAGES.
93 |
94 | Effective Date - April 18, 2008 (C) 2008 Amazon.com, Inc. or its
95 | affiliates. All rights reserved.
96 |
97 | Note: Other license terms may apply to certain, identified software files
98 | contained within or distributed with the accompanying software if such terms
99 | are included in the directory containing the accompanying software. Such other
100 | license terms will then apply in lieu of the terms of the software
101 | license above.
102 |
--------------------------------------------------------------------------------
/code/cross-account-handler/NOTICE.txt:
--------------------------------------------------------------------------------
1 | Cross Account Manager
2 | Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 |
--------------------------------------------------------------------------------
/code/cross-account-handler/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Amazon Software License (the "License"). You
5 | * may not use this file except in compliance with the License. A copy
6 | * of the License is located at
7 | *
8 | * http://aws.amazon.com/asl/
9 | *
10 | * or in the "license" file accompanying this file. This file is
11 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12 | * ANY KIND, express or implied. See the License for the specific
13 | * language governing permissions and limitations under the License.
14 | */
15 |
16 | var fileHandler = require('./lib/file_handler.js');
17 | var eventHandler = require('./lib/event_handler.js');
18 | var accessLinksHandler = require("./lib/accessLinks_handler.js");
19 | var accountInit = require("./lib/account_init.js");
20 |
21 | /*
22 | * Entry point for the lambda functions.
23 | * Each handler function is called from the respective Lambda function in master account.
24 | */
25 |
26 | exports.handleAccountS3File = function(event, context, callback) {
27 | console.log('EVENT ' + JSON.stringify(event, null, 2));
28 | console.log('CONTEXT ' + JSON.stringify(context, null, 2));
29 |
30 | fileHandler.handleAccountS3File(event, context, callback);
31 | };
32 |
33 | exports.handleAccountEvent = function(event, context, callback) {
34 | console.log('EVENT ' + JSON.stringify(event, null, 2));
35 | console.log('CONTEXT ' + JSON.stringify(context, null, 2));
36 |
37 | eventHandler.handleAccountEvent(event, context, callback);
38 | };
39 |
40 | exports.handleRoleS3File = function(event, context, callback) {
41 | console.log('EVENT ' + JSON.stringify(event, null, 2));
42 | console.log('CONTEXT ' + JSON.stringify(context, null, 2));
43 |
44 | fileHandler.handleRoleS3File(event, context, callback);
45 | };
46 |
47 | exports.handleRoleEvent = function(event, context, callback) {
48 | console.log('EVENT ' + JSON.stringify(event, null, 2));
49 | console.log('CONTEXT ' + JSON.stringify(context, null, 2));
50 |
51 | eventHandler.handleRoleEvent(event, context, callback);
52 | };
53 |
54 | exports.handleAccessLinksEvent = function(event, context, callback) {
55 | console.log('EVENT ' + JSON.stringify(event, null, 2));
56 | console.log('CONTEXT ' + JSON.stringify(context, null, 2));
57 |
58 | accessLinksHandler.handleAccessLinksEvent(event, context, callback);
59 | };
60 |
61 | exports.handleSubAccountInit = function(event, context, callback) {
62 | console.log('EVENT ' + JSON.stringify(event, null, 2));
63 | console.log('CONTEXT ' + JSON.stringify(context, null, 2));
64 |
65 | accountInit.initSubAccount(event, context, callback);
66 | };
67 |
68 | exports.handleMasterAccountInit = function(event, context, callback) {
69 | console.log('EVENT ' + JSON.stringify(event, null, 2));
70 | console.log('CONTEXT ' + JSON.stringify(context, null, 2));
71 |
72 | accountInit.initMasterAccount(event, context, callback);
73 | };
--------------------------------------------------------------------------------
/code/cross-account-handler/lib/accessLinks_handler.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Amazon Software License (the "License"). You
5 | * may not use this file except in compliance with the License. A copy
6 | * of the License is located at
7 | *
8 | * http://aws.amazon.com/asl/
9 | *
10 | * or in the "license" file accompanying this file. This file is
11 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12 | * ANY KIND, express or implied. See the License for the specific
13 | * language governing permissions and limitations under the License.
14 | */
15 | "use strict";
16 |
17 | var AWS = require('aws-sdk');
18 | var DDB_Helper = require("./ddb_helper.js");
19 | var S3_Helper = require("./s3_helper.js");
20 | var Helper = require("./helper.js");
21 | var pre_html = '
Cross Account Manager LinksAWS Console Access
';
22 | var post_html = '';
23 |
24 | /*
25 | * This function updates the access links static webpage with shortcut
26 | * links to the accounts/roles managed by the solution.
27 | */
28 | exports.handleAccessLinksEvent = function(event, context, callback) {
29 | try
30 | {
31 | var s3 = new AWS.S3();
32 | var message = JSON.parse(event.Records[0].Sns.Message);
33 | console.log(message.Action + ":" + message.Role + ":" + message.SubAccountId);
34 |
35 | var role = "";
36 | var body_html = "";
37 |
38 |
39 | // Get all accounts managed by solution from Dynamo DB
40 | DDB_Helper.getAccountsByAccountGroup('*').then(function (accountItems) {
41 | var accounts = {};
42 |
43 | // Create a hashmap of account information by account ID
44 | accountItems.map(function (item) {
45 | accounts[item.AccountId] = item;
46 | });
47 |
48 | // Get all active accounts / roles combination in use
49 | DDB_Helper.getActiveAccountAndRoles().then(function (accRoles) {
50 | // For each combination create a shortcut URL
51 | accRoles.forEach(function(accRoleItem) {
52 | if (role != accRoleItem.Role)
53 | body_html += ''+accRoleItem.Role+'
';
54 | role = accRoleItem.Role;
55 | body_html += ''+accounts[accRoleItem.AccountId].Email+' '+accRoleItem.AccountId+' ';
56 | if (accounts[accRoleItem.AccountId].AccountGroup != '*')
57 | body_html += '('+accounts[accRoleItem.AccountId].AccountGroup+')';
58 | body_html += '
';
59 | });
60 |
61 | // Get the Access Links bucket name from the Stack Outputs
62 | Helper.getCFStackOutputs(context.invokedFunctionArn).then(function(outputs){
63 | outputs.map(function (output) {
64 | if (output.OutputKey == 'AccessLinksBucket') {
65 | var bucket = output.OutputValue;
66 |
67 | // Put the webpage in the access links bucket as cross-account-manager-links.html
68 | s3.putObject({Bucket: bucket,
69 | Key: 'cross-account-manager-links.html',
70 | Body: pre_html+body_html+post_html,
71 | ContentType: 'text/html'
72 | }, function(err, data) {
73 | if (err) {
74 | console.log(err, err.stack);
75 | callback(err, null);
76 | }
77 | });
78 | }
79 | });
80 | });
81 |
82 | });
83 | });
84 |
85 | } catch (e) {
86 | console.error(e, e.stack);
87 | callback(e, null);
88 | }
89 | }
90 |
91 |
92 |
--------------------------------------------------------------------------------
/code/cross-account-handler/lib/account_init.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Amazon Software License (the "License"). You
5 | * may not use this file except in compliance with the License. A copy
6 | * of the License is located at
7 | *
8 | * http://aws.amazon.com/asl/
9 | *
10 | * or in the "license" file accompanying this file. This file is
11 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12 | * ANY KIND, express or implied. See the License for the specific
13 | * language governing permissions and limitations under the License.
14 | */
15 |
16 | /*
17 | * Entry point for the lambda function for account init.
18 | * Each handler function is called from the respective Lambda function in master and sub account.
19 | */
20 |
21 | "use strict";
22 |
23 | var AWS = require('aws-sdk');
24 | var https = require("https");
25 | var url = require("url");
26 | var params = {};
27 |
28 | /*
29 | * This function sends the response back to the stack since this lambda function is uses as custom resource
30 | * during the stack initialization and destruction
31 | */
32 |
33 | function sendResponse(event, callback, logStreamName, status, data, err) {
34 | var reason = err ? err.message : '';
35 | var responseBody = {
36 | StackId : event.StackId,
37 | RequestId : event.RequestId,
38 | LogicalResourceId : event.LogicalResourceId,
39 | PhysicalResourceId : logStreamName,
40 | Status : status,
41 | Reason : reason + " See details in CloudWatch Log: " + logStreamName,
42 | Data : data
43 | };
44 |
45 | console.log("RESPONSE:\n", responseBody);
46 | var json = JSON.stringify(responseBody);
47 |
48 | const parsedUrl = url.parse(event.ResponseURL);
49 | const options = {
50 | hostname : parsedUrl.hostname,
51 | port : 443,
52 | path : parsedUrl.path,
53 | method : "PUT",
54 | headers : {
55 | "content-type" : "",
56 | "content-length" : json.length
57 | }
58 | };
59 |
60 | const request = https.request(options, function(response) {
61 | console.log("STATUS: " + response.statusCode);
62 | console.log("HEADERS: " + JSON.stringify(response.headers));
63 | callback(null, 'Successfully sent stack response!');
64 | });
65 |
66 | request.on("error", function(error) {
67 | console.log("sendResponse Error:\n", error);
68 | callback(err);
69 | });
70 |
71 | request.write(json);
72 | request.end();
73 | }
74 |
75 | /*
76 | * This callback function to delete all solution managed roles (CrossAccountManager-*) roles from account.
77 | */
78 | function _listroles_callback(err, data) {
79 | var iam = new AWS.IAM();
80 |
81 | if (err) {
82 | console.error("Unable to listRoles. Error JSON:", JSON.stringify(err, null, 2));
83 | } else {
84 | try {
85 | data.Roles.forEach(function(item) {
86 | var roleName = item.RoleName;
87 |
88 | if (roleName != 'CrossAccountManager-Admin-DO-NOT-DELETE' && roleName.startsWith('CrossAccountManager-')) {
89 | iam.deleteRolePolicy({
90 | PolicyName : roleName + '-Permission',
91 | RoleName : roleName
92 | }, function(err, data) {
93 | if (err) {
94 | console.log("ROLE policy does not exist " + roleName);
95 | } else {
96 | console.log("Deleted ROLE policy " + roleName);
97 | }
98 | iam.deleteRole({
99 | RoleName : roleName
100 | }, function(err, data) {
101 | if (err) {
102 | console.log("ERROR deleting ROLE: " + roleName);
103 | console.log(err, err.stack);
104 | } else {
105 | console.log("Deleted ROLE " + roleName);
106 | }
107 | });
108 | })
109 | }
110 | });
111 |
112 | // continue if we have more roles
113 | if (data.IsTruncated == true) {
114 | console.log("data.Marker=" + data.Marker);
115 | console.log("data.IsTruncated=" + data.IsTruncated);
116 | params.Marker = data.Marker;
117 | iam.listRoles(params, _listroles_callback);
118 | }
119 | }
120 | catch (e) {
121 | console.error(e, e.stack);
122 | }
123 | }
124 | }
125 |
126 | /*
127 | * This function deletes all solution managed roles (CrossAccountManager-*) roles from account.
128 | */
129 | function deleteCAMRoles() {
130 | console.log("Deleting CrossAccountManager-* roles managed by the solution");
131 | var iam = new AWS.IAM();
132 |
133 | try {
134 | params = {
135 | PathPrefix: '/'
136 | };
137 | iam.listRoles(params, function(err, data){
138 | _listroles_callback(err, data);
139 | });
140 | }
141 | catch (e) {
142 | console.error(e, e.stack);
143 | }
144 | }
145 |
146 | /* This function is called when the sub-account template is deployed.
147 | * It pubslihses a message with action = ADD or REMOVE to the account topic in master account
148 | * It also remvoes the solution managed roles (CrossAccountManager-*) roles from the sub account
149 | */
150 | function _initSubAccount(event, context, callback) {
151 | console.log('Started initSubAccount....');
152 |
153 | var sns = new AWS.SNS();
154 | var subAccountId = context.invokedFunctionArn.split(':')[4];
155 | var region = context.invokedFunctionArn.split(':')[3];
156 | var masterAccountId = event.ResourceProperties.MasterAccountID;
157 |
158 | console.log('subAccountId', subAccountId);
159 | console.log('region', region);
160 | console.log('masterAccountId', masterAccountId);
161 |
162 | var action = "ADD";
163 |
164 | if (event.RequestType == 'Delete') {
165 | action = "REMOVE";
166 | deleteCAMRoles();
167 | }
168 |
169 | var msg = {
170 | Action : action,
171 | SubAccountId : subAccountId
172 | };
173 | var topic = "arn:aws:sns:" + region + ":" + masterAccountId + ":CrossAccountManager-AccountTopic";
174 |
175 | var params = {
176 | Message : JSON.stringify(msg),
177 | TopicArn : topic
178 | };
179 |
180 | console.log('Publishing message: ' + JSON.stringify(msg) + " to " + topic);
181 |
182 | sns.publish(params, function(err, data) {
183 | if (err) {
184 | console.log(err, err.stack);
185 | callback({Error: err});
186 | }
187 | else {
188 | console.log(data);
189 | callback(null, {Success: "Successfully published the message"});
190 | }
191 | });
192 | console.log('Completed initSubAccount....');
193 | }
194 |
195 | //Called from index.js
196 | exports.initSubAccount = function(event, context, callback) {
197 | _initSubAccount(event, context, function(err, result) {
198 | var status = err ? 'FAILED' : 'SUCCESS';
199 | sendResponse(event, callback, context.logStreamName, status, result, err);
200 | });
201 | };
202 |
203 | /* This function is called when the master-account template is deployed.
204 | * It does not do anything at the time of stack creation
205 | * It remvoes the solution managed roles (CrossAccountManager-*) roles from the master account when stack is deleted
206 | */
207 | function _initMasterAccount(event, context, callback) {
208 | console.log('Started initMasterAccount....');
209 |
210 | if (event.RequestType == 'Delete') {
211 | deleteCAMRoles();
212 | }
213 |
214 | callback(null, {Success: "Successfully completed"});
215 |
216 | console.log('Completed initMasterAccount....');
217 | }
218 |
219 | // Called from index.js
220 | exports.initMasterAccount = function(event, context, callback) {
221 | _initMasterAccount(event, context, function(err, result) {
222 | var status = err ? 'FAILED' : 'SUCCESS';
223 | sendResponse(event, callback, context.logStreamName, status, result, err);
224 | });
225 | };
--------------------------------------------------------------------------------
/code/cross-account-handler/lib/ddb_helper.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Amazon Software License (the "License"). You
5 | * may not use this file except in compliance with the License. A copy
6 | * of the License is located at
7 | *
8 | * http://aws.amazon.com/asl/
9 | *
10 | * or in the "license" file accompanying this file. This file is
11 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12 | * ANY KIND, express or implied. See the License for the specific
13 | * language governing permissions and limitations under the License.
14 | */
15 |
16 | /*
17 | * This module has functions to read and update data from Dynamo DB tables. The solution manages its operation state in following tables:
18 | * CrossAccountManager-Accounts: maintains the state of sub-accounts managed by solution
19 | * CrossAccountManager-Roles: maintains the state of roles managed by solution
20 | * CrossAccountManager-Account-Roles: maintains the state of roles provisioned into sub-accounts.
21 | */
22 |
23 | "use strict";
24 |
25 | var Promise = require('promise');
26 | var AWS = require('aws-sdk');
27 | var docClient = new AWS.DynamoDB.DocumentClient();
28 | var list = [];
29 | var params = {};
30 | var table = '';
31 |
32 | /*
33 | * Private: Callback function to handle dynamo db response and paginatation.
34 | */
35 | function _dynamo_callback(err, data) {
36 | if (err) {
37 | console.error("Unable to scan the " + table + " table. Error JSON:", JSON.stringify(err, null, 2));
38 | } else {
39 | try {
40 | data.Items.forEach(function(item) {
41 | list.push(item);
42 | });
43 |
44 | // continue scanning if we have more accounts
45 | if (typeof data.LastEvaluatedKey != "undefined") {
46 | console.log("Scanning for more...");
47 | params.ExclusiveStartKey = data.LastEvaluatedKey;
48 | docClient.scan(params, _dynamo_callback);
49 | }
50 | }
51 | catch (e) {
52 | console.error(e, e.stack);
53 | }
54 | }
55 | }
56 |
57 | /*
58 | * Private: Function to scan Dynamo DB table for account, role or account-role data
59 | */
60 | function _scanByAccountGroup(tableName, account_group, callback) {
61 | try {
62 | // Filter data by status = Active
63 | params = {
64 | TableName : tableName,
65 | FilterExpression: "(#status = :status)",
66 | ExpressionAttributeNames: {
67 | "#status": "Status"
68 | },
69 | ExpressionAttributeValues: {
70 | ":status": 'active'
71 | }
72 | };
73 |
74 | // Some more filtering based on tablename to handle account grouping feature
75 | if ((tableName == 'CrossAccountManager-Accounts' && account_group != '*')) {
76 | params.FilterExpression += ' and #account_group = :account_group';
77 | params.ExpressionAttributeNames['#account_group'] = 'AccountGroup';
78 | params.ExpressionAttributeValues[':account_group'] = account_group;
79 | } else if (tableName == 'CrossAccountManager-Roles') {
80 | params.FilterExpression += ' and (#account_group = :account_group1 OR #account_group = :account_group2)';
81 | params.ExpressionAttributeNames['#account_group'] = 'AccountGroup';
82 | params.ExpressionAttributeValues[':account_group1'] = account_group;
83 | params.ExpressionAttributeValues[':account_group2'] = '*';
84 | }
85 |
86 | // Scan DDB table based on filtering criteria
87 | docClient.scan(params, function(err, data){
88 | list = [];
89 | table = tableName;
90 | _dynamo_callback(err, data);
91 | console.log('Scaning ' + tableName + ' for Status=active, and AccountGroup = ' + account_group);
92 | return callback(null, list);
93 | });
94 | }
95 | catch (e) {
96 | console.error(e, e.stack);
97 | return callback(e);
98 | }
99 | }
100 |
101 | /*
102 | * Public: Function to get all items from a given table
103 | */
104 | exports.getListOfItems = function (tableName, callback) {
105 | try {
106 | console.log("Scaning " + tableName + " table.");
107 |
108 | docClient.scan({ TableName : tableName }, function(err, data){
109 | list = [];
110 | table = tableName;
111 | _dynamo_callback(err, data);
112 | return callback(null, list);
113 | });
114 | }
115 | catch (e) {
116 | console.error(e, e.stack);
117 | return callback(e);
118 | }
119 | }
120 |
121 | /*
122 | * Public: Function to get account information by account_group from CrossAccountManager-Accounts table.
123 | * If account_group = '*', it will return all accounts.
124 | * Else it only returns accounts with matching account_group
125 | */
126 | exports.getAccountsByAccountGroup = function(account_group) {
127 | return new Promise(function(resolve, reject) {
128 | _scanByAccountGroup('CrossAccountManager-Accounts', account_group, function(err, data) {
129 | if (err)
130 | return reject();
131 | else
132 | return resolve(data);
133 | });
134 | });
135 | }
136 |
137 | /*
138 | * Public: Function to get role information by account_group from CrossAccountManager-Roles table.
139 | * If account_group = '*', it will return roles that can be applied to accounts that are not part of any group
140 | * Else it only returns roles with matching account_group
141 | */
142 | exports.getRolesByAccountGroup = function(account_group) {
143 | return new Promise(function(resolve, reject) {
144 | _scanByAccountGroup('CrossAccountManager-Roles', account_group, function(err, data) {
145 | if (err)
146 | return reject();
147 | else
148 | return resolve(data);
149 | });
150 | });
151 | }
152 |
153 | /*
154 | * Public: Function to get all items from CrossAccountManager-Account-Roles table with status = Active
155 | */
156 | exports.getActiveAccountAndRoles = function() {
157 | return new Promise(function(resolve, reject) {
158 | _scanByAccountGroup('CrossAccountManager-Account-Roles', '', function(err, data) {
159 | if (err)
160 | return reject();
161 | else
162 | return resolve(data);
163 | });
164 | });
165 | }
166 |
167 | /*
168 | * Public: Function to update item in CrossAccountManager-Account-Roles table
169 | */
170 | exports.updateAccountRoles = function (roleName, account, status) {
171 | return new Promise(function(resolve, reject) {
172 | var params = {
173 | TableName : 'CrossAccountManager-Account-Roles',
174 | Item : {
175 | Role : roleName,
176 | AccountId: account.toString(),
177 | Status : status,
178 | Timestamp : new Date().getTime()
179 | }
180 | };
181 |
182 | docClient.put(params, function(err, data) {
183 | if (err) {
184 | console.error(err, err.stack);
185 | return reject(err);
186 | } else {
187 | return resolve();
188 | }
189 | });
190 | });
191 | }
192 |
193 | /*
194 | * Public: Function to update item in CrossAccountManager-Accounts table
195 | */
196 | exports.updateAccounts = function (account, email, account_group, status) {
197 | var params = {
198 | TableName : 'CrossAccountManager-Accounts',
199 | Item : {
200 | AccountId : account.toString(),
201 | Email : email,
202 | AccountGroup : account_group,
203 | Status : status,
204 | Timestamp : new Date().getTime()
205 | }
206 | };
207 |
208 | docClient.put(params, function(err, data) {
209 | if (err) {
210 | console.error(err, err.stack);
211 | throw new Error(err);
212 | }
213 | });
214 | }
215 |
216 |
--------------------------------------------------------------------------------
/code/cross-account-handler/lib/event_handler.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Amazon Software License (the "License"). You
5 | * may not use this file except in compliance with the License. A copy
6 | * of the License is located at
7 | *
8 | * http://aws.amazon.com/asl/
9 | *
10 | * or in the "license" file accompanying this file. This file is
11 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12 | * ANY KIND, express or implied. See the License for the specific
13 | * language governing permissions and limitations under the License.
14 | */
15 |
16 | /*
17 | * This module has the handler functions which are invoked in response to the messages posted on CrossAccountManager-Account and CrossAccountManager-Role Topics
18 | */
19 |
20 | "use strict";
21 |
22 | var Promise = require('promise');
23 | var AWS = require('aws-sdk');
24 | var Iam_Helper = require("./iam_helper.js");
25 | var DDB_Helper = require("./ddb_helper.js");
26 | var S3_Helper = require("./s3_helper.js");
27 | var SNS_Helper = require("./sns_helper.js");
28 |
29 | /*
30 | * This function is called whenever a new role has to be added or removed in a sub-account. It does the following
31 | * 1 - Gets the STS temporary tokens for the sub-account's CrossAccountManager-Admin-DO-NOT-DELETE role
32 | * 2 - Using the temporary tokens, it adds or removes CrossAccountManager-* role in sub-account
33 | * 3 - Updates the CrossAccountManager-AccountRoles table
34 | * 4 - Publishes a message to CrossAccountManager-AccessLinks topic to update the static web page
35 | */
36 | exports.handleRoleEvent = function(event, context, callback) {
37 | try{
38 | var sts = new AWS.STS();
39 | var message = JSON.parse(event.Records[0].Sns.Message);
40 | var action = message.Action.toUpperCase();
41 | var role = message.Role;
42 | var subAccountId = message.SubAccountId;
43 | var policy = JSON.parse(message.Policy);
44 | var masterAccountId = context.invokedFunctionArn.split(':')[4];
45 | var region = context.invokedFunctionArn.split(':')[3];
46 | var accessLinkstopic = "arn:aws:sns:" + region + ":" + masterAccountId + ":CrossAccountManager-AccessLinksTopic";
47 | var assumeRole = 'CrossAccountManager-Admin-DO-NOT-DELETE';
48 |
49 | console.log(action + ":" + subAccountId + ":" + role);
50 |
51 | // Get the temporary STS tokens for CrossAccountManager-Admin-DO-NOT-DELETE role in sub-account
52 | sts.assumeRole({
53 | RoleArn: 'arn:aws:iam::'+subAccountId+':role/CrossAccountManager-Admin-DO-NOT-DELETE',
54 | RoleSessionName: masterAccountId + '-handleRoleEvent'
55 | }, function(err, data) {
56 | if (err) {
57 | console.log(err, err.stack);
58 | callback(e, null);
59 | }
60 | else {
61 | // Assume Role policy for the role in sub-account. It restricts the access to master account.
62 | var assumeRolePolicy = {
63 | Version: "2012-10-17",
64 | Statement: {
65 | Effect: "Allow",
66 | Principal: {AWS: 'arn:aws:iam::' + masterAccountId + ':role/' + role},
67 | Action: "sts:AssumeRole"
68 | }
69 | }
70 |
71 | // Using the STS temporary tokens create the role in sub-account
72 | Iam_Helper.deleteIamRole(role, data.Credentials).then(setTimeout(function() {
73 | if (action == 'ADD') {
74 | // Add a new role in sub-account
75 | Iam_Helper.createIamRole(role, JSON.stringify(assumeRolePolicy), policy, data.Credentials).then(function (){
76 | // Update DynamoDB CrossAccountManager-Account-Roles table status to active
77 | DDB_Helper.updateAccountRoles(role, subAccountId, 'active').then(setTimeout(function(){
78 | // Publish a message to accessLinkstopic to update the static web page with shortcut URLs
79 | SNS_Helper.publishOnSNSTopic(accessLinkstopic, JSON.stringify({
80 | Action : action,
81 | SubAccountId : subAccountId,
82 | Role : role
83 | }));
84 | }, 3000));
85 | });
86 | } else {
87 | // Update DynamoDB CrossAccountManager-Account-Roles table status to deleted
88 | DDB_Helper.updateAccountRoles(role, subAccountId, 'deleted');
89 | }
90 | }, 60000));
91 | }
92 | });
93 | } catch (e) {
94 | console.error(e, e.stack);
95 | callback(e, null);
96 | }
97 |
98 | }
99 |
100 | /*
101 | * This function is called whenever a sub-account has successfully deployed the solution template. It does the following
102 | * 1 - It updates the sub-account status from pending to active
103 | * 2 - It gets all the applicable roles for that sub-account
104 | * 3 - For each role, it updates the policy in master account, by adding or removing the sub-account ID
105 | * 4 - It retrieves the corresponding JSON policy docment from config bucket and publishes a message to CrossAccountManager-Role topic
106 | *
107 | */
108 | exports.handleAccountEvent = function(event, context, callback) {
109 | try{
110 | var ddb = new AWS.DynamoDB.DocumentClient();
111 | var iam = new AWS.IAM();
112 | var message = JSON.parse(event.Records[0].Sns.Message);
113 | var action = message.Action.toUpperCase();
114 | var subAccountId = message.SubAccountId;
115 | var masterAccountId = context.invokedFunctionArn.split(':')[4];
116 | var region = context.invokedFunctionArn.split(':')[3];
117 |
118 | console.log(action + ":" + subAccountId);
119 |
120 | // Get account information for this sub-account
121 | ddb.get({
122 | TableName: 'CrossAccountManager-Accounts',
123 | Key:{
124 | "AccountId": subAccountId
125 | }
126 | }, function(err, account) {
127 | if (err) {
128 | console.error("Unable to read item. Error JSON:", JSON.stringify(err, null, 2));
129 | } else {
130 | console.log("Found Account information");
131 | var account_group = account.Item.AccountGroup;
132 | var accountId = account.Item.AccountId;
133 | // Update the account status to active or deleted
134 | DDB_Helper.updateAccounts(accountId, account.Item.Email, account_group,(action == 'ADD') ? 'active' : 'deleted');
135 |
136 | // Get roles that apply to the account_group for this account.
137 | DDB_Helper.getRolesByAccountGroup(account_group).then(function (roles) {
138 | console.log("Roles to update:", JSON.stringify(roles, null, 2));
139 |
140 | // Update each one of these roles in master account
141 | roles.forEach(function(roleItem) {
142 | var role = roleItem.Role;
143 |
144 | // First check if the in-line policy for the role exists?
145 | iam.getRolePolicy({
146 | PolicyName: role + '-Permission',
147 | RoleName: role
148 | }, function(err, data) {
149 | if (err) {
150 | console.log(err, err.stack);
151 |
152 | // If not, create an in-line policy and update the role
153 | if (action == "ADD") {
154 | Iam_Helper.createPolicyDoc(role, [accountId]).then(function (policy){
155 | console.log("Updating policy for role: " + role );
156 | Iam_Helper.updateRolePolicy(role, JSON.stringify(policy));
157 |
158 | });
159 | }
160 | }
161 | else {
162 | // Otherwise, retrieve the existing in-line policy for this role and add/remove the new sub-account
163 | // to the list of accounts that it can switch role to.
164 | var policy = JSON.parse(decodeURIComponent(data.PolicyDocument));
165 | var resources = policy.Statement[0].Resource;
166 | var new_resource = 'arn:aws:iam::' + accountId + ':role/' + role;
167 |
168 | if (action == "ADD") {
169 | var index = resources.indexOf(new_resource);
170 | if (index < 0) {
171 | resources.push(new_resource);
172 | }
173 |
174 | console.log("Adding resource: " + new_resource + " for Role: " + role);
175 | policy.Statement[0].Resource = resources;
176 | } else if (action == "REMOVE"){
177 | var index = resources.indexOf(new_resource);
178 | console.log("Removing resource: " + new_resource + " for Role: " + role);
179 | if (index > -1) {
180 | resources.splice(index, 1);
181 | }
182 |
183 | if (resources.length > 0) {
184 | console.log("Updating policy for role: " + role );
185 | Iam_Helper.updateRolePolicy(role, JSON.stringify(policy));
186 | } else {
187 | console.log("Removing policy for role: " + role);
188 | Iam_Helper.deleteRolePolicy(role);
189 | }
190 | }
191 |
192 | }
193 |
194 | // Update the CrossAccountManager-Account-Roles table with the new account/role combination and publish a message to CrossAccountManager-Role topic
195 | DDB_Helper.updateAccountRoles(role, accountId, (action == 'ADD') ? 'pending' : 'deleted').then(function() {
196 | if (action == "ADD") {
197 | var roleTopic = "arn:aws:sns:" + region + ":" + masterAccountId + ":CrossAccountManager-RoleTopic";
198 | var bucket = roleItem.Policy.split(':')[0];
199 | var policyName = roleItem.Policy.split(':')[1];
200 |
201 | // Retrive the corresponding JSON policy document for this role from config bucket
202 | S3_Helper.getS3Object(bucket, 'custom_policy/'+policyName).then(function(fileContent) {
203 | var policy = JSON.stringify(fileContent);
204 | // Publish message on CrossAccountManager-Role topic to provision the role in sub-account
205 | SNS_Helper.publishOnSNSTopic(roleTopic, JSON.stringify({
206 | Action : action,
207 | SubAccountId : accountId,
208 | Role : role,
209 | Policy: policy
210 | }));
211 | });
212 | }
213 | });
214 | });
215 |
216 | });
217 | });
218 | }
219 | });
220 | } catch (e) {
221 | console.error(e, e.stack);
222 | callback(e, null);
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/code/cross-account-handler/lib/file_handler.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Amazon Software License (the "License"). You
5 | * may not use this file except in compliance with the License. A copy
6 | * of the License is located at
7 | *
8 | * http://aws.amazon.com/asl/
9 | *
10 | * or in the "license" file accompanying this file. This file is
11 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12 | * ANY KIND, express or implied. See the License for the specific
13 | * language governing permissions and limitations under the License.
14 | */
15 |
16 | /*
17 | * This module has the handler functions to process account and role YAML files uploaded to the Config bucket.
18 | */
19 |
20 | "use strict";
21 |
22 | var Promise = require('promise');
23 | var AWS = require('aws-sdk');
24 | var YAML = require("js-yaml");
25 | var Iam_Helper = require("./iam_helper.js");
26 | var DDB_Helper = require("./ddb_helper.js");
27 | var SNS_Helper = require("./sns_helper.js");
28 | var S3_Helper = require("./s3_helper.js");
29 | var Helper = require("./helper.js");
30 |
31 | /*
32 | * This function is called whenever a new account YAML file is uploaded to the config bucket by Administrator. It does following
33 | * 1 - It parses the file
34 | * 2 - It updates the CrossAccountManager-Accounts table with new sub-accounts, with status = pending
35 | * 3 - It grants the sub-account permission to publish to the CrossAccountManager-Account topic in master account
36 | */
37 | exports.handleAccountS3File = function(event, context, callback) {
38 | var ddb = new AWS.DynamoDB.DocumentClient();
39 | var bucket = event.Records[0].s3.bucket.name;
40 | var key = event.Records[0].s3.object.key;
41 |
42 | // Read the account YAML file from config bucket
43 | S3_Helper.getS3Object(bucket, key).then(function(fileContent) {
44 | return new Promise(function(resolve, reject) {
45 | try {
46 | var e = YAML.safeLoad(fileContent);
47 | var accounts = e.accounts[0];
48 |
49 | var masterAccountId = context.invokedFunctionArn.split(':')[4];
50 | var region = context.invokedFunctionArn.split(':')[3];
51 | var topic = "arn:aws:sns:" + region + ":" + masterAccountId + ":CrossAccountManager-AccountTopic";
52 |
53 | console.log("Granting ACCOUNTS: " + accounts + " permission to publish on topic: " + topic);
54 |
55 | // Grant Publish permission to sub accounts for Account Topic
56 | SNS_Helper.removePublishPermission(topic).then(function () {
57 | SNS_Helper.addPublishPermission(topic, Object.keys(accounts)).then(function (){
58 | // Store account information in CrossAccountManager-Accounts table in Dynamo DB
59 | Object.keys(accounts).map( function (account) {
60 | var account_properties = accounts[account];
61 | var email = account_properties.email;
62 | var account_group = account_properties.accountgroup || "*";
63 | console.log("Storing ACCOUNT: " + account + " information into DynamoDB");
64 |
65 | // First check if the sub-account data exists?
66 | ddb.get({
67 | TableName : 'CrossAccountManager-Accounts',
68 | Key : { "AccountId" : account }
69 | }, function(err, data) {
70 | if (err) {
71 | console.error(err, err.stack);
72 | callback(err, null);
73 | } else {
74 | // If the sub-account exists and is active, update the item
75 | if (data.Item !== undefined && data.Item.Status == 'active') {
76 | DDB_Helper.updateAccounts(account, email, account_group, 'active');
77 | } else {
78 | // Else create a new item
79 | DDB_Helper.updateAccounts(account, email, account_group, 'pending');
80 | }
81 | }
82 | });
83 | });
84 | resolve();
85 | });
86 | });
87 | } catch (e) {
88 | console.error(e, e.stack);
89 | callback(e, null);
90 | process.exit();
91 | }
92 | }).then(function (){
93 | S3_Helper.deleteS3Object(bucket, key);
94 | });
95 | });
96 |
97 | // Send anonymous data to Amazon if customer has opt-in
98 | Helper.sendAnonymousData(context);
99 | }
100 |
101 | /*
102 | * This function is called whenever a new role YAML file is uploaded to the config bucket by Administrator. It does following
103 | * 1 - It parses the file
104 | * 2 - For each role, it creates the CrossAccountManager-* role and policy in master account
105 | * 3 - For each role, it finds the sub-account that will require the role
106 | * 4 - For each sub-account, it updates the CrossAccountManager-Account-Roles table and publishes a message to CrossAccountManager-Role topic
107 | */
108 | exports.handleRoleS3File = function(event, context, callback) {
109 | var ddb = new AWS.DynamoDB.DocumentClient();
110 | var bucket = event.Records[0].s3.bucket.name;
111 | var key = event.Records[0].s3.object.key;
112 |
113 | S3_Helper.getS3Object(bucket, key).then(function(fileContent) {
114 |
115 | return new Promise(function(resolve, reject) {
116 |
117 | try {
118 | var e = YAML.safeLoad(fileContent);
119 | var roles = e.roles[0];
120 |
121 | Object.keys(roles).map( function (role, index, array) {
122 | var role_properties = roles[role];
123 | var action = role_properties.action.toUpperCase();
124 | var policy = role_properties.policy;
125 | var account_group = role_properties.accountgroup || "*";
126 | var roleName = 'CrossAccountManager-' + role;
127 | var masterAccountId = context.invokedFunctionArn.split(':')[4];
128 | var region = context.invokedFunctionArn.split(':')[3];
129 |
130 | // Validation
131 | if (action != 'ADD' && action != 'REMOVE') {
132 | throw new Error('Invalid action found in ' + bucket + key + ' It should be either ADD or REMOVE');
133 | }
134 |
135 | if (policy == undefined) {
136 | throw new Error('Missing policy tag in ' + bucket + key + ' for role: ' + role);
137 | }
138 |
139 | if (roleName.length > 64) {
140 | throw new Error('Role name too long in ' + bucket + key + ' for role: ' + role);
141 | }
142 |
143 | //Store in DDB
144 | var ddb_params = {
145 | TableName : 'CrossAccountManager-Roles',
146 | Item : {
147 | Role : roleName,
148 | Policy: bucket + ':' + policy,
149 | AccountGroup: account_group,
150 | Status : (action == 'ADD') ? 'active' : 'deleted',
151 | Timestamp : new Date().getTime()
152 | }
153 | };
154 |
155 | //Create CrossAccountManager-* role in master account
156 | var assumeRolePolicy = {
157 | Version: "2012-10-17",
158 | Statement: {
159 | Effect: "Allow",
160 | Principal: {Service: "ds.amazonaws.com"},
161 | Action: "sts:AssumeRole"
162 | }
163 | }
164 |
165 | // Get accounts that apply to the account_group for this role.
166 | DDB_Helper.getAccountsByAccountGroup(account_group).then(function (accountItems) {
167 | var accounts = [];
168 |
169 | // Create an array of account IDs
170 | accountItems.map(function (item) {
171 | accounts.push(item.AccountId)
172 | });
173 |
174 | // Add or remove the role and policy from Master account
175 | return new Promise(function(resolve, reject) {
176 | if (action == "ADD") {
177 | Iam_Helper.deleteIamRole(roleName).then(setTimeout(function() {
178 | Iam_Helper.createPolicyDoc(roleName, accounts).then(function (rolePolicy) {
179 | Iam_Helper.createIamRole(roleName, JSON.stringify(assumeRolePolicy),
180 | JSON.stringify(rolePolicy)).then(function(_role) {
181 | // Update Dynamo DB CrossAccountManager-Roles table
182 | ddb.put(ddb_params, function(err, data) {
183 | if (err) {
184 | console.error(err, err.stack);
185 | callback(err, null);
186 | }
187 | setTimeout(resolve(), 10000);
188 | });
189 | }, function(error) {
190 | console.error(error, error.stack);
191 | callback(error, null);
192 | process.exit();
193 | });
194 | });
195 | }, 10000));
196 | } else if (action == "REMOVE"){
197 | Iam_Helper.deleteIamRole(roleName).then(function(_role) {
198 | // Update Dynamo DB CrossAccountManager-Roles table
199 | ddb.put(ddb_params, function(err, data) {
200 | if (err) {
201 | console.error(err, err.stack);
202 | callback(err, null);
203 | }
204 | setTimeout(resolve(), 10000);
205 | });
206 | }, function(error) {
207 | console.error(error, error.stack);
208 | callback(error, null);
209 | process.exit();
210 | });
211 | }
212 | }).then(setTimeout(function (){
213 | // Get the JSON policy file from config bucket
214 | S3_Helper.getS3Object(bucket, 'custom_policy/'+policy).then(function(fileContent) {
215 | accounts.forEach(function(account) {
216 | var status = (action == 'ADD') ? 'pending' : 'deleting';
217 | // For each sub-account, update the CrossAccountManager-Account-Roles table
218 | DDB_Helper.updateAccountRoles(roleName, account, status).then(function() {
219 | var roleTopic = "arn:aws:sns:" + region + ":" + masterAccountId + ":CrossAccountManager-RoleTopic";
220 | var policy = JSON.stringify(fileContent);
221 |
222 | // For each sub-account, publish a message to CrossAccountManager-Role topic
223 | // for provisioning the role in sub-account
224 | SNS_Helper.publishOnSNSTopic(roleTopic, JSON.stringify({
225 | Action : action,
226 | SubAccountId : account,
227 | Role : roleName,
228 | Policy: policy
229 | }));
230 | });
231 | });
232 | if (index == array.length-1) {
233 | resolve();
234 | }
235 |
236 | }, function(error) {
237 | console.error(error, error.stack);
238 | callback(error, null);
239 | process.exit();
240 | });
241 | }, 3000));
242 | });
243 | });
244 |
245 | } catch (e) {
246 | console.error(e, e.stack);
247 | callback(e, null);
248 | process.exit();
249 | }
250 | }).then(function (){
251 | S3_Helper.deleteS3Object(bucket, key);
252 | });
253 | });
254 |
255 | // Send anonymous data to Amazon if customer has opt-in
256 | Helper.sendAnonymousData(context);
257 | }
258 |
--------------------------------------------------------------------------------
/code/cross-account-handler/lib/helper.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Amazon Software License (the "License"). You
5 | * may not use this file except in compliance with the License. A copy
6 | * of the License is located at
7 | *
8 | * http://aws.amazon.com/asl/
9 | *
10 | * or in the "license" file accompanying this file. This file is
11 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12 | * ANY KIND, express or implied. See the License for the specific
13 | * language governing permissions and limitations under the License.
14 | */
15 |
16 | /*
17 | * This module has the generic helper functions for this solution
18 | */
19 |
20 | "use strict";
21 |
22 | var AWS = require('aws-sdk');
23 | var Promise = require('promise');
24 | var https = require('https');
25 | var HOST = 'metrics.awssolutionsbuilder.com';
26 | var PATH = '/generic';
27 | var DDB_Helper = require("./ddb_helper.js");
28 |
29 | /*
30 | * Private: This function uses CloudFormation api to get the outputs from the solution stack created in the master account.
31 | */
32 | function _getCFStackOutputs(invokedFunctionArn) {
33 | return new Promise(function(resolve, reject) {
34 | var cf = new AWS.CloudFormation();
35 |
36 | // Parse the stack name from the context.invokedFunctionArn
37 | var strArr = invokedFunctionArn.split(':')[6].split('-');
38 | strArr.splice(-2);
39 | var stack_name = strArr.join('-');
40 |
41 | cf.describeStacks({StackName: stack_name}, function(err, data) {
42 | if (err) {
43 | console.log(err, err.stack);
44 | reject(err);
45 | }
46 | else {
47 | resolve(data.Stacks[0].Outputs);
48 | }
49 | });
50 | });
51 | }
52 |
53 | exports.getCFStackOutputs = _getCFStackOutputs;
54 |
55 | /*
56 | * Private: This function is calling the Amazon Backend metrics REST api to post the anonymous data
57 | */
58 | function _callBackendMetricsAPI(anonymousData, uuid) {
59 | return new Promise(function(resolve, reject) {
60 | // Build the post string from an object
61 | var post_data = JSON.stringify({
62 | 'Solution' : 'SO0015',
63 | 'UUID': uuid,
64 | 'TimeStamp': Date(),
65 | 'Data' : anonymousData
66 | });
67 |
68 | // An object of options to indicate where to post to
69 | var post_options = {
70 | host: HOST,
71 | port: '443',
72 | path: PATH,
73 | method: 'POST',
74 | headers: {
75 | 'Content-Type': 'application/json',
76 | 'Content-Length': Buffer.byteLength(post_data)
77 | }
78 | };
79 |
80 | // Set up the request
81 | var post_req = https.request(post_options, function(res) {
82 | res.setEncoding('utf8');
83 | res.on('data', function (chunk) {
84 | });
85 | });
86 |
87 | // post the data
88 | post_req.write(post_data);
89 | post_req.end();
90 | resolve();
91 | });
92 | }
93 |
94 | /*
95 | * Public: This function collects the anonymous data and posts it to Amazon backend metrics only if the customer has opt-in
96 | */
97 | exports.sendAnonymousData = function(context) {
98 | var senddata = 'NO';
99 | var uuid = '';
100 |
101 | // Get the stack outputs to determine if customer has opt-in for anonymous data or not
102 | _getCFStackOutputs(context.invokedFunctionArn).then(function(outputs){
103 | outputs.map(function (output) {
104 | if (output.OutputKey == 'AnonymousData') {
105 | senddata = output.OutputValue.toUpperCase();
106 | }
107 | if (output.OutputKey == 'UUID') {
108 | uuid = output.OutputValue;
109 | }
110 | });
111 | var accounts=0, roles=0, acc_roles=0;
112 |
113 | // If customer has opt-in, collect and post the anonymous data
114 | if (senddata == 'YES') {
115 | DDB_Helper.getListOfItems('CrossAccountManager-Accounts', function(err, data){
116 | if (data) {
117 | accounts = data.length;
118 | DDB_Helper.getListOfItems('CrossAccountManager-Roles', function(err, data){
119 | roles = data.length;
120 | DDB_Helper.getListOfItems('CrossAccountManager-Account-Roles', function(err, data){
121 | acc_roles = data.length;
122 |
123 | var anonymousData = {
124 | 'accounts' : accounts,
125 | 'roles' : roles,
126 | 'account-roles' : acc_roles
127 | };
128 | _callBackendMetricsAPI(anonymousData, uuid);
129 | });
130 | });
131 | }
132 | });
133 | }
134 | });
135 | }
136 |
--------------------------------------------------------------------------------
/code/cross-account-handler/lib/iam_helper.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Amazon Software License (the "License"). You
5 | * may not use this file except in compliance with the License. A copy
6 | * of the License is located at
7 | *
8 | * http://aws.amazon.com/asl/
9 | *
10 | * or in the "license" file accompanying this file. This file is
11 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12 | * ANY KIND, express or implied. See the License for the specific
13 | * language governing permissions and limitations under the License.
14 | */
15 |
16 | /*
17 | * This module has functions to perform all IAM related activities in master and sub-accounts.
18 | */
19 |
20 | "use strict";
21 |
22 | var Promise = require('promise');
23 | var AWS = require('aws-sdk');
24 |
25 | /*
26 | * Public: This function creates the permission policy for CrossAccountManager-* roles in master account
27 | */
28 | exports.createPolicyDoc = function(roleName, accounts) {
29 | return new Promise(function(resolve, reject) {
30 | try {
31 | var accountsArn = [];
32 |
33 | console.log("Creating JSON policy document for accounts = " + accounts);
34 |
35 | accounts.forEach(function(account) {
36 | accountsArn.push('arn:aws:iam::' + account + ':role/' + roleName);
37 | });
38 |
39 | if (accountsArn.length > 0) {
40 | return resolve({
41 | Version : "2012-10-17",
42 | Statement : [
43 | {
44 | Effect : "Allow",
45 | Action : [ "sts:AssumeRole" ],
46 | Resource : accountsArn
47 | },
48 | {
49 | "Effect": "Allow",
50 | "Action": [
51 | "s3:Get*",
52 | "s3:List*"
53 | ],
54 | "Resource": "*"
55 | }
56 | ]
57 | });
58 | } else {
59 | return resolve();
60 | }
61 | }
62 | catch (e) {
63 | console.error(e, e.stack);
64 | return reject();
65 | }
66 | });
67 | }
68 |
69 | /*
70 | * Public: This function updates the CrossAccountManager-* role policy in master account
71 | */
72 | exports.updateRolePolicy = function(roleName, rolePolicy) {
73 | return new Promise(function(resolve, reject) {
74 | try {
75 | var iam = new AWS.IAM();
76 |
77 | iam.getRole({
78 | RoleName : roleName
79 | }, function(err, data) {
80 | if (err) {
81 | console.log("ROLE does not exist " + roleName);
82 | console.log(err, err.stack);
83 | return reject(err);
84 | } else {
85 | iam.putRolePolicy({
86 | PolicyDocument : rolePolicy,
87 | PolicyName : roleName + '-Permission',
88 | RoleName : roleName
89 | }, function(err, data) {
90 | if (err) {
91 | console.log("Failed to update policy for role: " + roleName);
92 | console.log(err, err.stack);
93 | return reject(err);
94 | } else {
95 | console.log("Updated POLICY: " + roleName);
96 | return resolve(roleName);
97 | }
98 | });
99 | }
100 | });
101 | }
102 | catch (e) {
103 | console.error(e, e.stack);
104 | return reject();
105 | }
106 | });
107 | }
108 |
109 | /*
110 | * Public: This function deletes the CrossAccountManager-* role policy in master account
111 | */
112 | exports.deleteRolePolicy = function(roleName) {
113 | return new Promise(function(resolve, reject) {
114 | try {
115 | var iam = new AWS.IAM();
116 |
117 | iam.getRole({
118 | RoleName : roleName
119 | }, function(err, data) {
120 | if (err) {
121 | console.log("ROLE does not exist " + roleName);
122 | console.log(err, err.stack);
123 | return reject(err);
124 | } else {
125 | iam.deleteRolePolicy({
126 | PolicyName : roleName + '-Permission',
127 | RoleName : roleName
128 | }, function(err, data) {
129 | if (err) {
130 | console.log("ROLE policy does not exist " + roleName);
131 | console.log(err, err.stack);
132 | return reject(err);
133 | } else {
134 | console.log("Deleted ROLE policy " + roleName);
135 | }
136 | });
137 | }
138 | });
139 | }
140 | catch (e) {
141 | console.error(e, e.stack);
142 | return reject();
143 | }
144 | });
145 | }
146 |
147 |
148 | /*
149 | * Public: This function deletes the CrossAccountManager-* role in Master or Sub-account
150 | */
151 | exports.deleteIamRole = function(roleName, Credentials) {
152 | return new Promise(function(resolve, reject) {
153 | try {
154 |
155 | //If Credentials are provided use that to delete the IAM role i.e. sub-account
156 | if ( typeof Credentials !== 'undefined' && Credentials ) {
157 | var creds = new AWS.Credentials(Credentials.AccessKeyId, Credentials.SecretAccessKey, Credentials.SessionToken);
158 | var iam = new AWS.IAM({credentials: creds});
159 | } else {
160 | var iam = new AWS.IAM();
161 | }
162 |
163 | iam.getRole({
164 | RoleName : roleName
165 | }, function(err, data) {
166 | if (err) {
167 | console.log("ROLE does not exist " + roleName + ". No action taken.");
168 | console.log(err, err.stack);
169 | return resolve(roleName);
170 | } else {
171 | console.log("ROLE exists " + roleName);
172 | iam.deleteRolePolicy({
173 | PolicyName : roleName + '-Permission',
174 | RoleName : roleName
175 | }, function(err, data) {
176 | iam.deleteRole({
177 | RoleName : roleName
178 | }, function(err, data) {
179 | if (err) {
180 | console.log("ERROR deleting ROLE: " + roleName);
181 | console.log(err, err.stack);
182 | return reject(err);
183 | } else {
184 | console.log("Deleted ROLE " + roleName);
185 | return resolve(roleName);
186 | }
187 | });
188 | })
189 |
190 | }
191 | });
192 | }
193 | catch (e) {
194 | console.error(e, e.stack);
195 | return reject();
196 | }
197 | });
198 | }
199 |
200 | /*
201 | * Public: This function creates the CrossAccountManager-* role in Master or Sub-account
202 | */
203 | exports.createIamRole = function(roleName, assumeRolePolicy, rolePolicy, Credentials) {
204 | return new Promise(function(resolve, reject) {
205 | try {
206 |
207 | //If Credentials are provided use that to delete the IAM role i.e. sub-account
208 | if ( typeof Credentials !== 'undefined' && Credentials ) {
209 | var creds = new AWS.Credentials(Credentials.AccessKeyId, Credentials.SecretAccessKey, Credentials.SessionToken);
210 | var iam = new AWS.IAM({credentials: creds});
211 | } else {
212 | var iam = new AWS.IAM();
213 | }
214 |
215 | iam.createRole({
216 | AssumeRolePolicyDocument : assumeRolePolicy,
217 | RoleName : roleName
218 | }, function(err, data) {
219 | if (err) {
220 | console.log("ERROR Creating ROLE: " + roleName);
221 | console.log(err, err.stack);
222 | return reject(err);
223 | } else {
224 | console.log("Created ROLE: " + roleName);
225 |
226 | if (rolePolicy) {
227 | iam.putRolePolicy({
228 | PolicyDocument : rolePolicy,
229 | PolicyName : roleName + '-Permission',
230 | RoleName : roleName
231 | }, function(err, data) {
232 | if (err) {
233 | console.log("ERROR Creating Policy for role : " + roleName);
234 | console.log(err, err.stack);
235 | return reject(err);
236 | } else {
237 | console.log("Created POLICY: " + roleName);
238 | return resolve(roleName);
239 | }
240 | });
241 | } else {
242 | return resolve(roleName);
243 | }
244 |
245 | }
246 | });
247 | }
248 | catch (e) {
249 | console.error(e, e.stack);
250 | return reject();
251 | }
252 |
253 | });
254 |
255 | }
256 |
--------------------------------------------------------------------------------
/code/cross-account-handler/lib/s3_helper.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Amazon Software License (the "License"). You
5 | * may not use this file except in compliance with the License. A copy
6 | * of the License is located at
7 | *
8 | * http://aws.amazon.com/asl/
9 | *
10 | * or in the "license" file accompanying this file. This file is
11 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12 | * ANY KIND, express or implied. See the License for the specific
13 | * language governing permissions and limitations under the License.
14 | */
15 |
16 | /*
17 | * CAM stands for Cross Account Manager
18 | * This module has functions to perform all S3 related activities in master account.
19 | */
20 |
21 | "use strict";
22 | var Promise = require('promise');
23 | var AWS = require('aws-sdk');
24 |
25 | /*
26 | * This function retrieves the file from S3 bucket. Used to retrieve account, role or policy files from config bucket.
27 | */
28 | exports.getS3Object = function (bucket, key) {
29 | return new Promise(function(resolve, reject) {
30 | var s3 = new AWS.S3({signatureVersion: 'v4'});
31 |
32 | var params = {
33 | Bucket : bucket,
34 | Key : key
35 | }
36 |
37 | console.log("Reading file from S3 bucket: " + bucket + ", key: " + key );
38 |
39 | s3.getObject(params, function(err, data) {
40 | if (err) {
41 | console.error(err, err.stack);
42 | reject(err);
43 | } else {
44 | resolve(data.Body.toString());
45 | }
46 | });
47 | });
48 | }
49 |
50 | /*
51 | * This function deletes the file from S3 bucket. Used to delete account or role files from config bucket.
52 | */
53 | exports.deleteS3Object = function (bucket, key) {
54 | return new Promise(function(resolve, reject) {
55 | var s3 = new AWS.S3({signatureVersion: 'v4'});
56 |
57 | var params = {
58 | Bucket : bucket,
59 | Key : key
60 | }
61 |
62 | console.log("Deleting file from S3 bucket: " + bucket + ", key: " + key );
63 |
64 | s3.deleteObject(params, function(err, data) {
65 | if (err) {
66 | console.error(err, err.stack);
67 | reject(err);
68 | } else {
69 | resolve();
70 | }
71 | });
72 | });
73 | }
74 |
--------------------------------------------------------------------------------
/code/cross-account-handler/lib/sns_helper.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Amazon Software License (the "License"). You
5 | * may not use this file except in compliance with the License. A copy
6 | * of the License is located at
7 | *
8 | * http://aws.amazon.com/asl/
9 | *
10 | * or in the "license" file accompanying this file. This file is
11 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12 | * ANY KIND, express or implied. See the License for the specific
13 | * language governing permissions and limitations under the License.
14 | */
15 |
16 | /*
17 | * CAM stands for Cross Account Manager
18 | * This module has functions to perform all SNS related activities in master account.
19 | */
20 |
21 | "use strict";
22 | var Promise = require('promise');
23 | var AWS = require('aws-sdk');
24 |
25 | /*
26 | * This function removes the publish permission from topic. Used to remove sub-account permission for publishing to the CrossAccountManager-Account topic.
27 | */
28 | exports.removePublishPermission = function(topic) {
29 | var sns = new AWS.SNS();
30 | return new Promise(function(resolve, reject) {
31 | sns.removePermission({
32 | Label : 'CAM',
33 | TopicArn : topic
34 | }, function (err, data) {
35 | if (err) {
36 | console.error(err, err.stack);
37 | return resolve();
38 | } else if (data) {
39 | return resolve();
40 | }
41 | });
42 | });
43 | }
44 |
45 | /*
46 | * This function adds the publish permission from topic. Used to add sub-account permission for publishing to the CrossAccountManager-Account topic.
47 | */
48 | exports.addPublishPermission = function(topic, accounts) {
49 | var sns = new AWS.SNS();
50 | return new Promise(function(resolve, reject) {
51 | sns.addPermission({
52 | AWSAccountId : accounts,
53 | ActionName : [ 'Publish' ],
54 | Label : 'CAM',
55 | TopicArn : topic
56 | }, function(err, data) {
57 | if (err) {
58 | console.error(err, err.stack);
59 | return reject(err);
60 | } else if (data) {
61 | return resolve();
62 | }
63 | });
64 | });
65 | }
66 |
67 | /*
68 | * This function publishes a message to topic
69 | */
70 | exports.publishOnSNSTopic = function (topic, message) {
71 | var sns = new AWS.SNS();
72 | return new Promise(function(resolve, reject) {
73 | var params = {
74 | TopicArn : topic,
75 | Message : message,
76 | };
77 |
78 | console.log('Publishing message: ' + JSON.stringify(params) + " to " + topic);
79 |
80 | sns.publish(params, function(err, data) {
81 | if (err) {
82 | console.error(err, err.stack);
83 | return reject(err);
84 | } else {
85 | return resolve();
86 | }
87 | });
88 | });
89 | }
90 |
--------------------------------------------------------------------------------
/code/cross-account-handler/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cross-account-manager",
3 | "version": "1.0.0",
4 | "description": "Cross Account Manager is an automated reference implementation that assists with setting up corss account roles for easy federation of users from one master AWS account to multiple AWS sub-accounts.",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "nodeunit ./tst"
8 | },
9 | "dependencies": {
10 | "promise": "7.x",
11 | "js-yaml": "3.6"
12 | },
13 | "devDependencies": {
14 | "nodeunit": "~0.9"
15 | },
16 | "engines": {
17 | "node": ">=4.3"
18 | },
19 | "homepage": "https://github.com/awslabs/aws-cross-account-manager",
20 | "repository": {
21 | "type": "git",
22 | "url": "git://github.com/awslabs/aws-cross-account-manager.git"
23 | },
24 | "bugs": {
25 | "url": "https://github.com/awslabs/aws-cross-account-manager"
26 | },
27 | "license": "SEE LICENSE IN http://aws.amazon.com/asl/",
28 | "keywords": [
29 | "aws",
30 | "account",
31 | "federation",
32 | "node.js",
33 | "vpc"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/samples/Administrator.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": "2012-10-17",
3 | "Statement":
4 | [
5 | {
6 | "Effect": "Allow",
7 | "Action": "*",
8 | "Resource": "*"
9 | }
10 | ]
11 | }
--------------------------------------------------------------------------------
/samples/Read-Only.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": "2012-10-17",
3 | "Statement":
4 | [
5 | {
6 | "Action":
7 | [
8 | "acm:DescribeCertificate",
9 | "acm:GetCertificate",
10 | "acm:ListCertificates",
11 | "acm:ListTagsForCertificate",
12 | "apigateway:GET",
13 | "appstream:Get*",
14 | "autoscaling:Describe*",
15 | "cloudformation:Describe*",
16 | "cloudformation:Get*",
17 | "cloudformation:List*",
18 | "cloudfront:Get*",
19 | "cloudfront:List*",
20 | "cloudsearch:Describe*",
21 | "cloudsearch:List*",
22 | "cloudtrail:DescribeTrails",
23 | "cloudtrail:GetTrailStatus",
24 | "cloudtrail:LookupEvents",
25 | "cloudtrail:ListTags",
26 | "cloudtrail:ListPublicKeys",
27 | "cloudwatch:Describe*",
28 | "cloudwatch:Get*",
29 | "cloudwatch:List*",
30 | "codecommit:BatchGetRepositories",
31 | "codecommit:Get*",
32 | "codecommit:GitPull",
33 | "codecommit:List*",
34 | "codedeploy:Batch*",
35 | "codedeploy:Get*",
36 | "codedeploy:List*",
37 | "config:Deliver*",
38 | "config:Describe*",
39 | "config:Get*",
40 | "config:List*",
41 | "datapipeline:DescribeObjects",
42 | "datapipeline:DescribePipelines",
43 | "datapipeline:EvaluateExpression",
44 | "datapipeline:GetPipelineDefinition",
45 | "datapipeline:ListPipelines",
46 | "datapipeline:QueryObjects",
47 | "datapipeline:ValidatePipelineDefinition",
48 | "directconnect:Describe*",
49 | "dms:Describe*",
50 | "dms:List*",
51 | "ds:Check*",
52 | "ds:Describe*",
53 | "ds:Get*",
54 | "ds:List*",
55 | "ds:Verify*",
56 | "dynamodb:BatchGetItem",
57 | "dynamodb:DescribeLimits",
58 | "dynamodb:DescribeTable",
59 | "dynamodb:GetItem",
60 | "dynamodb:ListTables",
61 | "dynamodb:Query",
62 | "dynamodb:Scan",
63 | "ec2:Describe*",
64 | "ec2:GetConsoleOutput",
65 | "ec2:GetConsoleScreenshot",
66 | "ecr:GetAuthorizationToken",
67 | "ecr:GetRepositoryPolicy",
68 | "ecr:BatchCheckLayerAvailability",
69 | "ecr:GetDownloadUrlForLayer",
70 | "ecr:GetManifest",
71 | "ecr:DescribeRepositories",
72 | "ecr:ListImages",
73 | "ecr:BatchGetImage",
74 | "ecs:Describe*",
75 | "ecs:List*",
76 | "elasticache:Describe*",
77 | "elasticache:List*",
78 | "elasticbeanstalk:Check*",
79 | "elasticbeanstalk:Describe*",
80 | "elasticbeanstalk:List*",
81 | "elasticbeanstalk:RequestEnvironmentInfo",
82 | "elasticbeanstalk:RetrieveEnvironmentInfo",
83 | "elasticfilesystem:Describe*",
84 | "elasticloadbalancing:Describe*",
85 | "elasticmapreduce:Describe*",
86 | "elasticmapreduce:List*",
87 | "elastictranscoder:List*",
88 | "elastictranscoder:Read*",
89 | "es:DescribeElasticsearchDomain",
90 | "es:DescribeElasticsearchDomains",
91 | "es:DescribeElasticsearchDomainConfig",
92 | "es:ListDomainNames",
93 | "es:ListTags",
94 | "es:ESHttpGet",
95 | "es:ESHttpHead",
96 | "events:DescribeRule",
97 | "events:ListRuleNamesByTarget",
98 | "events:ListRules",
99 | "events:ListTargetsByRule",
100 | "events:TestEventPattern",
101 | "firehose:Describe*",
102 | "firehose:List*",
103 | "glacier:ListVaults",
104 | "glacier:DescribeVault",
105 | "glacier:GetDataRetrievalPolicy",
106 | "glacier:GetVaultAccessPolicy",
107 | "glacier:GetVaultLock",
108 | "glacier:GetVaultNotifications",
109 | "glacier:ListJobs",
110 | "glacier:ListMultipartUploads",
111 | "glacier:ListParts",
112 | "glacier:ListTagsForVault",
113 | "glacier:DescribeJob",
114 | "glacier:GetJobOutput",
115 | "iam:GenerateCredentialReport",
116 | "iam:GenerateServiceLastAccessedDetails",
117 | "iam:Get*",
118 | "iam:List*",
119 | "inspector:Describe*",
120 | "inspector:Get*",
121 | "inspector:List*",
122 | "inspector:LocalizeText",
123 | "inspector:PreviewAgentsForResourceGroup",
124 | "iot:Describe*",
125 | "iot:Get*",
126 | "iot:List*",
127 | "kinesisanalytics:DescribeApplication",
128 | "kinesisanalytics:DiscoverInputSchema",
129 | "kinesisanalytics:GetApplicationState",
130 | "kinesisanalytics:ListApplications",
131 | "kinesis:Describe*",
132 | "kinesis:Get*",
133 | "kinesis:List*",
134 | "kms:Describe*",
135 | "kms:Get*",
136 | "kms:List*",
137 | "lambda:List*",
138 | "lambda:Get*",
139 | "logs:Describe*",
140 | "logs:Get*",
141 | "logs:FilterLogEvents",
142 | "logs:TestMetricFilter",
143 | "machinelearning:Describe*",
144 | "machinelearning:Get*",
145 | "mobilehub:GetProject",
146 | "mobilehub:ListAvailableFeatures",
147 | "mobilehub:ListAvailableRegions",
148 | "mobilehub:ListProjects",
149 | "mobilehub:ValidateProject",
150 | "mobilehub:VerifyServiceRole",
151 | "opsworks:Describe*",
152 | "opsworks:Get*",
153 | "rds:Describe*",
154 | "rds:ListTagsForResource",
155 | "redshift:Describe*",
156 | "redshift:ViewQueriesInConsole",
157 | "route53:Get*",
158 | "route53:List*",
159 | "route53domains:CheckDomainAvailability",
160 | "route53domains:GetDomainDetail",
161 | "route53domains:GetOperationDetail",
162 | "route53domains:ListDomains",
163 | "route53domains:ListOperations",
164 | "route53domains:ListTagsForDomain",
165 | "s3:Get*",
166 | "s3:List*",
167 | "sdb:GetAttributes",
168 | "sdb:List*",
169 | "sdb:Select*",
170 | "ses:Get*",
171 | "ses:List*",
172 | "sns:Get*",
173 | "sns:List*",
174 | "sqs:GetQueueAttributes",
175 | "sqs:ListQueues",
176 | "sqs:ReceiveMessage",
177 | "storagegateway:Describe*",
178 | "storagegateway:List*",
179 | "swf:Count*",
180 | "swf:Describe*",
181 | "swf:Get*",
182 | "swf:List*",
183 | "tag:Get*",
184 | "trustedadvisor:Describe*",
185 | "waf:Get*",
186 | "waf:List*",
187 | "workspaces:Describe*"
188 | ],
189 |
190 | "Effect": "Allow",
191 | "Resource": "*"
192 | }
193 | ]
194 | }
--------------------------------------------------------------------------------
/samples/accounts.yaml:
--------------------------------------------------------------------------------
1 | accounts:
2 | -
3 | # AWS Account ID
4 | 999999999999 :
5 | # Email associated with the account
6 | email: you@example.com
7 | # Optional to group the accounts
8 | accountgroup : production
9 | 888888888888 :
10 | email: me@example.com
11 | accountgroup : development
12 |
--------------------------------------------------------------------------------
/samples/roles.yaml:
--------------------------------------------------------------------------------
1 | roles:
2 | -
3 | #Role name
4 | Prod-Administrator :
5 | #Action add or remove
6 | action : add
7 | #Reference to JSON policy file in custom_policy folder
8 | policy : Administrator.json
9 | #Optional: apply this role to only accounts in this group
10 | accountgroup : production
11 | Dev-Administrator :
12 | action : add
13 | policy : Administrator.json
14 | accountgroup : development
15 | Read-Only :
16 | action : add
17 | policy : Read-Only.json
18 |
--------------------------------------------------------------------------------