-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
1,057 additions
and
28 deletions.
There are no files selected for viewing
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
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
target |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
#Setup | ||
|
||
In order to manage a domain across accounts, you will need to set up the Lambda function and the appropriate roles. These changes are only needed when you add a new domain, or when you add a new account and/or region where you want to create CloudFormation stacks that use the domain. | ||
|
||
The Lambda function needs to be uploaded once for every account that will use this function. When you upload it, it will also create SNS topics in every region that can be used for calls from CloudFormation stacks. | ||
|
||
There is no harm in using this function in templates that are being run from the same account that owns the domain. While you don't need the cross-account functionality in this case and could use the native Route 53 types, if you want to author a template that will be used in multiple accounts, it may be more straightforward to use this function universally rather than try to create conditionals or multiple template versions. | ||
|
||
## Pre-reqs: | ||
|
||
1. If you haven't already, install the AWS CLI. You'll either want to setup profiles for both accounts (http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cli-multiple-profiles), or just have both credentials ready and reconfigure. | ||
2. When we refer to authenticating to an account below, you do this either by setting the `AWS_DEFAULT_PROFILE=<profile>` environment variable if you are using profiles, or by running `aws configure` and enter in the credentials for that account. If you are using profiles, and you want to execute against a different region than is in the profile, you can override it with the environment variable `AWS_DEFAULT_REGION=<region>`. | ||
- References to the "Route 53 account" below refer to the AWS account that owns the hosted zone for the domain. | ||
- References to the "CF account" below refer to another AWS account where you want to be able to run templates that create route 53 entries in the "Route 53 account." | ||
3. You also need `npm` and `jq` installed. | ||
|
||
|
||
## Per domain setup: | ||
|
||
These steps need to be performed for each domain that you want to manage from other accounts. This only needs to be done once in the account that owns the domain, not in each AWS account. | ||
|
||
1. You will need access to change IAM policies and roles in the Route53 account that is managing the domain ("Hosted Zone" in AWS terminology). | ||
2. Authenticate to the **Route 53 account.** | ||
3. Look up the hosted zone ID for the domain. The zone ID is an alphanumeric code, not the actual domain name. | ||
4. From the `assets/custom-types/remote-route53` directory, run `./create-zone-admin.sh <zone-id-to-manage>`. If you've already setup another domain in this account, it will add this domain to the role. | ||
5. The ARN of the role will be used as the "DestinationRole" parameter to the Cloud Formation resource, regardless of what account you are using to run the template. | ||
6. If you are adding an additional domain but have already setup all the accounts and regions per the instructions below, you do not need to repeat these steps. They will pick up the new domain on the existing role. | ||
|
||
|
||
## Per AWS account setup: | ||
|
||
These steps need to be performed for each account where you want to run CloudFormation templates that use this function (the "CF Account"). | ||
|
||
1. You will need access to change IAM policies and roles in both the Route53 account and the "CF Account". You will also need permissions to create Lambda functions and SNS topics in the CF account. | ||
2. Authenticate to the **CF account.** Regardless of the profile's region, `us-east-1` will be used, since that is where the AWS Route 53 endpoints live. | ||
3. Run `./deploy.sh` to create the Lambda function and the appropriate execution role, along with an SNS topic for the function in all regions. Note that SNS topics have no charge for just existing, so there is no cost for topics in regions you aren't using here. | ||
4. Look at the output from the script above and note the command it asks you to run (`./add-zone-admin-trust <role-name>`). | ||
5. Authenticate to the **Route 53 account.** | ||
6. Run the command from step 4. This will grant the function permissions to assume the Route 53 role created earlier. | ||
|
||
## Now what? | ||
|
||
Now that you have setup your roles, Lambda functions, and SNS topics, you will need two pieces of information to use this in your templates. | ||
|
||
1. When you created the zone admin role, it output the ARN of the role. This value needs to be specified as the `DestinationRole` parameter to the Route 53 type in your template. It will take the form of `arn:aws:iam::<route-53-account-number>:role/remote-route53-cf-admin`. The value will be the same regardless of what account you are running the Cloud Formation template from. Therefore, this value can be hard coded in your template or be set as a default value if you want to parameterize it. | ||
2. The SNS topic ARN for the account and region where the Cloud Formation template is running must be specified as the `ServiceToken` parameter to the Route 53 type in your template. It will take the form of `arn:aws:sns:<cf-region>:<cf-account-num>:cf-remote-route53`. Because the account and region should always be the values for where you are running the template, you can assemble this dynamically, using CF psuedo-parameters. This would look like `` `Fn::Join`(":", Seq("arn:aws:sns", `AWS::Region`, `AWS::AccountId`, "cf-remote-route53"))``. This can then be hard coded into your template and will fill in with the proper region and account ID automatically. | ||
|
||
## Cleaning up | ||
|
||
1. Authenticate to the **CF account** that you want to clean up. Run `./delete-lambda.sh` to remove the Lambda function in any region you have uploaded it, all SNS topics created for this function, and the Lambda execution role in this account. | ||
2. Authenticate to the **Route 53 account** that you want to clean up. Run `./delete-zone-admin.sh` to remove the zone admin role in this account and its associated policies. |
13 changes: 13 additions & 0 deletions
13
assets/custom-types/remote-route53/add-zone-admin-trust.sh
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
#!/usr/bin/env bash | ||
lambdaRoleARN=$1 | ||
|
||
route53RoleName=remote-route53-cf-admin | ||
|
||
currentPolicy=$(aws iam get-role --role-name $route53RoleName | jq .AssumeRolePolicyDocument) | ||
|
||
aws iam get-role --role-name $route53RoleName | \ | ||
jq ".Role.AssumeRolePolicyDocument | if (.Statement[0].Principal.AWS | type) == \"string\" then .Statement[0].Principal.AWS = [.Statement[0].Principal.AWS ] + [\"$lambdaRoleARN\"] else .Statement[0].Principal.AWS |= . + [\"$lambdaRoleARN\"] end" > target/tmp-zone-admin-trust-policy.json | ||
|
||
aws iam update-assume-role-policy --role-name $route53RoleName --policy-document file://target/tmp-zone-admin-trust-policy.json | ||
|
||
rm target/tmp-zone-admin-trust-policy.json |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#!/usr/bin/env bash | ||
hostedZoneId=$1 | ||
|
||
roleName=remote-route53-cf-admin | ||
|
||
if ! aws iam get-role --role-name $roleName >/dev/null 2>&1 ; then | ||
echo "Creating $roleName" | ||
|
||
aws iam create-role --role-name $roleName \ | ||
--assume-role-policy-document file://zone-admin-trust-policy-stub.json >/dev/null | ||
|
||
aws iam put-role-policy --role-name $roleName \ | ||
--policy-name list-zones \ | ||
--policy-document file://zone-admin-list-policy.json >/dev/null | ||
else | ||
echo "$roleName exists, adding zone to it" | ||
fi | ||
|
||
cat zone-admin-policy.json | jq ".Statement[0].Resource = \"arn:aws:route53:::hostedzone/$hostedZoneId\"" > target/tmp-zone-admin-policy.json | ||
|
||
aws iam put-role-policy --role-name $roleName \ | ||
--policy-name zone-${hostedZoneId}-admin \ | ||
--policy-document file://target/tmp-zone-admin-policy.json >/dev/null | ||
|
||
roleArn=$(aws iam get-role --role-name $roleName --query Role.Arn) | ||
|
||
echo "" | ||
echo "Role ARN: $roleArn" | ||
echo "Use this ARN in the as the 'DestinationRole' parameter to the Cloud Formation resource, regardless of what account you are running the template from." | ||
echo "" | ||
|
||
rm target/tmp-zone-admin-policy.json |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#!/usr/bin/env bash | ||
|
||
role=lambda-execution-cf-remote-route53 | ||
function_name=cf-remote-route53 | ||
account_id=$(aws iam get-user | jq -r .User.Arn | perl -pe 's/arn:aws:iam::(\d+):.*/$1/') | ||
|
||
for region in $(aws ec2 describe-regions | jq -r .Regions[].RegionName) ; do | ||
echo "Checking region $region" | ||
if aws lambda get-function --region $region --function-name $function_name >/dev/null 2>&1 ; then | ||
echo " Deleting function in region $region" | ||
aws lambda delete-function --region $region --function-name $function_name >/dev/null | ||
fi | ||
|
||
if aws sns get-topic-attributes --region $region --topic-arn arn:aws:sns:${region}:${account_id}:cf-remote-route53 >/dev/null 2>&1 ; then | ||
echo " Deleting SNS topic in region $region" | ||
aws sns delete-topic --region $region --topic-arn arn:aws:sns:${region}:${account_id}:cf-remote-route53 >/dev/null | ||
fi | ||
done | ||
|
||
if aws iam get-role --role-name $role >/dev/null 2>&1 ; then | ||
for policy in $(aws iam list-role-policies --role-name $role --query PolicyNames --output text) ; do | ||
echo "Deleting policy $policy on role $role" | ||
aws iam delete-role-policy --role-name $role --policy-name $policy | ||
done | ||
echo "Deleting role $role" | ||
aws iam delete-role --role-name $role >/dev/null | ||
fi |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
#!/usr/bin/env bash | ||
|
||
role=remote-route53-cf-admin | ||
|
||
if aws iam get-role --role-name $role >/dev/null 2>&1 ; then | ||
for policy in $(aws iam list-role-policies --role-name $role --query PolicyNames --output text) ; do | ||
echo "Deleting policy $policy on role $role" | ||
aws iam delete-role-policy --role-name $role --policy-name $policy | ||
done | ||
echo "Deleting role $role" | ||
aws iam delete-role --role-name $role >/dev/null | ||
fi |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
#!/bin/bash | ||
|
||
role=lambda-execution-cf-remote-route53 | ||
function_name=cf-remote-route53 | ||
account_id=$(aws iam get-user | jq -r .User.Arn | perl -pe 's/arn:aws:iam::(\d+):.*/$1/') | ||
|
||
# Hard coded to us-east-1, since that's where Route 53 lives | ||
lambdaRegion=us-east-1 | ||
|
||
echo "Packaging function..." | ||
rm -rf target | ||
mkdir -p target | ||
cd target | ||
cp ../remote_route53.js remote_route53.js | ||
npm install aws-sdk | ||
zip -r remote_route53.zip remote_route53.js node_modules/ >/dev/null | ||
cd .. | ||
|
||
|
||
if ! aws iam get-role --role-name $role >/dev/null 2>&1 ; then | ||
echo "Creating role..." | ||
aws iam create-role --role-name $role \ | ||
--assume-role-policy-document file://lambda-trust-policy.json >/dev/null | ||
# The role seems to take a little time to settle down and work. Because...amazon. | ||
echo "Giving Amazon time to settle"... | ||
sleep 10 | ||
fi | ||
|
||
echo "Setting role policy..." | ||
aws iam put-role-policy --role-name $role \ | ||
--policy-name $role \ | ||
--policy-document file://lambda-policy.json | ||
|
||
|
||
|
||
echo "Uploading function..." | ||
|
||
if aws lambda get-function --region $lambdaRegion --function-name $function_name >/dev/null 2>&1 ; then | ||
aws lambda update-function-code --region $lambdaRegion --function-name $function_name \ | ||
--zip-file fileb://target/remote_route53.zip > /dev/null | ||
else | ||
while ! aws lambda create-function --region $lambdaRegion --function-name $function_name \ | ||
--description "Custom CloudFormation function for managing Route 53 in another account" \ | ||
--runtime nodejs \ | ||
--role arn:aws:iam::${account_id}:role/${role} \ | ||
--handler remote_route53.handler \ | ||
--timeout 300 \ | ||
--zip-file fileb://target/remote_route53.zip > /dev/null ; do | ||
|
||
echo "The 'The role defined for the function cannot be assumed by Lambda' error you may have just seen is time based. Sleeping 1 and trying again." | ||
echo "If you saw a different error or this doesn't resolve itself after a few tries, hit ctrl-c" | ||
sleep 1 | ||
done | ||
fi | ||
lambdaArn=$(aws lambda get-function --region $lambdaRegion --function-name $function_name --output text --query Configuration.FunctionArn) | ||
lambdaRole=$(aws lambda get-function --region $lambdaRegion --function-name $function_name --output text --query Configuration.Role) | ||
|
||
|
||
|
||
|
||
# Create SNS topic for each region. | ||
for snsRegion in $(aws ec2 describe-regions | jq -r .Regions[].RegionName) ; do | ||
topicSubscriptions=$(aws sns list-subscriptions-by-topic --region $snsRegion --topic-arn arn:aws:sns:${snsRegion}:${account_id}:cf-remote-route53 2>/dev/null) | ||
|
||
if [[ $? != 0 ]] ; then | ||
echo "Creating SNS topic in $snsRegion" | ||
topicArn=$(aws sns create-topic --region $snsRegion --name cf-remote-route53 --query TopicArn --output text) | ||
|
||
aws sns subscribe --region $snsRegion --topic-arn $topicArn --protocol lambda --notification-endpoint $lambdaArn > /dev/null | ||
sid=$(cat /dev/urandom | env LC_CTYPE=C tr -dc a-zA-Z0-9 | head -c 16; echo) | ||
aws lambda add-permission --region $lambdaRegion --function-name $lambdaArn \ | ||
--statement-id $sid --action lambda:invokeFunction \ | ||
--principal sns.amazonaws.com --source-arn $topicArn > /dev/null | ||
else | ||
topicIsSubscribed=$(echo "$topicSubscriptions" | jq -r 'select(.Subscriptions[].Endpoint == "'$lambdaArn'") | any') | ||
if [[ "$topicIsSubscribed" != "true" ]] ; then | ||
echo "Subscribing existing SNS topic in $snsRegion to Lambda function." | ||
topicArn=$(aws sns get-topic-attributes --region $snsRegion --topic-arn arn:aws:sns:${snsRegion}:${account_id}:cf-remote-route53 --output text --query Attributes.TopicArn) | ||
|
||
aws sns subscribe --region $snsRegion --topic-arn $topicArn --protocol lambda --notification-endpoint $lambdaArn > /dev/null | ||
sid=$(cat /dev/urandom | env LC_CTYPE=C tr -dc a-zA-Z0-9 | head -c 16; echo) | ||
aws lambda add-permission --region $lambdaRegion --function-name $lambdaArn \ | ||
--statement-id $sid --action lambda:invokeFunction \ | ||
--principal sns.amazonaws.com --source-arn $topicArn > /dev/null | ||
else | ||
echo "SNS topic already exists in $snsRegion" | ||
fi | ||
|
||
fi | ||
done | ||
|
||
echo "" | ||
echo "To grant this function permission to manage Route 53, please re-authenticate to the Route 53 AWS account and run:" | ||
echo "./add-zone-admin-trust.sh $lambdaRole" | ||
echo "" |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"Version": "2012-10-17", | ||
"Statement": [ | ||
{ | ||
"Effect": "Allow", | ||
"Action": [ | ||
"logs:CreateLogGroup", | ||
"logs:CreateLogStream", | ||
"logs:PutLogEvents" | ||
], | ||
"Resource": "arn:aws:logs:*:*:*" | ||
}, | ||
{ | ||
"Effect": "Allow", | ||
"Action": [ | ||
"sts:AssumeRole" | ||
], | ||
"Resource": "*" | ||
} | ||
] | ||
} |
16 changes: 16 additions & 0 deletions
16
assets/custom-types/remote-route53/lambda-trust-policy.json
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"Version": "2012-10-17", | ||
"Statement": [ | ||
{ | ||
"Effect": "Allow", | ||
"Principal": { | ||
"Service": [ | ||
"lambda.amazonaws.com" | ||
] | ||
}, | ||
"Action": [ | ||
"sts:AssumeRole" | ||
] | ||
} | ||
] | ||
} |
Oops, something went wrong.