Files
certctl/internal/api/middleware/auth_test.go
T
Shankar 269d341e50 fix: security audit remediation (AUDIT-001, 003, 004, 005, 006, 018)
- AUDIT-001: Validate OpenSSL revoke inputs (hex-only serials, RFC 5280 reasons)
- AUDIT-003: Enforce /20 CIDR size cap at API level (create + update)
- AUDIT-004: Support comma-separated CERTCTL_AUTH_SECRET for zero-downtime key rotation
- AUDIT-005: Add ReadHeaderTimeout (5s) to prevent Slowloris
- AUDIT-006: Document audit trail query parameter exclusion rationale
- AUDIT-018: Add immediate-run-on-start to short-lived expiry scheduler loop

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 14:11:16 -04:00

190 lines
5.1 KiB
Go

package middleware
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestNewAuth_MultiKeyAcceptsBothKeys(t *testing.T) {
cfg := AuthConfig{
Type: "api-key",
Secret: "key-one,key-two",
}
mw := NewAuth(cfg)
handler := mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
// First key should work
req1 := httptest.NewRequest(http.MethodGet, "/api/v1/certificates", nil)
req1.Header.Set("Authorization", "Bearer key-one")
rr1 := httptest.NewRecorder()
handler.ServeHTTP(rr1, req1)
if rr1.Code != http.StatusOK {
t.Errorf("expected 200 for first key, got %d", rr1.Code)
}
// Second key should work
req2 := httptest.NewRequest(http.MethodGet, "/api/v1/certificates", nil)
req2.Header.Set("Authorization", "Bearer key-two")
rr2 := httptest.NewRecorder()
handler.ServeHTTP(rr2, req2)
if rr2.Code != http.StatusOK {
t.Errorf("expected 200 for second key, got %d", rr2.Code)
}
}
func TestNewAuth_MultiKeyRejectsInvalidKey(t *testing.T) {
cfg := AuthConfig{
Type: "api-key",
Secret: "key-one,key-two",
}
mw := NewAuth(cfg)
handler := mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
// Invalid key should be rejected
req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates", nil)
req.Header.Set("Authorization", "Bearer wrong-key")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusUnauthorized {
t.Errorf("expected 401 for invalid key, got %d", rr.Code)
}
}
func TestNewAuth_MultiKeyWithSpaces(t *testing.T) {
// Keys with leading/trailing spaces should be trimmed
cfg := AuthConfig{
Type: "api-key",
Secret: " key-one , key-two ",
}
mw := NewAuth(cfg)
handler := mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates", nil)
req.Header.Set("Authorization", "Bearer key-one")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("expected 200 for trimmed key, got %d", rr.Code)
}
}
func TestNewAuth_SingleKeyStillWorks(t *testing.T) {
cfg := AuthConfig{
Type: "api-key",
Secret: "my-single-key",
}
mw := NewAuth(cfg)
handler := mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates", nil)
req.Header.Set("Authorization", "Bearer my-single-key")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("expected 200 for single key, got %d", rr.Code)
}
}
func TestNewAuth_NoneMode(t *testing.T) {
cfg := AuthConfig{
Type: "none",
Secret: "",
}
mw := NewAuth(cfg)
handler := mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
// No auth header needed in none mode
req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates", nil)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("expected 200 in none mode, got %d", rr.Code)
}
}
func TestNewAuth_MissingAuthHeader(t *testing.T) {
cfg := AuthConfig{
Type: "api-key",
Secret: "test-key",
}
mw := NewAuth(cfg)
handler := mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates", nil)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusUnauthorized {
t.Errorf("expected 401 for missing auth, got %d", rr.Code)
}
}
func TestNewAuth_InvalidBearerFormat(t *testing.T) {
cfg := AuthConfig{
Type: "api-key",
Secret: "test-key",
}
mw := NewAuth(cfg)
handler := mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates", nil)
req.Header.Set("Authorization", "Basic dGVzdDp0ZXN0")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusUnauthorized {
t.Errorf("expected 401 for non-Bearer auth, got %d", rr.Code)
}
}
func TestNewAuth_RemovedKeyIsRejected(t *testing.T) {
// Simulate key rotation: only key-two is configured (key-one was removed)
cfg := AuthConfig{
Type: "api-key",
Secret: "key-two",
}
mw := NewAuth(cfg)
handler := mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
// Old key should be rejected
req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates", nil)
req.Header.Set("Authorization", "Bearer key-one")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusUnauthorized {
t.Errorf("expected 401 for removed key, got %d", rr.Code)
}
// New key should work
req2 := httptest.NewRequest(http.MethodGet, "/api/v1/certificates", nil)
req2.Header.Set("Authorization", "Bearer key-two")
rr2 := httptest.NewRecorder()
handler.ServeHTTP(rr2, req2)
if rr2.Code != http.StatusOK {
t.Errorf("expected 200 for current key, got %d", rr2.Code)
}
}