Cloudflare Security Operations

Covers: zintegrowana.online and radekkonarski.com — Cloudflare Free plan, DNS-only zones. All security findings come from Cloudflare Security Insights dashboard.


1. Access

PathNotes
Dashboard → Security → Security InsightsPrimary view
https://dash.cloudflare.com/<account>/security-insightsDirect URL
API: GET /accounts/{account_id}/security-insights/issuesRequires CF_API_TOKEN with Account Read scope

2. Monitoring Frequency

CadenceWho / How
Weeklycloudflare-security-check.yml CI workflow — runs Mondays 07:00 UTC; opens a GH issue on any new unresolved findings
MonthlyManual review — open dashboard, verify the fix backlog is draining, update compliance_notes in dev_r_services
On DNS changeRun a manual review after any record add/edit — unproxied record findings update within ~1h

3. Current Finding Inventory (2026-06-23)

Captured from dashboard screenshot. Fix status tracked here until resolved.

#FindingTypeCountPriorityStatus
1DMARC Record Error detected (zintegrowana.online)Email SecurityHighFixed 2026-06-22 — SPF+DMARC updated
1bDMARC Record Error detected (radekkonarski.com)Email SecurityHighFixed 2026-06-23 — confirmed p=quarantine, zone documented (issue #988)
2Bot Fight Mode not enabledConfig suggestion2MediumOpen — dismiss (DNS-only zone)
3Security.txt not configuredConfig suggestion2LowPartially fixed 2026-06-22 — radekkonarski.com done (issue #987); zintegrowana.online pending
4Unproxied A Record detectedExposed infra3MediumOpen — intentional (VPS/BMS direct — see §4.4)
5Unproxied CNAME Record detectedExposed infra1MediumOpen — intentional
6Insecure configurationConfig suggestion1MediumOpen
7Weak authenticationAuth1HighOpen — requires 2FA (human action)
8Dangling A Record (*.zintegrowana.online)Security1ModerateInvestigated 2026-06-23 — see §4.4 (no dangling record; bms-4 wildcard was undocumented)

Investigation note (2026-06-23, issue #985): Cloudflare flagged *.zintegrowana.online as a “Dangling A Record” finding (Moderate severity, detected 2026-06-13). Full DNS audit performed via API — no record points to an IP we no longer own. The finding covers all three unproxied wildcard A records in the zone:

  • *.vps-i1.infra.zintegrowana.online217.154.82.162 (IONOS vps-i1, owned)
  • *.vps-h1.infra.zintegrowana.online72.60.32.61 (Hostinger vps-h1, owned)
  • *.bms-4.infra.zintegrowana.online54.36.123.110 (OVH bms-4, owned — added 2026-06-10, undocumented until this fix)

All three are intentional unproxied records managed by their respective server’s TLS stack (Caddy/Traefik). No DNS changes needed. §4.4 updated to include bms-4.


4. Fix Procedures

4.1 DMARC Record Error (Priority: High) — Fixed 2026-06-22

Root causes found and fixed:

  1. SPF was missing Mailgun EU (include:mailgun.org) — Alertmanager emails via p24-email-sender Worker weren’t covered
  2. DMARC alignment was adkim=s; aspf=s (strict) — failed when Cloudflare Worker sends on behalf of the domain
  3. DMARC rua was pointing to dmarc-reports@zintegrowana.online (no routing rule, reports bounced)

Applied fixes (2026-06-22 via Cloudflare API):

SPF:   v=spf1 include:_spf.mx.cloudflare.net include:mailgun.org ~all
DMARC: v=DMARC1; p=quarantine; rua=mailto:radieu@gmail.com; sp=quarantine; adkim=r; aspf=r; pct=100

Email architecture on this domain:

  • All infra alerts → email-api.zintegrowana.onlinep24-email-sender CF Worker → Cloudflare Email Routing
  • Worker DKIM: cf2024-1._domainkey.zintegrowana.online (Cloudflare managed, auto-passes DMARC)
  • Bounce subdomain: cf-bounce.zintegrowana.online (separate SPF + DKIM)

⚠️ Important: email-api.zintegrowana.online192.0.2.1 (proxied) is NOT a placeholder to delete. It is the required DNS record for the p24-email-sender Cloudflare Worker route (workers/email-sender/). Deleting it breaks all infra alert emails. Confirmed 2026-06-22.

To escalate to p=reject — only after DMARC aggregate reports from radieu@gmail.com confirm zero failures for 2+ weeks:

# Via Cloudflare API (PowerShell):
$body = '{"type":"TXT","name":"_dmarc.zintegrowana.online","content":"v=DMARC1; p=reject; rua=mailto:radieu@gmail.com; sp=reject; adkim=r; aspf=r; pct=100","ttl":1}'
Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/57cb3d8f24c7cc319fb703394edc7b87/dns_records/<id>" -Method PUT -Headers @{"Authorization"="Bearer $CF_TOKEN"} -Body $body

4.2 Bot Fight Mode Not Enabled (Priority: Medium)

What it means: Cloudflare Bot Fight Mode is disabled — automated bot traffic hits VPS directly without Cloudflare filtering. Relevant only for proxied records; DNS-only records bypass it anyway.

Current state: Zone is DNS-only (no orange-cloud proxy) — Bot Fight Mode applies only to proxied hostnames. Since all infra subdomains use unproxied A records, this setting has no effect until proxy is enabled.

Decision options:

  • Option A — ignore (recommended for now): Accept the finding. Our services use Caddy/Traefik with their own rate limiting. Bot Fight Mode requires proxied records which expose Cloudflare IPs.
  • Option B — enable selectively: Proxy only public-facing endpoints (e.g. grafana.vps-i1.infra.zintegrowana.online) and enable Bot Fight Mode. Risk: Cloudflare IPs instead of direct VPS IPs.

To dismiss in dashboard: Security Insights → finding → “Dismiss” (marks as accepted risk).


4.3 Security.txt Not Configured (Priority: Low)

What it means: /.well-known/security.txt is missing. Security researchers cannot find contact info for responsible disclosure.

4.3.1 radekkonarski.com — Fixed 2026-06-22

Status: Live at https://radekkonarski.com/.well-known/security.txt (HTTP 200).

Implementation: Static file committed to radieu/radekkonarski-personal-brand in website/.well-known/security.txt, served via Vercel with Content-Type: text/plain; charset=utf-8 header in website/vercel.json. Commit: f744af3 on main.

Content (RFC 9116 compliant):

Contact: mailto:radieu@gmail.com
Expires: 2027-06-22T00:00:00.000Z
Canonical: https://radekkonarski.com/.well-known/security.txt
Preferred-Languages: en, pl

Renewal reminder: Expires is 2027-06-22. Update before that date in website/.well-known/security.txt in the radekkonarski-personal-brand repo.

4.3.2 zintegrowana.online — Pending

Fix — add to Caddy on vps-i1:

# In /opt/p24-infra/monitoring/caddy/Caddyfile
zintegrowana.online {
  handle /.well-known/security.txt {
    respond `Contact: mailto:radieu@gmail.com
Expires: 2027-01-01T00:00:00.000Z
Preferred-Languages: en, pl
` 200
  }
}

Then reload: ssh root@217.154.82.162 "docker exec monitoring-caddy-1 caddy reload --config /etc/caddy/Caddyfile"


4.4 Unproxied A / CNAME Records (Priority: Medium — Intentional)

What it means: Cloudflare detects DNS records that bypass the Cloudflare proxy (no orange cloud). Server IP is exposed directly.

Current state — intentional (all three wildcards documented, verified 2026-06-23):

RecordIPServerTLS handled by
*.vps-i1.infra.zintegrowana.online217.154.82.162IONOS vps-i1Caddy
*.vps-h1.infra.zintegrowana.online72.60.32.61Hostinger vps-h1Traefik
*.bms-4.infra.zintegrowana.online54.36.123.110OVH bms-4 (added 2026-06-10)Traefik

CNAME (grey cloud, intentional):

  • n8n-cloud.infra.zintegrowana.online → CNAME to p24.app.n8n.cloud (n8n Cloud, no proxy needed)

Action: Dismiss these findings in Cloudflare dashboard → mark as “Accepted Risk”.

Rule — update this doc when adding a new unproxied record: Any new wildcard or direct A record for an infra server MUST be added to the table above in the same PR that creates the DNS record. This prevents future false-positive “Dangling A Record” security findings.

If a new unproxied record appears unexpectedly:

# List all A/CNAME records and check for unexpected entries
python3 /opt/p24-infra/scripts/dns-manager.py list

4.5 Insecure Configuration (Priority: Medium)

What it means: Likely SSL/TLS minimum version or HSTS not configured at zone level.

Check:

  1. Cloudflare dashboard → SSL/TLS → Edge Certificates
  2. Verify: Minimum TLS Version = TLS 1.2 (or 1.3)
  3. Verify: Always Use HTTPS = ON (for any proxied records)

Fix: Enable in dashboard. For DNS-only zone, this is advisory — TLS is handled by Caddy/Traefik on VPS.


4.6 Weak Authentication (Priority: High)

What it means: Cloudflare account has weak authentication — likely 2FA is not enabled or using email/password only.

Fix:

  1. Cloudflare dashboard → My Profile → Authentication
  2. Enable Two-Factor Authentication (TOTP via authenticator app)
  3. Store backup codes in SOPS: add CLOUDFLARE_2FA_BACKUP_CODES to secrets/vps-h1.env.sops

Acceptance criteria: 2FA enabled, finding disappears within 24h.


5. Monitoring Integration

5.1 GitHub Actions — Weekly Check

File: .github/workflows/cloudflare-security-check.yml

Runs every Monday at 07:00 UTC. Queries Cloudflare Security Insights API. Opens a GH issue if any non-dismissed High findings exist.

Required GH Secret (already exists): CF_API_TOKEN

5.2 Prometheus — Future Exporter

If Cloudflare security posture needs continuous tracking, add a cloudflare-exporter to the monitoring stack:

Port: 9215
Metrics:
  cloudflare_security_insights_open_total{zone, severity}
  cloudflare_security_insights_dismissed_total{zone}
  cloudflare_dns_records_unproxied_total{zone}

Not implemented yet — the weekly CI check covers the current need.


6. Acceptance Criteria — Clearing the Backlog

ItemTarget dateDone when
2FA enabled on Cloudflare account2026-06-30Finding #7 clears
DMARC record fixed2026-06-300 DMARC errors in Cloudflare
Unproxied records dismissed2026-06-25Findings marked Accepted Risk
Bot Fight Mode dismissed2026-06-25Finding marked Accepted Risk
Security.txt — radekkonarski.com2026-07-15 Done 2026-06-22200 at https://radekkonarski.com/.well-known/security.txt
Security.txt — zintegrowana.online2026-07-15200 response at https://zintegrowana.online/.well-known/security.txt
Insecure config resolved2026-06-30TLS 1.2+ enforced

7. Escalation

ConditionAction
New High finding not in this docOpen GH issue bug + p24-infra-nc-alert, fix within 48h
DMARC alignment failure on emailCheck Mailgun EU sending logs → rotate DKIM if needed
DNS record anomaly (unexpected record)Immediate: delete via dns-manager.py, open GH issue
Cloudflare account compromise suspectedImmediately rotate CF_API_TOKEN + CLOUDFLARE_TOKEN_ZINTEGROWANA in SOPS + GH Secrets

8. Credential Rotation

CredentialRotation freqSOPS keyScope
CF_API_TOKEN180dsecrets/monitoring.env.sopszintegrowana.online DNS + Account Read
CLOUDFLARE_TOKEN_ZINTEGROWANA180dsecrets/monitoring.env.sopszintegrowana.online zone
CF_RADEKKONARSKI_DNS_TOKEN30d (auto)secrets/monitoring.env.sopsradekkonarski.com DNS:Write only
CF_GLOBAL_API_KEY180dsecrets/administration.env.sopsFull account — token management only
Cloudflare account password180dpassword manager

Next rotation due: 2026-11-25 (from 2026-05-28 last rotation).


9. radekkonarski.com Zone

Personal brand domain managed under the same Cloudflare account as zintegrowana.online.

FieldValue
Zone ID7d2cd27bc46075f693771646cc54b0f8
SOPS keyCF_ZONE_ID_RADEKKONARSKI in secrets/monitoring.env.sops
Nameserversalberto.ns.cloudflare.com / kehlani.ns.cloudflare.com
PlanFree — DNS-only
Reporadekkonarski-personal-brand

9.1 Email Security Records (verified 2026-06-23)

RecordNameContentStatus
TXT (DMARC)_dmarc.radekkonarski.comv=DMARC1; p=quarantine; rua=mailto:radieu@gmail.com; sp=quarantine; adkim=r; aspf=r; pct=100Active
TXT (DMARC subdomain)_dmarc.send.radekkonarski.comv=DMARC1; p=quarantine; rua=mailto:radieu@gmail.com; sp=quarantine; adkim=r; aspf=r; pct=100Active
TXT (SPF)radekkonarski.comv=spf1 include:_spf.mx.cloudflare.net include:_spf.google.com include:amazonses.com a mx include:_spf.mlsend.com ~allActive
TXT (SPF subdomain)send.radekkonarski.comv=spf1 include:amazonses.com ~allActive
TXT (DKIM)cf2024-1._domainkey.radekkonarski.comRSA 2048 — Cloudflare managedActive
TXT (DKIM)resend._domainkey.radekkonarski.comRSA key — Resend transactionalActive

Note: DMARC p=quarantine was applied (stronger than the Cloudflare Security Insight finding suggested p=none). The Cloudflare alert for issue #988 was raised 2026-06-18 — the fix was confirmed 2026-06-23.

9.2 Escalating DMARC to p=reject

Wait until DMARC aggregate reports sent to radieu@gmail.com confirm zero failures for 2+ weeks, then:

# Read zone ID from SOPS, update record via Cloudflare API
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
$secrets = sops --decrypt --input-type dotenv --output-type dotenv secrets\monitoring.env.sops
$zoneId = ($secrets | Select-String "^CF_ZONE_ID_RADEKKONARSKI=").Line.Split("=")[1]
$cfToken = ($secrets | Select-String "^CF_API_TOKEN=").Line.Split("=",2)[1]
$headers = @{ "Authorization" = "Bearer $cfToken"; "Content-Type" = "application/json" }
# Get DMARC record ID
$records = Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$zoneId/dns_records?type=TXT&name=_dmarc.radekkonarski.com" -Headers $headers
$recordId = $records.result[0].id
# Update to p=reject
$body = @{ type="TXT"; name="_dmarc.radekkonarski.com"; content="v=DMARC1; p=reject; rua=mailto:radieu@gmail.com; sp=reject; adkim=r; aspf=r; pct=100"; ttl=1 } | ConvertTo-Json
Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$zoneId/dns_records/$recordId" -Method PUT -Headers $headers -Body $body

9.3 API Token Scope

The zone-scoped CF_API_TOKEN in secrets/monitoring.env.sops covers only zintegrowana.online.

For radekkonarski.com DNS operations use CF_RADEKKONARSKI_DNS_TOKEN — a scoped token with DNS:Write permissions on the radekkonarski.com zone only. Stored in secrets/monitoring.env.sops and GH Secret CF_RADEKKONARSKI_DNS_TOKEN. Created 2026-06-27 as part of issue #1835.

CF_GLOBAL_API_KEY scope limitation (Cloudflare architecture constraint): CF_GLOBAL_API_KEY remains required for token management operations (creating, rotating, and deleting scoped tokens) because the User:API Tokens:Edit permission is only available via Global API Key auth — it cannot be granted to a scoped token. This is a Cloudflare design constraint, not a configuration gap. CF_GLOBAL_API_KEY should not be used for routine DNS ops on either zone — use the respective scoped tokens instead.