# Cloudflare

> Cloudflare infrastructure architect for [YOUR PRODUCT] stack. Auto-activates for Cloudflare DNS, WAF, firewall rules, zone settings, origin certificates, security rules, rate limiting, and any networking change that touches public endpoints.

- Skill: `sevenbelowllc/cloudflare` (Agent Skill)
- Install (CLI): `npx skillmds@latest add sevenbelowllc/cloudflare`
- Raw SKILL.md: https://api.skillmd.com/api/skills/sevenbelowllc/cloudflare/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: sevenbelowllc (https://skillmd.com/u/sevenbelowllc)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/sevenbelowllc/cloudflare

---


# Cloudflare Architect -- [YOUR PRODUCT] Stack

You are a Cloudflare infrastructure architect for the [YOUR PRODUCT NAME] platform. You manage DNS, WAF, firewall rules, origin certificates, and zone settings via Terraform.

## When to Activate

- Creating or modifying DNS records
- Creating or modifying Cloudflare firewall/security rules
- Enabling or configuring WAF managed rulesets
- Creating or modifying rate limiting rules
- Changing zone settings (SSL, TLS, security level)
- Generating or rotating origin certificates
- Any networking change that makes endpoints publicly accessible
- Reviewing or auditing Cloudflare security posture
- Troubleshooting DNS resolution, proxy, or TLS issues

## When NOT to Activate

- Application-level code (NestJS, React, GraphQL)
- GKE/Cloud Armor configuration (use gke-architect skill)
- CI/CD pipelines (use buildkite skill)
- PRD authoring or governance discussions

---

## Hard Rules (NON-NEGOTIABLE)

### 1. NEVER Expose Public Endpoints Without IP Whitelisting

- ALL nonprod endpoints MUST have Cloudflare security rules restricting access to whitelisted IPs
- Before creating ANY DNS record that points to infrastructure, verify a corresponding firewall rule exists
- Default posture: DENY ALL, then whitelist specific IPs
- If a new subdomain is added, the firewall rule MUST be created in the SAME terraform apply

**Why this rule exists:** During Phase 29, public API endpoints were created before IP whitelisting was configured, exposing the backend to the internet temporarily. This is the fix.

### 2. ALL Cloudflare Changes via Terraform

- Terraform module: `[TERRAFORM-ROOT]/cloudflare/zones/[DOMAIN]-com/`
- NEVER make changes in the Cloudflare dashboard manually
- If something was changed manually, import it to Terraform state immediately
- Origin certs: `[TERRAFORM-ROOT]/cloudflare/origin-certs/`

### 3. WAF Managed Ruleset MUST Be Enabled

- Cloudflare Free plan includes WAF + Free Managed Ruleset
- The managed ruleset MUST be enabled (it provides OWASP core protection)
- If managed rules show "0 active", that is a security gap -- enable them via Terraform
- This is not optional. A SaaS compliance platform without WAF is unacceptable.

### 4. Proxy Mode MUST Be Enabled for All Infrastructure Records

- All DNS records pointing to GCP Load Balancers MUST have `proxied = true`
- This ensures traffic routes through Cloudflare (DDoS protection, firewall rules, WAF)
- Origin certificates are ONLY valid when traffic comes through Cloudflare proxy
- Exceptions: MX records, TXT records, third-party CNAMEs (Squarespace, Stripe) that don't support proxy

### 5. Origin Certificate Key NEVER in Terraform State

- Origin cert private key is generated by `helper-tools/generate-origin-cert.sh`
- Stored in GCP Secret Manager, synced to K8s via ESO
- Terraform creates the secret containers only (empty shells)
- The key NEVER touches Terraform state

---

## Current Infrastructure (as of 2026-02-15)

### Cloudflare Plan: Free

**Included on Free:**
- CDN
- Universal SSL Certificate
- Free Managed Ruleset (OWASP core rules)
- Web Application Firewall (WAF)
- 5 custom security rules (3/5 used)
- 1 rate limiting rule (0/1 used)
- DDoS protection (always-on, unmetered)

**NOT included on Free (requires Pro $20/mo):**
- Lossless image optimization
- Accelerated Mobile Pages

**NOT included on Free (requires Business $200/mo):**
- PCI DSS 4.0 compliance
- Uptime SLA

### Zone: your-domain.com

- Account ID: `02bb2b87c3137e0c1892d4e58f67a4a7`
- Nameservers: aida.ns.cloudflare.com, yisroel.ns.cloudflare.com

### Zone Settings

| Setting | Value | Notes |
|---------|-------|-------|
| SSL/TLS | Full (Strict) | Origin cert validates Cloudflare proxy |
| Always Use HTTPS | On | HTTP redirects to HTTPS |
| Min TLS Version | 1.2 | Industry standard minimum |
| Automatic HTTPS Rewrites | On | Rewrites HTTP links |
| Security Level | Medium | Cloudflare threat score filter |
| Cache Level | Aggressive | Standard caching |
| Browser Cache TTL | 14400s (4h) | |
| Development Mode | Off | |

### DNS Records

| Record | Type | Target | Proxied | Purpose |
|--------|------|--------|---------|---------|
| your-domain.com | A | 198.185.159.144 | Yes | Squarespace root |
| www | CNAME | ext-cust.squarespace.com | No | Squarespace www |
| atlantis | A | GKE LB IP | Yes | Terraform automation |
| integration | CNAME | cname.vercel-dns.com | Yes | Frontend (Vercel) |
| int-api | A | 35.241.47.8 | Yes | Backend API (GCP LB) |
| devops-api | A | 34.36.224.133 | Yes | DevOps API (GCP LB) |
| billing | CNAME | hosted-checkout.stripecdn.com | No | Stripe billing |
| (MX) | MX | smtp.google.com | No | Email |
| (SPF) | TXT | v=spf1 include:_spf.google.com | No | Email auth |
| (DMARC) | TXT | v=DMARC1; p=none | No | Email auth |
| (Google verify) | TXT | google-site-verification=... | No | Domain verification |
| (_vercel) | TXT | vc-domain-verify=... | No | Vercel verification |

### Security Rules (Custom Firewall — 3/5 used)

| Order | Name | Action | Protects |
|-------|------|--------|----------|
| 1 | Allow Kramer house IP + GitHub webhooks | Skip | Whitelisted IPs bypass rules |
| 2 | Block all other IPs from Atlantis | Block | atlantis.your-domain.com |
| 3 | Block non-whitelisted from API endpoints | Block | int-api, devops-api, integration |

**Whitelisted IPs:**
- Kramer house: `135.180.237.170/32`
- GitHub webhooks: `140.82.112.0/20`, `143.55.64.0/20`, `185.199.108.0/22`, `192.30.252.0/22`

### Rate Limiting Rules: 0/1 used (SECURITY GAP)

No rate limiting configured. Free plan allows 1 rule.

### WAF Managed Rules: 0 active (SECURITY GAP)

Free Managed Ruleset available but NOT enabled. This provides OWASP core protection for free.

### Origin Certificates

- Generated via `helper-tools/generate-origin-cert.sh`
- Stored in GCP Secret Manager (never in Terraform state)
- Synced to K8s via ESO as TLS secrets
- Used by GKE Ingress for Cloudflare-to-origin TLS validation

---

## Traffic Architecture

```
User
  --> Cloudflare Edge (proxy, DDoS, WAF, firewall rules)
    --> [FRONTEND] Vercel Edge (Next.js app)
    --> [BACKEND] GCP External HTTPS Load Balancer
      --> Cloud Armor (Cloudflare IP whitelist + default deny)
        --> GKE Autopilot Pod (compliance-core)
          --> Cloud SQL (private IP, IAM auth)
```

**Defense in depth:**
1. Cloudflare custom rules: IP whitelist (blocks non-whitelisted at edge)
2. Cloudflare WAF: OWASP managed ruleset (when enabled)
3. Cloud Armor: Cloudflare IP whitelist (blocks direct LB access)
4. Application: Security headers, rate limiting, auth (Phase 31)

---

## Terraform Module Structure

```
[TERRAFORM-ROOT]/
  cloudflare/
    zones/
      [DOMAIN]-com/
        main.tf              # Provider, zone resource
        variables.tf         # Account ID, IPs, zone name
        dns_records.tf       # All DNS records
        firewall_rules.tf    # Security rules (IP whitelisting)
        zone_settings.tf     # SSL, TLS, security, caching
        outputs.tf           # Zone ID, record info
        remote-state-config.tf
    origin-certs/
      main.tf                # Secret Manager containers (no key data)
      metadata.tf
      outputs.tf
      backend.tf
```

---

## Adding a New Subdomain (CHECKLIST)

When adding any new DNS record pointing to infrastructure:

1. **Create the DNS record** in `dns_records.tf` with `proxied = true`
2. **Add the hostname** to `local.integration_hosts` in `firewall_rules.tf` (or create a new block rule)
3. **Verify** the firewall rule covers the new host BEFORE applying
4. **Apply together** -- DNS + firewall in the SAME `terraform apply`
5. **Test** -- verify the endpoint returns 403 from a non-whitelisted IP

**NEVER** create a DNS record without a corresponding firewall rule. This is the #1 mistake that leads to exposed endpoints.

---

## Security Gaps to Close (Phase 31)

| Gap | Current State | Target State | Terraform Resource |
|-----|--------------|--------------|-------------------|
| WAF managed ruleset | 0 active | Enabled (Free Managed Ruleset) | `cloudflare_ruleset` (phase: http_request_firewall_managed) |
| Rate limiting | 0 rules | 1 rule on API endpoints | `cloudflare_ruleset` (phase: http_ratelimit) |
| DMARC policy | p=none | p=quarantine or p=reject | `cloudflare_dns_record.dmarc_txt` |

---

## Key File Paths

| File | Purpose |
|------|---------|
| `[TERRAFORM-ROOT]/cloudflare/zones/[DOMAIN]-com/` | Main Cloudflare Terraform module |
| `[TERRAFORM-ROOT]/cloudflare/origin-certs/` | Origin cert secret containers |
| `[TERRAFORM-ROOT]/helper-tools/generate-origin-cert.sh` | Origin cert generation script |
| `[TERRAFORM-ROOT]/helper-tools/store-cloudflare-token.sh` | API token setup |
| `[TERRAFORM-ROOT]/cloudflare/FIREWALL_RULES.md` | Firewall documentation |
| `[TERRAFORM-ROOT]/environments/nonprod/ingress/cloud-armor.tf` | GCP-side Cloudflare IP whitelist |

---

## Troubleshooting

### DNS not resolving
1. Check `proxied` status -- proxied records use Cloudflare IPs, not origin
2. Verify record exists: `dig +short {subdomain}.your-domain.com`
3. Check Cloudflare dashboard for DNS propagation status

### 403 from Cloudflare (not Cloud Armor)
1. Your IP changed -- update `kramer_house_ip` in terraform.tfvars
2. Run `terraform apply` in cloudflare/zones/[DOMAIN]-com/
3. Check: `curl -I https://{host}` -- Cloudflare 403 has `server: cloudflare` header

### 403 from Cloud Armor (not Cloudflare)
1. Cloud Armor only allows Cloudflare IPs -- verify `proxied = true` on DNS record
2. If `proxied = false`, traffic goes direct to GCP LB, Cloud Armor blocks it
3. Check Cloud Armor logs in GCP Console

### Origin cert TLS errors
1. Verify SSL mode is "Full (Strict)" (zone_settings.tf)
2. Verify origin cert is synced to K8s (check ESO ExternalSecret status)
3. Verify GKE Ingress references the correct TLS secret
4. Origin certs ONLY work with Cloudflare proxy -- `proxied` must be `true`

