using deployment operations from aws-static-site-with-cd

This commit is contained in:
undergroundwires
2020-04-26 20:49:35 +01:00
parent 3e3bc07576
commit 997be7113f
14 changed files with 61 additions and 1201 deletions

View File

@@ -26,79 +26,105 @@ jobs:
build-and-deploy: build-and-deploy:
needs: increase-version needs: increase-version
runs-on: ubuntu-18.04 runs-on: ubuntu-latest
steps: steps:
- -
name: "Prepare: Checkout" name: "Infrastructure: Checkout"
uses: actions/checkout@v1 uses: actions/checkout@v2
with:
path: aws
repository: undergroundwires/aws-static-site-with-cd
- -
name: "Prepare: Create AWS user profile" name: "Infrastructure: Create AWS user profile & session name"
run: >- run: >-
bash "aws/scripts/configure/create-user-profile.sh" \ bash "scripts/configure/create-user-profile.sh" \
--profile user \ --profile user \
--access-key-id ${{secrets.AWS_DEPLOYMENT_USER_ACCESS_KEY_ID}} \ --access-key-id ${{secrets.AWS_DEPLOYMENT_USER_ACCESS_KEY_ID}} \
--secret-access-key ${{secrets.AWS_DEPLOYMENT_USER_SECRET_ACCESS_KEY}} \ --secret-access-key ${{secrets.AWS_DEPLOYMENT_USER_SECRET_ACCESS_KEY}} \
--region us-east-1 --region us-east-1 \
&& \
echo "::set-env name=SESSION_NAME::${{github.actor}}-${{github.event_name}}-$(echo ${{github.sha}} | cut -c1-8)"
working-directory: aws
- -
name: "Infrastructure: Deploy IAM stack" name: "Infrastructure: Deploy IAM stack"
run: >- run: >-
bash "aws/scripts/deploy/deploy-stack.sh" \ bash "scripts/deploy/deploy-stack.sh" \
--template-file aws/iam-stack.yaml \ --template-file stacks/iam-stack.yaml \
--stack-name privacysexy-iam-stack \ --stack-name privacysexy-iam-stack \
--capabilities CAPABILITY_IAM \ --capabilities CAPABILITY_IAM \
--parameter-overrides "WebStackName=privacysexy-web-stack DnsStackName=privacysexy-dns-stack \
CertificateStackName=privacysexy-cert-stack RootDomainName=privacy.sexy" \
--region us-east-1 --role-arn ${{secrets.AWS_IAM_STACK_DEPLOYMENT_ROLE_ARN}} \ --region us-east-1 --role-arn ${{secrets.AWS_IAM_STACK_DEPLOYMENT_ROLE_ARN}} \
--profile user --session ${{github.actor}}-${{github.event_name}}-${{github.sha}} --profile user --session ${{ env.SESSION_NAME }}
- working-directory: aws
name: "Infrastructure: Deploy certificate stack"
run: >-
bash "aws/scripts/deploy/deploy-stack.sh" \
--template-file aws/certificate-stack.yaml \
--stack-name privacysexy-certificate-stack \
--region us-east-1 \
--role-arn ${{secrets.AWS_CERTIFICATE_STACK_DEPLOYMENT_ROLE_ARN}} \
--profile user --session ${{github.actor}}-${{github.event_name}}-${{github.sha}}
- -
name: "Infrastructure: Deploy DNS stack" name: "Infrastructure: Deploy DNS stack"
run: >- run: >-
bash "aws/scripts/deploy/deploy-stack.sh" \ bash "scripts/deploy/deploy-stack.sh" \
--template-file aws/dns-stack.yaml \ --template-file stacks/dns-stack.yaml \
--stack-name privacysexy-dns-stack \ --stack-name privacysexy-dns-stack \
--parameter-overrides "RootDomainName=privacy.sexy" \
--region us-east-1 \ --region us-east-1 \
--role-arn ${{secrets.AWS_DNS_STACK_DEPLOYMENT_ROLE_ARN}} \ --role-arn ${{secrets.AWS_DNS_STACK_DEPLOYMENT_ROLE_ARN}} \
--profile user --session ${{github.actor}}-${{github.event_name}}-${{github.sha}} --profile user --session ${{ env.SESSION_NAME }}
working-directory: aws
-
name: "Infrastructure: Deploy certificate stack"
run: >-
bash "scripts/deploy/deploy-stack.sh" \
--template-file stacks/certificate-stack.yaml \
--stack-name privacysexy-cert-stack \
--capabilities CAPABILITY_IAM \
--parameter-overrides "IamStackName=privacysexy-iam-stack RootDomainName=privacy.sexy DnsStackName=privacysexy-dns-stack" \
--region us-east-1 \
--role-arn ${{secrets.AWS_CERTIFICATE_STACK_DEPLOYMENT_ROLE_ARN}} \
--profile user --session ${{ env.SESSION_NAME }}
working-directory: aws
- -
name: "Infrastructure: Deploy web stack" name: "Infrastructure: Deploy web stack"
run: >- run: >-
bash "aws/scripts/deploy/deploy-stack.sh" \ bash "scripts/deploy/deploy-stack.sh" \
--template-file aws/web-stack.yaml \ --template-file stacks/web-stack.yaml \
--stack-name privacysexy-web-stack \ --stack-name privacysexy-web-stack \
--parameter-overrides "CertificateStackName=privacysexy-cert-stack DnsStackName=privacysexy-dns-stack \
RootDomainName=privacy.sexy UseDeepLinks=true" \
--capabilities CAPABILITY_IAM \
--region us-east-1 \ --region us-east-1 \
--role-arn ${{secrets.AWS_WEB_STACK_DEPLOYMENT_ROLE_ARN}} \ --role-arn ${{secrets.AWS_WEB_STACK_DEPLOYMENT_ROLE_ARN}} \
--profile user --session ${{github.actor}}-${{github.event_name}}-${{github.sha}} --profile user --session ${{ env.SESSION_NAME }}
working-directory: aws
-
name: "App: Checkout"
uses: actions/checkout@v2
with:
path: site
- -
name: "App: Setup node" name: "App: Setup node"
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: '11.x' node-version: '12.x'
- -
name: "App: Install dependencies" name: "App: Install dependencies"
run: npm install run: npm install
working-directory: site
- -
name: "App: Run tests" name: "App: Run tests"
run: npm run test:unit run: npm run test:unit
working-directory: site
- -
name: "App: Build" name: "App: Build"
run: npm run build run: npm run build
working-directory: site
- -
name: "App: Deploy to S3" name: "App: Deploy to S3"
run: >- run: >-
bash "aws/scripts/deploy/deploy-to-s3.sh" \ bash "aws/scripts/deploy/deploy-to-s3.sh" \
--folder dist \ --folder site/dist \
--web-stack-name privacysexy-web-stack --web-stack-s3-name-output-name S3BucketName \ --web-stack-name privacysexy-web-stack --web-stack-s3-name-output-name S3BucketName \
--storage-class ONEZONE_IA \ --storage-class ONEZONE_IA \
--role-arn ${{secrets.AWS_S3_SITE_DEPLOYMENT_ROLE_ARN}} \ --role-arn ${{secrets.AWS_S3_SITE_DEPLOYMENT_ROLE_ARN}} \
--region us-east-1 \ --region us-east-1 \
--profile user --session ${{github.actor}}-${{github.event_name}}-${{github.sha}} --profile user --session ${{ env.SESSION_NAME }}
- -
name: "App: Invalidate CloudFront cache" name: "App: Invalidate CloudFront cache"
run: >- run: >-
@@ -107,4 +133,4 @@ jobs:
--web-stack-name privacysexy-web-stack --web-stack-cloudfront-arn-output-name CloudFrontDistributionArn \ --web-stack-name privacysexy-web-stack --web-stack-cloudfront-arn-output-name CloudFrontDistributionArn \
--role-arn ${{secrets.AWS_CLOUDFRONT_SITE_DEPLOYMENT_ROLE_ARN}} \ --role-arn ${{secrets.AWS_CLOUDFRONT_SITE_DEPLOYMENT_ROLE_ARN}} \
--region us-east-1 \ --region us-east-1 \
--profile user --session ${{github.actor}}-${{github.event_name}}-${{github.sha}} --profile user --session ${{ env.SESSION_NAME }}

View File

@@ -56,64 +56,18 @@ Fork it & add more scripts in [application.yaml](src/application/application.yam
### AWS Infrastructure ### AWS Infrastructure
- The application runs in AWS 100% serverless and automatically provisioned using [CloudFormation files](/aws) and GitHub Actions. [![AWS solution](docs/aws-solution.png)](https://github.com/undergroundwires/aws-static-site-with-cd)
- Maximum security & automation and minimum AWS costs were the highest priorities of the design.
![AWS solution](docs/aws-solution.png) - It uses infrastructure from the following repository: [aws-static-site-with-cd](https://github.com/undergroundwires/aws-static-site-with-cd)
- Runs on AWS 100% serverless and automatically provisioned using [GitHub Actions](.github/workflows/).
- Maximum security & automation and minimum AWS costs are the highest priorities of the design.
#### GitOps: CI/CD to AWS #### GitOps: CI/CD to AWS
- Everything that's merged in the master goes directly to production. - Everything that's merged in the master goes directly to production.
- Deploy infrastructure ► Deploy web application ► Invalidate CloudFront Cache - See more at [build-and-deploy.yaml](.github/workflows/build-and-deploy.yaml), and [run-tests.yaml](.github/workflows/run-tests.yaml)
- See more at [build-and-deploy.yaml](.GitHub/workflows/build-and-deploy.yaml)
![CI/CD to AWS with GitHub Actions](docs/gitops.png) [![CI/CD to AWS with GitHub Actions](docs/gitops.png)](.github/workflows/build-and-deploy.yaml)
##### CloudFormation
![CloudFormation design](docs/aws-cloudformation.png)
- AWS infrastructure is defined as code with following files:
- `iam-stack`: Creates & updates the deployment user.
- Everything in IAM layer is fine-grained using least privileges principle.
- Each deployment step has its own temporary credentials with own permissions.
- `certificate-stack.yaml`
- It'll generate SSL certification for the root domain and www subdomain.
- ❗ It [must](https://aws.amazon.com/premiumsupport/knowledge-center/cloudfront-invalid-viewer-certificate/) be deployed in `us-east-1` to be able to be used by CloudFront by `web-stack`.
- It uses CustomResource and a lambda instead of native `AWS::CertificateManager::Certificate` because:
- Problem:
- AWS variant waits until a certificate is validated.
- There's no way to automate validation without workaround.
- Solution:
- Deploy a lambda that deploys the certificate (so we don't wait until certificate is validated)
- Get DNS records to be used in validation & export it to be used later.
- `web-stack.yaml`: It'll deploy S3 bucket and CloudFront in front of it.
- `dns-stack.yaml`: It'll deploy Route53 hosted zone
- Each time Route53 hosted zone is re-created it's required to update the DNS records in the domain registrar. See *Configure your domain registrar*.
- I use cross stacks instead of single stack or nested stacks because:
- Easier to test & maintain & smaller files and different lifecycles for different areas.
- It allows to deploy web bucket in different region than others as other stacks are global (`us-east-1`) resources.
##### Initial deployment
- ❗ Prerequisite: A registered domain name for website.
1. **Configure build agent (GitHub actions)**
- Deploy manually `iam-stack.yaml` with stack name `privacysexy-iam-stack` (to follow the convention)
- It'll give you deploy user. Go to console & generate secret id + key (Security credentials => Create access key) for the user [IAM users](https://console.aws.amazon.com/iam/home#/users).
- 🚶 Deploy secrets:
- Add secret id & key in GitHub Secrets.
- `AWS_DEPLOYMENT_USER_ACCESS_KEY_ID`, `AWS_DEPLOYMENT_USER_SECRET_ACCESS_KEY`
- Add more secrets given from Outputs section of the CloudFormation stack.
- Run GitHub actions to deploy rest of the application.
- It'll run `certificate-stack.yaml` and then `iam-stack.yaml`.
2. **Configure your domain registrar**
-**Web stack will fail** after DNS stack because you need to validate your domain.
- 🚶 Go to your domain registrar and change name servers to NS values
- `dns-stack.yaml` outputs those in CloudFormation stack.
- You can alternatively find those in [Route53](https://console.aws.amazon.com/route53/home#hosted-zones)
- When nameservers of your domain updated, the certification will get validated automatically, you can then delete the failed stack in CloudFormation & re-run the GitHub actions.
## Thank you for the awesome projects 🍺 ## Thank you for the awesome projects 🍺

View File

@@ -1,211 +0,0 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: Creates certificate for the root + www subdomain. !! It must be deployed in us-east-1 to be able to be used by CloudFront.
Parameters:
RootDomainName:
Type: String
Default: privacy.sexy
Description: The root DNS name of the website e.g. privacy.sexy
AllowedPattern: (?!-)[a-zA-Z0-9-.]{1,63}(?<!-)
ConstraintDescription: Must be a valid root domain name
IamStackName:
Type: String
Default: privacysexy-iam-stack
Description: Name of the IAM stack.
Resources:
# The lambda workaround exists to be able to automate certificate deployment.
# Problem:
# Normally AWS AWS::CertificateManager::Certificate waits until a certificate is validated
# And there's no way to get validation DNS records from it to validate it.
# Solution:
# Deploy a lambda that deploys the certificate (so we don't wait until certificate is validated)
# Get DNS records to be used in validation & export it to be used later.
AcmCertificateForHostedZone:
Type: Custom::VerifiableCertificate #A Can use AWS::CloudFormation::CustomResource or Custom::String
Properties:
ServiceToken: !GetAtt ResolveCertificateLambda.Arn
# Lambda gets the following data:
RootDomainName: !Ref RootDomainName # Lambda will create both for root and www.root
Tags:
-
Key: Name
Value: !Ref RootDomainName
-
Key: Application
Value: privacy.sexy
ResolveCertificateLambda:
Type: AWS::Lambda::Function
Properties:
Description: Deploys certificate for root domain name + www and returns immediately arn + verification records.
Role:
Fn::ImportValue: !Join [':', [!Ref IamStackName, ResolveCertificateLambdaRoleArn]]
FunctionName: !Sub ${AWS::StackName}-cert-resolver-lambda # StackName- required for role to function
Handler: index.handler
Runtime: nodejs12.x
Timeout: 30
Tags:
-
Key: Application
Value: privacy.sexy
Code:
# Inline script is not the best way. Some variables are named shortly to not exceed the limit 4096 but it's the cheapest way (no s3 file)
ZipFile: >
'use strict';
const aws = require('aws-sdk');
const acm = new aws.ACM();
const log = (t) => console.log(t);
exports.handler = async (event, context) => {
log(`Request recieved:\n${JSON.stringify(event)}`);
const userData = event.ResourceProperties;
const rootDomain = userData.RootDomainName;
let data = null;
try {
switch(event.RequestType) {
case 'Create':
data = await handleCreateAsync(rootDomain, userData.Tags);
break;
case 'Update':
data = await handleUpdateAsync();
break;
case 'Delete':
data = await handleDeleteAsync(rootDomain);
break;
}
await sendResponseAsync(event, context, 'SUCCESS', data);
} catch(error) {
await sendResponseAsync(event, context, 'ERROR', {
title: `Failed to ${event.RequestType}, see error`,
error: error
});
}
}
async function handleCreateAsync(rootDomain, tags) {
const { CertificateArn } = await acm.requestCertificate({
DomainName: rootDomain,
SubjectAlternativeNames: [`www.${rootDomain}`],
Tags: tags,
ValidationMethod: 'DNS',
}).promise();
log(`Cert requested:${CertificateArn}`);
const waitAsync = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const maxAttempts = 10;
let options = undefined;
for (let attempt = 0; attempt < maxAttempts && !options; attempt++) {
await waitAsync(2000);
const { Certificate } = await acm.describeCertificate({ CertificateArn }).promise();
if(Certificate.DomainValidationOptions.filter((o) => o.ResourceRecord).length === 2) {
options = Certificate.DomainValidationOptions;
}
}
if(!options) {
throw new Error(`No records after ${maxAttempts} attempts.`);
}
return getResponseData(options, CertificateArn, rootDomain);
}
async function handleDeleteAsync(rootDomain) {
const certs = await acm.listCertificates({}).promise();
const cert = certs.CertificateSummaryList.find((cert) => cert.DomainName === rootDomain);
if (cert) {
await acm.deleteCertificate({ CertificateArn: cert.CertificateArn }).promise();
log(`Deleted ${cert.CertificateArn}`);
} else {
log('Cannot find'); // Do not fail, delete can be called when e.g. CF fails before creating cert
}
return null;
}
async function handleUpdateAsync() {
throw new Error(`Not yet implemented update`);
}
function getResponseData(options, arn, rootDomain) {
const findRecord = (url) => options.find(option => option.DomainName === url).ResourceRecord;
const root = findRecord(rootDomain);
const www = findRecord(`www.${rootDomain}`);
const data = {
CertificateArn: arn,
RootVerificationRecordName: root.Name,
RootVerificationRecordValue: root.Value,
WwwVerificationRecordName: www.Name,
WwwVerificationRecordValue: www.Value,
};
return data;
}
/* cfn-response can't async / await :( */
async function sendResponseAsync(event, context, responseStatus, responseData, physicalResourceId) {
return new Promise((s, f) => {
var b = JSON.stringify({
Status: responseStatus,
Reason: `See the details in CloudWatch Log Stream: ${context.logStreamName}`,
PhysicalResourceId: physicalResourceId || context.logStreamName,
StackId: event.StackId,
RequestId: event.RequestId,
LogicalResourceId: event.LogicalResourceId,
Data: responseData
});
log(`Response body:\n${b}`);
var u = require("url").parse(event.ResponseURL);
var r = require("https").request(
{
hostname: u.hostname,
port: 443,
path: u.path,
method: "PUT",
headers: {
"content-type": "",
"content-length": b.length
}
}, (p) => {
log(`Status code: ${p.statusCode}`);
log(`Status message: ${p.statusMessage}`);
s(context.done());
});
r.on("error", (e) => {
log(`request failed: ${e}`);
f(context.done(e));
});
r.write(b);
r.end();
});
}
Outputs:
CertificateArn:
Description: The Amazon Resource Name (ARN) of an AWS Certificate Manager (ACM) certificate.
Value: !GetAtt AcmCertificateForHostedZone.CertificateArn
Export:
Name: !Join [':', [ !Ref 'AWS::StackName', CertificateArn ]]
RootVerificationRecordName:
Description: Name for root domain CNAME verification record
Value: !GetAtt AcmCertificateForHostedZone.RootVerificationRecordName
Export:
Name: !Join [':', [ !Ref 'AWS::StackName', RootVerificationRecordName ]]
RootVerificationRecordValue:
Description: Value for root domain name CNAME verification record
Value: !GetAtt AcmCertificateForHostedZone.RootVerificationRecordValue
Export:
Name: !Join [':', [ !Ref 'AWS::StackName', RootVerificationRecordValue ]]
WwwVerificationRecordName:
Description: Name for www domain name CNAME verification record
Value: !GetAtt AcmCertificateForHostedZone.WwwVerificationRecordName
Export:
Name: !Join [':', [ !Ref 'AWS::StackName', WwwVerificationRecordName ]]
WwwVerificationRecordValue:
Description: Value for www domain name CNAME verification record
Value: !GetAtt AcmCertificateForHostedZone.WwwVerificationRecordValue
Export:
Name: !Join [':', [ !Ref 'AWS::StackName', WwwVerificationRecordValue ]]

View File

@@ -1,61 +0,0 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: Creates hosted zone & sets up records for the CloudFront URL.
Parameters:
RootDomainName:
Type: String
Default: privacy.sexy
Description: The root DNS name of the website e.g. privacy.sexy
AllowedPattern: (?!-)[a-zA-Z0-9-.]{1,63}(?<!-)
ConstraintDescription: Must be a valid root domain name
CertificateStackName:
Type: String
Default: privacysexy-certificate-stack
Description: Name of the certificate stack.
Resources:
DNSHostedZone:
Type: AWS::Route53::HostedZone
Properties:
Name: !Ref RootDomainName
HostedZoneConfig:
Comment: !Join ['', ['Hosted zone for ', !Ref RootDomainName]]
HostedZoneTags:
-
Key: Application
Value: privacy.sexy
CertificateValidationDNSRecords:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneId: !Ref DNSHostedZone
RecordSets:
-
Name:
Fn::ImportValue: !Join [':', [!Ref CertificateStackName, RootVerificationRecordName]]
Type: 'CNAME'
TTL: '60'
ResourceRecords:
- Fn::ImportValue: !Join [':', [!Ref CertificateStackName, RootVerificationRecordValue]]
-
Name:
Fn::ImportValue: !Join [':', [!Ref CertificateStackName, WwwVerificationRecordName]]
Type: 'CNAME'
TTL: '60'
ResourceRecords:
- Fn::ImportValue: !Join [':', [!Ref CertificateStackName, WwwVerificationRecordValue]]
Outputs:
DNSHostedZoneNameServers:
Description: Name servers to update in domain registrar.
Value: !Join [' ', !GetAtt DNSHostedZone.NameServers]
DNSHostedZoneId:
Description: The ID of the hosted zone that you want to create the record in.
Value: !Ref DNSHostedZone
Export:
Name: !Join [':', [ !Ref 'AWS::StackName', DNSHostedZoneId ]]

View File

@@ -1,496 +0,0 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: |-
> Deploys the identity management for the deployment
# Granulatiy cheatsheet: https://iam.cloudonaut.io/
Parameters:
WebStackName:
Type: String
Default: privacysexy-web-stack
Description: Name of the web stack.
DnsStackName:
Type: String
Default: privacysexy-dns-stack
Description: Name of the DNS stack.
CertificateStackName:
Type: String
Default: privacysexy-certificate-stack
Description: Name of the IAM stack.
Resources:
# -----------------------------
# ------ User & Group ---------
# -----------------------------
DeploymentGroup:
Type: AWS::IAM::Group
Properties:
# GroupName: No hardcoded naming because of easier CloudFormation management
ManagedPolicyArns:
- !Ref AllowValidateTemplatePolicy
DeploymentUser:
Type: AWS::IAM::User
Properties:
# # UserName: No hardcoded naming because of easier CloudFormation management
# # Policies: Assing policies on group level
Tags:
-
Key: Application
Value: privacy.sexy
AddDeploymentUserToDeploymentGroup:
Type: AWS::IAM::UserToGroupAddition
Properties:
GroupName: !Ref DeploymentGroup
Users:
- !Ref DeploymentUser
# -----------------------------
# ----------- Roles -----------
# -----------------------------
IamStackDeployRole:
Type: AWS::IAM::Role
Properties:
Description: Allows to deploy IAM stack
AssumeRolePolicyDocument:
Statement:
-
Effect: Allow
Principal:
AWS: !GetAtt DeploymentUser.Arn
Action: sts:AssumeRole
Tags:
-
Key: Application
Value: privacy.sexy
ManagedPolicyArns:
- !Ref CloudFormationDeployPolicy
- !Ref PolicyDeployPolicy
- !Ref IamStackDeployPolicy
CertificateStackDeployRole:
Type: AWS::IAM::Role
Properties:
Description: Allows to deploy certificate stack
AssumeRolePolicyDocument:
Statement:
-
Effect: Allow
Principal:
AWS: !GetAtt DeploymentUser.Arn
Action: sts:AssumeRole
Tags:
-
Key: Application
Value: privacy.sexy
ManagedPolicyArns:
- !Ref CloudFormationDeployPolicy
- !Ref LambdaBackedCustomResourceDeployPolicy
DnsStackDeployRole:
Type: AWS::IAM::Role
Properties:
Description: Allows to deploy DNS stack
AssumeRolePolicyDocument:
Statement:
-
Effect: Allow
Principal:
AWS: !GetAtt DeploymentUser.Arn
Action: sts:AssumeRole
Tags:
-
Key: Application
Value: privacy.sexy
ManagedPolicyArns:
- !Ref CloudFormationDeployPolicy
- !Ref DnsStackDeployPolicy
WebStackDeployRole:
Type: AWS::IAM::Role
Properties:
Description: Allows to deploy web stack
AssumeRolePolicyDocument:
Statement:
-
Effect: Allow
Principal:
AWS: !GetAtt DeploymentUser.Arn
Action: sts:AssumeRole
Tags:
-
Key: Application
Value: privacy.sexy
ManagedPolicyArns:
- !Ref CloudFormationDeployPolicy
- !Ref WebStackDeployPolicy
S3SiteDeployRole:
Type: 'AWS::IAM::Role'
Properties:
Description: "Allows to deploy website to S3"
AssumeRolePolicyDocument:
Statement:
-
Effect: Allow
Principal:
AWS: !GetAtt DeploymentUser.Arn
Action: sts:AssumeRole
Tags:
-
Key: Application
Value: privacy.sexy
ManagedPolicyArns:
- !Ref S3SiteDeployPolicy
- !Ref StackExportReaderPolicy
CloudFrontSiteDeployRole:
Type: 'AWS::IAM::Role'
Properties:
Description: "Allows to informs to CloudFront to renew its cache from S3"
AssumeRolePolicyDocument:
Statement:
-
Effect: Allow
Principal:
AWS: !GetAtt DeploymentUser.Arn
Action: sts:AssumeRole
Tags:
-
Key: Application
Value: privacy.sexy
ManagedPolicyArns:
- !Ref CloudFrontInvalidationPolicy
- !Ref StackExportReaderPolicy
ResolveCertificateLambdaRole: # See certificate stack
Type: AWS::IAM::Role
Properties:
Description: Allow deployment of certificates
AssumeRolePolicyDocument:
Statement:
-
Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- !Ref CertificateDeployPolicy
# --------------------------------
# ----------- Policies -----------
# --------------------------------
AllowValidateTemplatePolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: "No read & writes to resources, reveals just basic CloudFormation API to be used for validating templates"
# ManagedPolicyName: No hardcoded naming because of easier CloudFormation management
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowCloudFormationTemplateValidation
Effect: Allow
Action:
- cloudformation:ValidateTemplate
Resource: '*'
CloudFormationDeployPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: "Allows deploying CloudFormation using CLI command 'aws cloudformation deploy' (with change sets)"
# ManagedPolicyName: No hardcoded naming because of easier CloudFormation management
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowCloudFormationStackOperations
Effect: Allow
Action:
- cloudformation:GetTemplateSummary
- cloudformation:DescribeStacks
- cloudformation:CreateChangeSet
- cloudformation:ExecuteChangeSet
- cloudformation:DescribeChangeSet
Resource:
- !Sub arn:aws:cloudformation:*:${AWS::AccountId}:stack/${WebStackName}/*
- !Sub arn:aws:cloudformation:*:${AWS::AccountId}:stack/${DnsStackName}/*
- !Sub arn:aws:cloudformation:*:${AWS::AccountId}:stack/${AWS::StackName}/*
- !Sub arn:aws:cloudformation:*:${AWS::AccountId}:stack/${CertificateStackName}/*
IamStackDeployPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows deploying IAM CloudFormation stack.
# ManagedPolicyName: No hardcoded naming because of easier CloudFormation management
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowUserArnExport
Effect: Allow
Action:
- iam:GetUser
Resource:
- !GetAtt DeploymentUser.Arn
-
Sid: AllowTagging
Effect: Allow
Action:
- iam:TagResource
Resource:
- !Sub arn:aws:cloudformation::${AWS::AccountId}:stack/${AWS::StackName}/*
- !GetAtt DeploymentUser.Arn
-
Sid: AllowRoleDeployment
Effect: Allow
Action:
- iam:CreateRole
Resource:
- !Sub arn:aws:iam::${AWS::AccountId}:role/${AWS::StackName}-*
LambdaBackedCustomResourceDeployPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows deploying a lambda-backed custom resource.
# ManagedPolicyName: # ManagedPolicyName: No hardcoded naming because of easier CloudFormation management
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowLambdaDeployment
Effect: Allow
Action:
- lambda:GetFunction
- lambda:DeleteFunction
- lambda:CreateFunction
- lambda:GetFunctionConfiguration
- lambda:InvokeFunction
Resource:
- !Sub arn:aws:lambda:*:${AWS::AccountId}:function:${CertificateStackName}*
-
Sid: AllowPassingLambdaRole
Effect: Allow
Action:
- iam:PassRole
Resource:
- !GetAtt ResolveCertificateLambdaRole.Arn
CertificateDeployPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows deploying certifications stack.
# ManagedPolicyName: # ManagedPolicyName: No hardcoded naming because of easier CloudFormation management
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowCertificateDeployment
Effect: Allow
Action:
- acm:RequestCertificate
- acm:DescribeCertificate
- acm:DeleteCertificate
- acm:AddTagsToCertificate
- acm:ListCertificates
Resource: '*' # Certificate Manager does not support resource level IAM
PolicyDeployPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows deployment of policies
# ManagedPolicyName: Commented out because CloudFormation requires to rename when replacing custom-named resource
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowPolicyUpdates
Effect: Allow
Action:
- iam:ListPolicyVersions
- iam:CreatePolicyVersion
- iam:DeletePolicyVersion
- iam:CreatePolicy
- iam:DeletePolicy
- iam:GetPolicy
Resource:
- !Sub arn:aws:iam::${AWS::AccountId}:policy/${AWS::StackName}-* # when ManagedPolicyName is not given policies get name like StackName-*
-
Sid: AllowPoliciesOnRoles
Effect: Allow
Action:
- iam:AttachRolePolicy
- iam:DetachRolePolicy
- iam:GetRole
Resource:
- !Sub arn:aws:iam::${AWS::AccountId}:role/${AWS::StackName}-*
-
Sid: AllowPolicyAssigmentToGroup
Effect: Allow
Action:
- iam:AttachGroupPolicy
- iam:DetachGroupPolicy
Resource:
- !GetAtt DeploymentGroup.Arn
-
Sid: AllowGettingGroupInformation
Effect: Allow
Action:
- iam:GetGroup
Resource: !Sub arn:aws:iam::${AWS::AccountId}:group/${DeploymentGroup}
DnsStackDeployPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows deployment of DNS stack
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowHostedZoneDeployment
Effect: Allow
Action:
- route53:CreateHostedZone
- route53:ListQueryLoggingConfigs
- route53:DeleteHostedZone
- route53:GetChange
- route53:ChangeTagsForResource
- route53:GetHostedZone
- route53:ChangeResourceRecordSets
Resource: '*' # Does not support resource-level permissions https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/access-control-overview.html#access-control-manage-access-intro-resource-policies
WebStackDeployPolicy:
# We need a role to run s3:PutBucketPolicy, IAM users cannot run it. See https://stackoverflow.com/a/48551383
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows deployment of web stack
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowCloudFrontOAIDeployment
Effect: Allow
Action:
- cloudfront:GetCloudFrontOriginAccessIdentity
- cloudfront:CreateCloudFrontOriginAccessIdentity
- cloudfront:GetCloudFrontOriginAccessIdentityConfig
- cloudfront:DeleteCloudFrontOriginAccessIdentity
Resource: '*' # Does not support resource-level permissions https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cf-api-permissions-ref.html
-
Sid: AllowCloudFrontDistributionDeployment
Effect: Allow
Action:
- cloudfront:CreateDistribution
- cloudfront:DeleteDistribution
- cloudfront:UpdateDistribution
- cloudfront:GetDistribution
- cloudfront:TagResource
- cloudfront:UpdateCloudFrontOriginAccessIdentity
Resource: !Sub arn:aws:cloudfront::${AWS::AccountId}:*
-
Sid: AllowS3BucketPolicyAccess
Effect: Allow
Action:
- s3:CreateBucket
- s3:DeleteBucket
- s3:PutBucketWebsite
- s3:DeleteBucketPolicy
- s3:PutBucketPolicy
- s3:GetBucketPolicy
Resource: !Sub arn:aws:s3:::${WebStackName}*
-
Sid: AllowRecordDeploymentToRoute53
Effect: Allow
Action:
- route53:GetHostedZone
- route53:ChangeResourceRecordSets
- route53:GetChange
- route53:ListResourceRecordSets
Resource: '*' # Does not support resource-level permissions https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/access-control-overview.html#access-control-manage-access-intro-resource-policies
S3SiteDeployPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows listing buckets to be able to list objects in a bucket
# ManagedPolicyName: Commented out because CloudFormation requires to rename when replacing custom-named resources
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowListingObjects
Effect: Allow
Action:
- s3:ListBucket # To allow ListObjectsV2
Resource: !Sub arn:aws:s3:::${WebStackName}*
-
Sid: AllowUpdatingObjects
Effect: Allow
Action:
- s3:PutObject
- s3:DeleteObject
Resource: !Sub arn:aws:s3:::${WebStackName}*/*
CloudFrontInvalidationPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows creating invalidations on CloudFront
# ManagedPolicyName: Commented out because CloudFormation requires to rename when replacing custom-named resource
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowCloudFrontInvalidations
Effect: Allow
Action:
- cloudfront:CreateInvalidation
Resource: "*" # Does not support resource-level permissions https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cf-api-permissions-ref.html
StackExportReaderPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows creating invalidations on CloudFront
# ManagedPolicyName: Commented out because CloudFormation requires to rename when replacing custom-named resource
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowGettingBucketName
Effect: Allow
Action:
- cloudformation:DescribeStacks
Resource: !Sub arn:aws:cloudformation:*:${AWS::AccountId}:stack/${WebStackName}/*
Outputs:
ResolveCertificateLambdaRoleArn:
Description: The Amazon Resource Name (ARN) of the lambda for deploying certificates.
Value: !GetAtt ResolveCertificateLambdaRole.Arn
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', ResolveCertificateLambdaRoleArn ] ]
CertificateStackDeployRoleArn:
Description: "GitHub secret: AWS_CERTIFICATE_STACK_DEPLOYMENT_ROLE_ARN"
Value: !GetAtt CertificateStackDeployRole.Arn
DnsStackDeployRoleArn:
Description: "GitHub secret: AWS_DNS_STACK_DEPLOYMENT_ROLE_ARN"
Value: !GetAtt DnsStackDeployRole.Arn
IamStackDeployRoleArn:
Description: "GitHub secret: AWS_IAM_STACK_DEPLOYMENT_ROLE_ARN"
Value: !GetAtt IamStackDeployRole.Arn
WebStackDeployRoleArn:
Description: "GitHub secret: AWS_WEB_STACK_DEPLOYMENT_ROLE_ARN"
Value: !GetAtt WebStackDeployRole.Arn
S3SiteDeployRoleArn:
Description: "GitHub secret: AWS_S3_SITE_DEPLOYMENT_ROLE_ARN"
Value: !GetAtt S3SiteDeployRole.Arn
CloudFrontSiteDeployRoleArn:
Description: "GitHub secret: AWS_CLOUDFRONT_SITE_DEPLOYMENT_ROLE_ARN"
Value: !GetAtt CloudFrontSiteDeployRole.Arn

View File

@@ -1,36 +0,0 @@
#!/bin/bash
# Parse parameters
while [[ "$#" -gt 0 ]]; do case $1 in
--user-profile) USER_PROFILE="$2"; shift;;
--role-profile) ROLE_PROFILE="$2"; shift;;
--role-arn) ROLE_ARN="$2"; shift;;
--session) SESSION="$2";shift;;
--region) REGION="$2";shift;;
*) echo "Unknown parameter passed: $1"; exit 1;;
esac; shift; done
# Verify parameters
if [ -z "$USER_PROFILE" ]; then echo "User profile name is not set."; exit 1; fi;
if [ -z "$ROLE_PROFILE" ]; then echo "Role profile name is not set."; exit 1; fi;
if [ -z "$ROLE_ARN" ]; then echo "Role ARN is not set"; exit 1; fi;
if [ -z "$SESSION" ]; then echo "Session name is not set."; exit 1; fi;
if [ -z "$REGION" ]; then echo "Region is not set."; exit 1; fi;
creds=$(aws sts assume-role --role-arn $ROLE_ARN --role-session-name $SESSION --profile $USER_PROFILE)
aws_access_key_id=$(echo $creds | jq -r '.Credentials.AccessKeyId')
echo ::add-mask::$aws_access_key_id
aws_secret_access_key=$(echo $creds | jq -r '.Credentials.SecretAccessKey')
echo ::add-mask::$aws_secret_access_key
aws_session_token=$(echo $creds | jq -r '.Credentials.SessionToken')
echo ::add-mask::$aws_session_token
aws configure --profile $ROLE_PROFILE set aws_access_key_id $aws_access_key_id
aws configure --profile $ROLE_PROFILE set aws_secret_access_key $aws_secret_access_key
aws configure --profile $ROLE_PROFILE set aws_session_token $aws_session_token
aws configure --profile $ROLE_PROFILE set region $REGION
echo Profile $ROLE_PROFILE is created
bash "${BASH_SOURCE%/*}/mask-identity.sh" --profile $ROLE_PROFILE

View File

@@ -1,25 +0,0 @@
#!/bin/bash
# Parse parameters
while [[ "$#" -gt 0 ]]; do case $1 in
--profile) PROFILE="$2"; shift;;
--access-key-id) ACCESS_KEY_ID="$2"; shift;;
--secret-access-key) SECRET_ACCESS_KEY="$2"; shift;;
--region) REGION="$2";shift;;
*) echo "Unknown parameter passed: $1"; exit 1;;
esac; shift; done
# Verify parameters
if [ -z "$PROFILE" ]; then echo "Profile name is not set."; exit 1; fi;
echo $PROFILE
if [ -z "$ACCESS_KEY_ID" ]; then echo "Access key ID is not set"; exit 1; fi;
if [ -z "$SECRET_ACCESS_KEY" ]; then echo "Secret access key is not set."; exit 1; fi;
if [ -z "$REGION" ]; then echo "Region is not set."; exit 1; fi;
aws configure --profile $PROFILE set aws_access_key_id $ACCESS_KEY_ID
aws configure --profile $PROFILE set aws_secret_access_key $SECRET_ACCESS_KEY
aws configure --profile $PROFILE set region $REGION
echo Profile $PROFILE is created
bash "${BASH_SOURCE%/*}/mask-identity.sh" --profile $PROFILE

View File

@@ -1,17 +0,0 @@
#!/bin/bash
# Parse parameters
while [[ "$#" -gt 0 ]]; do case $1 in
--profile) PROFILE="$2";shift;;
*) echo "Unknown parameter passed: $1"; exit 1;;
esac; shift; done
# Verify parameters
if [ -z "$PROFILE" ]; then echo "Profile name is not set."; exit 1; fi;
aws_identity=$(aws sts get-caller-identity --profile $PROFILE)
echo ::add-mask::$(echo $aws_identity | jq -r '.Account')
echo ::add-mask::$(echo $aws_identity | jq -r '.UserId')
echo ::add-mask::$(echo $aws_identity | jq -r '.Arn')
echo Credentials are masked

View File

@@ -1,43 +0,0 @@
#!/bin/bash
# Parse parameters
while [[ "$#" -gt 0 ]]; do case $1 in
--template-file) TEMPLATE_FILE="$2"; shift;;
--stack-name) STACK_NAME="$2"; shift;;
--profile) PROFILE="$2"; shift;;
--capabilities) CAPABILITY_IAM="$2"; shift;;
--role-arn) ROLE_ARN="$2";shift;;
--session) SESSION="$2";shift;;
--region) REGION="$2";shift;;
*) echo "Unknown parameter passed: $1"; exit 1;;
esac; shift; done
# Verify parameters
if [ -z "$TEMPLATE_FILE" ]; then echo "Template file is not set."; exit 1; fi;
if [ -z "$STACK_NAME" ]; then echo "Template file is not set."; exit 1; fi;
if [ -z "$PROFILE" ]; then echo "Profile is not set."; exit 1; fi;
if [ -z "$ROLE_ARN" ]; then echo "Role ARN is not set."; exit 1; fi;
if [ -z "$SESSION" ]; then echo "Role session is not set."; exit 1; fi;
echo Validating stack "$STACK_NAME"
aws cloudformation validate-template \
--template-body file://$TEMPLATE_FILE \
--profile $PROFILE
ROLE_PROFILE=$STACK_NAME
echo Assuming role
bash "${BASH_SOURCE%/*}/../configure/create-role-profile.sh" \
--role-profile $ROLE_PROFILE --user-profile $PROFILE \
--role-arn $ROLE_ARN \
--session $SESSION \
--region $REGION
echo Deploying stack "$TEMPLATE_FILE"
aws cloudformation deploy \
--template-file $TEMPLATE_FILE \
--stack-name $STACK_NAME \
${CAPABILITY_IAM:+ --capabilities $CAPABILITY_IAM} \
--no-fail-on-empty-changeset \
--profile $ROLE_PROFILE

View File

@@ -1,47 +0,0 @@
#!/bin/bash
# Parse parameters
while [[ "$#" -gt 0 ]]; do case $1 in
--folder) FOLDER="$2"; shift;;
--web-stack-name) WEB_STACK_NAME="$2"; shift;;
--web-stack-s3-name-output-name) WEB_STACK_S3_NAME_OUTPUT_NAME="$2"; shift;;
--storage-class) STORAGE_CLASS="$2"; shift;;
--profile) PROFILE="$2"; shift;;
--role-arn) ROLE_ARN="$2";shift;;
--session) SESSION="$2";shift;;
--region) REGION="$2";shift;;
*) echo "Unknown parameter passed: $1"; exit 1;;
esac; shift; done
# Verify parameters
if [ -z "$FOLDER" ]; then echo "Folder is not set."; exit 1; fi;
if [ -z "$PROFILE" ]; then echo "Profile is not set."; exit 1; fi;
if [ -z "$ROLE_ARN" ]; then echo "Role ARN is not set."; exit 1; fi;
if [ -z "$SESSION" ]; then echo "Role session is not set."; exit 1; fi;
if [ -z "$WEB_STACK_NAME" ]; then echo "Web stack name is not set."; exit 1; fi;
if [ -z "$WEB_STACK_S3_NAME_OUTPUT_NAME" ]; then echo "S3 name output name is not set."; exit 1; fi;
if [ -z "$STORAGE_CLASS" ]; then echo "S3 object storage class is not set."; exit 1; fi;
echo Assuming role
ROLE_PROFILE=deploy-s3
bash "${BASH_SOURCE%/*}/../configure/create-role-profile.sh" \
--role-profile $ROLE_PROFILE --user-profile $PROFILE \
--role-arn $ROLE_ARN \
--session $SESSION \
--region $REGION
echo Getting S3 bucket name from stack "$WEB_STACK_NAME" with output "$WEB_STACK_S3_NAME_OUTPUT_NAME"
S3_BUCKET_NAME=$(aws cloudformation describe-stacks \
--stack-name $WEB_STACK_NAME \
--query "Stacks[0].Outputs[?OutputKey=='$WEB_STACK_S3_NAME_OUTPUT_NAME'].OutputValue" \
--output text \
--profile $ROLE_PROFILE)
if [ -z "$S3_BUCKET_NAME" ]; then echo "Could not read S3 bucket name"; exit 1; fi;
echo ::add-mask::$S3_BUCKET_NAME # Just being extra cautious
echo Syncing folder to S3
aws s3 sync $FOLDER s3://$S3_BUCKET_NAME \
--storage-class $STORAGE_CLASS \
--no-progress --follow-symlinks --delete \
--profile $ROLE_PROFILE

View File

@@ -1,45 +0,0 @@
#!/bin/bash
# Parse parameters
while [[ "$#" -gt 0 ]]; do case $1 in
--paths) PATHS="$2"; shift;;
--web-stack-name) WEB_STACK_NAME="$2"; shift;;
--web-stack-cloudfront-arn-output-name) WEB_STACK_CLOUDFRONT_ARN_OUTPUT_NAME="$2"; shift;;
--profile) PROFILE="$2"; shift;;
--role-arn) ROLE_ARN="$2";shift;;
--session) SESSION="$2";shift;;
--region) REGION="$2";shift;;
*) echo "Unknown parameter passed: $1"; exit 1;;
esac; shift; done
# Verify parameters
if [ -z "$PATHS" ]; then echo "Paths is not set."; exit 1; fi;
if [ -z "$PROFILE" ]; then echo "Profile is not set."; exit 1; fi;
if [ -z "$ROLE_ARN" ]; then echo "Role ARN is not set."; exit 1; fi;
if [ -z "$SESSION" ]; then echo "Role session is not set."; exit 1; fi;
if [ -z "$WEB_STACK_NAME" ]; then echo "Web stack name is not set."; exit 1; fi;
if [ -z "$WEB_STACK_CLOUDFRONT_ARN_OUTPUT_NAME" ]; then echo "CloudFront ARN output name is not set."; exit 1; fi;
echo Assuming role
ROLE_PROFILE=invalidate-cloudfront
bash "${BASH_SOURCE%/*}/../configure/create-role-profile.sh" \
--role-profile $ROLE_PROFILE --user-profile $PROFILE \
--role-arn $ROLE_ARN \
--session $SESSION \
--region $REGION
echo Getting CloudFront ARN from stack "$WEB_STACK_NAME" with output "$WEB_STACK_CLOUDFRONT_ARN_OUTPUT_NAME"
CLOUDFRONT_ARN=$(aws cloudformation describe-stacks \
--stack-name $WEB_STACK_NAME \
--query "Stacks[0].Outputs[?OutputKey=='$WEB_STACK_CLOUDFRONT_ARN_OUTPUT_NAME'].OutputValue" \
--output text \
--profile $ROLE_PROFILE)
if [ -z "$CLOUDFRONT_ARN" ]; then echo "Could not read CloudFront ARN"; exit 1; fi;
echo ::add-mask::$CLOUDFRONT_ARN
echo Syncing folder to S3
aws cloudfront create-invalidation \
--paths $PATHS \
--distribution-id $CLOUDFRONT_ARN \
--profile $ROLE_PROFILE

View File

@@ -1,138 +0,0 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: |-
> Creates an S3 bucket configured for hosting a static webpage.
> Creates CloudFront distribution that has access to read the S3 bucket.
Parameters:
RootDomainName:
Type: String
Default: privacy.sexy
Description: The root DNS name of the website e.g. privacy.sexy
AllowedPattern: (?!-)[a-zA-Z0-9-.]{1,63}(?<!-)
ConstraintDescription: Must be a valid root domain name
CertificateStackName:
Type: String
Default: privacysexy-certificate-stack
Description: Name of the certificate stack.
DnsStackName:
Type: String
Default: privacysexy-dns-stack
Description: Name of the certificate stack.
PriceClass:
Type: String
Description: The CloudFront distribution price class
Default: 'PriceClass_100'
AllowedValues:
- 'PriceClass_100'
- 'PriceClass_200'
- 'PriceClass_All'
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${AWS::StackName}-${RootDomainName} # Must have stack name for IAM to allow
WebsiteConfiguration:
IndexDocument: index.html
Tags:
-
Key: Application
Value: privacy.sexy
S3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref S3Bucket
PolicyDocument: # Only used for CloudFront as it's the only way, otherwise use IAM roles in IAM stack.
Statement:
-
Sid: AllowCloudFrontRead
Action: s3:GetObject
Effect: Allow
Principal:
CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId
Resource: !Join ['', ['arn:aws:s3:::', !Ref S3Bucket, /*]]
CloudFrontOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: !Sub 'CloudFront OAI for ${S3Bucket}'
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Comment: Cloudfront Distribution pointing to S3 bucket
Origins:
-
DomainName: !GetAtt S3Bucket.DomainName
Id: S3Origin
S3OriginConfig:
OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}"
Enabled: true
HttpVersion: 'http2'
DefaultRootObject: index.html
Aliases:
- !Ref RootDomainName
- !Sub 'www.${RootDomainName}'
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
Compress: true
TargetOriginId: S3Origin
ForwardedValues:
QueryString: true
Cookies:
Forward: none
ViewerProtocolPolicy: redirect-to-https
PriceClass: !Ref PriceClass
ViewerCertificate:
AcmCertificateArn:
# Certificate must be validated before it can be used here
Fn::ImportValue: !Join [':', [!Ref CertificateStackName, CertificateArn]]
SslSupportMethod: sni-only
MinimumProtocolVersion: TLSv1.1_2016
Tags:
-
Key: Application
Value: privacy.sexy
CloudFrontDNSRecords:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneId:
Fn::ImportValue: !Join [':', [!Ref DnsStackName, DNSHostedZoneId]]
RecordSets:
-
Name: !Ref RootDomainName
Type: A
AliasTarget:
DNSName: !GetAtt CloudFrontDistribution.DomainName
EvaluateTargetHealth: false
HostedZoneId: Z2FDTNDATAQYW2 # Static CloudFront distribution zone https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html#cfn-route53-aliastarget-hostedzoneid
-
Name: !Join ['', ['www.', !Ref RootDomainName]]
Type: A
AliasTarget:
DNSName: !GetAtt CloudFrontDistribution.DomainName
EvaluateTargetHealth: false
HostedZoneId: Z2FDTNDATAQYW2 # Static CloudFront distribution zone https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html#cfn-route53-aliastarget-hostedzoneid
Outputs:
CloudFrontDistributionArn: # Used by deployment script to be able to deploy to right S3 bucket
Description: Tthe Amazon Resource Name (ARN) of the CloudFront distribution.
Value: !Ref CloudFrontDistribution
S3BucketName: # Used by deployment script to be able to deploy to right S3 bucket
Description: Name of the S3 bucket.
Value: !Ref S3Bucket

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB