├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── functions
└── password_policy
│ ├── __init__.py
│ ├── app.py
│ └── requirements.txt
├── resources
├── console1.png
├── main.png
├── ou.png
└── samcli1.png
└── template.yaml
/.gitignore:
--------------------------------------------------------------------------------
1 | local/
2 | .DS_Store
3 | .vscode/
4 | .idea/
5 | .aws-sam/
6 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Code of Conduct
2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4 | opensource-codeofconduct@amazon.com with any additional questions or comments.
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 |
10 | ## Reporting Bugs/Feature Requests
11 |
12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13 |
14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already
15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16 |
17 | * A reproducible test case or series of steps
18 | * The version of our code being used
19 | * Any modifications you've made relevant to the bug
20 | * Anything unusual about your environment or deployment
21 |
22 |
23 | ## Contributing via Pull Requests
24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25 |
26 | 1. You are working against the latest source on the *main* branch.
27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29 |
30 | To send us a pull request, please:
31 |
32 | 1. Fork the repository.
33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34 | 3. Ensure local tests pass.
35 | 4. Commit to your fork using clear commit messages.
36 | 5. Send us a pull request, answering any default questions in the pull request interface.
37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38 |
39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
41 |
42 |
43 | ## Finding contributions to work on
44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
45 |
46 |
47 | ## Code of Conduct
48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
50 | opensource-codeofconduct@amazon.com with any additional questions or comments.
51 |
52 |
53 | ## Security issue notifications
54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
55 |
56 |
57 | ## Licensing
58 |
59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
60 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 |
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # IAM Password Policy Deployment via CloudFormation StackSets
2 | ## Overview
3 | In this sample code, I will show you how you can centrally manage the deployment of [IAM password policies](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_passwords_account-policy.html) across a fleet of AWS accounts in your organization. You can accomplish this by using [AWS Organizations](https://aws.amazon.com/organizations/), [AWS CloudFormation StackSets](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/what-is-cfnstacksets.html) and [CloudFormation custom resources](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html).
4 |
5 |
6 | CloudFormation StackSets make it easy to deploy AWS resources, via a CloudFormation template, to multiple AWS accounts and regions. When you create a stack set, you can use [self-managed](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-getting-started-create.html#stacksets-getting-started-create-self-managed) or [service-managed](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-getting-started-create.html#stacksets-orgs-associate-stackset-with-org) permissions to orchestrate the deployment of your CloudFormation template. If you use self-managed permissions, you can deploy your CloudFormation template to specific AWS accounts and regions. If you go with service-managed permissions, you can target organizational units (OU's) or your entire AWS organization. This solution uses service-managed permissions with automated deployments, and it targets specific OU's in an AWS organization.
7 |
8 | ## Prerequisites
9 | - **AWS SAM CLI** - You can follow the AWS [documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) for the installation of this tool
10 | - **S3 bucket** - An S3 bucket that SAM can use to orchestrate the deployment. You can use an existing bucket or create a new one
11 | - **AWS access keys configured** - You can follow the AWS [documentation](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html) for detailed instructions. You need to have access keys for the management account. If you are deploying the solution from an EC2 instance, you can leverage an EC2 [instance profile](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2_instance-profiles.html) instead, this eliminates the need to create access keys for an IAM user
12 | - **AWS Organizations management account** - AWS account where the stack set will be deployed
13 | - **Organizational unit (OU)** - AWS Organization's OU where you can place a member account
14 | - **Member account in the organization** - AWS account to be placed in an OU
15 |
16 | ## Use cases
17 | This solution can help you deploy a custom IAM password policy to accounts that are already members of an AWS Organizations OU. Once deployed, the solution will automatically deploy the specified password policy to new member accounts of the specified OU.
18 |
19 | ## Workflow
20 | Figure 1 shows the resources deployed in the involved AWS accounts. Since IAM password policies are currently not natively supported via CloudFormation, we will be using a custom resource with a single Lambda function deployed in the management account.
21 |
22 |
23 |
24 | *Figure 1*
25 |
26 |
27 | 1. In the AWS Organizations management account, an Administrator adds a new or an existing AWS account to a target OU.
28 |
29 | 2. Since this solution uses StackSets automated deployments, the stack set in the management account automatically responds to the event in step 1 by deploying a stack instance to the account that was just added to the target OU. If an account is removed from the target OU, the stack set automatically removes the stack instance from the account.
30 |
31 |
32 | The stack instance deploys the CloudFormation template that was configured with the stack set. The template contains two AWS resources:
33 |
34 | An IAM role for cross account access. This is the first resource that gets created, and it is meant to be assumed by an AWS Lambda function in the management account. The permissions of this role are specific to managing an IAM password policy.
35 |
36 | ```yaml
37 | CFCustomResourceProviderRoleSpoke:
38 | Type: "AWS::IAM::Role"
39 | Properties:
40 | RoleName: !Sub ${ResourceNamePrefix}-role-${Environment}
41 | AssumeRolePolicyDocument:
42 | Version: 2012-10-17
43 | Statement:
44 | - Effect: Allow
45 | Principal:
46 | AWS: !Ref TrustedRoleForCustomResources
47 | Action: sts:AssumeRole
48 | Path: /
49 | Policies:
50 | - PolicyName: IAMPermissions
51 | PolicyDocument:
52 | Version: 2012-10-17
53 | Statement:
54 | Effect: Allow
55 | Action:
56 | - iam:DeleteAccountPasswordPolicy
57 | - iam:UpdateAccountPasswordPolicy
58 | Resource: '*'
59 | ```
60 |
61 |
62 | A password policy resource. This is a custom resource that uses a Lambda function in the management account as the backend service. In this resource, you can specify a password policy that comply with the security requirements of your organization. To review a description of the supported properties of the password policy, visit the documentation for the boto3 [update_account_password_policy](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/iam.html#IAM.Client.update_account_password_policy) method.
63 |
64 | ```yaml
65 | PasswordPolicy:
66 | Type: Custom::PasswordPolicy
67 | DependsOn:
68 | - CFCustomResourceProviderRoleSpoke
69 | Properties:
70 | MinimumPasswordLength: 10
71 | RequireSymbols: true
72 | RequireNumbers: false
73 | RequireUppercaseCharacters: true
74 | RequireLowercaseCharacters: true
75 | AllowUsersToChangePassword: true
76 | MaxPasswordAge: 10
77 | PasswordReusePrevention: 1
78 | HardExpiry: true
79 | ServiceToken: !Ref PasswordPolicyTopicArn
80 | ```
81 |
82 |
83 | 3. The stack instance deployed to the target accounts request a password policy creation by publishing a message to the SNS topic in the management account.
84 |
85 | 4. The SNS topic invokes the Lambda function for the creation of the password policy.
86 |
87 | 5. The Lambda function assumes the role created in the target accounts and performs the required actions to create the password policy. The Lambda function then informs CloudFormation that the custom resources were created successfully.
88 |
89 |
90 | ## Deploying the solution
91 |
92 | We will use the AWS SAM CLI to facilitate the build and deployment of the solution in the management account. We will be using standard commands to deploy and update our resources as needed. First lets explore some important directories and files that are part of the solution:
93 |
94 | - *functions/password_policy/* directory. Contains the code for the Lambda function that creates an IAM password policy via a CloudFormation custom resources.
95 |
96 | - *template.yaml*. This is the AWS SAM CLI template that deploys the solution. This template creates the following resources:
97 | - The Lambda function that creates the password policy custom resource
98 | - Lambda execution role that can assume another role in target accounts
99 | - SNS topic that can trigger the Lambda function. A topic policy is created so that accounts from the organization can publish to the topic when creating custom resources. This is how other accounts can invoke the Lambda function in the management account.
100 |
101 | In order to test, ensure that you have at least one AWS account as a member of an OU in the AWS Organizations console. We will be targeting the OU in our stack set deployment. Figure 2 shows an account named *sandbox* inside of an OU called Dev.
102 |
103 |
104 |
105 | *Figure 2*
106 |
107 | ### Notes ###
108 |
109 | - The deployment of this solution happens in the AWS Organizations management account. Make sure you have configured your AWS CLI for this account.
110 | - Refer to the Limitations section of this project if you are interested in deploying this solution in a Delegated Account.
111 |
112 | ### Build
113 |
114 | Build the SAM package with the command build command:
115 |
116 | ```code
117 | sam build
118 | ```
119 |
120 | ### Deployment
121 |
122 | Replace the placeholder values and run the command below:
123 |
124 | Syntax
125 |
126 | ```code
127 | sam deploy --stack-name \
128 | --s3-bucket \
129 | --s3-prefix \
130 | --region \
131 | --capabilities \
132 | --parameter-overrides ParameterKey=,ParameterValue=
133 | ```
134 |
135 | ### Note ###
136 |
137 | - This solution requires the IAM capability ```CAPABILITY_NAMED_IAM```
138 | - The solution expects the following template parameters in the ```--parameter-overrides``` option:
139 | - ResourceNamePrefix
140 | - OrganizationID
141 | - TargetOU
142 | - Environment
143 |
144 | Example
145 |
146 | ```code
147 | sam deploy --stack-name iam-password-policy-dev \
148 | --s3-bucket aws-sam-cli-managed-default-samclisourcebucket-1234567891 \
149 | --s3-prefix iam-password-policy \
150 | --region us-east-1 \
151 | --capabilities CAPABILITY_NAMED_IAM \
152 | --parameter-overrides ParameterKey=ResourceNamePrefix,ParameterValue=iam-password-policy \
153 | ParameterKey=OrganizationID,ParameterValue=o-12dsf43dsafa \
154 | ParameterKey=TargetOU,ParameterValue=ou-td4h-1123dfgs \
155 | ParameterKey=Environment,ParameterValue=dev
156 |
157 | ```
158 |
159 | Enter the parameter as follows:
160 |
161 | - **Stack name** The name of the CloudFormation stack that will be created by the SAM CLI
162 |
163 | - **S3 bucket** The name of the S3 bucket that the SAM CLI can use for the deployment
164 |
165 | - **S3 prefix** A prefix in the S3 bucket where the SAM CLI can upload files for the deployment
166 |
167 | - **AWS region** The AWS region in which you will be deploying the SAM CLI templates
168 |
169 | - **Capabilities** IAM capabilities to be used by the SAM CLI
170 |
171 | - **ResourceNamePrefix** A string prefix that will be used to name resources created by CloudFormation
172 |
173 | - **OrganizationID** The AWS Organizations ID where the solution will be deployed. You can retrieve this value from the AWS Organizations console in the management account.
174 |
175 | - **TargetOU** The OU that you will be targeting in the stack set
176 |
177 | - **Environment** An environment name that will be used to name the resources deployed
178 |
179 | ### Note ###
180 |
181 | You can optionally create a [AWS SAM CLI configuration file](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html) to store default parameters for future use. This can be useful if you want to configure multiple environments with different OUs.
182 |
183 | After a successful deployment, you can check an updated password policy in a member account of the OU you selected for the deployment. Figure 3 shows the password policy that was deployed.
184 |
185 |
186 |
187 | *Figure 3*
188 |
189 | If you want to update the password policy for all the member accounts of an OU, simply update the template, and run the build and deploy commands once again. The SAM CLI will monitor the progress of the update. The output from below corresponds to a password policy update. I updated the ```MinimumPasswordLength``` from ```10``` to ```12```. Figure 4 shows the outputs from the AWS CLI.
190 |
191 |
192 |
193 | *Figure 4*
194 |
195 | ## Limitations
196 |
197 | Currently this solution cannot be deployed in an AWS [Delegated Administrator](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-orgs-delegated-admin.html) account using the instructions I provided in the deployment section. CloudFormation currently does not support creating stack sets with service-managed permissions in a Delegated Administration account. There is an open [issue](https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap/issues/799) in the [AWS CloudFormation Coverage Roadmap](https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap) GitHub repository to add this support.
198 |
199 | ## Security
200 |
201 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.
202 |
203 | ## License
204 |
205 | This library is licensed under the MIT-0 License. See the LICENSE file.
--------------------------------------------------------------------------------
/functions/password_policy/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/iam-password-policy-deployment-via-cloudformation-stacksets/9819c2c3a9cec1560cfbe891bba3b16e662a226f/functions/password_policy/__init__.py
--------------------------------------------------------------------------------
/functions/password_policy/app.py:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | import json
5 | import os
6 | import logging
7 | import boto3
8 | from crhelper import CfnResource
9 | from botocore.exceptions import ClientError
10 |
11 | logger = logging.getLogger(__name__)
12 | helper = CfnResource()
13 |
14 |
15 | @helper.create
16 | @helper.update
17 | def create(event, context):
18 | """Updates the IAM password policy."""
19 | logger.info("Create Event")
20 | iam_client = get_client(event)
21 | logger.info(json.dumps(event))
22 | event = format_properties(event)
23 | logger.info(json.dumps(event))
24 | resource_properties = event['ResourceProperties']
25 | iam_client.update_account_password_policy(
26 | MinimumPasswordLength=resource_properties['MinimumPasswordLength'],
27 | RequireSymbols=resource_properties['RequireSymbols'],
28 | RequireNumbers=resource_properties['RequireNumbers'],
29 | RequireUppercaseCharacters=resource_properties['RequireUppercaseCharacters'],
30 | RequireLowercaseCharacters=resource_properties['RequireLowercaseCharacters'],
31 | AllowUsersToChangePassword=resource_properties['AllowUsersToChangePassword'],
32 | MaxPasswordAge=resource_properties['MaxPasswordAge'],
33 | PasswordReusePrevention=resource_properties['PasswordReusePrevention'],
34 | HardExpiry=resource_properties['HardExpiry']
35 | )
36 |
37 |
38 | @helper.delete
39 | def delete(event, context):
40 | """Deletes custom the IAM password policy."""
41 | logger.info("Delete Event")
42 | try:
43 | iam_client = get_client(event)
44 | response = iam_client.delete_account_password_policy()
45 | logger.info(response)
46 | except ClientError as e:
47 | if e.response['Error']['Code'] == 'NoSuchEntity':
48 | logger.warn("The entity does not exist")
49 | else:
50 | raise Exception("Unexpected error: %s" % e)
51 |
52 |
53 | def get_client(event):
54 | """Creates and returns an IAM client."""
55 | stack_id = event['StackId']
56 | stack_account = stack_id.split(":")[4]
57 | stack_region = stack_id.split(":")[3]
58 | aws_partition = os.getenv("AWS_PARTITION")
59 | target_role = os.getenv("TARGET_ROLE")
60 | role_arn = "arn:{}:iam::{}:role/{}".format(aws_partition, stack_account, target_role)
61 | session = CustomSession(role_arn=role_arn, role_session_name="assumed-role-session").session
62 | iam_client = session.client('iam', stack_region)
63 | return iam_client
64 |
65 |
66 | def format_properties(event):
67 | """Formats the user input for a password policy."""
68 | event_properties = event["ResourceProperties"]
69 | allowed_properties_bool = [
70 | "RequireSymbols",
71 | "RequireNumbers",
72 | "RequireUppercaseCharacters",
73 | "RequireLowercaseCharacters",
74 | "AllowUsersToChangePassword",
75 | "HardExpiry"
76 | ]
77 | for i in allowed_properties_bool:
78 | if event_properties[i] == "true":
79 | event_properties[i] = True
80 | elif event_properties[i] == "false":
81 | event_properties[i] = False
82 | else:
83 | raise Exception("Resource property values not supported. Values must be boolean.")
84 |
85 | allowed_properties_int = [
86 | "MinimumPasswordLength",
87 | "MaxPasswordAge",
88 | "PasswordReusePrevention"
89 | ]
90 | for j in allowed_properties_int:
91 | event_properties[j] = int(event['ResourceProperties'][j])
92 |
93 | return event
94 |
95 |
96 | def lambda_handler(event, context):
97 | """AWS Lambda function handler."""
98 | cf_event = json.loads(event['Records'][0]['Sns']['Message'])
99 | helper(cf_event, context)
100 |
101 |
102 | class CustomSession:
103 | def __init__(self, role_arn, role_session_name):
104 | self.role_arn = role_arn
105 | self.role_session_name = role_session_name
106 | self.session = self.create_session()
107 |
108 | def create_session(self):
109 | """Creates boto3 session."""
110 | target_account = self.role_arn.split(":")[4]
111 | sts_client = boto3.client('sts')
112 | caller_identity = sts_client.get_caller_identity()
113 | if caller_identity['Account'] != target_account:
114 | response = sts_client.assume_role(RoleArn=self.role_arn, RoleSessionName=self.role_session_name)
115 | return boto3.Session(
116 | aws_access_key_id=response['Credentials']['AccessKeyId'],
117 | aws_secret_access_key=response['Credentials']['SecretAccessKey'],
118 | aws_session_token=response['Credentials']['SessionToken']
119 | )
120 | else:
121 | return boto3.Session()
122 |
--------------------------------------------------------------------------------
/functions/password_policy/requirements.txt:
--------------------------------------------------------------------------------
1 | crhelper
--------------------------------------------------------------------------------
/resources/console1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/iam-password-policy-deployment-via-cloudformation-stacksets/9819c2c3a9cec1560cfbe891bba3b16e662a226f/resources/console1.png
--------------------------------------------------------------------------------
/resources/main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/iam-password-policy-deployment-via-cloudformation-stacksets/9819c2c3a9cec1560cfbe891bba3b16e662a226f/resources/main.png
--------------------------------------------------------------------------------
/resources/ou.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/iam-password-policy-deployment-via-cloudformation-stacksets/9819c2c3a9cec1560cfbe891bba3b16e662a226f/resources/ou.png
--------------------------------------------------------------------------------
/resources/samcli1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/iam-password-policy-deployment-via-cloudformation-stacksets/9819c2c3a9cec1560cfbe891bba3b16e662a226f/resources/samcli1.png
--------------------------------------------------------------------------------
/template.yaml:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 | AWSTemplateFormatVersion: '2010-09-09'
4 | Transform: AWS::Serverless-2016-10-31
5 | Description: >
6 | Deploys an IAM password policy via CloudFormation StackSets
7 | Parameters:
8 | ResourceNamePrefix:
9 | Type: String
10 | Description: String prefix for resource names
11 | Default: cfn-custom-resource-deployer
12 | OrganizationID:
13 | Type: String
14 | Description: The AWS organization ID. This parameter will be used to allow other accounts in the organization to communicate with the central account.
15 | Environment:
16 | Type: String
17 | Default: dev
18 | TargetOU:
19 | Type: String
20 | Description: The AWS Organizations OU to which the stack set template will be deployed
21 | Resources:
22 | PasswordPolicyProviderFunction:
23 | Type: AWS::Serverless::Function
24 | Properties:
25 | FunctionName: !Sub ${ResourceNamePrefix}-function-${Environment}
26 | Timeout: 20
27 | CodeUri: functions/password_policy/
28 | Handler: app.lambda_handler
29 | Runtime: python3.8
30 | Role: !GetAtt CustomResourceExecutionRole.Arn
31 | Environment:
32 | Variables:
33 | AWS_PARTITION: !Sub ${AWS::Partition}
34 | AWS_ACCOUNT: !Sub ${AWS::AccountId}
35 | TARGET_ROLE: !Sub ${ResourceNamePrefix}-role-${Environment}
36 | SNSTopicEncryptionKey:
37 | Type: AWS::KMS::Key
38 | Properties:
39 | Description: SNS topic encryption key
40 | EnableKeyRotation: true
41 | PendingWindowInDays: 20
42 | KeyPolicy:
43 | Version: '2012-10-17'
44 | Id: Main
45 | Statement:
46 | - Sid: Enable IAM User Permissions
47 | Effect: Allow
48 | Principal:
49 | AWS: !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:root
50 | Action: kms:*
51 | Resource: "*"
52 | - Sid: Allow an external account to use this CMK
53 | Effect: Allow
54 | Principal:
55 | AWS: "*"
56 | Action:
57 | - kms:Encrypt
58 | - kms:Decrypt
59 | - kms:ReEncrypt*
60 | - kms:GenerateDataKey*
61 | - kms:DescribeKey
62 | Resource: "*"
63 | Condition:
64 | StringEquals:
65 | aws:PrincipalOrgID: !Ref OrganizationID
66 | SNSTopicEncryptionKeyAlias:
67 | Type: AWS::KMS::Alias
68 | Properties:
69 | AliasName: alias/SNSTopicEncryption
70 | TargetKeyId: !Ref SNSTopicEncryptionKey
71 | PasswordPolicyTopic:
72 | Type: AWS::SNS::Topic
73 | Properties:
74 | DisplayName: !Sub ${ResourceNamePrefix}-topic-${Environment}
75 | TopicName: !Sub ${ResourceNamePrefix}-topic-${Environment}
76 | KmsMasterKeyId: !Ref SNSTopicEncryptionKeyAlias
77 | PasswordPolicyTopicPolicy:
78 | Type: AWS::SNS::TopicPolicy
79 | Properties:
80 | PolicyDocument:
81 | Version: 2008-10-17
82 | Id: CrossAccountPublishAccess
83 | Statement:
84 | - Sid: allow-publish-from-organization-accounts
85 | Effect: Allow
86 | Principal:
87 | AWS: "*"
88 | Action:
89 | - sns:Publish
90 | Resource:
91 | - !Ref PasswordPolicyTopic
92 | Condition:
93 | StringEquals:
94 | aws:PrincipalOrgID: !Ref OrganizationID
95 | Topics:
96 | - !Ref PasswordPolicyTopic
97 | PasswordPolicyLambdaSubscription:
98 | Type: AWS::SNS::Subscription
99 | Properties:
100 | Endpoint: !GetAtt PasswordPolicyProviderFunction.Arn
101 | Protocol: lambda
102 | TopicArn: !Ref PasswordPolicyTopic
103 | PasswordPolicyLambdaPermission:
104 | Type: AWS::Lambda::Permission
105 | Properties:
106 | FunctionName: !Ref PasswordPolicyProviderFunction
107 | Action: lambda:InvokeFunction
108 | Principal: sns.amazonaws.com
109 | SourceArn: !Ref PasswordPolicyTopic
110 | CustomResourceExecutionRole:
111 | Type: "AWS::IAM::Role"
112 | Properties:
113 | RoleName: !Sub ${ResourceNamePrefix}-role-${Environment}
114 | AssumeRolePolicyDocument:
115 | Version: 2012-10-17
116 | Statement:
117 | - Effect: Allow
118 | Principal:
119 | Service: 'lambda.amazonaws.com'
120 | Action: sts:AssumeRole
121 | Path: /
122 | Policies:
123 | - PolicyName: IAMPermissions
124 | PolicyDocument:
125 | Version: 2012-10-17
126 | Statement:
127 | - Effect: Allow
128 | Action:
129 | - sts:AssumeRole
130 | Resource:
131 | - !Sub 'arn:${AWS::Partition}:iam::*:role/${ResourceNamePrefix}-role-${Environment}'
132 | - Effect: Allow
133 | Action:
134 | - 'logs:CreateLogGroup'
135 | - 'logs:CreateLogStream'
136 | - 'logs:PutLogEvents'
137 | Resource: "*"
138 | CFCustomResourceStackSet:
139 | Type: AWS::CloudFormation::StackSet
140 | DependsOn:
141 | - PasswordPolicyProviderFunction
142 | - PasswordPolicyTopic
143 | - PasswordPolicyTopicPolicy
144 | - PasswordPolicyLambdaPermission
145 | - PasswordPolicyLambdaSubscription
146 | - SNSTopicEncryptionKey
147 | - SNSTopicEncryptionKeyAlias
148 | Properties:
149 | AutoDeployment:
150 | Enabled: true
151 | RetainStacksOnAccountRemoval: false
152 | Capabilities:
153 | - CAPABILITY_NAMED_IAM
154 | Description: Custom resource deployer
155 | StackInstancesGroup:
156 | - DeploymentTargets:
157 | OrganizationalUnitIds:
158 | - !Ref TargetOU
159 | Regions:
160 | - !Sub ${AWS::Region}
161 | Parameters:
162 | - ParameterKey: ResourceNamePrefix
163 | ParameterValue: !Ref ResourceNamePrefix
164 | - ParameterKey: TrustedRoleForCustomResources
165 | ParameterValue: !GetAtt CustomResourceExecutionRole.Arn
166 | - ParameterKey: PasswordPolicyTopicArn
167 | ParameterValue: !Ref PasswordPolicyTopic
168 | - ParameterKey: Environment
169 | ParameterValue: !Ref Environment
170 |
171 | Tags:
172 | - Key: Environment
173 | Value: !Ref Environment
174 |
175 | PermissionModel: SERVICE_MANAGED
176 | StackSetName: !Sub ${ResourceNamePrefix}-${Environment}
177 | TemplateBody: |
178 | AWSTemplateFormatVersion: '2010-09-09'
179 | Parameters:
180 | ResourceNamePrefix:
181 | Type: String
182 | Description: String prefix for resource names
183 | TrustedRoleForCustomResources:
184 | Type: String
185 | Description: The ARN of the trusted IAM role
186 | PasswordPolicyTopicArn:
187 | Type: String
188 | Description: The ARN of the SNS topic for the custom password policy
189 | Environment:
190 | Type: String
191 | Resources:
192 | CFCustomResourceProviderRoleSpoke:
193 | Type: "AWS::IAM::Role"
194 | Properties:
195 | RoleName: !Sub ${ResourceNamePrefix}-role-${Environment}
196 | AssumeRolePolicyDocument:
197 | Version: 2012-10-17
198 | Statement:
199 | - Effect: Allow
200 | Principal:
201 | AWS: !Ref TrustedRoleForCustomResources
202 | Action: sts:AssumeRole
203 | Path: /
204 | Policies:
205 | - PolicyName: IAMPermissions
206 | PolicyDocument:
207 | Version: 2012-10-17
208 | Statement:
209 | - # PasswordPolicyPermissions
210 | Effect: Allow
211 | Action:
212 | - iam:DeleteAccountPasswordPolicy
213 | - iam:UpdateAccountPasswordPolicy
214 | Resource: '*'
215 | PasswordPolicy:
216 | Type: Custom::PasswordPolicy
217 | DependsOn:
218 | - CFCustomResourceProviderRoleSpoke
219 | Properties:
220 | MinimumPasswordLength: 10
221 | RequireSymbols: true
222 | RequireNumbers: false
223 | RequireUppercaseCharacters: true
224 | RequireLowercaseCharacters: true
225 | AllowUsersToChangePassword: true
226 | MaxPasswordAge: 10
227 | PasswordReusePrevention: 1
228 | HardExpiry: true
229 | ServiceToken: !Ref PasswordPolicyTopicArn
230 | Outputs:
231 | ResourceNamePrefix:
232 | Description: Input value for stack set parameter ResourceNamePrefix
233 | Value: !Ref ResourceNamePrefix
234 | TrustedRoleForCustomResources:
235 | Description: Input value for stack set parameter TrustedRoleForCustomResources
236 | Value: !GetAtt CustomResourceExecutionRole.Arn
237 | PasswordPolicyTopicArn:
238 | Description: Input value for stack set parameter PasswordPolicyTopicArn
239 | Value: !Ref PasswordPolicyTopic
--------------------------------------------------------------------------------