Initial commit: Cloudflare infrastructure with WAF Intelligence

- Complete Cloudflare Terraform configuration (DNS, WAF, tunnels, access)
- WAF Intelligence MCP server with threat analysis and ML classification
- GitOps automation with PR workflows and drift detection
- Observatory monitoring stack with Prometheus/Grafana
- IDE operator rules for governed development
- Security playbooks and compliance frameworks
- Autonomous remediation and state reconciliation
This commit is contained in:
Vault Sovereign
2025-12-16 18:31:53 +00:00
commit 37a867c485
123 changed files with 25407 additions and 0 deletions

355
terraform/.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,355 @@
stages:
- validate
- plan
- gitops
- approve
- apply
- compliance
- reconcile
variables:
TF_ROOT: ${CI_PROJECT_DIR}
TF_STATE_NAME: cloudflare-infra
TF_PLAN_FILE: tfplan.binary
TF_PLAN_JSON: tfplan.json
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- ${TF_ROOT}/.terraform
.terraform_base:
image: hashicorp/terraform:1.6
before_script:
- cd ${TF_ROOT}
- terraform init -input=false
# Stage 1: Validate
terraform_fmt:
extends: .terraform_base
stage: validate
script:
- terraform fmt -check -recursive
allow_failure: false
terraform_validate:
extends: .terraform_base
stage: validate
script:
- terraform validate
allow_failure: false
# Stage 2: Plan
terraform_plan:
extends: .terraform_base
stage: plan
script:
- terraform plan -out=${TF_PLAN_FILE} -input=false
- terraform show -json ${TF_PLAN_FILE} > ${TF_PLAN_JSON}
artifacts:
name: "terraform-plan-${CI_COMMIT_SHORT_SHA}"
paths:
- ${TF_ROOT}/${TF_PLAN_FILE}
- ${TF_ROOT}/${TF_PLAN_JSON}
expire_in: 7 days
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Stage 3: Manual Approval Gate
manual_approval:
stage: approve
script:
- echo "Terraform plan approved by ${GITLAB_USER_NAME}"
when: manual
allow_failure: false
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
needs:
- terraform_plan
# Stage 4: Apply
terraform_apply:
extends: .terraform_base
stage: apply
script:
- terraform apply -input=false ${TF_PLAN_FILE}
- terraform output -json > terraform_outputs.json
artifacts:
name: "terraform-outputs-${CI_COMMIT_SHORT_SHA}"
paths:
- ${TF_ROOT}/terraform_outputs.json
expire_in: 30 days
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
needs:
- manual_approval
environment:
name: production
action: start
# Stage 5: Compliance
compliance_report:
stage: compliance
image: python:3.11-slim
before_script:
- pip install blake3
script:
- |
# Generate compliance snapshot
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
COMMIT_SHA=${CI_COMMIT_SHA}
# Hash all terraform files
find ${TF_ROOT} -name "*.tf" -exec cat {} \; | python3 -c "
import sys
import blake3
import json
content = sys.stdin.read()
tf_hash = blake3.blake3(content.encode()).hexdigest()
receipt = {
'receipt_type': 'terraform_compliance',
'schema_version': 'vm_tf_compliance_v1',
'timestamp': '${TIMESTAMP}',
'commit_sha': '${COMMIT_SHA}',
'pipeline_id': '${CI_PIPELINE_ID}',
'job_id': '${CI_JOB_ID}',
'tf_files_hash': tf_hash,
'applied_by': '${GITLAB_USER_NAME}',
'environment': 'production'
}
print(json.dumps(receipt, indent=2))
" > compliance_receipt.json
cat compliance_receipt.json
artifacts:
name: "compliance-${CI_COMMIT_SHORT_SHA}"
paths:
- compliance_receipt.json
- ${TF_ROOT}/terraform_outputs.json
expire_in: 365 days
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
needs:
- terraform_apply
# Merge Request: Plan Only
mr_plan:
extends: .terraform_base
stage: plan
script:
- terraform plan -input=false -no-color -out=plan.tfplan | tee plan_output.txt
- terraform show -json plan.tfplan > plan.json
artifacts:
paths:
- ${TF_ROOT}/plan_output.txt
- ${TF_ROOT}/plan.tfplan
- ${TF_ROOT}/plan.json
expire_in: 7 days
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# ==============================================================================
# PHASE 6 - GITOPS PR WORKFLOWS
# ==============================================================================
# Post plan summary as MR comment
gitops:plan_comment:
stage: gitops
image: python:3.12-slim
before_script:
- pip install requests pyyaml
script:
- |
cd ${CI_PROJECT_DIR}/gitops
python3 ci_plan_comment.py
variables:
GITLAB_TOKEN: ${GITLAB_TOKEN}
artifacts:
paths:
- plan_output.env
reports:
dotenv: plan_output.env
expire_in: 1 day
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
needs:
- mr_plan
# Drift remediation (scheduled or alert-triggered)
gitops:drift_remediation:
stage: gitops
image: python:3.12-slim
before_script:
- pip install requests pyyaml
- apt-get update && apt-get install -y git
- git config --global user.email "gitops-bot@cloudflare-mesh.local"
- git config --global user.name "GitOps Bot"
script:
- |
cd ${CI_PROJECT_DIR}/gitops
python3 drift_pr_bot.py \
--trigger-source "${GITOPS_TRIGGER_SOURCE:-scheduled}"
variables:
GITLAB_TOKEN: ${GITLAB_TOKEN}
GITOPS_DRY_RUN: "false"
rules:
# Scheduled runs
- if: $CI_PIPELINE_SOURCE == "schedule" && $GITOPS_DRIFT_CHECK == "true"
# Alert-triggered runs
- if: $CI_PIPELINE_SOURCE == "trigger" && $GITOPS_TRIGGER_SOURCE == "alert"
needs: []
# Risk gate - block high-risk changes without approval
gitops:risk_gate:
stage: gitops
image: python:3.12-slim
before_script:
- pip install pyyaml
script:
- |
cd ${CI_PROJECT_DIR}/gitops
RISK=$(python3 plan_summarizer.py --format json | python3 -c "import sys,json; print(json.load(sys.stdin)['overall_risk'])")
echo "Overall risk level: $RISK"
if [ "$RISK" = "CRITICAL" ]; then
echo "CRITICAL risk detected. Manual approval required."
exit 1
fi
echo "Risk level acceptable for auto-merge consideration."
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
needs:
- mr_plan
allow_failure: true
# Stage 6: Deep Binding - State Reconciliation
state_reconcile:
stage: reconcile
image: python:3.11-slim
variables:
SCRIPTS_DIR: ${CI_PROJECT_DIR}/../scripts
before_script:
- pip install requests
script:
- |
echo "=== Cloudflare State Reconciliation ==="
# Run state reconciler
python3 ${CI_PROJECT_DIR}/../scripts/state-reconciler.py \
--zone-id ${CLOUDFLARE_ZONE_ID} \
--account-id ${CLOUDFLARE_ACCOUNT_ID} \
--output-dir ${CI_PROJECT_DIR}/../snapshots \
--receipt-dir ${CI_PROJECT_DIR}/../receipts
# Find latest snapshot
SNAPSHOT=$(ls -t ${CI_PROJECT_DIR}/../snapshots/cloudflare-*.json | head -1)
echo "Snapshot: $SNAPSHOT"
# Run invariant checker
python3 ${CI_PROJECT_DIR}/../scripts/invariant-checker.py \
--snapshot "$SNAPSHOT" \
--output-dir ${CI_PROJECT_DIR}/../anomalies || INVARIANT_FAILED=1
# Find latest report
REPORT=$(ls -t ${CI_PROJECT_DIR}/../anomalies/invariant-report-*.json | head -1)
echo "Report: $REPORT"
# Copy artifacts
mkdir -p reconcile_artifacts
cp "$SNAPSHOT" reconcile_artifacts/ 2>/dev/null || true
cp "$REPORT" reconcile_artifacts/ 2>/dev/null || true
cp ${CI_PROJECT_DIR}/../anomalies/anomaly-*.json reconcile_artifacts/ 2>/dev/null || true
# Summary
python3 -c "
import json
with open('$REPORT') as f:
r = json.load(f)
print(f\"Passed: {r['summary']['passed']}\")
print(f\"Failed: {r['summary']['failed']}\")
"
if [ "${INVARIANT_FAILED:-0}" = "1" ]; then
echo "WARNING: Invariant failures detected"
exit 1
fi
artifacts:
name: "reconcile-${CI_COMMIT_SHORT_SHA}"
paths:
- reconcile_artifacts/
expire_in: 365 days
when: always
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
needs:
- compliance_report
allow_failure: true
# Scheduled Reconciliation (Daily)
scheduled_reconcile:
extends: state_reconcile
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
needs: []
# Monthly Tunnel Rotation
monthly_rotation:
stage: reconcile
image: python:3.11-slim
before_script:
- pip install requests
script:
- |
echo "=== Monthly Tunnel Rotation ==="
python3 ${CI_PROJECT_DIR}/../scripts/tunnel-rotation-scheduler.py \
--account-id ${CLOUDFLARE_ACCOUNT_ID} \
--zone-id ${CLOUDFLARE_ZONE_ID} \
--max-age 90 \
--output-dir ${CI_PROJECT_DIR}/../receipts
artifacts:
name: "rotation-${CI_COMMIT_SHORT_SHA}"
paths:
- receipts/tunnel-rotation-*.json
expire_in: 365 days
rules:
- if: $CI_PIPELINE_SOURCE == "schedule" && $ROTATION_CYCLE == "monthly"
needs: []
# ProofChain Anchor (Post-Apply)
proofchain_anchor:
stage: reconcile
image: python:3.11-slim
before_script:
- pip install requests
script:
- |
echo "=== ProofChain Anchoring ==="
# Run full anchor workflow
bash ${CI_PROJECT_DIR}/../scripts/anchor-cloudflare-state.sh \
--zone-id ${CLOUDFLARE_ZONE_ID} \
--account-id ${CLOUDFLARE_ACCOUNT_ID}
# Copy artifacts
mkdir -p anchor_artifacts
cp ${CI_PROJECT_DIR}/../snapshots/*.json anchor_artifacts/ 2>/dev/null || true
cp ${CI_PROJECT_DIR}/../receipts/*.json anchor_artifacts/ 2>/dev/null || true
cp ${CI_PROJECT_DIR}/../anomalies/*.json anchor_artifacts/ 2>/dev/null || true
cp ${CI_PROJECT_DIR}/../proofchain-anchors.jsonl anchor_artifacts/ 2>/dev/null || true
echo "Anchoring complete"
artifacts:
name: "anchor-${CI_COMMIT_SHORT_SHA}"
paths:
- anchor_artifacts/
expire_in: 365 days
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
needs:
- terraform_apply
allow_failure: true

80
terraform/README.md Normal file
View File

@@ -0,0 +1,80 @@
# Cloudflare Terraform Configuration
Infrastructure as Code for VaultMesh and OffSec Cloudflare resources.
## Prerequisites
1. Terraform >= 1.0
2. Cloudflare API token with permissions:
- Zone: Edit
- DNS: Edit
- Access: Edit
- Argo Tunnel: Edit
- WAF: Edit
## Files
| File | Description |
|------|-------------|
| `main.tf` | Provider configuration |
| `variables.tf` | Input variables |
| `zones.tf` | Zone creation and settings |
| `dns.tf` | DNS records |
| `waf.tf` | WAF and firewall rules |
| `tunnels.tf` | Cloudflare Tunnels |
| `access.tf` | Zero Trust Access apps |
| `outputs.tf` | Output values |
## Usage
```bash
# Initialize
terraform init
# Create terraform.tfvars
cat > terraform.tfvars <<EOF
cloudflare_api_token = "your-api-token"
cloudflare_account_name = "your-account-name"
tunnel_secret_vaultmesh = "base64-encoded-secret"
tunnel_secret_offsec = "base64-encoded-secret"
admin_emails = ["admin@vaultmesh.org"]
EOF
# Plan
terraform plan
# Apply
terraform apply
```
## Generate Tunnel Secrets
```bash
# Generate 32-byte random secret, base64 encoded
openssl rand -base64 32
```
## Domains Managed
- vaultmesh.org
- offsec.global
- offsecglobal.com
- offsecagent.com
- offsecshield.com
## Security Notes
- Never commit `terraform.tfvars` to git
- Use environment variables for CI/CD:
```bash
export TF_VAR_cloudflare_api_token="..."
```
- Rotate tunnel secrets every 90 days
- Review Access policies regularly
## VaultMesh Integration
After applying, emit a VaultMesh receipt:
```bash
terraform output -json > /var/lib/vaultmesh/snapshots/cloudflare-$(date +%Y%m%d).json
```

122
terraform/access.tf Normal file
View File

@@ -0,0 +1,122 @@
# Cloudflare Access - Zero Trust Applications
# Access Application for VaultMesh Dashboard
resource "cloudflare_access_application" "vaultmesh_dash" {
zone_id = cloudflare_zone.domains["vaultmesh.org"].id
name = "VaultMesh Dashboard"
domain = "dash.vaultmesh.org"
type = "self_hosted"
session_duration = "24h"
auto_redirect_to_identity = true
allowed_idps = var.allowed_idps
}
# Access Application for VaultMesh Guardian (Admin)
resource "cloudflare_access_application" "vaultmesh_guardian" {
zone_id = cloudflare_zone.domains["vaultmesh.org"].id
name = "VaultMesh Guardian"
domain = "guardian.vaultmesh.org"
type = "self_hosted"
session_duration = "8h" # Shorter for admin
auto_redirect_to_identity = true
allowed_idps = var.allowed_idps
}
# Access Application for OffSec Internal
resource "cloudflare_access_application" "offsec_internal" {
zone_id = cloudflare_zone.domains["offsec.global"].id
name = "OffSec Internal Tools"
domain = "internal.offsec.global"
type = "self_hosted"
session_duration = "12h"
auto_redirect_to_identity = true
allowed_idps = var.allowed_idps
}
# Access Policy - Allow specific emails
resource "cloudflare_access_policy" "vaultmesh_dash_policy" {
application_id = cloudflare_access_application.vaultmesh_dash.id
zone_id = cloudflare_zone.domains["vaultmesh.org"].id
name = "Allow VaultMesh Team"
precedence = 1
decision = "allow"
include {
email_domain = var.allowed_email_domains
}
require {
# Require MFA
auth_method = "mfa"
}
}
# Access Policy - Guardian (more restrictive)
resource "cloudflare_access_policy" "vaultmesh_guardian_policy" {
application_id = cloudflare_access_application.vaultmesh_guardian.id
zone_id = cloudflare_zone.domains["vaultmesh.org"].id
name = "Allow Guardian Admins"
precedence = 1
decision = "allow"
include {
email = var.admin_emails
}
require {
# Require hardware key MFA
auth_method = "mfa"
}
}
# Access Policy - OffSec Internal
resource "cloudflare_access_policy" "offsec_internal_policy" {
application_id = cloudflare_access_application.offsec_internal.id
zone_id = cloudflare_zone.domains["offsec.global"].id
name = "Allow OffSec Team"
precedence = 1
decision = "allow"
include {
email_domain = var.allowed_email_domains
}
require {
auth_method = "mfa"
}
}
# Service Tokens for machine-to-machine auth
resource "cloudflare_access_service_token" "vaultmesh_api" {
zone_id = cloudflare_zone.domains["vaultmesh.org"].id
name = "VaultMesh API Service Token"
min_days_for_renewal = 30
}
resource "cloudflare_access_service_token" "offsec_api" {
zone_id = cloudflare_zone.domains["offsec.global"].id
name = "OffSec API Service Token"
min_days_for_renewal = 30
}
# Variables for Access
variable "allowed_idps" {
description = "List of allowed Identity Provider IDs"
type = list(string)
default = []
}
variable "allowed_email_domains" {
description = "Email domains allowed to access applications"
type = list(string)
default = ["vaultmesh.org", "offsec.global"]
}
variable "admin_emails" {
description = "Specific admin email addresses for sensitive apps"
type = list(string)
default = []
}

73
terraform/dns.tf Normal file
View File

@@ -0,0 +1,73 @@
# DNS Records for each zone
# Root A record (proxied) - points to tunnel or origin
resource "cloudflare_record" "root_a" {
for_each = cloudflare_zone.domains
zone_id = each.value.id
name = "@"
value = var.origin_ip
type = "A"
proxied = true
ttl = 1 # Auto when proxied
}
# WWW CNAME
resource "cloudflare_record" "www" {
for_each = cloudflare_zone.domains
zone_id = each.value.id
name = "www"
value = each.key
type = "CNAME"
proxied = true
ttl = 1
}
# SPF Record
resource "cloudflare_record" "spf" {
for_each = cloudflare_zone.domains
zone_id = each.value.id
name = "@"
content = "v=spf1 include:_spf.mx.cloudflare.net -all"
type = "TXT"
ttl = 3600
}
# DMARC Record
resource "cloudflare_record" "dmarc" {
for_each = cloudflare_zone.domains
zone_id = each.value.id
name = "_dmarc"
value = "v=DMARC1; p=reject; rua=mailto:dmarc@${each.key}"
type = "TXT"
ttl = 3600
}
# MX Records (using Cloudflare Email Routing or custom)
resource "cloudflare_record" "mx_primary" {
for_each = cloudflare_zone.domains
zone_id = each.value.id
name = "@"
value = "route1.mx.cloudflare.net"
type = "MX"
priority = 10
ttl = 3600
}
resource "cloudflare_record" "mx_secondary" {
for_each = cloudflare_zone.domains
zone_id = each.value.id
name = "@"
value = "route2.mx.cloudflare.net"
type = "MX"
priority = 20
ttl = 3600
}
resource "cloudflare_record" "mx_tertiary" {
for_each = cloudflare_zone.domains
zone_id = each.value.id
name = "@"
value = "route3.mx.cloudflare.net"
type = "MX"
priority = 30
ttl = 3600
}

29
terraform/main.tf Normal file
View File

@@ -0,0 +1,29 @@
terraform {
required_version = ">= 1.0"
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
}
}
provider "cloudflare" {
api_token = var.cloudflare_api_token
}
# Data source for account (optional - fails gracefully)
data "cloudflare_accounts" "main" {
count = var.cloudflare_account_name != "" ? 1 : 0
name = var.cloudflare_account_name
}
locals {
# Use account ID from data source if available, otherwise use variable
account_id = (
var.cloudflare_account_name != "" && length(data.cloudflare_accounts.main) > 0 && length(data.cloudflare_accounts.main[0].accounts) > 0
? data.cloudflare_accounts.main[0].accounts[0].id
: var.cloudflare_account_id
)
}

57
terraform/outputs.tf Normal file
View File

@@ -0,0 +1,57 @@
# Outputs
output "zone_ids" {
description = "Map of domain names to zone IDs"
value = {
for domain, zone in cloudflare_zone.domains : domain => zone.id
}
}
output "zone_name_servers" {
description = "Name servers for each zone"
value = {
for domain, zone in cloudflare_zone.domains : domain => zone.name_servers
}
}
output "tunnel_ids" {
description = "Tunnel IDs"
value = {
vaultmesh = cloudflare_tunnel.vaultmesh.id
offsec = cloudflare_tunnel.offsec.id
}
}
output "tunnel_cnames" {
description = "Tunnel CNAME targets"
value = {
vaultmesh = "${cloudflare_tunnel.vaultmesh.id}.cfargotunnel.com"
offsec = "${cloudflare_tunnel.offsec.id}.cfargotunnel.com"
}
}
output "access_application_ids" {
description = "Access Application IDs"
value = {
vaultmesh_dash = cloudflare_access_application.vaultmesh_dash.id
vaultmesh_guardian = cloudflare_access_application.vaultmesh_guardian.id
offsec_internal = cloudflare_access_application.offsec_internal.id
}
}
output "service_token_client_ids" {
description = "Service token client IDs (secrets are sensitive)"
value = {
vaultmesh_api = cloudflare_access_service_token.vaultmesh_api.client_id
offsec_api = cloudflare_access_service_token.offsec_api.client_id
}
}
output "service_token_secrets" {
description = "Service token secrets"
value = {
vaultmesh_api = cloudflare_access_service_token.vaultmesh_api.client_secret
offsec_api = cloudflare_access_service_token.offsec_api.client_secret
}
sensitive = true
}

View File

@@ -0,0 +1,3 @@
cloudflare_api_token = "placeholder-token"
cloudflare_account_id = "placeholder-account-id"
cloudflare_account_name = "" # Leave empty to use hardcoded account_id

121
terraform/tunnels.tf Normal file
View File

@@ -0,0 +1,121 @@
# Cloudflare Tunnels
# Tunnel for VaultMesh services
resource "cloudflare_tunnel" "vaultmesh" {
account_id = local.account_id
name = "vaultmesh-tunnel"
secret = var.tunnel_secret_vaultmesh
}
# Tunnel for OffSec services
resource "cloudflare_tunnel" "offsec" {
account_id = local.account_id
name = "offsec-tunnel"
secret = var.tunnel_secret_offsec
}
# Tunnel configuration for VaultMesh
resource "cloudflare_tunnel_config" "vaultmesh" {
account_id = local.account_id
tunnel_id = cloudflare_tunnel.vaultmesh.id
config {
# VaultMesh Core API
ingress_rule {
hostname = "api.vaultmesh.org"
service = "http://localhost:8080"
origin_request {
connect_timeout = "10s"
no_tls_verify = false
}
}
# VaultMesh Dashboard
ingress_rule {
hostname = "dash.vaultmesh.org"
service = "http://localhost:3000"
}
# VaultMesh Guardian
ingress_rule {
hostname = "guardian.vaultmesh.org"
service = "http://localhost:8081"
}
# Catch-all
ingress_rule {
service = "http_status:404"
}
}
}
# Tunnel configuration for OffSec
resource "cloudflare_tunnel_config" "offsec" {
account_id = local.account_id
tunnel_id = cloudflare_tunnel.offsec.id
config {
# OffSec main site
ingress_rule {
hostname = "offsec.global"
service = "http://localhost:8090"
}
# OffSec Agent portal
ingress_rule {
hostname = "offsecagent.com"
service = "http://localhost:8091"
}
# OffSec Shield dashboard
ingress_rule {
hostname = "offsecshield.com"
service = "http://localhost:8092"
}
# Catch-all
ingress_rule {
service = "http_status:404"
}
}
}
# DNS records pointing to tunnels
resource "cloudflare_record" "tunnel_vaultmesh_api" {
zone_id = cloudflare_zone.domains["vaultmesh.org"].id
name = "api"
value = "${cloudflare_tunnel.vaultmesh.id}.cfargotunnel.com"
type = "CNAME"
proxied = true
}
resource "cloudflare_record" "tunnel_vaultmesh_dash" {
zone_id = cloudflare_zone.domains["vaultmesh.org"].id
name = "dash"
value = "${cloudflare_tunnel.vaultmesh.id}.cfargotunnel.com"
type = "CNAME"
proxied = true
}
resource "cloudflare_record" "tunnel_vaultmesh_guardian" {
zone_id = cloudflare_zone.domains["vaultmesh.org"].id
name = "guardian"
value = "${cloudflare_tunnel.vaultmesh.id}.cfargotunnel.com"
type = "CNAME"
proxied = true
}
# Variables for tunnel secrets
variable "tunnel_secret_vaultmesh" {
description = "Secret for VaultMesh tunnel (base64 encoded 32+ bytes)"
type = string
sensitive = true
default = ""
}
variable "tunnel_secret_offsec" {
description = "Secret for OffSec tunnel (base64 encoded 32+ bytes)"
type = string
sensitive = true
default = ""
}

66
terraform/variables.tf Normal file
View File

@@ -0,0 +1,66 @@
variable "cloudflare_api_token" {
description = "Cloudflare API token with Zone:Edit, DNS:Edit, Access:Edit permissions"
type = string
sensitive = true
}
variable "cloudflare_account_name" {
description = "Cloudflare account name"
type = string
default = ""
}
variable "cloudflare_account_id" {
description = "Cloudflare account ID (used if account name lookup fails)"
type = string
sensitive = true
default = ""
}
variable "domains" {
description = "Map of domains to manage"
type = map(object({
plan = string
jump_start = bool
}))
default = {
"offsec.global" = {
plan = "free"
jump_start = false
}
"offsecglobal.com" = {
plan = "free"
jump_start = false
}
"offsecagent.com" = {
plan = "free"
jump_start = false
}
"offsecshield.com" = {
plan = "free"
jump_start = false
}
"vaultmesh.org" = {
plan = "free"
jump_start = false
}
}
}
variable "origin_ip" {
description = "Origin server IP (should be tunnel, but fallback)"
type = string
default = "192.0.2.1" # Placeholder - use tunnel instead
}
variable "trusted_admin_ips" {
description = "List of trusted admin IP addresses"
type = list(string)
default = []
}
variable "blocked_countries" {
description = "Countries to challenge/block"
type = list(string)
default = ["CN", "RU", "KP", "IR"]
}

91
terraform/waf.tf Normal file
View File

@@ -0,0 +1,91 @@
# WAF Rulesets and Firewall Rules
# Block non-HTTPS (should be handled by always_use_https, but explicit rule)
resource "cloudflare_ruleset" "security_rules" {
for_each = cloudflare_zone.domains
zone_id = each.value.id
name = "Security Rules"
kind = "zone"
phase = "http_request_firewall_custom"
# Rule 1: Block requests to /admin from non-trusted IPs
rules {
action = "block"
expression = "(http.request.uri.path contains \"/admin\") and not (ip.src in {${join(" ", var.trusted_admin_ips)}})"
description = "Block admin access from untrusted IPs"
enabled = length(var.trusted_admin_ips) > 0
}
# Rule 2: Challenge suspicious countries
rules {
action = "managed_challenge"
expression = "(ip.src.country in {\"${join("\" \"", var.blocked_countries)}\"})"
description = "Challenge traffic from high-risk countries"
enabled = true
}
# Rule 3: Block known bad user agents
rules {
action = "block"
expression = "(http.user_agent contains \"sqlmap\") or (http.user_agent contains \"nikto\") or (http.user_agent contains \"nmap\")"
description = "Block known scanning tools"
enabled = true
}
# Rule 4: Rate limit API endpoints
rules {
action = "block"
ratelimit {
characteristics = ["ip.src"]
period = 10
requests_per_period = 30
mitigation_timeout = 60
}
expression = "(http.request.uri.path starts_with \"/api/\")"
description = "Rate limit API endpoints"
enabled = true
}
}
# Enable Cloudflare Managed WAF Ruleset
resource "cloudflare_ruleset" "managed_waf" {
for_each = cloudflare_zone.domains
zone_id = each.value.id
name = "Managed WAF"
kind = "zone"
phase = "http_request_firewall_managed"
# Cloudflare Managed Ruleset
rules {
action = "execute"
action_parameters {
id = "efb7b8c949ac4650a09736fc376e9aee" # Cloudflare Managed Ruleset
}
expression = "true"
description = "Execute Cloudflare Managed Ruleset"
enabled = true
}
# OWASP Core Ruleset
rules {
action = "execute"
action_parameters {
id = "4814384a9e5d4991b9815dcfc25d2f1f" # OWASP Core Ruleset
}
expression = "true"
description = "Execute OWASP Core Ruleset"
enabled = true
}
}
# Bot Management (if available on plan)
resource "cloudflare_bot_management" "domains" {
for_each = cloudflare_zone.domains
zone_id = each.value.id
enable_js = true
fight_mode = true
sbfm_definitely_automated = "block"
sbfm_likely_automated = "managed_challenge"
sbfm_verified_bots = "allow"
sbfm_static_resource_protection = false
}

48
terraform/zones.tf Normal file
View File

@@ -0,0 +1,48 @@
# Zone resources for each domain
resource "cloudflare_zone" "domains" {
for_each = var.domains
account_id = local.account_id
zone = each.key
plan = each.value.plan
jump_start = each.value.jump_start
}
# Enable DNSSEC on all zones
resource "cloudflare_zone_dnssec" "domains" {
for_each = cloudflare_zone.domains
zone_id = each.value.id
}
# Zone settings - TLS, security, etc.
resource "cloudflare_zone_settings_override" "domains" {
for_each = cloudflare_zone.domains
zone_id = each.value.id
settings {
# TLS Settings
ssl = "strict"
min_tls_version = "1.2"
tls_1_3 = "on"
automatic_https_rewrites = "on"
always_use_https = "on"
# Security
security_level = "medium"
browser_check = "on"
# Performance
minify {
css = "on"
js = "on"
html = "on"
}
brotli = "on"
# Caching
browser_cache_ttl = 14400
# Privacy
email_obfuscation = "on"
server_side_exclude = "on"
}
}