Skip to content

Commit 67d9223

Browse files
committed
Initial version
1 parent 4e32b5e commit 67d9223

File tree

13 files changed

+317
-0
lines changed

13 files changed

+317
-0
lines changed

Diff for: accelerator-stack/resources/dns-records.yml

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Resources:
2+
HostedZone:
3+
Type: 'AWS::Route53::HostedZone'
4+
Properties:
5+
HostedZoneConfig:
6+
Comment: 'Hosted zone for ${self:custom.domain}'
7+
Name: '${self:custom.domain}'
8+
9+
ExternalDnsRecord:
10+
Type: 'AWS::Route53::RecordSet'
11+
Properties:
12+
Comment: 'External subdomain for ${self:custom.domain}'
13+
HostedZoneId: !Ref 'HostedZone'
14+
Type: A
15+
Name: 'external.${self:custom.domain}'
16+
AliasTarget:
17+
# Will redirect to the GlobalAccelerator DNS name
18+
DNSName: !Sub '${Accelerator.DnsName}'
19+
# Default (static) hosted zone for GlobalAccelerator
20+
# See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget-1.html#cfn-route53-aliastarget-hostedzoneid
21+
HostedZoneId: 'Z2BJ6XQ5FK7U4H'

Diff for: accelerator-stack/resources/global-accelerator.yml

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
Resources:
2+
Accelerator:
3+
Type: AWS::GlobalAccelerator::Accelerator
4+
Properties:
5+
Name: 'External-Accelerator'
6+
Enabled: true
7+
8+
Listener:
9+
Type: AWS::GlobalAccelerator::Listener
10+
Properties:
11+
AcceleratorArn:
12+
Ref: Accelerator
13+
Protocol: TCP
14+
ClientAffinity: NONE
15+
PortRanges:
16+
- FromPort: 443
17+
ToPort: 443
18+
19+
EndpointGroup1:
20+
Type: AWS::GlobalAccelerator::EndpointGroup
21+
Properties:
22+
EndpointConfigurations:
23+
- EndpointId: '${self:custom.ec2.instance1.id}'
24+
Weight: 1
25+
EndpointGroupRegion: '${self:custom.ec2.instance1.region}'
26+
HealthCheckIntervalSeconds: 30
27+
HealthCheckPath: '/health'
28+
HealthCheckPort: 80
29+
HealthCheckProtocol: 'HTTP'
30+
ListenerArn: !Ref 'Listener'
31+
ThresholdCount: 3

Diff for: accelerator-stack/serverless.yml

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
service: global-reverse-proxy-accelerator
2+
3+
custom:
4+
5+
# Target domain name
6+
domain: 'your-target-domain.com'
7+
8+
ec2:
9+
# Add instance information manually, and add a new endpoint group for each instance in global-accelerator.yml
10+
instance1:
11+
id: '${cf:global-reverse-proxy-server-1-${self:provider.stage}.EC2InstanceId}'
12+
region: '${cf:global-reverse-proxy-server-1-${self:provider.stage}.EC2Region}'
13+
14+
provider:
15+
name: aws
16+
region: 'us-east-1' # Needs to be in us-east-1 because GlobalAccelerator!
17+
stage: ${opt:stage, 'prd'} # Can be changed by the --stage CLI option
18+
19+
resources:
20+
- ${file(resources/dns-records.yml)}
21+
- ${file(resources/global-accelerator.yml)}

Diff for: domain-service-stack/resources/dynamo-db.yml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Resources:
2+
CertificatesTable:
3+
Type: AWS::DynamoDB::Table
4+
Properties:
5+
TableName: ${self:custom.dynamodb.tableName}
6+
BillingMode: PAY_PER_REQUEST
7+
AttributeDefinitions:
8+
- AttributeName: PrimaryKey
9+
AttributeType: S
10+
KeySchema:
11+
- AttributeName: PrimaryKey
12+
KeyType: HASH

Diff for: domain-service-stack/resources/outputs.yml

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Outputs:
2+
DomainVerifierFunctionUrl:
3+
Description: The URL for the domain Lambda function
4+
Value: !GetAtt 'DomainVerifierLambdaFunctionUrl.FunctionUrl'
5+
CertificateTableName:
6+
Description: Name of the certificate DynamoDB table
7+
Value: '${self:custom.dynamodb.tableName}'

Diff for: domain-service-stack/serverless.yml

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
service: global-reverse-proxy-domain-service
2+
3+
custom:
4+
5+
# DynamoDB
6+
dynamodb:
7+
tableName: 'global-proxy-certificate-table'
8+
9+
provider:
10+
name: aws
11+
runtime: nodejs16.x
12+
region: ${opt:region, 'us-east-1'} # Can be changed by the --region CLI option
13+
stage: ${opt:stage, 'prd'} # Can be changed by the --stage CLI option
14+
logRetentionInDays: 14
15+
16+
functions:
17+
domainVerifier:
18+
handler: src/domainVerifier.handler
19+
memorySize: 128
20+
timeout: 5
21+
# Use Function URLs
22+
url:
23+
cors: true
24+
25+
resources:
26+
- ${file(resources/dynamo-db.yml)}
27+
- ${file(resources/outputs.yml)}

Diff for: domain-service-stack/src/domainVerifier.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// For event format, see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.proxy-format
2+
3+
const allowedDomains = [
4+
'mydomain.com',
5+
'myotherdomain.com'
6+
];
7+
8+
exports.handler = async (event) => {
9+
let statusCode;
10+
11+
// Check if there's a "domain" query string paramenter, and check if the give value is in the list of allowed domains
12+
if (event.queryStringParameters.hasOwnProperty('domain') && allowedDomains.includes(event.queryStringParameters.domain)) {
13+
// If yes, send a 200 status, which will then trigger a certificate generation in Caddy
14+
statusCode = 200;
15+
} else {
16+
// If not, send a 400 status, which will NOT trigger a certificate generation in Caddy (we should only do this for whitelisted domains!)
17+
statusCode = 400;
18+
}
19+
20+
return {
21+
statusCode,
22+
};
23+
};

Diff for: proxy-server-stack/caddy-config/Caddyfile

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
debug
3+
admin off
4+
on_demand_tls {
5+
ask {$DOMAIN_SERVICE_ENDPOINT}
6+
}
7+
8+
storage dynamodb {$TABLE_NAME} {
9+
aws_region {$AWS_REGION}
10+
}
11+
}
12+
13+
:80 {
14+
respond /health "Im healthy!" 200
15+
}
16+
17+
:443 {
18+
tls {$LETSENCRYPT_EMAIL_ADDRESS} {
19+
on_demand
20+
}
21+
22+
reverse_proxy https://{$TARGET_DOMAIN} {
23+
header_up Host {$TARGET_DOMAIN}
24+
header_up User-Custom-Domain {host}
25+
header_up X-Forwarded-Port {server_port}
26+
27+
health_timeout 5s
28+
}
29+
}

Diff for: proxy-server-stack/caddy-config/caddy.service

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[Unit]
2+
Description=Caddy
3+
Documentation=https://caddyserver.com/docs/
4+
After=network.target
5+
6+
[Service]
7+
User=caddy
8+
Group=caddy
9+
ExecStart=/usr/bin/caddy run --config /etc/caddy/Caddyfile
10+
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile
11+
TimeoutStopSec=5s
12+
LimitNOFILE=1048576
13+
LimitNPROC=512
14+
PrivateTmp=true
15+
ProtectSystem=full
16+
AmbientCapabilities=CAP_NET_BIND_SERVICE
17+
EnvironmentFile=/etc/caddy/environment
18+
19+
[Install]
20+
WantedBy=multi-user.target

Diff for: proxy-server-stack/configs.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const { readFileSync } = require('fs');
2+
const { join } = require('path');
3+
4+
module.exports.caddyFile = readFileSync(join(__dirname, 'caddy-config', 'Caddyfile'), { encoding: 'utf-8'}).replace(/\n/g, "\\n");
5+
module.exports.caddyService = readFileSync(join(__dirname, 'caddy-config', 'caddy.service'), { encoding: 'utf-8'}).replace(/\n/g, "\\n");

Diff for: proxy-server-stack/resources/ec2.yml

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
Resources:
2+
EC2Instance:
3+
Type: AWS::EC2::Instance
4+
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-init.html#aws-resource-cloudformation-init-syntax
5+
Properties:
6+
InstanceType: '${self:custom.ec2.instanceType}'
7+
KeyName: '${self:custom.ec2.keyName}'
8+
SecurityGroups:
9+
- !Ref 'InstanceSecurityGroup'
10+
ImageId: 'ami-0b5eea76982371e91' # Amazon Linux 2 AMI
11+
IamInstanceProfile: !Ref 'InstanceProfile'
12+
UserData: !Base64
13+
'Fn::Join':
14+
- ''
15+
- - |
16+
#!/bin/bash -xe
17+
- |
18+
sudo wget -O /usr/bin/caddy "https://github.com/tobilg/aws-caddy-build/raw/main/releases/aws_caddy_v2.6.2_linux"
19+
- |
20+
sudo chmod +x /usr/bin/caddy
21+
- |
22+
sudo groupadd --system caddy
23+
- |
24+
sudo useradd --system --gid caddy --create-home --home-dir /var/lib/caddy --shell /usr/sbin/nologin --comment "Caddy web server" caddy
25+
- |
26+
sudo mkdir -p /etc/caddy
27+
- |
28+
sudo echo -e "${file(./configs.js):caddyService}" | sudo tee /etc/systemd/system/caddy.service
29+
- |
30+
sudo echo -e "${file(./configs.js):caddyFile}" | sudo tee /etc/caddy/Caddyfile
31+
- |
32+
sudo echo -e "AWS_REGION=${self:provider.region}\nTABLE_NAME=${self:custom.caddy.dynamoDBTableName}\nDOMAIN_SERVICE_ENDPOINT=${self:custom.caddy.domainServiceEndpoint}\nLETSENCRYPT_EMAIL_ADDRESS=${self:custom.caddy.letsEncryptEmailAddress}\nTARGET_DOMAIN=${self:custom.caddy.targetDomainName}" | sudo tee /etc/caddy/environment
33+
- |
34+
sudo systemctl daemon-reload
35+
- |
36+
sudo systemctl enable caddy
37+
- |
38+
sudo systemctl start --now caddy
39+
40+
InstanceSecurityGroup:
41+
Type: AWS::EC2::SecurityGroup
42+
Properties:
43+
GroupDescription: Enable HTTP(S) and SSH access
44+
SecurityGroupIngress:
45+
- IpProtocol: tcp
46+
FromPort: 80
47+
ToPort: 80
48+
CidrIp: 0.0.0.0/0
49+
- IpProtocol: tcp
50+
FromPort: 443
51+
ToPort: 443
52+
CidrIp: 0.0.0.0/0
53+
54+
InstanceProfile:
55+
Type: AWS::IAM::InstanceProfile
56+
Properties:
57+
Path: '/'
58+
Roles:
59+
- !Ref 'InstanceRole'
60+
61+
InstanceRole:
62+
Type: 'AWS::IAM::Role'
63+
Properties:
64+
Path: '/'
65+
AssumeRolePolicyDocument:
66+
Version: '2012-10-17'
67+
Statement:
68+
- Effect: Allow
69+
Principal:
70+
Service:
71+
- ec2.amazonaws.com
72+
Action:
73+
- sts:AssumeRole
74+
75+
InstancePolicy:
76+
Type: 'AWS::IAM::Policy'
77+
Properties:
78+
PolicyName: 'use-dynamodb-ssm-policy'
79+
PolicyDocument:
80+
Statement:
81+
- Effect: 'Allow'
82+
Action:
83+
- dynamodb:Scan
84+
- dynamodb:Query
85+
- dynamodb:GetItem
86+
- dynamodb:PutItem
87+
- dynamodb:UpdateItem
88+
- dynamodb:DeleteItem
89+
Resource: !Sub '${CertificatesTable.Arn}'
90+
Roles:
91+
- !Ref 'InstanceRole'

Diff for: proxy-server-stack/resources/outputs.yml

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Outputs:
2+
EC2InstanceId:
3+
Description: The EC2 instance is
4+
Value: !Ref 'EC2Instance'
5+
EC2Region:
6+
Description: The region the EC2 instance is deployed to
7+
Value: '${self:provider.region}'

Diff for: proxy-server-stack/serverless.yml

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
service: global-reverse-proxy-server-1 # Assuming there's only one EC2 instance per region, otherwise change stack name manually
2+
3+
custom:
4+
5+
# Caddy config
6+
caddy:
7+
targetDomainName: 'your-target-domain.com' # The target domain name where to proxy to
8+
letsEncryptEmailAddress: '[email protected]' # Use for automatic certificate generation
9+
domainServiceEndpoint: '${cf:global-reverse-proxy-domain-service-${self:provider.stage}.DomainVerifierFunctionUrl}' # Get Function URL from domain service stack export
10+
dynamoDBTableName: '${cf:global-reverse-proxy-domain-service-${self:provider.stage}.CertificateTableName}' # Get DynamoDB table name from base stack export
11+
12+
# EC2
13+
ec2:
14+
instanceType: 't2.micro' # For free tier usage
15+
keyName: 'my-key-name' # Update this to your key name, which you need to create in the AWS Console BEFORE deploying this stack
16+
17+
provider:
18+
name: aws
19+
region: ${opt:region, 'us-east-1'} # Can be changed by the --region CLI option
20+
stage: ${opt:stage, 'prd'} # Can be changed by the --stage CLI option
21+
22+
resources:
23+
- ${file(resources/ec2.yml)}

0 commit comments

Comments
 (0)