<#816 osctrl-tls: hardening — constant-time secret...
# osctrl
g
#816 osctrl-tls: hardening — constant-time secrets, per-IP enroll rate-limit, audit-log on failed enrolls (round 1b of 3) Pull request opened by alvarofraguas Carves the osctrl-tls-facing changes out of #813 into their own PR, as requested in #813 (comment), so the TLS surface can be reviewed and tested in isolation.
Stack order: depends on #813. The shared infrastructure consumed here —
pkg/auditlog.FailedEnroll
,
pkg/ratelimit
,
utils.SetTrustedProxies
/ updated
GetIP
— ships in #813. If you read the diff here against
main
it will include those #813 changes; the carve-out is the 5 files under
cmd/tls/
+
deploy/config/tls.yml
. Once #813 merges this PR's diff drops to those 5 files only.
What this changes 5 files in
cmd/tls/
+
deploy/config/tls.yml
. Three independent defenses on the only osctrl service that is internet-facing in a typical deployment: 1. Constant-time secret comparison
cmd/tls/handlers/utils.go
checkValidSecret
and
checkValidRemovePath
use
subtle.ConstantTimeCompare
instead of the previous byte-by-byte short-circuiting compare. Removes a response-time timing oracle on the public enroll endpoint. 2. Per-IP rate-limit on
/enroll
cmd/tls/main.go
— 20 req/min per remote IP, idle eviction after 10 min, backed by
pkg/ratelimit
(from #813). Rejected requests: • return
429
• record
AuditLog.FailedEnroll(ip, env, "rate limit exceeded", 0)
so SoC tooling can alert 3. Audit-log on failed enrollment
cmd/tls/handlers/handlers.go
HandlersTLS
gains an
AuditLog
field +
WithAuditLog
option; defensive default is a disabled manager so handler code can call
h.AuditLog.FailedEnroll(...)
without nil-checks. •
cmd/tls/handlers/post.go
EnrollHandler
records
FailedEnroll
on the invalid-secret path. •
cmd/tls/main.go
— initializes
auditlog.CreateAuditLogManager
and threads it via
WithAuditLog
. Service name
osctrl-tls
flows through. 4. Trusted-proxies plumbing
cmd/tls/main.go
— reads
flagParams.Service.TrustedProxies
(CIDR list) and calls
utils.SetTrustedProxies
before any request reaches
GetIP
. Empty default =
GetIP
ignores
X-Forwarded-For
/
X-Real-IP
, preventing internet-side header-spoofed bypass of the rate-limiter or audit-log poisoning. Config `deploy/config/tls.yml`: •
auditLog: true
(on by default; was
false
) •
trustedProxies: ""
(empty default; operators behind a trusted reverse proxy can list CIDRs) Verified
go build ./...
clean •
go vet ./...
clean •
go test ./cmd/tls/...
green • Live smoke on a dev stack: 4 osquery nodes enrolled,
audit_logs
records
enroll_failure
rows with correct IPs for forced bad-secret attempts Test plan • CI: lint + build + tests pass once workflows are approved • Manual: with
trustedProxies
empty, confirm
X-Forwarded-For: 1.2.3.4
doesn't override
RemoteAddr
in
audit_logs
• Manual: trigger >20 enroll requests from the same IP in <1 min, confirm 429 + audit-log entry • Manual: bad enroll-secret produces an
enroll_failure
audit-log row with the source IP jmpsec/osctrl