├── 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 Links

AWS 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 | --------------------------------------------------------------------------------