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