diff --git a/README.md b/README.md index 929c030..2aafb9c 100644 --- a/README.md +++ b/README.md @@ -38,3 +38,26 @@ Add extra website files with: Run ```/stack.sh $STACK_NAME delete $WEB_BUCKET_NAME``` This will also delete the S3 bucket contents. + + +## Optional: Use a Custom Domain Name + +### Certificate setup: manual process + +1. Sign in to the AWS Certificate Manager console **and set the region to us-east-1**. +2. Choose "Request a certificate". +3. Enter the custom domain name for the API in "Domain name". +4. Choose "Review, request and confirm". +5. In AWS Certificate Manager, copy the CNAME name/values of the pending certificate and paste into the domain provider's system as a new CNAME record. The certificate will remain pending until the DNS updates (up to 48 hours). +6. Copy the certificate ARN for later use. + +### Stack creation + +1. Run ```source setup.sh``` as usual, but set ```USE_CUSTOM_DOMAIN=true``` and provide the domain name and certificate ARN. +2. An intermediate (cloudfront) URL is created by AWS. Obtain this URL and use it to create a new CNAME record (for a subdomain) or a new ALIAS record (for the root domain) in the domain provider's system. +3. Wait for the DNS to update. + +### Important notes + +- This process disables the usual API Gateway URL, so the website can only be accessed via the custom domain name. +- The intermediate cloudfront URL **can** be accessed directly, as a test, **but must have the request ```Host``` header set to the custom domain**. Therefore it cannot be tested with simple web browser use. diff --git a/setup.sh b/setup.sh index 4dc5da7..024c646 100755 --- a/setup.sh +++ b/setup.sh @@ -3,7 +3,15 @@ # Run this script to deploy the project on AWS export AWS_REGION=eu-west-3 -export STACK_NAME=webtest1 +export STACK_NAME=webtest2 + +export STAGE_NAME=v1 + +# Optional: custom domain settings. +# Must then update the custom DNS records with the new cloudfront URL. +export USE_CUSTOM_DOMAIN=false +export DOMAIN_NAME=mtest.dev +export DOMAIN_CERT_ARN="define this" ################################################################## @@ -14,26 +22,46 @@ export WEB_BUCKET_NAME="${STACK_NAME}-bucket-${RAND_ID}" # Prevent terminal output waiting: export AWS_PAGER="" -source stack.sh $STACK_NAME create $WEB_BUCKET_NAME +if [ "$USE_CUSTOM_DOMAIN" == "true" ]; then + source stack.sh $STACK_NAME create $WEB_BUCKET_NAME "stageName=$STAGE_NAME enableCustomDomainName=true domainName=$DOMAIN_NAME certificateArn=$DOMAIN_CERT_ARN" +else + source stack.sh $STACK_NAME create $WEB_BUCKET_NAME stageName=$STAGE_NAME +fi echo "" echo "Waiting for stack creation..." -GATEWAY_ID= -while [[ -z $GATEWAY_ID ]]; do - export GATEWAY_ID=$(aws apigateway get-rest-apis --no-paginate | \ - python3 -c \ +if [ "$USE_CUSTOM_DOMAIN" != "true" ]; then + GATEWAY_ID= + while [[ -z $GATEWAY_ID ]]; do + export GATEWAY_ID=$(aws apigateway get-rest-apis --no-paginate | \ + python3 -c \ "import sys, json for item in json.load(sys.stdin)['items']: if item['name'] == '$STACK_NAME-api-gateway': print(item['id'])") - sleep 1 -done + sleep 1 + done -export GATEWAY_URL="https://${GATEWAY_ID}.execute-api.${AWS_REGION}.amazonaws.com/" + export WEBSITE_URL="https://${GATEWAY_ID}.execute-api.${AWS_REGION}.amazonaws.com/${STAGE_NAME}" + echo "" + echo "The website URL is:" + echo $WEBSITE_URL + echo "" +else + CLOUDFRONT_URL= + while [[ -z $CLOUDFRONT_URL ]]; do + export CLOUDFRONT_URL=$(aws apigateway get-domain-names --no-paginate | \ + python3 -c \ +"import sys, json +for item in json.load(sys.stdin)['items']: + if item['domainName'] == '$DOMAIN_NAME': + print(item['distributionDomainName'])") + sleep 1 + done -echo "" -echo "The API Gateway URL is:" -echo $GATEWAY_URL -echo "" -# Note: must go to GATEWAY_URL/ to access the actual website + echo "" + echo "The CloudFront URL is:" + echo $CLOUDFRONT_URL + echo "" +fi diff --git a/stack.sh b/stack.sh index 58174b6..1cc100d 100755 --- a/stack.sh +++ b/stack.sh @@ -12,11 +12,17 @@ entryFuncs=("delete" "create") # "create": Create the stack. An error occurs if a stack already # exists with the provided name, and no update occurs. +# For command=create, the optional 4th argument is a space-separated list +# of parameter-overrides to pass to cloudformation deploy, +# which become template parameters. + ############################################################ STACK_NAME=$1 WEB_BUCKET_NAME=$3 +ARG4=$4 + if [[ -z $STACK_NAME ]]; then echo ERROR: Please set STACK_NAME return 1 @@ -87,7 +93,7 @@ create() { --template-file out.yml \ --stack-name $STACK_NAME \ --capabilities CAPABILITY_NAMED_IAM \ - --parameter-overrides stackName=$STACK_NAME bucketName=$WEB_BUCKET_NAME + --parameter-overrides stackName=$STACK_NAME bucketName=$WEB_BUCKET_NAME $ARG4 if [[ "$?" -ne 0 ]]; then aws cloudformation describe-stack-events \ diff --git a/template.yml b/template.yml index 62e7400..8035188 100644 --- a/template.yml +++ b/template.yml @@ -18,6 +18,30 @@ Parameters: MinLength: 1 MaxLength: 50 Default: v1 + enableCustomDomainName: + Description: Whether to use the custom domain name + Default: false + Type: String + AllowedValues: + - true + - false + domainName: + Description: The custom domain name + Type: String + MinLength: 1 + MaxLength: 50 + Default: "x" + certificateArn: + Description: The ARN of the DNS certificate for the custom domain name + Type: String + MinLength: 1 + MaxLength: 100 + Default: "x" + +Conditions: + useCustomDomain: !Equals + - !Ref enableCustomDomainName + - true Resources: @@ -78,7 +102,7 @@ Resources: Properties: BinaryMediaTypes: - "*/*" - DisableExecuteApiEndpoint: false + DisableExecuteApiEndpoint: !Ref enableCustomDomainName EndpointConfiguration: Types: - "EDGE" @@ -276,3 +300,24 @@ Resources: Action: lambda:InvokeFunction FunctionName: !Ref theLambda Principal: apigateway.amazonaws.com + + customDomain: + Type: AWS::ApiGateway::DomainName + Condition: useCustomDomain + Properties: + DomainName: !Ref domainName + CertificateArn: !Ref certificateArn + SecurityPolicy: TLS_1_2 + EndpointConfiguration: + Types: + - EDGE + + basePathMapping: + Type: AWS::ApiGateway::BasePathMapping + Condition: useCustomDomain + DependsOn: + - customDomain + Properties: + DomainName: !Ref domainName + RestApiId: !GetAtt apiGateway.RestApiId + Stage: !Ref stageName