Skip to content
Merged
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
14 changes: 10 additions & 4 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ name: Deploy documentation

on:
workflow_dispatch:
inputs:
environment:
description: Environment to deploy to.
default: development
required: true
type: environment
push:
paths:
# Only trigger on changes to documentation files.
Expand All @@ -16,8 +22,8 @@ permissions:

jobs:
deploy:
name: Deploy Documentation
environment: 'docs-dev'
name: Deploy Documentation to ${{ inputs.environment }}
environment: ${{ inputs.environment }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -26,7 +32,7 @@ jobs:
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
aws-region: ${{ env.AWS_REGION || 'us-east-1' }}
- uses: actions/setup-python@v5
with:
python-version: 3.x
Expand All @@ -39,4 +45,4 @@ jobs:
mkdocs-material-
- run: pip install mkdocs-material markdown-callouts mdx_truly_sane_lists mkdocs-nav-weight pymdown-extensions
- run: mkdocs build
- run: aws s3 sync ./site "s3://${{ env.BUCKET_NAME || 'dev.docs.cfa.codes' }}/${{ env.PREFIX || 'shared-services' }}"
- run: aws s3 sync ./site "s3://${{ env.DOCS_BUCKET || 'docs.dev.services.cfa.codes' }}/${{ env.PREFIX || 'shared-services' }}"
20 changes: 20 additions & 0 deletions tofu/config/development/docs/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions tofu/config/development/docs/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
terraform {
backend "s3" {
bucket = "shared-services-development-tfstate"
key = "docs.tfstate"
region = "us-east-1"
dynamodb_table = "development.tfstate"
}
}

module "docs" {
source = "../../../modules/docs"

environment = "development"
bucket_name = "docs.dev.services.cfa.codes"
force_delete = true
domain = "dev.services.cfa.codes"
subdomain = "docs"

# Use the same VPC we use for shared hosting.
# TODO: Use data resources to look this up.
logging_bucket = "shared-services-development-logs"
vpc_id = "vpc-024d66fcc4f521d0a"
}
4 changes: 4 additions & 0 deletions tofu/config/development/docs/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
output "endpoint_url" {
description = "The URL of the documentation endpoint."
value = module.docs.endpoint_url
}
12 changes: 12 additions & 0 deletions tofu/config/development/docs/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
provider "aws" {
region = "us-east-1"

default_tags {
tags = {
application = "cfa-documentation-development"
environment = "development"
program = "engineering"
project = "cfa-documentation"
}
}
}
Empty file.
10 changes: 10 additions & 0 deletions tofu/config/development/docs/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.6"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.44"
}
}
}
18 changes: 18 additions & 0 deletions tofu/modules/docs/data.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
data "aws_caller_identity" "identity" {}

data "aws_partition" "current" {}

data "aws_region" "current" {}

data "aws_route53_zone" "domain" {
name = var.domain
}

data "aws_vpc_endpoint" "s3" {
vpc_id = var.vpc_id
service_name = "com.amazonaws.${data.aws_region.current.name}.s3"
}

data "aws_cloudfront_cache_policy" "endpoint" {
name = "Managed-CachingOptimized"
}
127 changes: 127 additions & 0 deletions tofu/modules/docs/endpoint.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
resource "aws_cloudfront_origin_access_control" "endpoint" {
name = "${local.prefix}-endpoint"
description = "Authorize CloudFront to serve content from the documentation S3 bucket."
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}

resource "aws_cloudfront_function" "endpoint_rewrite" {
name = "cfa-documentation-${var.environment}-rewrite"
comment = "Rewrite requests to direct to the index.html file in the S3 bucket."
runtime = "cloudfront-js-2.0"
publish = true

code = file("${path.module}/files/rewrite-function.js")

lifecycle {
create_before_destroy = true
}
}

# TODO: Request OIDC authentication for the CloudFront distribution.
# TODO: Use a WAF?
#trivy:ignore:AVD-AWS-0011
resource "aws_cloudfront_distribution" "endpoint" {
enabled = true
comment = "Serve static documentation from S3."
is_ipv6_enabled = true
aliases = [local.fqdn]
price_class = "PriceClass_100"
default_root_object = "index.html"

origin {
domain_name = module.bucket.bucket_regional_domain_name
origin_id = local.prefix
origin_access_control_id = aws_cloudfront_origin_access_control.endpoint.id
}

logging_config {
include_cookies = false
bucket = "${var.logging_bucket}.s3.amazonaws.com"
prefix = "cloudfront/${local.fqdn}"
}

default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = local.prefix
compress = true
default_ttl = 0
max_ttl = 0
min_ttl = 0

cache_policy_id = data.aws_cloudfront_cache_policy.endpoint.id
function_association {
event_type = "viewer-request"
function_arn = aws_cloudfront_function.endpoint_rewrite.arn
}

viewer_protocol_policy = "redirect-to-https"
}

restrictions {
geo_restriction {
restriction_type = "none"
locations = []
}
}

viewer_certificate {
acm_certificate_arn = aws_acm_certificate.endpoint.arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}

tags = local.tags
}

resource "aws_acm_certificate" "endpoint" {
domain_name = local.fqdn
validation_method = "DNS"

lifecycle {
create_before_destroy = true
}

tags = local.tags
}

resource "aws_route53_record" "endpoint_validation" {
for_each = {
for dvo in aws_acm_certificate.endpoint.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}

allow_overwrite = true
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = data.aws_route53_zone.domain.zone_id
}

resource "aws_acm_certificate_validation" "endpoint" {
certificate_arn = aws_acm_certificate.endpoint.arn
validation_record_fqdns = [
for record in aws_route53_record.endpoint_validation : record.fqdn
]
}

resource "aws_route53_record" "endpoint" {
for_each = toset(["A", "AAAA"])

name = local.fqdn
type = each.value
zone_id = data.aws_route53_zone.domain.zone_id

alias {
# CloudFront doesn't provide a health check.
evaluate_target_health = false
name = aws_cloudfront_distribution.endpoint.domain_name
zone_id = aws_cloudfront_distribution.endpoint.hosted_zone_id
}
}
9 changes: 9 additions & 0 deletions tofu/modules/docs/files/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<title>Code for America Engineering Documentation (Development)</title>
</head>
<body>
<h1>Code for America Engineering Documentation (Development)</h1>
</body>
</html>
16 changes: 16 additions & 0 deletions tofu/modules/docs/files/rewrite-function.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
function handler(event) {
var request = event.request;
var uri = request.uri;

// If the request is being made to a directory (e.g. / or /docs), we want to
// append "index.html" so that S3 serves the proper object. If the path
// doesn't contain a file extension, we assume it's a directory as well.
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
else if (!uri.includes('.')) {
request.uri += '/index.html'
}

return request;
}
2 changes: 2 additions & 0 deletions tofu/modules/docs/files/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
User-agent: *
Disallow: /
11 changes: 11 additions & 0 deletions tofu/modules/docs/local.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
locals {
fqdn = "${var.subdomain}.${var.domain}"
prefix = "cfa-documentation-${var.environment}"
tags_base = {
application = local.prefix
program = "engineering"
project = "cfa-documenation"
environment = var.environment
}
tags = merge(local.tags_base, resource.aws_servicecatalogappregistry_application.docs.application_tag)
}
59 changes: 59 additions & 0 deletions tofu/modules/docs/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
resource "aws_servicecatalogappregistry_application" "docs" {
name = local.prefix
description = "Static documentation hosting for Code for America."

tags = local.tags_base
}

resource "aws_kms_key" "docs" {
description = "Encryption key for static documentation hosting"
enable_key_rotation = true
policy = jsonencode(yamldecode(templatefile("${path.module}/templates/key-policy.yaml.tftpl", {
account_id : data.aws_caller_identity.identity.account_id,
partition : data.aws_partition.current.partition,
bucket_name : var.bucket_name,
cloudfront_distribution_arn : aws_cloudfront_distribution.endpoint.arn,
})))

tags = resource.aws_servicecatalogappregistry_application.docs.application_tag
}

resource "aws_kms_alias" "docs" {
name = "alias/cfa-docummentation/${var.environment}"
target_key_id = aws_kms_key.docs.id
}

module "bucket" {
source = "boldlink/s3/aws"
version = "~> 2.5.0"

bucket = var.bucket_name
sse_sse_algorithm = "aws:kms"
sse_bucket_key_enabled = true
sse_kms_master_key_arn = aws_kms_key.docs.arn
versioning_status = "Enabled"
force_destroy = var.force_delete

bucket_policy = jsonencode(yamldecode(templatefile("${path.module}/templates/bucket-policy.yaml.tftpl", {
bucket_arn : module.bucket.arn,
vpc_endpoint_id : data.aws_vpc_endpoint.s3.id,
cloudfront_distribution_arn : aws_cloudfront_distribution.endpoint.arn,
})))

tags = local.tags
}

resource "aws_s3_object" "robots" {
bucket = module.bucket.bucket
key = "robots.txt"
source = "${path.module}/files/robots.txt"
force_destroy = var.force_delete
}

resource "aws_s3_object" "index" {
bucket = module.bucket.bucket
key = "index.html"
source = "${path.module}/files/index.html"
content_type = "text/html"
force_destroy = var.force_delete
}
4 changes: 4 additions & 0 deletions tofu/modules/docs/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
output "endpoint_url" {
description = "The URL of the documentation endpoint."
value = aws_route53_record.endpoint["A"].fqdn
}
Loading