Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: custom API URL using CloudFront distribution #13

Merged
merged 4 commits into from
Feb 13, 2023
Merged

Conversation

patheard
Copy link
Member

Summary

Add a CloudFront distribution to provide a custom URL and caching to the Lambda API.

Also add a WAF ACL to filter malicious traffic.

Related

Add a CloudFront distribution to provide a custom URL and caching
to the Lambda API.

Also add a WAF ACL to filter malicious traffic.
@patheard patheard self-assigned this Feb 13, 2023
@github-actions
Copy link

Production: cloudfront

✅   Terraform Format: success
✅   Terraform Plan: success
✅   Conftest: success

Plan: 15 to add, 0 to change, 0 to destroy
Show summary
CHANGE NAME
add aws_acm_certificate.api
aws_acm_certificate_validation.api
aws_cloudfront_distribution.api
aws_cloudfront_response_headers_policy.security_headers_api
aws_iam_policy.write_waf_logs
aws_iam_role.waf_log_role
aws_iam_role_policy_attachment.write_waf_logs
aws_kinesis_firehose_delivery_stream.api
aws_route53_health_check.api
aws_route53_record.api_A
aws_route53_record.api_cert_validation["*.github-secret-scanning.alpha.canada.ca"]
aws_route53_record.api_cert_validation["github-secret-scanning.alpha.canada.ca"]
aws_wafv2_regex_pattern_set.valid_uri_paths
aws_wafv2_web_acl.api
aws_wafv2_web_acl_logging_configuration.api
Show plan
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_acm_certificate.api will be created
  + resource "aws_acm_certificate" "api" {
      + arn                       = (known after apply)
      + domain_name               = "github-secret-scanning.alpha.canada.ca"
      + domain_validation_options = [
          + {
              + domain_name           = "*.github-secret-scanning.alpha.canada.ca"
              + resource_record_name  = (known after apply)
              + resource_record_type  = (known after apply)
              + resource_record_value = (known after apply)
            },
          + {
              + domain_name           = "github-secret-scanning.alpha.canada.ca"
              + resource_record_name  = (known after apply)
              + resource_record_type  = (known after apply)
              + resource_record_value = (known after apply)
            },
        ]
      + id                        = (known after apply)
      + key_algorithm             = (known after apply)
      + not_after                 = (known after apply)
      + not_before                = (known after apply)
      + pending_renewal           = (known after apply)
      + renewal_eligibility       = (known after apply)
      + renewal_summary           = (known after apply)
      + status                    = (known after apply)
      + subject_alternative_names = [
          + "*.github-secret-scanning.alpha.canada.ca",
          + "github-secret-scanning.alpha.canada.ca",
        ]
      + tags                      = {
          + "CostCentre" = "github-secret-scanning-production"
          + "Terraform"  = "true"
        }
      + tags_all                  = {
          + "CostCentre" = "github-secret-scanning-production"
          + "Terraform"  = "true"
        }
      + type                      = (known after apply)
      + validation_emails         = (known after apply)
      + validation_method         = "DNS"
    }

  # aws_acm_certificate_validation.api will be created
  + resource "aws_acm_certificate_validation" "api" {
      + certificate_arn         = (known after apply)
      + id                      = (known after apply)
      + validation_record_fqdns = (known after apply)
    }

  # aws_cloudfront_distribution.api will be created
  + resource "aws_cloudfront_distribution" "api" {
      + aliases                        = [
          + "github-secret-scanning.alpha.canada.ca",
        ]
      + arn                            = (known after apply)
      + caller_reference               = (known after apply)
      + domain_name                    = (known after apply)
      + enabled                        = true
      + etag                           = (known after apply)
      + hosted_zone_id                 = (known after apply)
      + http_version                   = "http2"
      + id                             = (known after apply)
      + in_progress_validation_batches = (known after apply)
      + is_ipv6_enabled                = false
      + last_modified_time             = (known after apply)
      + price_class                    = "PriceClass_100"
      + retain_on_delete               = false
      + status                         = (known after apply)
      + tags                           = {
          + "CostCentre" = "github-secret-scanning-production"
          + "Terraform"  = "true"
        }
      + tags_all                       = {
          + "CostCentre" = "github-secret-scanning-production"
          + "Terraform"  = "true"
        }
      + trusted_key_groups             = (known after apply)
      + trusted_signers                = (known after apply)
      + wait_for_deployment            = true
      + web_acl_id                     = (known after apply)

      + default_cache_behavior {
          + allowed_methods            = [
              + "DELETE",
              + "GET",
              + "HEAD",
              + "OPTIONS",
              + "PATCH",
              + "POST",
              + "PUT",
            ]
          + cache_policy_id            = "658327ea-f89d-4fab-a63d-7e88639e58f6"
          + cached_methods             = [
              + "GET",
              + "HEAD",
            ]
          + compress                   = false
          + default_ttl                = (known after apply)
          + max_ttl                    = (known after apply)
          + min_ttl                    = 0
          + origin_request_policy_id   = "216adef6-5c7f-47e4-b989-5492eafa07d3"
          + response_headers_policy_id = (known after apply)
          + target_origin_id           = "github-secret-scanning-api"
          + trusted_key_groups         = (known after apply)
          + trusted_signers            = (known after apply)
          + viewer_protocol_policy     = "redirect-to-https"
        }

      + ordered_cache_behavior {
          + allowed_methods            = [
              + "GET",
              + "HEAD",
            ]
          + cache_policy_id            = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad"
          + cached_methods             = [
              + "GET",
              + "HEAD",
            ]
          + compress                   = false
          + default_ttl                = (known after apply)
          + max_ttl                    = (known after apply)
          + min_ttl                    = 0
          + origin_request_policy_id   = "216adef6-5c7f-47e4-b989-5492eafa07d3"
          + path_pattern               = "/healthcheck"
          + response_headers_policy_id = (known after apply)
          + target_origin_id           = "github-secret-scanning-api"
          + viewer_protocol_policy     = "redirect-to-https"
        }

      + origin {
          + connection_attempts = 3
          + connection_timeout  = 10
          + domain_name         = "uj4yzvpdmhi77isvxwx4us3aee0unncc.lambda-url.ca-central-1.on.aws"
          + origin_id           = "github-secret-scanning-api"

          + custom_origin_config {
              + http_port                = 80
              + https_port               = 443
              + origin_keepalive_timeout = 5
              + origin_protocol_policy   = "https-only"
              + origin_read_timeout      = 60
              + origin_ssl_protocols     = [
                  + "TLSv1.2",
                ]
            }
        }

      + restrictions {
          + geo_restriction {
              + locations        = (known after apply)
              + restriction_type = "none"
            }
        }

      + viewer_certificate {
          + acm_certificate_arn      = (known after apply)
          + minimum_protocol_version = "TLSv1.2_2021"
          + ssl_support_method       = "sni-only"
        }
    }

  # aws_cloudfront_response_headers_policy.security_headers_api will be created
  + resource "aws_cloudfront_response_headers_policy" "security_headers_api" {
      + etag = (known after apply)
      + id   = (known after apply)
      + name = "api-cloudfront-headers"

      + security_headers_config {
          + content_security_policy {
              + content_security_policy = "report-uri https://csp-report-to.security.cdssandbox.xyz/report; default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self'; frame-ancestors 'self'; form-action 'self';"
              + override                = false
            }

          + content_type_options {
              + override = true
            }

          + frame_options {
              + frame_option = "DENY"
              + override     = true
            }

          + referrer_policy {
              + override        = true
              + referrer_policy = "same-origin"
            }

          + strict_transport_security {
              + access_control_max_age_sec = 31536000
              + include_subdomains         = true
              + override                   = true
              + preload                    = true
            }

          + xss_protection {
              + mode_block = true
              + override   = true
              + protection = true
            }
        }
    }

  # aws_iam_policy.write_waf_logs will be created
  + resource "aws_iam_policy" "write_waf_logs" {
      + arn         = (known after apply)
      + description = "Allow Firehose to write WAF logs to S3"
      + id          = (known after apply)
      + name        = "github-secret-scanning-waf-logs"
      + path        = "/"
      + policy      = jsonencode(
            {
              + Statement = [
                  + {
                      + Action   = "s3:ListBucket"
                      + Effect   = "Allow"
                      + Resource = "arn:aws:s3:::cbs-satellite-283582579564"
                      + Sid      = ""
                    },
                  + {
                      + Action   = [
                          + "s3:PutObject*",
                          + "s3:GetObject*",
                        ]
                      + Effect   = "Allow"
                      + Resource = "arn:aws:s3:::cbs-satellite-283582579564/waf_acl_logs/*"
                      + Sid      = ""
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + policy_id   = (known after apply)
      + tags        = {
          + "CostCentre" = "github-secret-scanning-production"
          + "Terraform"  = "true"
        }
      + tags_all    = {
          + "CostCentre" = "github-secret-scanning-production"
          + "Terraform"  = "true"
        }
    }

  # aws_iam_role.waf_log_role will be created
  + resource "aws_iam_role" "waf_log_role" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRole"
                      + Effect    = "Allow"
                      + Principal = {
                          + Service = "firehose.amazonaws.com"
                        }
                      + Sid       = ""
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + create_date           = (known after apply)
      + force_detach_policies = false
      + id                    = (known after apply)
      + managed_policy_arns   = (known after apply)
      + max_session_duration  = 3600
      + name                  = "github-secret-scanning-waf-logs"
      + name_prefix           = (known after apply)
      + path                  = "/"
      + tags                  = {
          + "CostCentre" = "github-secret-scanning-production"
          + "Terraform"  = "true"
        }
      + tags_all              = {
          + "CostCentre" = "github-secret-scanning-production"
          + "Terraform"  = "true"
        }
      + unique_id             = (known after apply)

      + inline_policy {
          + name   = (known after apply)
          + policy = (known after apply)
        }
    }

  # aws_iam_role_policy_attachment.write_waf_logs will be created
  + resource "aws_iam_role_policy_attachment" "write_waf_logs" {
      + id         = (known after apply)
      + policy_arn = (known after apply)
      + role       = "github-secret-scanning-waf-logs"
    }

  # aws_kinesis_firehose_delivery_stream.api will be created
  + resource "aws_kinesis_firehose_delivery_stream" "api" {
      + arn            = (known after apply)
      + destination    = "extended_s3"
      + destination_id = (known after apply)
      + id             = (known after apply)
      + name           = "aws-waf-logs-github-secret-scanning"
      + tags           = {
          + "CostCentre" = "github-secret-scanning-production"
          + "Terraform"  = "true"
        }
      + tags_all       = {
          + "CostCentre" = "github-secret-scanning-production"
          + "Terraform"  = "true"
        }
      + version_id     = (known after apply)

      + extended_s3_configuration {
          + bucket_arn         = "arn:aws:s3:::cbs-satellite-283582579564"
          + buffer_interval    = 300
          + buffer_size        = 5
          + compression_format = "GZIP"
          + prefix             = "waf_acl_logs/AWSLogs/283582579564/"
          + role_arn           = (known after apply)
          + s3_backup_mode     = "Disabled"

          + cloudwatch_logging_options {
              + enabled = false
            }
        }

      + server_side_encryption {
          + enabled  = true
          + key_type = "AWS_OWNED_CMK"
        }
    }

  # aws_route53_health_check.api will be created
  + resource "aws_route53_health_check" "api" {
      + arn               = (known after apply)
      + disabled          = false
      + enable_sni        = (known after apply)
      + failure_threshold = 5
      + fqdn              = (known after apply)
      + id                = (known after apply)
      + measure_latency   = false
      + port              = 443
      + regions           = [
          + "us-east-1",
          + "us-west-1",
          + "us-west-2",
        ]
      + request_interval  = 30
      + resource_path     = "/healthcheck"
      + tags              = {
          + "CostCentre" = "github-secret-scanning-production"
          + "Terraform"  = "true"
        }
      + tags_all          = {
          + "CostCentre" = "github-secret-scanning-production"
          + "Terraform"  = "true"
        }
      + type              = "HTTPS"
    }

  # aws_route53_record.api_A will be created
  + resource "aws_route53_record" "api_A" {
      + allow_overwrite = (known after apply)
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = "github-secret-scanning.alpha.canada.ca"
      + type            = "A"
      + zone_id         = "Z09595232YY40XHEI9KL6"

      + alias {
          + evaluate_target_health = false
          + name                   = (known after apply)
          + zone_id                = (known after apply)
        }
    }

  # aws_route53_record.api_cert_validation["*.github-secret-scanning.alpha.canada.ca"] will be created
  + resource "aws_route53_record" "api_cert_validation" {
      + allow_overwrite = true
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = (known after apply)
      + records         = (known after apply)
      + ttl             = 60
      + type            = (known after apply)
      + zone_id         = "Z09595232YY40XHEI9KL6"
    }

  # aws_route53_record.api_cert_validation["github-secret-scanning.alpha.canada.ca"] will be created
  + resource "aws_route53_record" "api_cert_validation" {
      + allow_overwrite = true
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = (known after apply)
      + records         = (known after apply)
      + ttl             = 60
      + type            = (known after apply)
      + zone_id         = "Z09595232YY40XHEI9KL6"
    }

  # aws_wafv2_regex_pattern_set.valid_uri_paths will be created
  + resource "aws_wafv2_regex_pattern_set" "valid_uri_paths" {
      + arn         = (known after apply)
      + description = "Regex to match the API's valid paths"
      + id          = (known after apply)
      + lock_token  = (known after apply)
      + name        = "valid-api-paths"
      + scope       = "CLOUDFRONT"
      + tags        = {
          + "CostCentre" = "github-secret-scanning-production"
          + "Terraform"  = "true"
        }
      + tags_all    = {
          + "CostCentre" = "github-secret-scanning-production"
          + "Terraform"  = "true"
        }

      + regular_expression {
          + regex_string = "^/(healthcheck|version|docs|openapi.json|\\.well-known\\/security\\.txt)$"
        }
      + regular_expression {
          + regex_string = "^/alert$"
        }
    }

  # aws_wafv2_web_acl.api will be created
  + resource "aws_wafv2_web_acl" "api" {
      + arn         = (known after apply)
      + capacity    = (known after apply)
      + description = "WAF for GitHub secret alert API"
      + id          = (known after apply)
      + lock_token  = (known after apply)
      + name        = "github-secret-scanning"
      + scope       = "CLOUDFRONT"
      + tags        = {
          + "CostCentre" = "github-secret-scanning-production"
          + "Terraform"  = "true"
        }
      + tags_all    = {
          + "CostCentre" = "github-secret-scanning-production"
          + "Terraform"  = "true"
        }

      + default_action {
          + allow {
            }
        }

      + rule {
          + name     = "APIInvalidPath"
          + priority = 1

          + action {

              + block {
                }
            }

          + statement {

              + not_statement {
                  + statement {

                      + regex_pattern_set_reference_statement {
                          + arn = (known after apply)

                          + field_to_match {

                              + uri_path {}
                            }

                          + text_transformation {
                              + priority = 1
                              + type     = "COMPRESS_WHITE_SPACE"
                            }
                          + text_transformation {
                              + priority = 2
                              + type     = "LOWERCASE"
                            }
                        }
                    }
                }
            }

          + visibility_config {
              + cloudwatch_metrics_enabled = true
              + metric_name                = "APIInvalidPaths"
              + sampled_requests_enabled   = true
            }
        }
      + rule {
          + name     = "APIRateLimit"
          + priority = 20

          + action {

              + block {
                }
            }

          + statement {

              + rate_based_statement {
                  + aggregate_key_type = "IP"
                  + limit              = 2000
                }
            }

          + visibility_config {
              + cloudwatch_metrics_enabled = true
              + metric_name                = "APIRateLimit"
              + sampled_requests_enabled   = true
            }
        }
      + rule {
          + name     = "AWSManagedRulesAmazonIpReputationList"
          + priority = 10

          + override_action {

              + none {}
            }

          + statement {

              + managed_rule_group_statement {
                  + name        = "AWSManagedRulesAmazonIpReputationList"
                  + vendor_name = "AWS"
                }
            }

          + visibility_config {
              + cloudwatch_metrics_enabled = true
              + metric_name                = "AWSManagedRulesAmazonIpReputationList"
              + sampled_requests_enabled   = true
            }
        }
      + rule {
          + name     = "AWSManagedRulesCommonRuleSet"
          + priority = 30

          + override_action {

              + none {}
            }

          + statement {

              + managed_rule_group_statement {
                  + name        = "AWSManagedRulesCommonRuleSet"
                  + vendor_name = "AWS"
                }
            }

          + visibility_config {
              + cloudwatch_metrics_enabled = true
              + metric_name                = "AWSManagedRulesCommonRuleSet"
              + sampled_requests_enabled   = true
            }
        }
      + rule {
          + name     = "AWSManagedRulesKnownBadInputsRuleSet"
          + priority = 40

          + override_action {

              + none {}
            }

          + statement {

              + managed_rule_group_statement {
                  + name        = "AWSManagedRulesKnownBadInputsRuleSet"
                  + vendor_name = "AWS"
                }
            }

          + visibility_config {
              + cloudwatch_metrics_enabled = true
              + metric_name                = "AWSManagedRulesKnownBadInputsRuleSet"
              + sampled_requests_enabled   = true
            }
        }
      + rule {
          + name     = "AWSManagedRulesLinuxRuleSet"
          + priority = 50

          + override_action {

              + none {}
            }

          + statement {

              + managed_rule_group_statement {
                  + name        = "AWSManagedRulesLinuxRuleSet"
                  + vendor_name = "AWS"
                }
            }

          + visibility_config {
              + cloudwatch_metrics_enabled = true
              + metric_name                = "AWSManagedRulesLinuxRuleSet"
              + sampled_requests_enabled   = true
            }
        }

      + visibility_config {
          + cloudwatch_metrics_enabled = true
          + metric_name                = "api"
          + sampled_requests_enabled   = false
        }
    }

  # aws_wafv2_web_acl_logging_configuration.api will be created
  + resource "aws_wafv2_web_acl_logging_configuration" "api" {
      + id                      = (known after apply)
      + log_destination_configs = (known after apply)
      + resource_arn            = (known after apply)
    }

Plan: 15 to add, 0 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: plan.tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "plan.tfplan"
Releasing state lock. This may take a few moments...
Show Conftest results
18 tests, 18 passed, 0 warnings, 0 failures, 0 exceptions

@patheard patheard requested a review from a team February 13, 2023 20:04
Copy link
Member

@CalvinRodo CalvinRodo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌮🐱

Copy link

@maxneuvians maxneuvians left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work!

@patheard patheard merged commit d2a664d into main Feb 13, 2023
@patheard patheard deleted the feat/cloudfront branch February 13, 2023 20:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants