mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 21:41:39 +00:00
ci: re-fix CodeQL #32 + repair loadtest f5-mock build context
Two unrelated CI failures from run #25305811340; fixed in one
commit since neither needs the other to land first.
CodeQL alert #32 (go/log-injection at middleware.go:68) reopened
after edb71fb. The previous fix introduced a scrubLogValue helper
backed by strings.NewReplacer; CodeQL's taint tracker only
recognizes the literal strings.ReplaceAll pattern as a sanitizer
(matches the OWASP example in the rule docs). Wrapper helpers and
NewReplacer don't trigger the recognition, so the analyzer kept
flagging.
Fix: drop the helper. Inline strings.ReplaceAll chains directly at
the call site for r.Method and r.URL.Path. Same runtime semantics
(strip CR/LF/NUL); CodeQL pattern-matches the literal call so the
alert can finally close.
Loadtest CI failure (run #25305811340 'k6 throughput run' job at
make loadtest):
ERROR: failed to compute cache key: failed to calculate checksum
of ref ...: "/deploy/test/f5-mock-icontrol": not found
The f5-mock-icontrol Dockerfile has `COPY deploy/test/f5-mock-icontrol/
./` which assumes the build context is the repo root. The
docker-compose.test.yml f5-mock-icontrol service correctly uses the
long-form build:
build:
context: .. # = repo root from deploy/docker-compose.test.yml
dockerfile: deploy/test/f5-mock-icontrol/Dockerfile
The loadtest compose at deploy/test/loadtest/docker-compose.yml
used the shorthand:
build: ../f5-mock-icontrol
That sets context = the f5-mock-icontrol directory itself, breaking
the Dockerfile's COPY (it tries to find the directory inside itself).
Fix: change the loadtest compose to the long-form pattern matching
docker-compose.test.yml, with context: ../../.. (= repo root from
deploy/test/loadtest/) and explicit dockerfile path.
Verified locally:
gofmt: clean.
go vet ./internal/api/middleware/...: exit 0.
go test -short -count=1 ./internal/api/middleware/...: ok 0.253s.
python3 -c 'import yaml; yaml.safe_load(...)' on the compose
file: parses clean.
grep -rnE 'scrubLogValue' internal/api/: zero references (helper
fully dropped).
References:
https://github.com/certctl-io/certctl/security/code-scanning/32
CI run https://github.com/certctl-io/certctl/actions/runs/25305811340
Closes CodeQL #32 + restores loadtest CI.
This commit is contained in:
@@ -290,7 +290,15 @@ services:
|
||||
# /healthz endpoint.
|
||||
# ---------------------------------------------------------------------------
|
||||
f5-mock-target:
|
||||
build: ../f5-mock-icontrol
|
||||
# Long-form build to match docker-compose.test.yml: the Dockerfile
|
||||
# has `COPY deploy/test/f5-mock-icontrol/ ./` which assumes the
|
||||
# build context is the REPO ROOT. The previous shorthand form
|
||||
# `build: ../f5-mock-icontrol` set the context to the
|
||||
# f5-mock-icontrol directory itself, breaking the COPY at CI build
|
||||
# time (run #25305811340: "deploy/test/f5-mock-icontrol: not found").
|
||||
build:
|
||||
context: ../../..
|
||||
dockerfile: deploy/test/f5-mock-icontrol/Dockerfile
|
||||
container_name: certctl-loadtest-f5-mock
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q -O- http://localhost:8080/healthz || exit 1"]
|
||||
|
||||
@@ -50,7 +50,14 @@ func RequestID(next http.Handler) http.Handler {
|
||||
// percent-decoded path segments in r.URL.Path, which can include CR/LF
|
||||
// in the decoded form even though the raw HTTP request line cannot).
|
||||
// strings.ReplaceAll on CR/LF/NUL strips the forgery vector before the
|
||||
// log line is emitted. Closes CodeQL #17 (go/log-injection).
|
||||
// log line is emitted. Closes CodeQL #17 + #32 (go/log-injection).
|
||||
//
|
||||
// The replacement is intentionally inlined at the call site (literal
|
||||
// strings.ReplaceAll chains) because CodeQL's go/log-injection
|
||||
// taint tracker only recognizes that exact pattern as a sanitizer —
|
||||
// strings.NewReplacer / wrapper helpers don't trigger the recognition,
|
||||
// reopening the alert. The OWASP example in the CodeQL rule docs uses
|
||||
// the same pattern.
|
||||
func Logging(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
@@ -62,29 +69,20 @@ func Logging(next http.Handler) http.Handler {
|
||||
|
||||
duration := time.Since(start)
|
||||
requestID := getRequestID(r.Context())
|
||||
log.Printf("[%s] %s %s %d %v",
|
||||
requestID,
|
||||
scrubLogValue(r.Method),
|
||||
scrubLogValue(r.URL.Path),
|
||||
wrapped.statusCode,
|
||||
duration,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// scrubLogValue strips control characters that an attacker could use to
|
||||
// forge log entries (CWE-117). The replacement is structural, not
|
||||
// destructive: we collapse CR/LF/NUL to a single space rather than
|
||||
// dropping them, so an operator scanning the log can still see that the
|
||||
// field was present (just neutralized). Defense in depth: the
|
||||
// production middleware should prefer NewLogging (slog with structured
|
||||
// fields) where the logger handles escape on its own.
|
||||
func scrubLogValue(v string) string {
|
||||
if !strings.ContainsAny(v, "\r\n\x00") {
|
||||
return v
|
||||
}
|
||||
r := strings.NewReplacer("\r", " ", "\n", " ", "\x00", " ")
|
||||
return r.Replace(v)
|
||||
// Strip CR/LF/NUL from attacker-controllable request fields
|
||||
// before logging. Inlined per CodeQL #32 — the ReplaceAll
|
||||
// chain is the pattern the analyzer pattern-matches as a
|
||||
// sanitizer.
|
||||
method := strings.ReplaceAll(r.Method, "\n", "")
|
||||
method = strings.ReplaceAll(method, "\r", "")
|
||||
method = strings.ReplaceAll(method, "\x00", "")
|
||||
urlPath := strings.ReplaceAll(r.URL.Path, "\n", "")
|
||||
urlPath = strings.ReplaceAll(urlPath, "\r", "")
|
||||
urlPath = strings.ReplaceAll(urlPath, "\x00", "")
|
||||
|
||||
log.Printf("[%s] %s %s %d %v", requestID, method, urlPath, wrapped.statusCode, duration)
|
||||
})
|
||||
}
|
||||
|
||||
// NewLogging creates a structured logging middleware using slog.
|
||||
|
||||
Reference in New Issue
Block a user