diff --git a/API.md b/API.md
index 5be1b2ae..81370b9c 100644
--- a/API.md
+++ b/API.md
@@ -1410,10 +1410,27 @@ const httpLoadBalancerProps: HttpLoadBalancerProps = { ... }
| **Name** | **Type** | **Description** |
| --- | --- | --- |
+| certificate
| aws-cdk-lib.aws_certificatemanager.ICertificate
| An ACM certificate to associate with this load balancer. |
| requestsPerTarget
| number
| The number of ALB requests per target. |
---
+##### `certificate`Optional
+
+```typescript
+public readonly certificate: ICertificate;
+```
+
+- *Type:* aws-cdk-lib.aws_certificatemanager.ICertificate
+- *Default:* undefined. The load balancer will listen on port 80 over HTTP.
+
+An ACM certificate to associate with this load balancer.
+
+If specified, this
+extension will listen over HTTPS on port 443.
+
+---
+
##### `requestsPerTarget`Optional
```typescript
diff --git a/src/extensions/http-load-balancer.ts b/src/extensions/http-load-balancer.ts
index f6ccab3c..66f8eef1 100644
--- a/src/extensions/http-load-balancer.ts
+++ b/src/extensions/http-load-balancer.ts
@@ -1,4 +1,5 @@
import { CfnOutput, Duration } from 'aws-cdk-lib';
+import * as acm from 'aws-cdk-lib/aws-certificatemanager';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as alb from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import { Construct } from 'constructs';
@@ -10,6 +11,14 @@ export interface HttpLoadBalancerProps {
* The number of ALB requests per target.
*/
readonly requestsPerTarget?: number;
+
+ /**
+ * An ACM certificate to associate with this load balancer. If specified, this
+ * extension will listen over HTTPS on port 443.
+ *
+ * @default - undefined. The load balancer will listen on port 80 over HTTP.
+ */
+ readonly certificate?: acm.ICertificate;
}
/**
* This extension add a public facing load balancer for sending traffic
@@ -19,10 +28,12 @@ export class HttpLoadBalancerExtension extends ServiceExtension {
private loadBalancer!: alb.IApplicationLoadBalancer;
private listener!: alb.IApplicationListener;
private requestsPerTarget?: number;
+ private certificate?: acm.ICertificate;
constructor(props: HttpLoadBalancerProps = {}) {
super('load-balancer');
this.requestsPerTarget = props.requestsPerTarget;
+ this.certificate = props.certificate;
}
// Before the service is created, go ahead and create the load balancer itself.
@@ -33,12 +44,28 @@ export class HttpLoadBalancerExtension extends ServiceExtension {
vpc: this.parentService.vpc,
internetFacing: true,
});
-
+ const protocol = this.certificate ? alb.ApplicationProtocol.HTTPS : alb.ApplicationProtocol.HTTP;
+ const port = this.certificate ? 443 : 80;
this.listener = this.loadBalancer.addListener(`${this.parentService.id}-listener`, {
- port: 80,
+ port,
+ protocol,
open: true,
});
+ if (this.certificate) {
+ this.listener.addCertificates('cert', [alb.ListenerCertificate.fromCertificateManager(this.certificate)]);
+ this.loadBalancer.addListener(`${this.parentService.id}-redirect-listener`, {
+ protocol: alb.ApplicationProtocol.HTTP,
+ port: 80,
+ open: true,
+ defaultAction: alb.ListenerAction.redirect({
+ port: '443',
+ protocol: alb.ApplicationProtocol.HTTPS,
+ permanent: true,
+ }),
+ });
+ }
+
// Automatically create an output
new CfnOutput(scope, `${this.parentService.id}-load-balancer-dns-output`, {
value: this.loadBalancer.loadBalancerDnsName,
diff --git a/test/http-load-balancer.test.ts b/test/http-load-balancer.test.ts
index f9e7e983..ce07b6c4 100644
--- a/test/http-load-balancer.test.ts
+++ b/test/http-load-balancer.test.ts
@@ -1,5 +1,6 @@
import { Stack } from 'aws-cdk-lib';
import { Template } from 'aws-cdk-lib/assertions';
+import * as acm from 'aws-cdk-lib/aws-certificatemanager';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import { Container, Environment, HttpLoadBalancerExtension, Service, ServiceDescription } from '../lib';
@@ -138,4 +139,56 @@ describe('http load balancer', () => {
});
}).toThrow(/Auto scaling target for the service 'my-service' hasn't been configured. Please use Service construct to configure 'minTaskCount' and 'maxTaskCount'./);
});
+
+ test('should create HTTP listener and redirect if certificate specified', () => {
+ // GIVEN
+ const stack = new Stack();
+
+ // WHEN
+ const environment = new Environment(stack, 'production');
+ const serviceDescription = new ServiceDescription();
+
+ serviceDescription.add(new Container({
+ cpu: 256,
+ memoryMiB: 512,
+ trafficPort: 80,
+ image: ecs.ContainerImage.fromRegistry('nathanpeck/name'),
+ }));
+ const certificate = acm.Certificate.fromCertificateArn(
+ stack,
+ 'importedCert',
+ 'arn:aws:acm:us-west-2:1234567:certificate/ABC123',
+ );
+ serviceDescription.add(new HttpLoadBalancerExtension({ certificate }));
+
+ new Service(stack, 'my-service', {
+ environment,
+ serviceDescription,
+ desiredCount: 2,
+ });
+
+ // THEN
+ Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', {
+ Port: 443,
+ Certificates: [
+ { CertificateArn: 'arn:aws:acm:us-west-2:1234567:certificate/ABC123' },
+ ],
+ Protocol: 'HTTPS',
+ });
+
+ Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', {
+ Port: 80,
+ Protocol: 'HTTP',
+ DefaultActions: [
+ {
+ RedirectConfig: {
+ Port: '443',
+ Protocol: 'HTTPS',
+ StatusCode: 'HTTP_301',
+ },
+ Type: 'redirect',
+ },
+ ],
+ });
+ });
});
\ No newline at end of file