Files
certctl/internal/service/ocsp_counters.go
T
shankar0123 21aeed4f4e legal: addlicense headers + normalize legacy variants (Phase 0 RED-4)
Phase 0 closure (Path B2, post-rewrite):

addlicense sweep — adds the canonical certctl LLC copyright + BUSL-1.1
SPDX header to every production Go file. Template:

  // Copyright 2026 certctl LLC. All rights reserved.
  // SPDX-License-Identifier: BUSL-1.1

Coverage: 338 / 338 production Go files (cmd/ + internal/, excluding
*_test.go and **/testdata/**). Pre-sweep coverage was 22 / 338 (6.5%);
post-sweep is 338 / 338 (100%).

Normalized 22 pre-existing legacy headers (`// Copyright (c) certctl`
+ `// SPDX-License-Identifier: BSL-1.1`) and 1 file using a
`Certctl Contributors` attribution. The legacy SPDX ID `BSL-1.1`
is non-standard; the official SPDX identifier for Business Source
License 1.1 is `BUSL-1.1` (capital U). All 338 files now share the
canonical form.

Generated via:
  addlicense -c "certctl LLC" -y 2026 \
    -f cowork/legal/copyright-header.tpl \
    -ignore '**/testdata/**' -ignore '**/*_test.go' \
    cmd/ internal/

Verification:
  find cmd internal -name '*.go' -not -name '*_test.go' \
    -not -path '*/testdata/*' \
    -exec grep -L '^// Copyright 2026 certctl LLC' {} \; | wc -l

  Returns: 0

gofmt clean. Header additions are comments only, no compile impact.

Closes: cowork/certctl-architecture-diligence-audit.html#fix-RED-4
2026-05-13 21:23:35 +00:00

114 lines
4.5 KiB
Go

// Copyright 2026 certctl LLC. All rights reserved.
// SPDX-License-Identifier: BUSL-1.1
package service
import "sync/atomic"
// Production hardening II Phase 1.3 — OCSP per-request counters.
//
// Mirrors the pattern in est_counters.go and scep_counters.go:
// sync/atomic primitives keep the hot path lock-free, while a snapshot
// accessor produces a stable map for the Prometheus exposition handler
// (Phase 8).
//
// Counter labels are stable strings — the Prometheus phase converts
// them into `certctl_ocsp_<label>_total` metric names. Adding a new
// label here without also adding it to the Prometheus exposer would
// be a "silent counter" bug; the exposer test in Phase 8 enumerates
// the labels to defend against drift.
// OCSPCounters is the shared counter table for OCSP request processing.
// A single instance lives on the certificate service (or the OCSP
// cache service when present) and ticks every OCSP request through
// its lifecycle:
//
// - request_get / request_post — incremented per inbound request
// by transport.
// - request_success — incremented when a signed OCSP response is
// written to the wire (regardless of cert status: good / revoked
// / unknown all count as success).
// - request_invalid — malformed request body (ocsp.ParseRequest
// failure) or path-extraction failure.
// - issuer_not_found — request's issuer_id doesn't resolve to a
// known issuer connector.
// - cert_not_found — request's serial doesn't resolve to any
// issued cert.
// - signing_failed — issuer connector returned an error.
// - nonce_echoed — request carried a well-formed nonce extension
// and the response echoed it (RFC 6960 §4.4.1 happy path).
// - nonce_malformed — request carried a nonce extension that was
// too long (>32 bytes, per CA/B Forum guidance) or empty. The
// response is unauthorized (status 6).
// - rate_limited — Phase 3 limiter tripped; the response is
// unauthorized (status 6) plus a Retry-After hint.
//
// New labels MUST also be added to OCSPCounters.Snapshot AND to the
// Prometheus exposer in Phase 8.
type OCSPCounters struct {
requestGET atomic.Uint64
requestPOST atomic.Uint64
requestSuccess atomic.Uint64
requestInvalid atomic.Uint64
issuerNotFound atomic.Uint64
certNotFound atomic.Uint64
signingFailed atomic.Uint64
nonceEchoed atomic.Uint64
nonceMalformed atomic.Uint64
rateLimited atomic.Uint64
}
// NewOCSPCounters constructs a zero-value counter table. The caller
// holds it for the process lifetime; counters are never reset.
func NewOCSPCounters() *OCSPCounters {
return &OCSPCounters{}
}
// IncRequestGET ticks the GET-form request counter.
func (c *OCSPCounters) IncRequestGET() { c.requestGET.Add(1) }
// IncRequestPOST ticks the POST-form request counter.
func (c *OCSPCounters) IncRequestPOST() { c.requestPOST.Add(1) }
// IncRequestSuccess ticks the response-written counter.
func (c *OCSPCounters) IncRequestSuccess() { c.requestSuccess.Add(1) }
// IncRequestInvalid ticks the parse-failure counter.
func (c *OCSPCounters) IncRequestInvalid() { c.requestInvalid.Add(1) }
// IncIssuerNotFound ticks the unknown-issuer counter.
func (c *OCSPCounters) IncIssuerNotFound() { c.issuerNotFound.Add(1) }
// IncCertNotFound ticks the unknown-serial counter.
func (c *OCSPCounters) IncCertNotFound() { c.certNotFound.Add(1) }
// IncSigningFailed ticks the issuer-error counter.
func (c *OCSPCounters) IncSigningFailed() { c.signingFailed.Add(1) }
// IncNonceEchoed ticks the well-formed-nonce-echoed counter.
func (c *OCSPCounters) IncNonceEchoed() { c.nonceEchoed.Add(1) }
// IncNonceMalformed ticks the bad-nonce-rejected counter.
func (c *OCSPCounters) IncNonceMalformed() { c.nonceMalformed.Add(1) }
// IncRateLimited ticks the limiter-tripped counter.
func (c *OCSPCounters) IncRateLimited() { c.rateLimited.Add(1) }
// Snapshot returns a stable map of label → counter value for the
// Prometheus exposer (Phase 8). The returned map is a copy; concurrent
// counter ticks during the snapshot read are not reflected.
func (c *OCSPCounters) Snapshot() map[string]uint64 {
return map[string]uint64{
"request_get": c.requestGET.Load(),
"request_post": c.requestPOST.Load(),
"request_success": c.requestSuccess.Load(),
"request_invalid": c.requestInvalid.Load(),
"issuer_not_found": c.issuerNotFound.Load(),
"cert_not_found": c.certNotFound.Load(),
"signing_failed": c.signingFailed.Load(),
"nonce_echoed": c.nonceEchoed.Load(),
"nonce_malformed": c.nonceMalformed.Load(),
"rate_limited": c.rateLimited.Load(),
}
}