package secret import ( "encoding/base64" "os" "path/filepath" "strings" "testing" ) func TestKeyringWrapRoundTrip(t *testing.T) { k, err := NewKeyring(randBytes(32)) if err != nil { t.Fatal(err) } wrapped, err := k.Wrap("vault-1", "the-vault-password") if err != nil { t.Fatal(err) } if wrapped == "the-vault-password" { t.Fatal("the secret was stored in the clear") } got, err := k.Unwrap("vault-1", wrapped) if err != nil || got != "the-vault-password" { t.Fatalf("unwrap = %q, %v", got, err) } } func TestWrappedSecretIsBoundToItsVault(t *testing.T) { k, _ := NewKeyring(randBytes(32)) wrapped, _ := k.Wrap("vault-1", "secret") if _, err := k.Unwrap("vault-2", wrapped); err == nil { t.Fatal("a secret wrapped for one vault opened under another id") } } func TestAnotherMasterKeyCannotUnwrap(t *testing.T) { a, _ := NewKeyring(randBytes(32)) b, _ := NewKeyring(randBytes(32)) wrapped, _ := a.Wrap("v", "secret") if _, err := b.Unwrap("v", wrapped); err == nil { t.Fatal("a different master key opened the secret") } } func TestLoadKeyringCreatesThenReusesTheKeyFile(t *testing.T) { t.Setenv(MasterKeyEnv, "") path := filepath.Join(t.TempDir(), "keys", "master.key") first, err := LoadKeyring(path) if err != nil { t.Fatal(err) } fi, err := os.Stat(path) if err != nil { t.Fatal(err) } if fi.Mode().Perm() != 0o600 { t.Fatalf("master key file mode = %v", fi.Mode().Perm()) } wrapped, _ := first.Wrap("v", "secret") second, err := LoadKeyring(path) if err != nil { t.Fatal(err) } if got, err := second.Unwrap("v", wrapped); err != nil || got != "secret" { t.Fatalf("reloaded keyring: %q, %v", got, err) } } func TestLoadKeyringPrefersTheEnvironment(t *testing.T) { key := randBytes(32) t.Setenv(MasterKeyEnv, base64.StdEncoding.EncodeToString(key)) path := filepath.Join(t.TempDir(), "master.key") k, err := LoadKeyring(path) if err != nil { t.Fatal(err) } if _, err := os.Stat(path); !os.IsNotExist(err) { t.Fatal("a key file was written although the environment supplied the key") } direct, _ := NewKeyring(key) wrapped, _ := direct.Wrap("v", "secret") if got, _ := k.Unwrap("v", wrapped); got != "secret" { t.Fatal("the environment key was not the one used") } } func TestLoadKeyringRejectsAShortKey(t *testing.T) { t.Setenv(MasterKeyEnv, base64.StdEncoding.EncodeToString([]byte("too short"))) if _, err := LoadKeyring(""); err == nil { t.Fatal("a short master key was accepted") } } // wrapWith is a helper: build a keyring from a known key and wrap a secret // with it, so another keyring can be checked for being the same key. func wrapWith(t *testing.T, key []byte) string { t.Helper() k, err := NewKeyring(key) if err != nil { t.Fatal(err) } wrapped, err := k.Wrap("v", "secret") if err != nil { t.Fatal(err) } return wrapped } // sameKey reports whether k is the keyring that produced wrapped. func sameKey(t *testing.T, k *Keyring, wrapped string) bool { t.Helper() got, err := k.Unwrap("v", wrapped) return err == nil && got == "secret" } func TestMasterKeyFileEnvReadsAProvisionedSecret(t *testing.T) { key := randBytes(32) wrapped := wrapWith(t, key) for _, tc := range []struct { name string content []byte }{ {"base64", []byte(base64.StdEncoding.EncodeToString(key))}, {"base64 with a trailing newline", []byte(base64.StdEncoding.EncodeToString(key) + "\n")}, {"base64 without padding", []byte(base64.RawStdEncoding.EncodeToString(key))}, {"32 raw bytes", key}, } { t.Run(tc.name, func(t *testing.T) { path := filepath.Join(t.TempDir(), "secret") if err := os.WriteFile(path, tc.content, 0o400); err != nil { t.Fatal(err) } t.Setenv(MasterKeyEnv, "") t.Setenv(MasterKeyFileEnv, path) k, err := LoadKeyring("") if err != nil { t.Fatal(err) } if !sameKey(t, k, wrapped) { t.Fatal("the key read from the secret file is not the one written") } }) } } // A secret that was supposed to be mounted and is not must be reported. A // new key minted in its place would open none of the existing vaults. func TestMissingSecretFileIsReportedAndMintsNothing(t *testing.T) { dir := t.TempDir() missing := filepath.Join(dir, "not-mounted") configured := filepath.Join(dir, "master.key") t.Setenv(MasterKeyEnv, "") t.Setenv(MasterKeyFileEnv, missing) _, err := LoadKeyring(configured) if err == nil { t.Fatal("a missing secret file was accepted") } if !strings.Contains(err.Error(), MasterKeyFileEnv) { t.Fatalf("the error does not name the variable: %v", err) } if _, err := os.Stat(configured); !os.IsNotExist(err) { t.Fatal("a replacement key was minted at the configured path") } if _, err := os.Stat(missing); !os.IsNotExist(err) { t.Fatal("a key was written where the secret should have been mounted") } } func TestMasterKeyEnvWinsOverTheSecretFile(t *testing.T) { envKey, fileKey := randBytes(32), randBytes(32) wrappedByEnv := wrapWith(t, envKey) path := filepath.Join(t.TempDir(), "secret") if err := os.WriteFile(path, fileKey, 0o400); err != nil { t.Fatal(err) } t.Setenv(MasterKeyEnv, base64.StdEncoding.EncodeToString(envKey)) t.Setenv(MasterKeyFileEnv, path) k, err := LoadKeyring("") if err != nil { t.Fatal(err) } if !sameKey(t, k, wrappedByEnv) { t.Fatal("the secret file took precedence over the environment key") } } func TestSecretFileWinsOverTheConfiguredPath(t *testing.T) { secretKey := randBytes(32) wrapped := wrapWith(t, secretKey) dir := t.TempDir() secretPath := filepath.Join(dir, "provisioned") if err := os.WriteFile(secretPath, secretKey, 0o400); err != nil { t.Fatal(err) } configured := filepath.Join(dir, "master.key") if err := os.WriteFile(configured, []byte(base64.StdEncoding.EncodeToString(randBytes(32))), 0o600); err != nil { t.Fatal(err) } t.Setenv(MasterKeyEnv, "") t.Setenv(MasterKeyFileEnv, secretPath) k, err := LoadKeyring(configured) if err != nil { t.Fatal(err) } if !sameKey(t, k, wrapped) { t.Fatal("the configured path took precedence over the provisioned secret") } } func TestSecretFileContentIsValidated(t *testing.T) { for _, content := range []string{"", "not base64 at all !!", "c2hvcnQ="} { path := filepath.Join(t.TempDir(), "secret") if err := os.WriteFile(path, []byte(content), 0o400); err != nil { t.Fatal(err) } t.Setenv(MasterKeyEnv, "") t.Setenv(MasterKeyFileEnv, path) if _, err := LoadKeyring(""); err == nil { t.Fatalf("content %q was accepted as a master key", content) } } }