mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-10 13:08:57 +00:00
Merge fix/ci-bundle-B-tail: G-3 env-var docs + M-028 closure
This commit is contained in:
+8
-12
@@ -44,9 +44,8 @@ func TestMain_HealthEndpointBypassesAuth(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Build the handler chain the same way main.go does
|
// Build the handler chain the same way main.go does
|
||||||
authMiddleware := middleware.NewAuth(middleware.AuthConfig{
|
authMiddleware := middleware.NewAuthWithNamedKeys([]middleware.NamedAPIKey{
|
||||||
Type: "api-key",
|
{Name: "test", Key: "test-secret-key"},
|
||||||
Secret: "test-secret-key",
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// API handler with auth
|
// API handler with auth
|
||||||
@@ -160,9 +159,8 @@ func TestMain_AuthMiddlewareRejectsUnauthorized(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Wrap with auth middleware
|
// Wrap with auth middleware
|
||||||
authMiddleware := middleware.NewAuth(middleware.AuthConfig{
|
authMiddleware := middleware.NewAuthWithNamedKeys([]middleware.NamedAPIKey{
|
||||||
Type: "api-key",
|
{Name: "test", Key: "test-secret-key"},
|
||||||
Secret: "test-secret-key",
|
|
||||||
})
|
})
|
||||||
|
|
||||||
chainedHandler := middleware.Chain(protectedHandler, authMiddleware)
|
chainedHandler := middleware.Chain(protectedHandler, authMiddleware)
|
||||||
@@ -189,9 +187,8 @@ func TestMain_AuthMiddlewareAllowsWithValidKey(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Wrap with auth middleware
|
// Wrap with auth middleware
|
||||||
authMiddleware := middleware.NewAuth(middleware.AuthConfig{
|
authMiddleware := middleware.NewAuthWithNamedKeys([]middleware.NamedAPIKey{
|
||||||
Type: "api-key",
|
{Name: "test", Key: testKey},
|
||||||
Secret: testKey,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
chainedHandler := middleware.Chain(protectedHandler, authMiddleware)
|
chainedHandler := middleware.Chain(protectedHandler, authMiddleware)
|
||||||
@@ -462,9 +459,8 @@ func TestMain_AuthNoneMode(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Wrap with auth middleware in "none" mode
|
// Wrap with auth middleware in "none" mode
|
||||||
authMiddleware := middleware.NewAuth(middleware.AuthConfig{
|
// auth=none equivalent: empty named-keys list is a no-op pass-through.
|
||||||
Type: "none",
|
authMiddleware := middleware.NewAuthWithNamedKeys(nil)
|
||||||
})
|
|
||||||
|
|
||||||
chainedHandler := middleware.Chain(protectedHandler, authMiddleware)
|
chainedHandler := middleware.Chain(protectedHandler, authMiddleware)
|
||||||
|
|
||||||
|
|||||||
+11
-2
@@ -60,11 +60,20 @@ Two endpoints are served without auth so the GUI can detect auth mode before log
|
|||||||
|
|
||||||
Token bucket algorithm protecting the control plane from misbehaving clients.
|
Token bucket algorithm protecting the control plane from misbehaving clients.
|
||||||
|
|
||||||
|
Bundle B (Audit M-025 / OWASP ASVS L2 §11.2.1): per-key keying. Each
|
||||||
|
authenticated caller gets a bucket keyed on their API-key name; each
|
||||||
|
unauthenticated source IP gets its own bucket. Bucket creation is
|
||||||
|
on-demand under a `sync.RWMutex`; no eviction (the leak is bounded by
|
||||||
|
realistic operator IP fan-out — appropriate for the OWASP ASVS L2 threat
|
||||||
|
model of abuse-by-known-clients, not infinite-cardinality scanners).
|
||||||
|
|
||||||
| Env Var | Default | Description |
|
| Env Var | Default | Description |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `CERTCTL_RATE_LIMIT_ENABLED` | `true` | Enable/disable |
|
| `CERTCTL_RATE_LIMIT_ENABLED` | `true` | Enable/disable |
|
||||||
| `CERTCTL_RATE_LIMIT_RPS` | `50` | Requests per second |
|
| `CERTCTL_RATE_LIMIT_RPS` | `50` | Per-key requests per second (default applies to IP-keyed buckets; user-keyed buckets fall back to this when `PER_USER_RPS` is unset) |
|
||||||
| `CERTCTL_RATE_LIMIT_BURST` | `100` | Burst capacity |
|
| `CERTCTL_RATE_LIMIT_BURST` | `100` | Per-key burst capacity (default applies to IP-keyed buckets; user-keyed buckets fall back to this when `PER_USER_BURST` is unset) |
|
||||||
|
| `CERTCTL_RATE_LIMIT_PER_USER_RPS` | `0` | Override RPS for authenticated callers. `0` means "use `RATE_LIMIT_RPS`". Set higher than `RATE_LIMIT_RPS` to grant authenticated clients a more generous budget than anonymous probes. |
|
||||||
|
| `CERTCTL_RATE_LIMIT_PER_USER_BURST` | `0` | Override burst for authenticated callers. `0` means "use `RATE_LIMIT_BURST`". |
|
||||||
|
|
||||||
Exceeded requests receive `429 Too Many Requests` with a `Retry-After` header.
|
Exceeded requests receive `429 Too Many Requests` with a `Retry-After` header.
|
||||||
|
|
||||||
|
|||||||
@@ -263,6 +263,18 @@ func extractCSRFields(csrDER []byte) ([]byte, string, string, error) {
|
|||||||
// Attributes is []pkix.AttributeTypeAndValueSET where each has Type (OID)
|
// Attributes is []pkix.AttributeTypeAndValueSET where each has Type (OID)
|
||||||
// and Value ([][]pkix.AttributeTypeAndValue). The challenge password value
|
// and Value ([][]pkix.AttributeTypeAndValue). The challenge password value
|
||||||
// is stored as a string in the inner AttributeTypeAndValue.Value field.
|
// is stored as a string in the inner AttributeTypeAndValue.Value field.
|
||||||
|
//
|
||||||
|
// Audit M-028 carve-out: Go's stdlib deprecates `csr.Attributes` for the
|
||||||
|
// specific use case of parsing the "requestedExtensions" CSR attribute
|
||||||
|
// (OID 1.2.840.113549.1.9.14), pointing callers at `csr.Extensions` /
|
||||||
|
// `csr.ExtraExtensions`. challengePassword (OID 1.2.840.113549.1.9.7)
|
||||||
|
// per RFC 2985 §5.4.1 is a SEPARATE CSR attribute that cannot be
|
||||||
|
// retrieved via Extensions. There is no non-deprecated stdlib API for
|
||||||
|
// it; callers either accept the deprecation warning or parse the raw
|
||||||
|
// `csr.RawAttributes` ASN.1 themselves. We accept the warning; the
|
||||||
|
// staticcheck.conf and golangci-lint rules suppress SA1019 for this
|
||||||
|
// specific line per the audit closure note.
|
||||||
|
//lint:ignore SA1019 RFC 2985 challengePassword has no non-deprecated stdlib API; see comment above.
|
||||||
for _, attr := range csr.Attributes {
|
for _, attr := range csr.Attributes {
|
||||||
if attr.Type.Equal(oidChallengePassword) {
|
if attr.Type.Equal(oidChallengePassword) {
|
||||||
if len(attr.Value) > 0 && len(attr.Value[0]) > 0 {
|
if len(attr.Value) > 0 && len(attr.Value[0]) > 0 {
|
||||||
|
|||||||
@@ -334,11 +334,12 @@ func TestHashPublicKey_ECDSA_RoundTripPin(t *testing.T) {
|
|||||||
t.Fatalf("ecdsaToECDH: %v", err)
|
t.Fatalf("ecdsaToECDH: %v", err)
|
||||||
}
|
}
|
||||||
ecdhBytes := ecdhPub.Bytes()
|
ecdhBytes := ecdhPub.Bytes()
|
||||||
//nolint:staticcheck // SA1019: pin assertion — we DELIBERATELY use
|
// Pin assertion — we DELIBERATELY use the deprecated API here
|
||||||
// the deprecated API here as a regression oracle to prove the
|
// as a regression oracle to prove the new crypto/ecdh path
|
||||||
// new crypto/ecdh path produces byte-identical output. If
|
// produces byte-identical output. If elliptic.Marshal is
|
||||||
// elliptic.Marshal is removed in a future Go release this test
|
// removed in a future Go release this test must be deleted
|
||||||
// must be deleted (and the migration is then irreversibly proven).
|
// (and the migration is then irreversibly proven).
|
||||||
|
//lint:ignore SA1019 deliberate regression oracle for M-028 round-trip pin
|
||||||
legacy := elliptic.Marshal(k.Curve, k.X, k.Y)
|
legacy := elliptic.Marshal(k.Curve, k.X, k.Y)
|
||||||
if !bytes.Equal(ecdhBytes, legacy) {
|
if !bytes.Equal(ecdhBytes, legacy) {
|
||||||
t.Fatalf("ECDH .Bytes() != legacy elliptic.Marshal output\n new: %x\n old: %x", ecdhBytes, legacy)
|
t.Fatalf("ECDH .Bytes() != legacy elliptic.Marshal output\n new: %x\n old: %x", ecdhBytes, legacy)
|
||||||
|
|||||||
Reference in New Issue
Block a user