User Password & Key Expiration in AWS

AWS provides the ability to set a password policy on an account that will require a user to change their password after a certain period of time. However there is no method by which you can notify a user it is about to expire, nor is there anything that would expire an access key that hasn’t been rotated.

I wanted something that would implement policy that would deny any usage if the password was past-due (even if they hadn’t logged in for awhile) and would de-activate a key if it was older than the date set in the password policy. This would bring my AWS stuff into corporate compliance and prevent me from having to bother my users myself.

Since all our IAM usernames match the user’s email address, this is pretty easy to implement via automation. I’ve created a lambda that uses both the AWS Credential Report and the IAM APIs to iterate across all users and send them an SES notification when there password or key is about to expire.

The cloudformation template creates an IAM Group called UsersWithExpiredCredentials that limits the user to just the IAM actions necessary to change their password. There is the Lambda that does the work (and the appropriate Role and Invocation permissions), an Invocation Failure Alarm that will send to my account’s Alert Topic and a scheduled CloudWatch Event to trigger it to run every day.

The template takes as parameters:
* GracePeriodInDays (how far out from expiration do we warn the user)
* Boolean to indicate if we just warn or actually disable the user or key
* Boolean to indicate if we should use SES to email the user
* The SES authorized From Address
* Header and footer language for the email
* and an SNS topic where a summary of all actions taken gets published
(plus all the usual stuff needed to deploy a lambda that’s too big to fit in-line)

The Lambda itself uses the GetCredentialReport to see how long till the password will expire and it queries ListAccessKeys to see when the key was created. The Lambda only does this for IAM Users with LoginProfiles (ie they have a password), so it would not expire a IAM User that only had an access key (that’s a bit more dangerous and requires different handling)1. It uses GetAccountPasswordPolicy to determine the expiration age for both passwords and keys.

If an Access Key is past the expiration age it is not deleted and the user is not added to the BlackHole group. The key is simply flipped to deactivated.

In order to email users, you must either have had AWS support remove the sandbox limitation on your account, or you must verify all the users you intend to send to. Since the users are all IAM Users of your account, either are a viable option2.

1) Yes, a user can be dumb and embed their personal key into a production process somewhere. Said users need a flogging.
2) And adding all users to SES so you can send them system-wide notices might not be a bad thing to add to my add_user script and my RequireMFA stack.

Deploying a CloudFormation Template simply

If you’re deploying a cloudformation stack via the CLI, there is a lot that goes into it. Here are the options in the AWS CLI:
[--template-body ]
[--template-url ]
[--parameters ]
[--disable-rollback | --no-disable-rollback]
[--timeout-in-minutes ]
[--notification-arns ]
[--capabilities ]
[--resource-types ]
[--role-arn ]
[--on-failure ]
[--stack-policy-body ]
[--stack-policy-url ]
[--tags ]

The Parameters are passed in via a string that looks like this:
ParameterKey=key1,ParameterValue=value1,UsePreviousValue=boolean ParameterKey=key2,ParameterValue=value2,UsePreviousValue=boolean ParameterKey=key3,ParameterValue=value3,UsePreviousValue=boolean

There are Stack Policies that you probably want to apply to protect important stateful resources from mistaken Replacement or Deletions.

You might want to reference resources across stacks (this is better now that stacks can export values, but you might not want to do that in every case).

So I created deploy_stack.rb. It takes as it’s input a Manifest file that contains:
o StackName
o Template Location (file or S3)
o Region to deploy to
o TimeOut
o Parameters
o Stack Tags
o Stack Policy
o Pre and Post install scripts that can be used to do things like zip up a lambda or execute an AWS CLI command that’s not available via Cloudformation

Continue reading “Deploying a CloudFormation Template simply”

Creating a set of generic SNS Topics

When I’m creating a new AWS account, I find it helpful to have a generic set of SNS topics that ping me and my team if something goes wrong.

The following CloudFormation template can be used for that purpose. It requires a few parameters and includes an optional Lambda that will send the alerts to a Slack Channel.

Three Topics are created for critical, error and info-level alerts. Critical alerts will send me a text and email, while error only sends an email. Info alerts typically only go to slack (or a filtered email folder).

A generic Spending Alarm is also created by the Template.

The SlackNotification Lambda is small enough to be included in-line. The Webhook URL, AccountDescription, Slack Channel and IconEmoji are template parameters that are passed to the function via Lambda Environment Variables.

The stack will output the ARN for the three Topics so they can be referenced in other places, and I’m using Stack Exports so you don’t need to use deploy_stack trickery to find them.

Requiring AWS IAM Users to Enable MFA

When AWS announced Lambda at the 2014 re:Invent, my immediate thought was “Cool, you can now program the cloud itself”. Since then, everyone has jumped on the “serverless” bandwagon for building apps. After this year’s re:Invent I’m inspired to get back to using Lambda to program the cloud.

One of the sessions I attended was on Security Automation. I’ll have more to say on that later. However, it gave me the idea for a setup that would require users to have MFA enabled, or otherwise be blocked from doing anything with their IAM User in the AWS account.

Thus over the holidays I published RequireMFA which you can get here:
Continue reading “Requiring AWS IAM Users to Enable MFA”

AWS API Keys in OSX Keychain

AWS API Keys are powerful things that you don’t want to leave lying around. Amazon’s suggestion is to keep them in ~/.aws/config. I’m not a fan of that. OSX has KeyChain, which is a secure repository for credentials and what most OSX Apps use for caching your login to various websites. This might not be the ideal solution, but it’s better than an unencrypted file in your home directory.

I’ve built a set of three scripts that will use OSX Keychain to store your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, and retrieve them into environment variables when needed to use the AWS API or any script that honors those environment variables. Continue reading “AWS API Keys in OSX Keychain”

AWS New Account Config

We’re getting ready to deploy our first production workload in AWS, and our AWS account team recommended we enable a bunch of auditing on our accounts in each region. That is a lot of clicking for 9 regions across three accounts.

This script will configure AWS CloudTrail and AWS Config Service in all regions, configure the logging bucket, and establish a reasonable password policy. Amazon is about to release 3 (or four) more regions in Ohio, England, Korea and India. As these regions spin up you’ll need to enable auditing trails there, even if you never plan to use the region.

The script can also be used with –status to make sure all your logging is enabled.

./ --status mydomain 49nnnnnnnnn
Using mydomain as my bucket suffix and 49nnnnnnnnn as my AWS Account Number
						Cloud Trail Status
Region 		Trail Name 		Bucket 		GlobalEvents?		Logging On?
eu-west-1 	Default 	logs-mydomain 		False 		True
ap-southeast-1 	Default 	logs-mydomain 		False 		True
ap-southeast-2 	Default 	logs-mydomain 		False 		True
eu-central-1 	Default 	logs-mydomain 		False 		True
ap-northeast-1 	Default 	logs-mydomain 		False 		True
us-east-1 	logs-mydomain 	arn:aws:cloudtrail:us-east-1:496486987401:trail/Default 		True
sa-east-1 	Default 	logs-mydomain 		False 		True
us-west-1 	Default 	logs-mydomain 		False 		True
us-west-2 	Default 	logs-mydomain 		False 		True

					AWS Config Service Status
Region 		Recorder Name 		Bucket 			Last Status?		Recording?
eu-west-1 	Default-eu-west-1 	logs-mydomain 		SUCCESS 		True
ap-southeast-1 	Default-ap-southeast-1 	logs-mydomain 		SUCCESS 		True
ap-southeast-2 	Default-ap-southeast-2 	logs-mydomain 		SUCCESS 		True
eu-central-1 	Default-eu-central-1 	logs-mydomain 		SUCCESS 		True
ap-northeast-1 	Default-ap-northeast-1 	logs-mydomain 		SUCCESS 		True
us-east-1 	Default-us-east-1 	logs-mydomain 		SUCCESS 		True
sa-east-1 	Default-sa-east-1 	logs-mydomain 		SUCCESS 		True
us-west-1 	Default-us-west-1 	logs-mydomain 		SUCCESS 		True
us-west-2 	Default-us-west-2 	logs-mydomain 		SUCCESS 		True

|        GetAccountPasswordPolicy         |
||            PasswordPolicy             ||
||  AllowUsersToChangePassword  |  True  ||
||  ExpirePasswords             |  True  ||
||  HardExpiry                  |  False ||
||  MaxPasswordAge              |  180   ||
||  MinimumPasswordLength       |  8     ||
||  RequireLowercaseCharacters  |  True  ||
||  RequireNumbers              |  True  ||
||  RequireSymbols              |  True  ||
||  RequireUppercaseCharacters  |  True  ||

This is a work in progress and as I delve deeper into account best practices I will be adding to this.