diff --git a/keys.go b/keys.go index 410bf90..f29dabc 100644 --- a/keys.go +++ b/keys.go @@ -118,13 +118,19 @@ func KeysExist(opts ...KeysOption) bool { if o.Path == "" { return false } - slots := [3][2]string{ - {encKeyBinFile, encKeyJSONFile}, - {evalKeyBinFile, evalKeyJSONFile}, - {secKeyBinFile, secKeyJSONFile}, + wantEnc, wantEval, wantSec := resolveKeyParts(o.Parts) + if wantEnc { + if _, _, ok := resolveKeySlot(o.Path, encKeyBinFile, encKeyJSONFile); !ok { + return false + } } - for _, s := range slots { - if _, _, ok := resolveKeySlot(o.Path, s[0], s[1]); !ok { + if wantEval { + if _, _, ok := resolveKeySlot(o.Path, evalKeyBinFile, evalKeyJSONFile); !ok { + return false + } + } + if wantSec { + if _, _, ok := resolveKeySlot(o.Path, secKeyBinFile, secKeyJSONFile); !ok { return false } } diff --git a/keys_test.go b/keys_test.go index 4d1830e..635066d 100644 --- a/keys_test.go +++ b/keys_test.go @@ -320,3 +320,44 @@ func TestRegisterKeys_WithoutEvalPart_ReturnsErr(t *testing.T) { t.Errorf("ActivateKeys without KeyPartEval: got %v, want ErrKeysNotForRegister", err) } } + +// TestKeysExist_PartsAware exercises the KeyParts-aware lookup. Vault's +// agent-manifest delivery only ships EncKey to the client (Eval/Sec stay +// in Vault), so the consumer opens that directory with +// WithKeyParts(KeyPartEnc) and expects KeysExist to return true on an +// Enc-only directory. The previous implementation walked all three slots +// unconditionally and rejected the bundle. +func TestKeysExist_PartsAware(t *testing.T) { + encOnly := func(t *testing.T) string { + t.Helper() + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, encKeyJSONFile), []byte("{}"), 0o600); err != nil { + t.Fatal(err) + } + return dir + } + + t.Run("enc-only dir + WithKeyParts(KeyPartEnc) → true", func(t *testing.T) { + dir := encOnly(t) + opts := append(baseKeyOpts(dir), WithKeyParts(KeyPartEnc)) + if !KeysExist(opts...) { + t.Error("KeysExist with KeyPartEnc must accept Enc-only directory") + } + }) + + t.Run("enc-only dir + default parts (= all three) → false", func(t *testing.T) { + dir := encOnly(t) + // No WithKeyParts → resolveKeyParts treats it as all three required. + if KeysExist(baseKeyOpts(dir)...) { + t.Error("default parts must require all 3 slots") + } + }) + + t.Run("enc-only dir + WithKeyParts(KeyPartEval) → false (eval missing)", func(t *testing.T) { + dir := encOnly(t) + opts := append(baseKeyOpts(dir), WithKeyParts(KeyPartEval)) + if KeysExist(opts...) { + t.Error("requesting Eval on an Enc-only dir must fail") + } + }) +}