Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 79 additions & 86 deletions .github/assets/php/createAlbYaml.php
Original file line number Diff line number Diff line change
@@ -1,129 +1,122 @@
<?php

# @link https://github.com/aws-samples/ecs-refarch-cloudformation/blob/master/infrastructure/load-balancers.yaml
# @link https://repost.aws/knowledge-center/elastic-beanstalk-ssl-load-balancer
# Generates a CloudFormation template for a shared Application Load Balancer
# that uses a default certificate and host-based rule. Optional CLI arguments
# allow populating default values for the certificate ARN and the host list.

# ignore the fact that this is technically initiated by php ./file.php
$certificateArns = $argv[1] ?? '';
$defaultCertificateArn = $argv[1] ?? '';
$defaultHosts = $argv[2] ?? '';

$hasCert = !empty($certificateArns);
$defaultCertificateParam = <<<EOT
DefaultCertificateArn:
Type: String
Description: ARN of the default ACM certificate for the listener
EOT;

$certificates = $hasCert ? explode(',', $certificateArns) : [];

$DefaultHttpAction = $hasCert ? <<<EOF
- Type: "redirect"
RedirectConfig:
Protocol: "HTTPS"
Port: "443"
Host: "#{host}"
Path: "/#{path}"
Query: "#{query}"
StatusCode: "HTTP_301"
EOF: <<<EOF
- Type: fixed-response
FixedResponseConfig:
StatusCode: 200
ContentType: text/plain
MessageBody: "No certificates provided, no target groups were matched."
EOF;


$PublicAlbHttpsListenerReturn = $hasCert ? <<<EOL
PublicAlbHttpsListenerArn:
Value: !Ref PublicAlbHttpsListener
Export:
Name: PublicAlbHttpsListenerArn
EOL: '';

$defaultCertificate = $hasCert ? array_shift($certificates) : '';

$httpsListener = $hasCert ? <<<EOF

PublicAlbHttpsListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
Certificates:
- CertificateArn: $defaultCertificate
DefaultActions:
- Type: fixed-response
FixedResponseConfig:
StatusCode: "404"
MessageBody: !Sub "Our AWS application load balancer did not resolve this request. Please contact a server administrator."
LoadBalancerArn: !Ref PublicAlb
Port: 443
Protocol: HTTPS


EOF: '';

foreach ($certificates as $key => $certificate) {
$httpsListener .= <<<EOF

Certificate$key:
Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
Properties:
Certificates:
- CertificateArn: "$certificate"
ListenerArn: !Ref PublicAlbHttpsListener
if (!empty($defaultCertificateArn)) {
$defaultCertificateParam .= "\n Default: $defaultCertificateArn";
}

$defaultHostsParam = <<<EOT
DefaultLoadBalancerHosts:
Type: List<String>
Description: Default hostnames handled by the listener
EOT;

EOF;
if (!empty($defaultHosts)) {
$defaultHostsParam .= "\n Default: $defaultHosts";
}

print <<<EOF
print <<<YAML
AWSTemplateFormatVersion: "2010-09-09"
Description: Deploys an Application Load Balancer (ALB) with a listeners
Description: Application Load Balancer with default certificate and host rule.

Parameters:
PublicSubnets:
Type: List<AWS::EC2::Subnet::Id>
Description: List of Private subnets to use for the application
Description: Subnets for the load balancer
LoadBalancerSecurityGroups:
Type: List<AWS::EC2::SecurityGroup::Id>
Description: Security groups for the load balancer
$defaultCertificateParam
$defaultHostsParam

Resources:
PublicAlb:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
IpAddressType: ipv4
Name: publicAlb
Scheme: internet-facing
SecurityGroups:
- !ImportValue Ec2SecurityGroup
Subnets: !Ref PublicSubnets
SecurityGroups: !Ref LoadBalancerSecurityGroups
Type: application
Tags:
- Key: Name
Value: ec2-alb
Type: application
Value: publicAlb

PublicAlbHttpListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
$DefaultHttpAction
LoadBalancerArn: !Ref PublicAlb
Port: 80
Protocol: HTTP
DefaultActions:
- Type: redirect
RedirectConfig:
Port: '443'
Protocol: HTTPS
StatusCode: HTTP_301

$httpsListener
PublicAlbHttpsListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref PublicAlb
Port: 443
Protocol: HTTPS
Certificates:
- CertificateArn: !Ref DefaultCertificateArn
DefaultActions:
- Type: fixed-response
FixedResponseConfig:
StatusCode: '404'
ContentType: text/plain
MessageBody: Not Found

DefaultHttpsListenerRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
Actions:
- Type: fixed-response
FixedResponseConfig:
StatusCode: '404'
ContentType: text/plain
MessageBody: Not Found
Conditions:
- Field: host-header
HostHeaderConfig:
Values: !Ref DefaultLoadBalancerHosts
ListenerArn: !Ref PublicAlbHttpsListener
Priority: 1

Outputs:
PublicAlb:
PublicAlbArn:
Value: !Ref PublicAlb
Export:
Name: PublicAlbArn
PublicAlbCanonicalHostedZoneId:
Value: !GetAtt PublicAlb.CanonicalHostedZoneID
PublicAlbDnsName:
Value: !GetAtt PublicAlb.DNSName
PublicAlbFullName:
Value: !GetAtt PublicAlb.LoadBalancerFullName
PublicAlbHostname:
Value: !Sub https://\${PublicAlb.DNSName}
Export:
Name: PublicAlbDnsName
PublicAlbCanonicalHostedZoneId:
Value: !GetAtt PublicAlb.CanonicalHostedZoneID
Export:
Name: PublicAlbCanonicalHostedZoneId
PublicAlbHttpListenerArn:
Value: !Ref PublicAlbHttpListener
Export:
Name: PublicAlbHttpListenerArn
$PublicAlbHttpsListenerReturn

EOF;

PublicAlbHttpsListenerArn:
Value: !Ref PublicAlbHttpsListener
Export:
Name: PublicAlbHttpsListenerArn
YAML;

20 changes: 14 additions & 6 deletions CloudFormation/web.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@ Parameters:
Description: Add a UDP listener to the NLB
Default: "false"

CertificateArns:
Type: CommaDelimitedList # List<AWS::CertificateManager::Certificate::Arn>
Description: List of ACM certificates to be used by the load balancer listener
CertificateArn:
Type: String
Description: ACM certificate to attach to the load balancer listener
Default: ""

UseGitHubRunNumberForASG:
Expand All @@ -128,8 +128,8 @@ Parameters:
Conditions:
linkAlb: !Equals [ !Ref AddAlbListener, 'true' ]
linkNlb: !Equals [ !Ref AddNlbListener, 'true' ]
HasCertificates: !Not [ !Equals [ !Join [ "", !Ref CertificateArns ], "" ] ]
linkAlbWithCerts: !And [ !Condition linkAlb, !Condition HasCertificates ]
HasCertificate: !Not [ !Equals [ !Ref CertificateArn, "" ] ]
linkAlbWithCert: !And [ !Condition linkAlb, !Condition HasCertificate ]
HasLoadBalancerHosts: !Not [ !Equals [ !Join [ "", !Ref LoadBalancerHosts], "" ] ]
IncludeGitHubRunNumberForASG: !Equals [!Ref UseGitHubRunNumberForASG, 'true']

Expand Down Expand Up @@ -176,8 +176,16 @@ Resources:
ListenerArn: !ImportValue PublicAlbHttpListenerArn
Priority: !Ref LoadBalancerRulePriority

AlbHttpsListenerCertificate:
Condition: linkAlbWithCert
Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
Properties:
Certificates:
- CertificateArn: !Ref CertificateArn
ListenerArn: !ImportValue PublicAlbHttpsListenerArn

AlbHttpsListenerRule:
Condition: linkAlbWithCerts
Condition: linkAlb
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
Actions:
Expand Down
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,64 @@ https://docs.aws.amazon.com/cloudformation/

This is a full example of a GitHub Actions workflow that utalizes this repository.

The shared Application Load Balancer stack is generated on the fly using the
`createAlbYaml.php` helper:

```bash
php .github/assets/php/createAlbYaml.php "$DEFAULT_CERT" "$DEFAULT_HOSTS" > CloudFormation/alb.yaml
```

### Branch-based deployment snippet

The following job derives CloudFormation parameters from **CURRENT_BRANCH**,
**FLAVOR**, and **DOMAINS** so each workflow creates its own listener rule and
certificate:

```yaml
jobs:
deploy:
runs-on: ubuntu-latest
env:
DEFAULT_BRANCH: www
CURRENT_BRANCH: ${{ github.ref_name }}
FLAVOR: ${{ inputs.flavor }}
steps:
- name: Compute parameters
id: params
run: |
if [ "$FLAVOR" = "openreplay" ]; then
DOMAIN="or.$CURRENT_BRANCH.assessorly.com"
DB_ENGINE="aurora-postgresql"
if [ "$CURRENT_BRANCH" = "$DEFAULT_BRANCH" ]; then
VOLUME=100
else
VOLUME=50
fi
elif [ "$CURRENT_BRANCH" = "$DEFAULT_BRANCH" ]; then
DOMAIN="www.assessorly.com"
DB_ENGINE="aurora-mysql"
VOLUME=50
else
DOMAIN="$CURRENT_BRANCH.assessorly.com"
DB_ENGINE="aurora-mysql"
VOLUME=50
fi
echo "domain=$DOMAIN" >> $GITHUB_OUTPUT
echo "engine=$DB_ENGINE" >> $GITHUB_OUTPUT
echo "volume=$VOLUME" >> $GITHUB_OUTPUT
- name: Deploy stack
run: |
aws cloudformation deploy \
--template-file CloudFormation/web.yaml \
--stack-name web-$CURRENT_BRANCH \
--parameter-overrides \
LoadBalancerHosts=${{ steps.params.outputs.domain }} \
CertificateArn=$ACM_CERT \
DatabaseEngine=${{ steps.params.outputs.engine }} \
AllocatedStorage=${{ steps.params.outputs.volume }} \
LoadBalancerRulePriority=100
```

```yaml
name: Aws Deployment Workflow

Expand Down