Last active
October 20, 2022 12:09
-
-
Save yumminhuang/5e928e526d7b3de8c55ee142624b37a4 to your computer and use it in GitHub Desktop.
Setting Password Policies to Follow CIS Benchmark via CloudFormation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
AWSTemplateFormatVersion: '2010-09-09' | |
Description: | |
This CloudFormation stack sets an IAM Password Policy for an account. It uses | |
a custom resource to manage the policy. Note that IAM password policies are | |
global, and this will apply to all regions--not just the region you create | |
the stack in. | |
Parameters: | |
# CIS Benchmark Check 1.5 | |
RequireUppercaseChars: | |
Type: 'String' | |
Description: 'Password requirement of at least one uppercase character.' | |
Default: 'True' | |
AllowedValues: | |
- 'True' | |
- 'False' | |
# CIS Benchmark Check 1.6 | |
RequireLowercaseChars: | |
Type: 'String' | |
Description: 'Password requirement of at least one lowercase character.' | |
Default: 'True' | |
AllowedValues: | |
- 'True' | |
- 'False' | |
# CIS Benchmark Check 1.7 | |
RequireSymbols: | |
Type: 'String' | |
Description: 'Password requirement of at least one nonalphanumeric character.' | |
Default: 'True' | |
AllowedValues: | |
- 'True' | |
- 'False' | |
# CIS Benchmark Check 1.8 | |
RequireNumbers: | |
Type: 'String' | |
Description: 'Password requirement of at least one number.' | |
Default: 'True' | |
AllowedValues: | |
- 'True' | |
- 'False' | |
# CIS Benchmark Check 1.9 | |
MinPasswordLength: | |
Type: 'Number' | |
Description: 'Minimum password length.' | |
Default: 14 | |
ConstraintDescription: '(8-128 characters)' | |
MinValue: 8 | |
MaxValue: 128 | |
# CIS Benchmark Check 1.10 | |
PasswordHistory: | |
Type: 'Number' | |
Description: 'Number of previous passwords to remember.' | |
Default: 24 | |
ConstraintDescription: '(1-24 passwords)' | |
MinValue: 1 | |
MaxValue: 24 | |
# CIS Benchmark Check 1.11 | |
MaxPasswordAge: | |
Type: 'Number' | |
Description: 'Maximum age for passwords, in number of days.' | |
Default: 90 | |
ConstraintDescription: '(90-365 days)' | |
MinValue: 90 | |
MaxValue: 365 | |
Resources: | |
IAMPasswordPolicy: | |
Type: 'Custom::IAMPolicyResource' | |
Properties: | |
ServiceToken: !GetAtt IAMPasswordPolicyResource.Arn | |
Region: !Ref "AWS::Region" | |
IAMPasswordPolicyResource: | |
Type: "AWS::Lambda::Function" | |
Properties: | |
Runtime: python3.7 | |
Handler: index.lambda_handler | |
MemorySize: 128 | |
Timeout: 30 | |
Role: !GetAtt IAMPasswordPolicyResourceExecutionRole.Arn | |
Code: | |
ZipFile: !Sub | | |
import json | |
import boto3 | |
from botocore.exceptions import ClientError | |
import cfnresponse | |
iam = boto3.client("iam") | |
# Lambda entry point | |
def lambda_handler(event, context): | |
if event['RequestType'] == 'Create' or event['RequestType'] == 'Update': | |
res, reason = update_policy() | |
elif event['RequestType'] == 'Delete': | |
res, reason = delete_policy() | |
else: | |
res = False | |
reason = "Unknown operation: " + event['RequestType'] | |
responseData = {} | |
responseData['Reason'] = reason | |
if res: | |
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) | |
else: | |
cfnresponse.send(event, context, cfnresponse.FAILED, responseData) | |
def update_policy(): | |
try: | |
response = iam.update_account_password_policy( | |
AllowUsersToChangePassword=True, | |
HardExpiry=False, | |
MaxPasswordAge=${MaxPasswordAge}, | |
MinimumPasswordLength=${MinPasswordLength}, | |
RequireLowercaseCharacters=${RequireLowercaseChars}, | |
RequireNumbers=${RequireNumbers}, | |
RequireSymbols=${RequireSymbols}, | |
RequireUppercaseCharacters=${RequireUppercaseChars}, | |
PasswordReusePrevention=${PasswordHistory}) | |
return (True, response) | |
except Exception as e: | |
return (False, "Cannot update policy: " + str(e)) | |
def delete_policy(): | |
try: | |
policy = iam.get_account_password_policy() | |
response = iam.delete_account_password_policy() | |
return (True, response) | |
except Exception as e: | |
return (False, "Cannot delete policy: " + str(e)) | |
IAMPasswordPolicyResourceExecutionRole: | |
Type: AWS::IAM::Role | |
Properties: | |
AssumeRolePolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Effect: Allow | |
Principal: | |
Service: | |
- lambda.amazonaws.com | |
Action: | |
- sts:AssumeRole | |
Path: "/" | |
Policies: | |
- PolicyName: IAMPasswordCreatorPolicy | |
PolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Effect: 'Allow' | |
Action: | |
- 'logs:CreateLogGroup' | |
- 'logs:CreateLogStream' | |
- 'logs:PutLogEvents' | |
Resource: '*' | |
- Effect: Allow | |
Action: | |
- iam:GetAccountPasswordPolicy | |
- iam:UpdateAccountPasswordPolicy | |
- iam:DeleteAccountPasswordPolicy | |
Resource: "*" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Good Morning,
great work, thank you!
Are there any copyright restrictions on your code?
Happy coding,
Heiko.