-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for custom domains and HTTPS (#561)
* Add domain config options to the network config objects in project-config module * Domain config has `hosted_zone`, `manage_dns`, and `certificate_configs` options * Certificate config has `source` option * Add domain module which creates Route53 hosted zone and ACM issued SSL certificates * Add `domain_name` and `enable_https` to environment config objects in app-config module * Update service module to create A record routing traffic from custom domain to load balancer * Update service module to attach SSL certificate to load balancer * Add instructions for setting up custom domains and HTTPS
- Loading branch information
Showing
22 changed files
with
486 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# HTTPS support | ||
|
||
Production systems will want to use HTTPS rather than HTTP to prevent man-in-the-middle attacks. This document describes how HTTPS is configured. This process will: | ||
|
||
1. Issue an SSL/TLS certificate using Amazon Certificate Manager (ACM) for each domain that we want to support HTTPS | ||
2. Associate the certificate with the application's load balancer so that the load balancer can serve HTTPS requests intended for that domain | ||
|
||
## Requirements | ||
|
||
In order to set up HTTPS support you'll also need to have [set up custom domains](/docs/infra/set-up-custom-domains.md). This is because SSL/TLS certificates must be properly configured for the specific domain to support establishing secure connections. | ||
|
||
## 1. Set desired certificates in domain configuration | ||
|
||
For each custom domain you want to set up in the network, define a certificate configuration object and set the `source` to `issued`. You'll probably want at least one custom domain for each application/service in the network. The custom domain must be either the same as the hosted zone or a subdomain of the hosted zone. | ||
|
||
## 2. Update the network layer to issue the certificates | ||
|
||
Run the following command to issue SSL/TLS certificates for each custom domain you configured | ||
|
||
```bash | ||
make infra-update-network NETWORK_NAME=<NETWORK_NAME> | ||
``` | ||
|
||
Run the following command to check the status of a certificate (replace `<CERTIFICATE_ARN>` using the output from the previous command): | ||
|
||
```bash | ||
aws acm describe-certificate --certificate-arn <CERTIFICATE_ARN> --query Certificate.Status | ||
``` | ||
|
||
## 4. Update `enable_https = true` in `app-config` | ||
|
||
Update `enable_https = true` in your application's `app-config` module. You should have already set `domain_name` as part of [setting up custom domain names](/docs/infra/set-up-custom-domains.md). | ||
|
||
## 5. Attach certificate to load balancer | ||
|
||
Run the following command to attach the SSL/TLS certificate to the load balancer | ||
|
||
```bash | ||
make infra-update-app-service APP_NAME=<APP_NAME> ENVIRONMENT=<ENVIRONMENT> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# Custom domains | ||
|
||
Production systems will want to set up custom domains to route internet traffic to their application services rather than using AWS-generated hostnames for the load balancers or the CDN. This document describes how to configure custom domains. The custom domain setup process will: | ||
|
||
1. Create an [Amazon Route 53 hosted zone](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-working-with.html) to manage DNS records for a domain and subdomains | ||
2. Create a DNS A (address) records to route traffic from a custom domain to the application's load balancer | ||
|
||
## Requirements | ||
|
||
Before setting up custom domains you'll need to have [set up the AWS account](./set-up-aws-account.md) | ||
|
||
## 1. Set hosted zone in domain configuration | ||
|
||
Update the value for the `hosted_zone` in the domain configuration. The custom domain configuration is defined as a `domain_config` object in the [network section of the project config module](/infra/project-config/networks.tf). A [hosted zone](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-working-with.html) represents a domain and all of its subdomains. For example, a hosted zone of `platform-test.navateam.com` includes `platform-test.navateam.com`, `cdn.platform-test.navateam.com`, `notifications.platform-test.navateam.com`, `foo.bar.platform-test.navateam.com`, etc. | ||
|
||
## 2. Update the network layer to create the hosted zone | ||
|
||
Run the following command to create the hosted zone specified in the domain configuration. | ||
|
||
```bash | ||
make infra-update-network NETWORK_NAME=<NETWORK_NAME> | ||
``` | ||
|
||
## 3. Delegate DNS requests to the newly created hosted zone | ||
|
||
You most likely registered your domain outside of this project. Using whichever service you used to register the domain name (e.g. Namecheap, GoDaddy, Google Domains, etc.), add a DNS NS (nameserver) record. Set the "name" equal to the `hosted_zone` and set the value equal to the list of hosted zone name servers that was created in the previous step. You can see the list of servers by running | ||
|
||
```bash | ||
terraform -chdir=infra/networks output -json hosted_zone_name_servers | ||
``` | ||
|
||
Your NS record might look something like this: | ||
|
||
**Name**: | ||
|
||
```text | ||
platform-test.navateam.com | ||
``` | ||
|
||
**Value**: (Note the periods after each of the server addresses) | ||
|
||
```text | ||
ns-1431.awsdns-50.org. | ||
ns-1643.awsdns-13.co.uk. | ||
ns-687.awsdns-21.net. | ||
ns-80.awsdns-10.com. | ||
``` | ||
|
||
Run the following command to verify that DNS requests are being served by the hosted zone nameservers using `nslookup`. | ||
|
||
```bash | ||
nslookup -type=NS <HOSTED_ZONE> | ||
``` | ||
|
||
## 4. Configure custom domain for your application | ||
|
||
Define the `domain_name` for each of the application environments in the `app-config` module. The `domain_name` must be either the same as the `hosted_zone` or a subdomain of the `hosted_zone`. For example, if your hosted zone is `platform-test.navateam.com`, then `platform-test.navateam.com` and `cdn.platform-test.navateam.com` are both valid values for `domain_name`. | ||
|
||
## 5. Create A (address) records to route traffice from the custom domain to your application's load balancer | ||
|
||
Run the following command to create the A record that routes traffic from the custom domain to the application's load balancer. | ||
|
||
```bash | ||
make infra-update-app-service APP_NAME=<APP_NAME> ENVIRONMENT=<ENVIRONMENT> | ||
``` | ||
|
||
## 6. Repeat for each application | ||
|
||
If you have multiple applications in the same network, repeat steps 4 and 5 for each application. | ||
|
||
## Externally managed DNS | ||
|
||
If you plan to manage DNS records outside of the project, then set `network_configs[*].domain_config.manage_dns = false` in [the networks section of the project-config module](/infra/project-config/networks.tf). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
locals { | ||
# Filter configs for issued certificates. | ||
# These certificates are managed by the project using AWS Certificate Manager. | ||
issued_certificate_configs = { | ||
for domain, config in var.certificate_configs : domain => config | ||
if config.source == "issued" | ||
} | ||
|
||
# Filter configs for imported certificates. | ||
# These certificates are created outside of the project and imported. | ||
imported_certificate_configs = { | ||
for domain, config in var.certificate_configs : domain => config | ||
if config.source == "imported" | ||
} | ||
|
||
domain_validation_options = merge([ | ||
for domain, config in local.issued_certificate_configs : | ||
{ | ||
for dvo in aws_acm_certificate.issued[domain].domain_validation_options : | ||
dvo.domain_name => { | ||
name = dvo.resource_record_name | ||
record = dvo.resource_record_value | ||
type = dvo.resource_record_type | ||
} | ||
} | ||
]...) | ||
} | ||
|
||
# ACM certificate that will be used by the load balancer. | ||
resource "aws_acm_certificate" "issued" { | ||
for_each = local.issued_certificate_configs | ||
|
||
domain_name = each.key | ||
validation_method = "DNS" | ||
|
||
lifecycle { | ||
create_before_destroy = true | ||
} | ||
} | ||
|
||
# DNS records for certificate validation. | ||
resource "aws_route53_record" "validation" { | ||
for_each = local.domain_validation_options | ||
|
||
allow_overwrite = true | ||
zone_id = aws_route53_zone.zone[0].zone_id | ||
name = each.value.name | ||
type = each.value.type | ||
ttl = 60 | ||
records = [each.value.record] | ||
} | ||
|
||
# Representation of successful validation of the ACM certificate. | ||
resource "aws_acm_certificate_validation" "validation" { | ||
for_each = local.imported_certificate_configs | ||
|
||
certificate_arn = aws_acm_certificate.issued[each.key].arn | ||
validation_record_fqdns = [for record in aws_route53_record.validation : record.fqdn] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Create a Route53 hosted zone for the domain. | ||
# Individual address records will be created in the service layer by the services that | ||
# need them (e.g. the load balancer or CDN). | ||
# If DNS is managed elsewhere then this resource will not be created. | ||
resource "aws_route53_zone" "zone" { | ||
count = var.manage_dns ? 1 : 0 | ||
name = var.name | ||
|
||
# checkov:skip=CKV2_AWS_38:TODO(https://github.com/navapbc/template-infra/issues/560) enable DNSSEC | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
output "hosted_zone_name_servers" { | ||
value = length(aws_route53_zone.zone) > 0 ? aws_route53_zone.zone[0].name_servers : [] | ||
} | ||
|
||
output "certificate_arns" { | ||
value = { | ||
for domain in keys(var.certificate_configs) : domain => aws_acm_certificate.issued[domain].arn | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# DNS query logging | ||
|
||
resource "aws_cloudwatch_log_group" "dns_query_logging" { | ||
count = var.manage_dns ? 1 : 0 | ||
|
||
name = "/dns/${var.name}" | ||
retention_in_days = 30 | ||
|
||
# checkov:skip=CKV_AWS_158:No need to manage KMS key for DNS query logs or audit access to these logs | ||
} | ||
|
||
resource "aws_route53_query_log" "dns_query_logging" { | ||
count = var.manage_dns ? 1 : 0 | ||
|
||
zone_id = aws_route53_zone.zone[0].zone_id | ||
cloudwatch_log_group_arn = aws_cloudwatch_log_group.dns_query_logging[0].arn | ||
|
||
depends_on = [aws_cloudwatch_log_resource_policy.dns_query_logging[0]] | ||
} | ||
|
||
# Allow Route53 to write logs to any log group under /dns/* | ||
data "aws_iam_policy_document" "dns_query_logging" { | ||
statement { | ||
actions = [ | ||
"logs:CreateLogStream", | ||
"logs:PutLogEvents", | ||
] | ||
|
||
resources = ["arn:aws:logs:*:*:log-group:/dns/*"] | ||
|
||
principals { | ||
identifiers = ["route53.amazonaws.com"] | ||
type = "Service" | ||
} | ||
} | ||
} | ||
|
||
resource "aws_cloudwatch_log_resource_policy" "dns_query_logging" { | ||
count = var.manage_dns ? 1 : 0 | ||
|
||
policy_document = data.aws_iam_policy_document.dns_query_logging.json | ||
policy_name = "dns-query-logging" | ||
} |
Oops, something went wrong.