From 430ce8dba7daa699003cbad908b73b737af3ceec Mon Sep 17 00:00:00 2001 From: David Costa Date: Mon, 27 Nov 2023 16:32:43 +0000 Subject: [PATCH] feat: add option to attach a security policy to the default backend --- README.md | 4 +- examples/cloud-armor/README.md | 32 ++++++ examples/cloud-armor/main.tf | 130 ++++++++++++++++++++++ examples/cloud-armor/server-atlantis.yaml | 6 + main.tf | 1 + variables.tf | 6 + 6 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 examples/cloud-armor/README.md create mode 100644 examples/cloud-armor/main.tf create mode 100644 examples/cloud-armor/server-atlantis.yaml diff --git a/README.md b/README.md index 170b953..440f758 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ This Terraform module deploys various resources to run Atlantis on Google Comput - **Shielded VM** - A Shielded VM is a VM that's hardened by a set of security controls that help defend against rootkits and bootkits. Using a Shielded VM helps protect enterprise workloads from threats like remote attacks, privilege escalation, and malicious insiders. +- **Cloud Armor** - Use Google Cloud Armor security policies to protect the default backend service from distributed denial-of-service (DDoS) and other web-based attacks. Security policies can be configured manually, with configurable match conditions and actions in a security policy. Google Cloud Armor also features preconfigured security policies, which cover a variety of use cases. + ## Prerequisites This module expects that you already own or create the below resources yourself. @@ -57,7 +59,7 @@ Here are some examples to choose from. Look at the prerequisites above to find o - [Basic](https://github.com/bschaatsbergen/atlantis-on-gcp-vm/tree/master/examples/basic) - [Complete](https://github.com/bschaatsbergen/atlantis-on-gcp-vm/tree/master/examples/complete) - [Secure Environment Variables](https://github.com/bschaatsbergen/atlantis-on-gcp-vm/tree/master/examples/secure-env-vars) - +- [Cloud Armor](https://github.com/bschaatsbergen/atlantis-on-gcp-vm/tree/master/examples/cloud-armor) ```hcl module "atlantis" { diff --git a/examples/cloud-armor/README.md b/examples/cloud-armor/README.md new file mode 100644 index 0000000..e7eb25d --- /dev/null +++ b/examples/cloud-armor/README.md @@ -0,0 +1,32 @@ +# Example usage + +This example deploys Cloud Armor to ensure requests to the default backend are coming from GitHub Webhooks. + +Since IAP is enabled, two backend services will be created: + +- atlantis: the backend to receive GitHub events, protected with Cloud Armor +- atlantis-iap: the backend to serve the Atlantis UI, protected with IAP + +Read through the below before you deploy this module. + +- [Prerequisites](#prerequisites) +- [How to deploy](#how-to-deploy) +- [After it's successfully deployed](#after-its-successfully-deployed) + +## Prerequisites + +This module expects that you already own or create the below resources yourself. + +- Google network, subnetwork and a Cloud NAT +- Service account, [specifics can be found here](../../README.md#service-account) +- Domain, [specifics can be found here](../../README.md#dns-record) + +If you prefer an example that includes the above resources, see [`complete example`](https://github.com/bschaatsbergen/atlantis-on-gcp-vm/tree/master/examples/complete). + +## How to deploy + +See [`main.tf`](https://github.com/bschaatsbergen/atlantis-on-gcp-vm/tree/master/examples/basic/main.tf) and the [`server-atlantis.yaml`](https://github.com/bschaatsbergen/atlantis-on-gcp-vm/tree/master/examples/basic/server-atlantis.yaml). + +## After it's successfully deployed + +Once you're done, see [Configuring Webhooks for Atlantis](https://www.runatlantis.io/docs/configuring-webhooks.html#configuring-webhooks) diff --git a/examples/cloud-armor/main.tf b/examples/cloud-armor/main.tf new file mode 100644 index 0000000..a0044a7 --- /dev/null +++ b/examples/cloud-armor/main.tf @@ -0,0 +1,130 @@ +locals { + project_id = "" + network = "" + subnetwork = "" + region = "" + zone = "" + domain = "" + managed_zone = "" + + github_repo_allow_list = "github.com/example/*" + github_user = "" + github_token = "" + github_webhook_secret = "" + + google_iap_brand_name = "" +} + +# Create a service account and attach the required Cloud Logging permissions to it. +resource "google_service_account" "atlantis" { + account_id = "atlantis" + display_name = "Service Account for Atlantis" + project = local.project_id +} + +resource "google_project_iam_member" "atlantis_log_writer" { + role = "roles/logging.logWriter" + member = "serviceAccount:${google_service_account.atlantis.email}" + project = local.project_id +} + +resource "google_project_iam_member" "atlantis_metric_writer" { + role = "roles/monitoring.metricWriter" + member = "serviceAccount:${google_service_account.atlantis.email}" + project = local.project_id +} + +module "atlantis" { + source = "bschaatsbergen/atlantis/gce" + name = "atlantis" + network = local.network + subnetwork = local.subnetwork + region = local.region + zone = local.zone + service_account = { + email = google_service_account.atlantis.email + scopes = ["cloud-platform"] + } + # Note: environment variables are shown in the Google Cloud UI + # See the `examples/secure-env-vars` if you want to protect sensitive information + env_vars = { + ATLANTIS_GH_USER = local.github_user + ATLANTIS_GH_TOKEN = local.github_token + ATLANTIS_GH_WEBHOOK_SECRET = local.github_webhook_secret + ATLANTIS_REPO_ALLOWLIST = local.github_repo_allow_list + ATLANTIS_ATLANTIS_URL = "https://${local.domain}" + ATLANTIS_REPO_CONFIG_JSON = jsonencode(yamldecode(file("${path.module}/server-atlantis.yaml"))) + } + domain = local.domain + project = local.project_id + + default_backend_security_policy = google_compute_security_policy.atlantis.name + + iap = { + oauth2_client_id = google_iap_client.atlantis.client_id + oauth2_client_secret = google_iap_client.atlantis.secret + } +} + +# As your DNS records might be managed at another registrar's site, we create the DNS record outside of the module. +# This record is mandatory in order to provision the managed SSL certificate successfully. +resource "google_dns_record_set" "default" { + name = "${local.domain}." + type = "A" + ttl = 60 + managed_zone = local.managed_zone + rrdatas = [ + module.atlantis.ip_address + ] + project = local.project_id +} + +resource "google_compute_security_policy" "atlantis" { + name = "atlantis-security-policy" + description = "Policy blocking all traffic except from Github Webhooks" + project = local.project_id + + rule { + # Allow from GitHub Webhooks + # https://api.github.com/meta + action = "allow" + priority = "2" + description = "Rule: Allow github hooks" + match { + expr { + expression = "(inIpRange(origin.ip, '140.82.112.0/20') || inIpRange(origin.ip, '185.199.108.0/22') || inIpRange(origin.ip, '143.55.64.0/20') || inIpRange(origin.ip, '192.30.252.0/22'))" + } + } + } + + rule { + # Deny all by default + action = "deny(403)" + priority = "2147483647" + description = "Default rule: deny all" + + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = ["*"] + } + } + } + + rule { + # Log4j vulnerability + action = "deny(403)" + priority = "1" + description = "CVE-2021-44228 (https://nvd.nist.gov/vuln/detail/CVE-2021-44228)" + match { + expr { + expression = "evaluatePreconfiguredExpr('cve-canary')" + } + } + } +} + +resource "google_iap_client" "atlantis" { + display_name = "iap-client" + brand = local.google_iap_brand_name +} diff --git a/examples/cloud-armor/server-atlantis.yaml b/examples/cloud-armor/server-atlantis.yaml new file mode 100644 index 0000000..71ec5f7 --- /dev/null +++ b/examples/cloud-armor/server-atlantis.yaml @@ -0,0 +1,6 @@ +repos: +- id: /.*/ + apply_requirements: [mergeable] + allowed_overrides: [apply_requirements, workflow] + allow_custom_workflows: true + delete_source_branch_on_merge: true diff --git a/main.tf b/main.tf index 965b633..dae8280 100644 --- a/main.tf +++ b/main.tf @@ -308,6 +308,7 @@ resource "google_compute_backend_service" "default" { connection_draining_timeout_sec = 5 load_balancing_scheme = "EXTERNAL_MANAGED" health_checks = [google_compute_health_check.default.id] + security_policy = var.default_backend_security_policy log_config { enable = true diff --git a/variables.tf b/variables.tf index 2c55785..32de93d 100644 --- a/variables.tf +++ b/variables.tf @@ -169,3 +169,9 @@ variable "labels" { description = "Key-value pairs representing labels attaching to instance & instance template" default = {} } + +variable "default_backend_security_policy" { + type = string + description = "Name of the security policy to apply to the default backend service" + default = null +}