mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-16 08:02:39 +00:00
Rebase of #1881 by @SaarMesh-Bot onto current master. Their three commits are preserved, two of them cherry-picked with authorship intact; the sweep itself had to be regenerated. Opened as a new PR rather than force-pushing their branch. Closes #1881 once merged. Addresses parts 1 and 3 of #1859; part 2 landed as #1937. ## Why regenerated rather than merged The sweep in #1881 was cut on 2026-09-02 07:13 and roughly forty PRs landed after it, so it went `CONFLICTING/DIRTY`. Re-running `gofmt` on current master is cheaper and less error-prone than resolving 72 conflicts that are all whitespace. The drift it fixes also grew in the meantime: 66 files now, against 72 then, but spread differently. ## The three commits 1. **`style(#1859)`** — `gofmt -w` across the 14 modules. 66 files. 2. **`test(#1859)`** — @SaarMesh-Bot's fix for the one `go vet` copylocks finding, `cmd/ingestor/coverage_boost_test.go`: the range variable copied a `Config` embedding `sync.Once`. Cherry-picked unchanged. 3. **`ci(#1859)`** — @SaarMesh-Bot's CI step that fails on gofmt drift or vet findings, plus `.git-blame-ignore-revs`. Cherry-picked with one change, noted in the commit message: the ignore file pointed at `04bc80ee`, the sweep commit on their branch, which does not exist on this base and would make `git blame --ignore-revs-file` error. Repointed at `d3a02599`, the sweep here. ## Verification The claim "formatting only" is checked twice rather than asserted: - Every changed file is byte-identical to `gofmt(previous content)`. 0 of 66 deviate. - With line comments and all whitespace stripped, 0 of 66 files differ, so no code outside comments changed. 14 of the 66 also show doc-comment reflow. Since Go 1.19 `gofmt` re-indents indented comment blocks to tabs and inserts a blank comment line before them; the behavior matrix above `resolveHopWithContext` in `cmd/ingestor/path_resolver.go` is a clear example. That is gofmt's own output, not an edit, but it is worth naming because it makes the diff look larger than "whitespace" suggests. The gate was run locally exactly as the workflow runs it: `gofmt` clean, and `go vet` clean in all 14 modules, including `cmd/ingestor` which is what commit 2 fixes. Suites: `cmd/server` ok (80.7s), `internal/packetpath` ok (2.3s), `cmd/ingestor` passes except `TestWriteStatsAtomic_SymlinkAtDestIsReplaced`, which fails identically on bare master with "A required privilege is not held by the client" (Windows symlink privilege on my host, not code). ## Sequencing This should go last in the queue. The sweep touches 66 files, so merging it before the remaining open Go PRs gives each of them a conflict about nothing but formatting. After it lands the gate is active, and any PR with drift fails CI until it runs `gofmt -w`. Excluded from the sweep: the misnamed `Dockerfile.go`, which is a Dockerfile that gofmt cannot parse (the workflow excludes it too), and `docs/DEPLOYMENT.md`, which a case-insensitive filesystem surfaces as a spurious modification against `docs/deployment.md` and is unrelated. --------- Co-authored-by: SaarMesh-Bot <300107934+SaarMesh-Bot@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
88 lines
3.2 KiB
Go
88 lines
3.2 KiB
Go
package main
|
|
|
|
// Issue #1561: detect CDN-fronted deployments and warn ONCE.
|
|
//
|
|
// When operators put CoreScope behind Cloudflare/Fastly without
|
|
// configuring a /api/* cache bypass, dashboards go stale — the origin
|
|
// emits Cache-Control: no-store (#1551), but the CDN's zone-level
|
|
// caching policy can still cache JSON responses for hours
|
|
// (cf-cache-status: HIT, age > 0). We can't fix the CDN config from
|
|
// the server side; the best we can do is detect the situation and
|
|
// loudly tell the operator at the logs.
|
|
//
|
|
// Detection: presence of any CDN-specific request header
|
|
// (CF-Connecting-IP, CF-Ray, Fastly-Client-IP, True-Client-IP).
|
|
// We deliberately exclude X-Forwarded-For and X-Real-IP: every
|
|
// generic reverse proxy (nginx, Caddy, Traefik, k8s ingress) sets
|
|
// those, so including them would warn operators who aren't behind
|
|
// a CDN at all and train them to ignore the warning entirely
|
|
// (defeating the point of #1561).
|
|
//
|
|
// Side effects: a single log line per process boot — never blocks
|
|
// the request, never modifies the response, never logs again.
|
|
|
|
import (
|
|
"log"
|
|
"net/http"
|
|
"sync"
|
|
"sync/atomic"
|
|
)
|
|
|
|
var cdnWarnOnce sync.Once
|
|
|
|
// cdnWarned is set true after the first CDN-fronted request has been
|
|
// observed and logged. Subsequent requests short-circuit before the
|
|
// per-request header scan in firstCDNHeader — a hot-path optimization
|
|
// for the steady state (warning already emitted, every /api request
|
|
// otherwise pays for 4 http.Header.Get lookups forever).
|
|
var cdnWarned atomic.Bool
|
|
|
|
// cdnHeaders are HTTP request headers injected ONLY by CDNs
|
|
// (Cloudflare, Fastly, Akamai) — never by a generic reverse proxy.
|
|
// Detected case-insensitively by http.Header.Get.
|
|
//
|
|
// X-Forwarded-For / X-Real-IP are intentionally NOT in this list:
|
|
// every nginx/Caddy/Traefik/k8s-ingress deployment sets them, so
|
|
// using them as a CDN signal produces a false positive on every
|
|
// reverse-proxied install (issue #1561 round-1 review).
|
|
var cdnHeaders = []string{
|
|
"CF-Connecting-IP", // Cloudflare
|
|
"CF-Ray", // Cloudflare
|
|
"Fastly-Client-IP", // Fastly
|
|
"True-Client-IP", // Akamai (also set by Cloudflare Enterprise)
|
|
}
|
|
|
|
// cdnDetectionMiddleware inspects each incoming request for CDN
|
|
// headers and, on the FIRST one observed, logs a single warning
|
|
// pointing the operator at docs/deployment-behind-cdn.md. The
|
|
// middleware always calls next; it never blocks or rewrites.
|
|
func cdnDetectionMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Fast path: once we've warned, skip the per-request header
|
|
// scan entirely. Steady state for any CDN-fronted deploy is
|
|
// ~every request hitting this branch.
|
|
if cdnWarned.Load() {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
if hdr := firstCDNHeader(r.Header); hdr != "" {
|
|
cdnWarnOnce.Do(func() {
|
|
log.Printf("[security] WARNING: detected request via CDN (%s header present). "+
|
|
"Ensure /api/* is bypassed in your CDN config — see docs/deployment-behind-cdn.md. "+
|
|
"Cached API responses cause observer-flap and incorrect dashboards.", hdr)
|
|
cdnWarned.Store(true)
|
|
})
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func firstCDNHeader(h http.Header) string {
|
|
for _, name := range cdnHeaders {
|
|
if h.Get(name) != "" {
|
|
return name
|
|
}
|
|
}
|
|
return ""
|
|
}
|