diff --git a/Makefile b/Makefile index 4ece674..85cd503 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,9 @@ test: _pull-tf echo "------------------------------------------------------------"; \ echo "# Terraform init"; \ echo "------------------------------------------------------------"; \ - if docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/t" --workdir "$${DOCKER_PATH}" hashicorp/terraform:$(TF_VERSION) \ + if docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/t" \ + --workdir "$${DOCKER_PATH}" --network host \ + hashicorp/terraform:$(TF_VERSION) \ init \ -lock=false \ -upgrade \ @@ -88,22 +90,30 @@ test: _pull-tf echo "OK"; \ else \ echo "Failed"; \ - docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/t" --workdir "$${DOCKER_PATH}" --entrypoint=rm hashicorp/terraform:$(TF_VERSION) -rf .terraform/ || true; \ + docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/t" \ + --workdir "$${DOCKER_PATH}" --network none --entrypoint=rm \ + hashicorp/terraform:$(TF_VERSION) -rf .terraform/ || true; \ exit 1; \ fi; \ echo; \ echo "------------------------------------------------------------"; \ echo "# Terraform validate"; \ echo "------------------------------------------------------------"; \ - if docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/t" --workdir "$${DOCKER_PATH}" hashicorp/terraform:$(TF_VERSION) \ + if docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/t" \ + --workdir "$${DOCKER_PATH}" --network host \ + hashicorp/terraform:$(TF_VERSION) \ validate \ $(ARGS) \ .; then \ echo "OK"; \ - docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/t" --workdir "$${DOCKER_PATH}" --entrypoint=rm hashicorp/terraform:$(TF_VERSION) -rf .terraform/ || true; \ + docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/t" \ + --workdir "$${DOCKER_PATH}" --network none --entrypoint=rm \ + hashicorp/terraform:$(TF_VERSION) -rf .terraform/ || true; \ else \ echo "Failed"; \ - docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/t" --workdir "$${DOCKER_PATH}" --entrypoint=rm hashicorp/terraform:$(TF_VERSION) -rf .terraform/ || true; \ + docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/t" \ + --workdir "$${DOCKER_PATH}" --network none --entrypoint=rm \ + hashicorp/terraform:$(TF_VERSION) -rf .terraform/ || true; \ exit 1; \ fi; \ echo; \ @@ -117,7 +127,7 @@ _gen-main: @echo "------------------------------------------------------------" @echo "# Main module" @echo "------------------------------------------------------------" - @if docker run $$(tty -s && echo "-it" || echo) --rm \ + @if docker run $$(tty -s && echo "-it" || echo) --rm --network none \ -v $(CURRENT_DIR):/data \ -e DELIM_START='' \ -e DELIM_CLOSE='' \ @@ -128,7 +138,7 @@ _gen-main: echo "Failed"; \ exit 1; \ fi - @if docker run $$(tty -s && echo "-it" || echo) --rm \ + @if docker run $$(tty -s && echo "-it" || echo) --rm --network none \ -v $(CURRENT_DIR):/data \ -e DELIM_START='' \ -e DELIM_CLOSE='' \ @@ -139,7 +149,7 @@ _gen-main: echo "Failed"; \ exit 1; \ fi - @if docker run $$(tty -s && echo "-it" || echo) --rm \ + @if docker run $$(tty -s && echo "-it" || echo) --rm --network none \ -v $(CURRENT_DIR):/data \ -e DELIM_START='' \ -e DELIM_CLOSE='' \ @@ -150,7 +160,7 @@ _gen-main: echo "Failed"; \ exit 1; \ fi - @if docker run $$(tty -s && echo "-it" || echo) --rm \ + @if docker run $$(tty -s && echo "-it" || echo) --rm --network none \ -v $(CURRENT_DIR):/data \ -e DELIM_START='' \ -e DELIM_CLOSE='' \ @@ -161,7 +171,7 @@ _gen-main: echo "Failed"; \ exit 1; \ fi - @if docker run $$(tty -s && echo "-it" || echo) --rm \ + @if docker run $$(tty -s && echo "-it" || echo) --rm --network none \ -v $(CURRENT_DIR):/data \ -e DELIM_START='' \ -e DELIM_CLOSE='' \ @@ -180,7 +190,7 @@ _gen-examples: echo "------------------------------------------------------------"; \ echo "# $${DOCKER_PATH}"; \ echo "------------------------------------------------------------"; \ - if docker run $$(tty -s && echo "-it" || echo) --rm \ + if docker run $$(tty -s && echo "-it" || echo) --rm --network none \ -v $(CURRENT_DIR):/data \ -e DELIM_START='$(DELIM_START)' \ -e DELIM_CLOSE='$(DELIM_CLOSE)' \ @@ -200,7 +210,7 @@ _gen-modules: echo "------------------------------------------------------------"; \ echo "# $${DOCKER_PATH}"; \ echo "------------------------------------------------------------"; \ - if docker run $$(tty -s && echo "-it" || echo) --rm \ + if docker run $$(tty -s && echo "-it" || echo) --rm --network none \ -v $(CURRENT_DIR):/data \ -e DELIM_START='$(DELIM_START)' \ -e DELIM_CLOSE='$(DELIM_CLOSE)' \ @@ -218,12 +228,12 @@ _lint-files: _pull-fl @echo "################################################################################" @echo "# File-lint" @echo "################################################################################" - @docker run $$(tty -s && echo "-it" || echo) --rm -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-cr --text --ignore '$(FL_IGNORE_PATHS)' --path . - @docker run $$(tty -s && echo "-it" || echo) --rm -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-crlf --text --ignore '$(FL_IGNORE_PATHS)' --path . - @docker run $$(tty -s && echo "-it" || echo) --rm -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-trailing-single-newline --text --ignore '$(FL_IGNORE_PATHS)' --path . - @docker run $$(tty -s && echo "-it" || echo) --rm -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-trailing-space --text --ignore '$(FL_IGNORE_PATHS)' --path . - @docker run $$(tty -s && echo "-it" || echo) --rm -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-utf8 --text --ignore '$(FL_IGNORE_PATHS)' --path . - @docker run $$(tty -s && echo "-it" || echo) --rm -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-utf8-bom --text --ignore '$(FL_IGNORE_PATHS)' --path . + @docker run $$(tty -s && echo "-it" || echo) --rm --network none -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-cr --text --ignore '$(FL_IGNORE_PATHS)' --path . + @docker run $$(tty -s && echo "-it" || echo) --rm --network none -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-crlf --text --ignore '$(FL_IGNORE_PATHS)' --path . + @docker run $$(tty -s && echo "-it" || echo) --rm --network none -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-trailing-single-newline --text --ignore '$(FL_IGNORE_PATHS)' --path . + @docker run $$(tty -s && echo "-it" || echo) --rm --network none -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-trailing-space --text --ignore '$(FL_IGNORE_PATHS)' --path . + @docker run $$(tty -s && echo "-it" || echo) --rm --network none -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-utf8 --text --ignore '$(FL_IGNORE_PATHS)' --path . + @docker run $$(tty -s && echo "-it" || echo) --rm --network none -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-utf8-bom --text --ignore '$(FL_IGNORE_PATHS)' --path . _lint-fmt: _pull-tf @# Lint all Terraform files @@ -234,7 +244,9 @@ _lint-fmt: _pull-tf @echo "------------------------------------------------------------" @echo "# *.tf files" @echo "------------------------------------------------------------" - @if docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/t:ro" --workdir "/t" hashicorp/terraform:$(TF_VERSION) \ + @if docker run $$(tty -s && echo "-it" || echo) --rm --network host \ + -v "$(CURRENT_DIR):/t:ro" --workdir "/t" \ + hashicorp/terraform:$(TF_VERSION) \ fmt -recursive -check=true -diff=true -write=true -list=true .; then \ echo "OK"; \ else \ @@ -245,7 +257,9 @@ _lint-fmt: _pull-tf @echo "------------------------------------------------------------" @echo "# *.tfvars files" @echo "------------------------------------------------------------" - @if docker run $$(tty -s && echo "-it" || echo) --rm --entrypoint=/bin/sh -v "$(CURRENT_DIR):/t:ro" --workdir "/t" hashicorp/terraform:$(TF_VERSION) \ + @if docker run $$(tty -s && echo "-it" || echo) --rm --network host \ + --entrypoint=/bin/sh -v "$(CURRENT_DIR):/t:ro" --workdir "/t" \ + hashicorp/terraform:$(TF_VERSION) \ -c "find . -name '*.tfvars' -type f -print0 | xargs -0 -n1 terraform fmt -check=true -write=true -diff=true -list=true"; then \ echo "OK"; \ else \ @@ -259,7 +273,9 @@ _lint-json: _pull-jl @echo "################################################################################" @echo "# Jsonlint" @echo "################################################################################" - @if docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/data:ro" cytopia/jsonlint:$(JL_VERSION) \ + @if docker run $$(tty -s && echo "-it" || echo) --rm --network none \ + -v "$(CURRENT_DIR):/data:ro" \ + cytopia/jsonlint:$(JL_VERSION) \ -t ' ' -i '*.terraform/*' '*.json'; then \ echo "OK"; \ else \ diff --git a/README.md b/README.md index a5a4c4f..bbf9138 100644 --- a/README.md +++ b/README.md @@ -49,10 +49,36 @@ Description: Cloudflare domain to apply rules for. Type: `string` +### [name](#input\_name) + +Description: Name of the ruleset. + +Type: `string` + +### [kind](#input\_kind) + +Description: Type of Ruleset to create. + +Type: `string` + +### [phase](#input\_phase) + +Description: Point in the request/response lifecycle where the ruleset will be created. + +Type: `string` + ## Optional Inputs The following input variables are optional (have default values): +### [description](#input\_description) + +Description: Brief summary of the ruleset and its intended use. + +Type: `string` + +Default: `null` + ### [rules](#input\_rules) Description: List of Cloudflare firewall rule objects. @@ -61,11 +87,11 @@ Type: ```hcl list(object({ - description = string - enabled = bool - action = string expression = string - products = list(string) + action = optional(string) + description = optional(string) + enabled = optional(bool, true) + products = optional(list(string), []) })) ``` diff --git a/examples/bots/README.md b/examples/bots/README.md new file mode 100644 index 0000000..0aed5f7 --- /dev/null +++ b/examples/bots/README.md @@ -0,0 +1,39 @@ +# Example + +This example will create multiple rulesets for `http_request_firewall_custom` phase. + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [rulesets](#module\_rulesets) | ./../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [api\_token](#input\_api\_token) | The Cloudflare API token. | `string` | n/a | yes | +| [domain](#input\_domain) | Cloudflare domain name to create | `string` | `"example.com"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [records](#output\_records) | Cloudflare Zone DNS Records | + + + +Copyright (c) 2024 **[Flaconi GmbH](https://github.com/flaconi)** diff --git a/examples/bots/main.tf b/examples/bots/main.tf new file mode 100644 index 0000000..3939d40 --- /dev/null +++ b/examples/bots/main.tf @@ -0,0 +1,43 @@ +module "rulesets" { + source = "./../../" + api_token = var.api_token + domain = var.domain + name = "default" + kind = "zone" + phase = "http_request_firewall_custom" + rules = [ + { + description = "User-Agent: skip" + enabled = true + action = "skip" + expression = <<-EOT + (http.user_agent contains "Bot/" and http.request.uri.path eq "/api/v1") + EOT + products = ["waf"] + }, + { + description = "User-Agent: log" + enabled = false + action = "log" + expression = <<-EOT + (http.user_agent contains "Bot/" and http.request.uri.path eq "/api/v1") + EOT + products = [] + }, + { + description = "Bots: log" + action = "log" + expression = <<-EOT + (cf.bot_management.score eq 2) + EOT + }, + { + description = "Bots: challenge" + enabled = false + action = "managed_challenge" + expression = <<-EOT + (cf.bot_management.score eq 9) + EOT + }, + ] +} diff --git a/examples/bots/outputs.tf b/examples/bots/outputs.tf new file mode 100644 index 0000000..de4f7bb --- /dev/null +++ b/examples/bots/outputs.tf @@ -0,0 +1,4 @@ +output "records" { + description = "Cloudflare Zone DNS Records" + value = module.rulesets.rules +} diff --git a/examples/bots/variables.tf b/examples/bots/variables.tf new file mode 100644 index 0000000..59baa16 --- /dev/null +++ b/examples/bots/variables.tf @@ -0,0 +1,10 @@ +variable "api_token" { + description = "The Cloudflare API token." + type = string +} + +variable "domain" { + description = "Cloudflare domain name to create" + type = string + default = "example.com" +} diff --git a/main.tf b/main.tf index 8e4e00b..97c8760 100644 --- a/main.tf +++ b/main.tf @@ -1,8 +1,9 @@ -resource "cloudflare_ruleset" "http_request_firewall_custom" { - zone_id = lookup(data.cloudflare_zones.domain.zones[0], "id") - name = "default" - kind = "zone" - phase = "http_request_firewall_custom" +resource "cloudflare_ruleset" "this" { + zone_id = lookup(data.cloudflare_zones.domain.zones[0], "id") + name = var.name + kind = var.kind + phase = var.phase + description = var.description dynamic "rules" { for_each = local.rules diff --git a/outputs.tf b/outputs.tf index 6cafae1..bba0e47 100644 --- a/outputs.tf +++ b/outputs.tf @@ -5,5 +5,5 @@ output "domain" { output "rules" { description = "Created Cloudflare rules for the current zone." - value = cloudflare_ruleset.http_request_firewall_custom.rules + value = cloudflare_ruleset.this.rules } diff --git a/variables.tf b/variables.tf index 1a3296a..398726a 100644 --- a/variables.tf +++ b/variables.tf @@ -9,14 +9,49 @@ variable "domain" { type = string } +variable "name" { + description = "Name of the ruleset." + type = string +} + +variable "kind" { + description = "Type of Ruleset to create." + type = string + + # Ensure we specify only allowed kind values + # https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset#kind + validation { + condition = can(contains(["custom", "managed", "root", "zone"], var.kind)) + error_message = "Only the following kind types are allowed: custom, managed, root, zone." + } +} + +variable "phase" { + description = "Point in the request/response lifecycle where the ruleset will be created." + type = string + + # Ensure we specify only allowed kind values + # https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset#phase + validation { + condition = can(contains(["ddos_l4", "ddos_l7", "http_config_settings", "http_custom_errors", "http_log_custom_fields", "http_ratelimit", "http_request_cache_settings", "http_request_dynamic_redirect", "http_request_firewall_custom", "http_request_firewall_managed", "http_request_late_transform", "http_request_origin", "http_request_redirect", "http_request_sanitize", "http_request_sbfm", "http_request_transform", "http_response_compression", "http_response_firewall_managed", "http_response_headers_transform", "magic_transit"], var.phase)) + error_message = "Only the following phase types are allowed: ddos_l4, ddos_l7, http_config_settings, http_custom_errors, http_log_custom_fields, http_ratelimit, http_request_cache_settings, http_request_dynamic_redirect, http_request_firewall_custom, http_request_firewall_managed, http_request_late_transform, http_request_origin, http_request_redirect, http_request_sanitize, http_request_sbfm, http_request_transform, http_response_compression, http_response_firewall_managed, http_response_headers_transform, magic_transit." + } +} + +variable "description" { + description = "Brief summary of the ruleset and its intended use." + type = string + default = null +} + variable "rules" { description = "List of Cloudflare firewall rule objects." type = list(object({ - description = string - enabled = bool - action = string expression = string - products = list(string) + action = optional(string) + description = optional(string) + enabled = optional(bool, true) + products = optional(list(string), []) })) default = []