Files
zeekay dba3a60c34 cosigner: external-custodian co-signing leaf module
Decomplect Fireblocks / Utila / Anchorage co-signing out of luxfi/bridge
into one orthogonal, stdlib-only leaf. Wire-shape validation keeps secret
material off the wire; env + KMS secret stores fetch the secret half by
public id; the real Fireblocks REST + RS256-JWT family signs, the Utila and
Anchorage families fail loud until their vendor clients land; the dispatcher
runs families in parallel behind an AllApproved gate.

Errors name the cosigner KIND and never echo the resolved URI, tenant id,
or secret path.
2026-06-27 15:48:14 -07:00

211 lines
5.7 KiB
Go

package secrets
import (
"context"
"errors"
"os"
"path/filepath"
"strings"
"testing"
)
func TestResolver_LiteralScheme(t *testing.T) {
got, err := Default().Resolve(context.Background(), "literal:hello-world")
if err != nil {
t.Fatalf("err: %v", err)
}
if got != "hello-world" {
t.Errorf("got %q, want %q", got, "hello-world")
}
}
func TestResolver_UnprefixedIsLiteral_BackCompat(t *testing.T) {
// Every bare secret value (e.g. a raw PEM) must be treated as
// literal so existing deploys keep working without changes.
got, err := Default().Resolve(context.Background(), "bare-token-value")
if err != nil {
t.Fatalf("err: %v", err)
}
if got != "bare-token-value" {
t.Errorf("got %q, want %q", got, "bare-token-value")
}
}
func TestResolver_EnvScheme(t *testing.T) {
t.Setenv("COSIGNER_TEST_SECRET", "the-shared-token")
got, err := Default().Resolve(context.Background(), "env:COSIGNER_TEST_SECRET")
if err != nil {
t.Fatalf("err: %v", err)
}
if got != "the-shared-token" {
t.Errorf("got %q, want %q", got, "the-shared-token")
}
}
func TestResolver_EnvScheme_UnsetVar(t *testing.T) {
_, err := Default().Resolve(context.Background(), "env:DOES_NOT_EXIST_xx_COSIGNER_TEST")
if err == nil {
t.Fatal("expected error for unset env var")
}
if !strings.Contains(err.Error(), "unset") {
t.Errorf("error %q should mention 'unset'", err.Error())
}
}
func TestResolver_EnvScheme_EmptyName(t *testing.T) {
_, err := Default().Resolve(context.Background(), "env:")
if err == nil {
t.Fatal("expected error for empty env name")
}
}
func TestResolver_FileScheme(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "secret.pem")
if err := os.WriteFile(path, []byte("-----BEGIN-----\nbody\n-----END-----\n"), 0o600); err != nil {
t.Fatal(err)
}
got, err := Default().Resolve(context.Background(), "file:"+path)
if err != nil {
t.Fatalf("err: %v", err)
}
want := "-----BEGIN-----\nbody\n-----END-----"
if got != want {
t.Errorf("got %q, want %q (trailing \\n should be stripped)", got, want)
}
}
func TestResolver_FileScheme_CRLF(t *testing.T) {
// Windows-style files: trailing \r\n should be stripped fully.
dir := t.TempDir()
path := filepath.Join(dir, "secret.crlf")
if err := os.WriteFile(path, []byte("token-value\r\n"), 0o600); err != nil {
t.Fatal(err)
}
got, err := Default().Resolve(context.Background(), "file:"+path)
if err != nil {
t.Fatalf("err: %v", err)
}
if got != "token-value" {
t.Errorf("got %q, want %q", got, "token-value")
}
}
func TestResolver_FileScheme_MissingPath(t *testing.T) {
_, err := Default().Resolve(context.Background(), "file:")
if err == nil {
t.Fatal("expected error for empty path")
}
}
func TestResolver_FileScheme_NotExist(t *testing.T) {
_, err := Default().Resolve(context.Background(), "file:/no/such/path/xyz123")
if err == nil {
t.Fatal("expected error for missing file")
}
}
func TestResolver_KMSScheme_NoProvider(t *testing.T) {
_, err := Default().Resolve(context.Background(), "kms:aws:us-east-1:cipher:xxx")
if err == nil {
t.Fatal("expected ErrKMSNotRegistered")
}
if !errors.Is(err, ErrKMSNotRegistered) {
t.Errorf("err = %v, want ErrKMSNotRegistered", err)
}
}
func TestResolver_KMSScheme_MalformedNoFamily(t *testing.T) {
_, err := Default().Resolve(context.Background(), "kms:onlyfamily")
if err == nil {
t.Fatal("expected error — kms URI must be kms:<family>:<opaque>")
}
}
// fakeKMS is a test KMSProvider that maps opaque strings back to plaintext.
type fakeKMS struct {
plaintext map[string]string
}
func (f *fakeKMS) Decrypt(_ context.Context, opaque string) (string, error) {
pt, ok := f.plaintext[opaque]
if !ok {
return "", errors.New("fakeKMS: opaque not found")
}
return pt, nil
}
func TestResolver_KMSScheme_HappyPath(t *testing.T) {
prev := kmsRegistry["fake"]
t.Cleanup(func() {
if prev == nil {
delete(kmsRegistry, "fake")
} else {
kmsRegistry["fake"] = prev
}
})
RegisterKMS("fake", &fakeKMS{plaintext: map[string]string{
"cipher-abc": "decrypted-token",
}})
got, err := Default().Resolve(context.Background(), "kms:fake:cipher-abc")
if err != nil {
t.Fatalf("err: %v", err)
}
if got != "decrypted-token" {
t.Errorf("got %q, want %q", got, "decrypted-token")
}
}
func TestResolver_UnknownScheme(t *testing.T) {
_, err := Default().Resolve(context.Background(), "vault:secret/data/cosigner/fb-pem")
if err == nil {
t.Fatal("expected ErrUnknownScheme")
}
if !errors.Is(err, ErrUnknownScheme) {
t.Errorf("err = %v, want ErrUnknownScheme", err)
}
}
func TestSplitScheme_RejectsWindowsPath(t *testing.T) {
// `C:/path/to/file` is a bare Windows path, NOT a `C:` scheme.
// splitScheme should reject it (uppercase scheme chars not allowed).
scheme, _, ok := splitScheme("C:/path/to/file")
if ok {
t.Errorf("splitScheme accepted Windows path as scheme=%q", scheme)
}
}
func TestSplitScheme_RejectsBareIP(t *testing.T) {
// "1.2.3.4:5000" should not be parsed as scheme=1.
scheme, _, ok := splitScheme("1.2.3.4:5000")
if ok {
t.Errorf("splitScheme accepted IP:port as scheme=%q", scheme)
}
}
func TestRegisterKMS_RejectsNilOrEmpty(t *testing.T) {
prevCount := len(kmsRegistry)
RegisterKMS("", &fakeKMS{})
RegisterKMS("validname", nil)
if len(kmsRegistry) != prevCount {
t.Errorf("registry grew after invalid Register calls — want size unchanged")
}
}
func TestMustResolve_PanicsOnError(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("MustResolve should panic on error")
}
}()
MustResolve(context.Background(), Default(), "env:DOES_NOT_EXIST_xx")
}
func TestMustResolve_HappyPath(t *testing.T) {
got := MustResolve(context.Background(), Default(), "literal:abc")
if got != "abc" {
t.Errorf("got %q, want %q", got, "abc")
}
}