Implement a two-level key hierarchy: the MEK now wraps per-engine DEKs stored in a new barrier_keys table, rather than encrypting all barrier entries directly. A v2 ciphertext format (0x02) embeds the key ID so the barrier can resolve which DEK to use on decryption. v1 ciphertext remains supported for backward compatibility. Key changes: - crypto: EncryptV2/DecryptV2/ExtractKeyID for v2 ciphertext with key IDs - barrier: key registry (CreateKey, RotateKey, ListKeys, MigrateToV2, ReWrapKeys) - seal: RotateMEK re-wraps DEKs without re-encrypting data - engine: Mount auto-creates per-engine DEK - REST + gRPC: barrier/keys, barrier/rotate-mek, barrier/rotate-key, barrier/migrate - proto: BarrierService (v1 + v2) with ListKeys, RotateMEK, RotateKey, Migrate - db: migration v2 adds barrier_keys table Also includes: security audit report, CSRF protection, engine design specs (sshca, transit, user), path-bound AAD migration tool, policy engine enhancements, and ARCHITECTURE.md updates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
106 lines
3.0 KiB
Go
106 lines
3.0 KiB
Go
package webserver
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestCSRFTokenGenerateAndValidate(t *testing.T) {
|
|
c := newCSRFProtect()
|
|
token, err := c.generateToken()
|
|
if err != nil {
|
|
t.Fatalf("generateToken: %v", err)
|
|
}
|
|
if !c.validToken(token) {
|
|
t.Fatal("valid token rejected")
|
|
}
|
|
}
|
|
|
|
func TestCSRFTokenInvalidFormats(t *testing.T) {
|
|
c := newCSRFProtect()
|
|
for _, bad := range []string{"", "nodot", "a.b.c", "abc.def"} {
|
|
if c.validToken(bad) {
|
|
t.Errorf("should reject %q", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCSRFTokenCrossSecret(t *testing.T) {
|
|
c1 := newCSRFProtect()
|
|
c2 := newCSRFProtect()
|
|
token, _ := c1.generateToken()
|
|
if c2.validToken(token) {
|
|
t.Fatal("token from different secret should be rejected")
|
|
}
|
|
}
|
|
|
|
func TestCSRFMiddlewareAllowsGET(t *testing.T) {
|
|
c := newCSRFProtect()
|
|
handler := c.middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("GET should pass through, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestCSRFMiddlewareBlocksPOSTWithoutToken(t *testing.T) {
|
|
c := newCSRFProtect()
|
|
handler := c.middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("foo=bar"))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
if w.Code != http.StatusForbidden {
|
|
t.Fatalf("POST without CSRF token should be forbidden, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestCSRFMiddlewareAllowsPOSTWithValidToken(t *testing.T) {
|
|
c := newCSRFProtect()
|
|
token, _ := c.generateToken()
|
|
|
|
handler := c.middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
|
|
body := csrfFieldName + "=" + token
|
|
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
req.AddCookie(&http.Cookie{Name: csrfCookieName, Value: token})
|
|
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("POST with valid CSRF token should pass, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestCSRFMiddlewareRejectsMismatch(t *testing.T) {
|
|
c := newCSRFProtect()
|
|
token1, _ := c.generateToken()
|
|
token2, _ := c.generateToken()
|
|
|
|
handler := c.middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
|
|
body := csrfFieldName + "=" + token1
|
|
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
req.AddCookie(&http.Cookie{Name: csrfCookieName, Value: token2})
|
|
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
if w.Code != http.StatusForbidden {
|
|
t.Fatalf("POST with mismatched tokens should be forbidden, got %d", w.Code)
|
|
}
|
|
}
|