add dev flag to override pgp key for plugin signature verification (#13559) (#14242) (#14256)

* add dev flag to override pgp key for plugin signature verification

-dev-plugin-pgp-key is the path to a PGP public key file to use for
plugin signature verification in dev mode. This prevents developers from
having to manually hard code their own pgp key into vault source code
and rebuild vault to develop and test enterprise plugins.

* fix sync once error handling

* add tests

* remove test

* tests: set random listener addr

* improve error message

* dedupe getVerifyFunc annd verifyPGPSignatureDetached

* accept raw key as well as path

* keep hc key and add custom key to keyring

Co-authored-by: John-Michael Faircloth <fairclothjm@users.noreply.github.com>
This commit is contained in:
Vault Automation 2026-05-07 14:44:32 -06:00 committed by GitHub
parent a3e1d00ea8
commit 068825afdc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 562 additions and 18 deletions

View File

@ -128,6 +128,7 @@ type ServerCommand struct {
flagDevNoStoreToken bool
flagDevPluginDir string
flagDevPluginInit bool
flagDevPluginPGPKey string
flagDevHA bool
flagDevLatency int
flagDevLatencyJitter int
@ -317,6 +318,13 @@ func (c *ServerCommand) Flags() *FlagSets {
Hidden: true,
})
f.StringVar(&StringVar{
Name: "dev-plugin-pgp-key",
Target: &c.flagDevPluginPGPKey,
Default: "",
Hidden: true,
})
f.BoolVar(&BoolVar{
Name: "dev-ha",
Target: &c.flagDevHA,
@ -3018,6 +3026,9 @@ func createCoreConfig(c *ServerCommand, config *server.Config, backend physical.
if c.flagDevPluginDir != "" {
coreConfig.PluginDirectory = c.flagDevPluginDir
}
if c.flagDevPluginPGPKey != "" {
coreConfig.DevPluginPGPKey = c.flagDevPluginPGPKey
}
if c.flagDevLatency > 0 {
injectLatency := time.Duration(c.flagDevLatency) * time.Millisecond
if _, txnOK := backend.(physical.Transactional); txnOK {

View File

@ -0,0 +1,221 @@
// Copyright IBM Corp. 2016, 2025
// SPDX-License-Identifier: BUSL-1.1
//go:build !race && !hsm
package command
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
// testPGPPublicKey is a sample PGP public key for testing purposes
const testPGPPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGB9+xkBEACabYZOWKmgZsHTdRDiyPJxhbuUiKX65GUWkyRMJKi/1dviVxOX
PG6hBPtF48IFnVgxKpIb7G6NjBousAV+CuLlv5yqFKpOZEGC6sBV+Gx8Vu1CICpl
Zm+HpQPcIzwBpN+Ar4l/exCG/f/MZq/oxGgH+TyRF3XcYDjG8dbJCpHO5nQ5Cy9h
QIp3/Bh09kET6lk+4QlofNgHKVT2epV8iK1cXlbQe2tZtfCUtxk+pxvU0UHXp+AB
0xc3/gIhjZp/dePmCOyQyGPJbp5bpO4UeAJ6frqhexmNlaw9Z897ltZmRLGq1p4a
RnWL8FPkBz9SCSKXS8uNyV5oMNVn4G1obCkc106iWuKBTibffYQzq5TG8FYVJKrh
RwWB6piacEB8hl20IIWSxIM3J9tT7CPSnk5RYYCTRHgA5OOrqZhC7JefudrP8n+M
pxkDgNORDu7GCfAuisrf7dXYjLsxG4tu22DBJJC0c/IpRpXDnOuJN1Q5e/3VUKKW
mypNumuQpP5lc1ZFG64TRzb1HR6oIdHfbrVQfdiQXpvdcFx+Fl57WuUraXRV6qfb
4ZmKHX1JEwM/7tu21QE4F1dz0jroLSricZxfaCTHHWNfvGJoZ30/MZUrpSC0IfB3
iQutxbZrwIlTBt+fGLtm3vDtwMFNWM+Rb1lrOxEQd2eijdxhvBOHtlIcswARAQAB
tERIYXNoaUNvcnAgU2VjdXJpdHkgKGhhc2hpY29ycC5jb20vc2VjdXJpdHkpIDxz
ZWN1cml0eUBoYXNoaWNvcnAuY29tPokCVAQTAQoAPhYhBMh0AR8KtAURDQIQVTQ2
XZRy10aPBQJgffsZAhsDBQkJZgGABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJ
EDQ2XZRy10aPtpcP/0PhJKiHtC1zREpRTrjGizoyk4Sl2SXpBZYhkdrG++abo6zs
buaAG7kgWWChVXBo5E20L7dbstFK7OjVs7vAg/OLgO9dPD8n2M19rpqSbbvKYWvp
0NSgvFTT7lbyDhtPj0/bzpkZEhmvQaDWGBsbDdb2dBHGitCXhGMpdP0BuuPWEix+
QnUMaPwU51q9GM2guL45Tgks9EKNnpDR6ZdCeWcqo1IDmklloidxT8aKL21UOb8t
cD+Bg8iPaAr73bW7Jh8TdcV6s6DBFub+xPJEB/0bVPmq3ZHs5B4NItroZ3r+h3ke
VDoSOSIZLl6JtVooOJ2la9ZuMqxchO3mrXLlXxVCo6cGcSuOmOdQSz4OhQE5zBxx
LuzA5ASIjASSeNZaRnffLIHmht17BPslgNPtm6ufyOk02P5XXwa69UCjA3RYrA2P
QNNC+OWZ8qQLnzGldqE4MnRNAxRxV6cFNzv14ooKf7+k686LdZrP/3fQu2p3k5rY
0xQUXKh1uwMUMtGR867ZBYaxYvwqDrg9XB7xi3N6aNyNQ+r7zI2lt65lzwG1v9hg
FG2AHrDlBkQi/t3wiTS3JOo/GCT8BjN0nJh0lGaRFtQv2cXOQGVRW8+V/9IpqEJ1
qQreftdBFWxvH7VJq2mSOXUJyRsoUrjkUuIivaA9Ocdipk2CkP8bpuGz7ZF4
=s1CX
-----END PGP PUBLIC KEY BLOCK-----`
// invalidPGPKey is not a valid PGP key format
const invalidPGPKey = `This is not a valid PGP key`
// testConfig is used to disable prometheus to avoid issues with the
// global prometheus registry in tests
const testConfig = `
storage "inmem" {}
listener "tcp" {
address = "127.0.0.1:0"
tls_disable = true
}
disable_mlock = true
telemetry {
prometheus_retention_time = "0s"
disable_hostname = true
}
`
// TestServer_DevPluginPGPKey_ValidKey tests that the server accepts a valid PGP key file
func TestServer_DevPluginPGPKey_ValidKey(t *testing.T) {
// Create a valid PGP key file
keyPath := filepath.Join(t.TempDir(), "test-pgp-key.asc")
err := os.WriteFile(keyPath, []byte(testPGPPublicKey), 0o644)
require.NoError(t, err)
ui, cmd := testServerCommand(t)
args := []string{
"-dev",
"-dev-plugin-pgp-key=" + keyPath,
"-dev-listen-address=127.0.0.1:0",
"-test-server-config",
}
retCode := cmd.Run(args)
output := ui.ErrorWriter.String() + ui.OutputWriter.String()
// The server should start successfully with a valid key
require.Equal(t, 0, retCode, "expected server to start successfully, output: %s", output)
}
// TestServer_DevPluginPGPKey_InvalidPath tests that the server handles invalid file paths
func TestServer_DevPluginPGPKey_InvalidPath(t *testing.T) {
ui, cmd := testServerCommand(t)
// Use an invalid path with null bytes (not allowed in file paths)
invalidPath := "/tmp/test\x00key.asc"
configPath := filepath.Join(t.TempDir(), "config.hcl")
err := os.WriteFile(configPath, []byte(testConfig), 0o644)
require.NoError(t, err)
args := []string{
"-dev",
"-config=" + configPath,
"-dev-plugin-pgp-key=" + invalidPath,
"-dev-listen-address=127.0.0.1:0",
"-test-server-config",
}
retCode := cmd.Run(args)
output := ui.ErrorWriter.String() + ui.OutputWriter.String()
// The server should fail to start with an invalid path
require.NotEqual(t, 0, retCode, "expected server to fail with invalid path")
require.Contains(t, output, "dev plugin PGP key", "expected error message about PGP key path, output: %s", output)
}
// TestServer_DevPluginPGPKey_NonExistentFile tests that the server handles non-existent key files
func TestServer_DevPluginPGPKey_NonExistentFile(t *testing.T) {
tmpDir := t.TempDir()
// Use a path that doesn't exist
keyPath := filepath.Join(tmpDir, "non-existent-key.asc")
ui, cmd := testServerCommand(t)
configPath := filepath.Join(tmpDir, "config.hcl")
err := os.WriteFile(configPath, []byte(testConfig), 0o644)
require.NoError(t, err)
args := []string{
"-dev",
"-config=" + configPath,
"-dev-plugin-pgp-key=" + keyPath,
"-dev-listen-address=127.0.0.1:0",
"-test-server-config",
}
retCode := cmd.Run(args)
output := ui.ErrorWriter.String() + ui.OutputWriter.String()
// The server should fail to start with a non-existent key file
require.NotEqual(t, 0, retCode, "expected server to fail with non-existent key file")
require.Contains(t, output, "dev plugin PGP key", "expected error message about PGP key path, output: %s", output)
}
// TestServer_DevPluginPGPKey_EmptyFlag tests default behavior when flag is not set
func TestServer_DevPluginPGPKey_EmptyFlag(t *testing.T) {
ui, cmd := testServerCommand(t)
configPath := filepath.Join(t.TempDir(), "config.hcl")
err := os.WriteFile(configPath, []byte(testConfig), 0o644)
require.NoError(t, err)
args := []string{
"-dev",
"-config=" + configPath,
"-dev-listen-address=127.0.0.1:0",
"-test-server-config",
}
retCode := cmd.Run(args)
output := ui.ErrorWriter.String() + ui.OutputWriter.String()
// The server should start successfully without the flag (uses default HashiCorp key)
require.Equal(t, 0, retCode, "expected server to start successfully without flag, output: %s", output)
}
// TestServer_DevPluginPGPKey_InvalidKeyContent tests handling of invalid PGP key content
func TestServer_DevPluginPGPKey_InvalidKeyContent(t *testing.T) {
tmpDir := t.TempDir()
// Create a file with invalid PGP key content
keyPath := filepath.Join(tmpDir, "invalid-key.asc")
err := os.WriteFile(keyPath, []byte(invalidPGPKey), 0o644)
require.NoError(t, err)
ui, cmd := testServerCommand(t)
configPath := filepath.Join(tmpDir, "config.hcl")
err = os.WriteFile(configPath, []byte(testConfig), 0o644)
require.NoError(t, err)
args := []string{
"-dev",
"-config=" + configPath,
"-dev-plugin-pgp-key=" + keyPath,
"-dev-listen-address=127.0.0.1:0",
"-test-server-config",
}
retCode := cmd.Run(args)
output := ui.ErrorWriter.String() + ui.OutputWriter.String()
// The server should start (validation happens when actually using the key)
// but we verify the path was set correctly
require.Equal(t, 0, retCode, "expected server to start (key validation happens at use time), output: %s", output)
}
// TestServer_DevPluginPGPKey_EmptyFile tests handling of an empty key file
func TestServer_DevPluginPGPKey_EmptyFile(t *testing.T) {
tmpDir := t.TempDir()
// Create an empty file
keyPath := filepath.Join(tmpDir, "empty-key.asc")
err := os.WriteFile(keyPath, []byte(""), 0o644)
require.NoError(t, err)
ui, cmd := testServerCommand(t)
configPath := filepath.Join(tmpDir, "config.hcl")
err = os.WriteFile(configPath, []byte(testConfig), 0o644)
require.NoError(t, err)
args := []string{
"-dev",
"-config=" + configPath,
"-dev-plugin-pgp-key=" + keyPath,
"-dev-listen-address=127.0.0.1:0",
"-test-server-config",
}
retCode := cmd.Run(args)
output := ui.ErrorWriter.String() + ui.OutputWriter.String()
// The server should start (validation happens when actually using the key)
require.Equal(t, 0, retCode, "expected server to start (key validation happens at use time), output: %s", output)
}

View File

@ -605,6 +605,10 @@ type Core struct {
// pluginFilePermissions is the permissions of the plugin files and directory
pluginFilePermissions int
// devPluginPGPKey is either a raw PGP public key or a path to a PGP public
// key file to use for plugin signature verification in dev mode.
devPluginPGPKey string
// pluginCatalog is used to manage plugin configurations
pluginCatalog *plugincatalog.PluginCatalog
@ -910,6 +914,11 @@ type CoreConfig struct {
PluginDirectory string
PluginTmpdir string
// DevPluginPGPKey is either a raw PGP public key or a path to a PGP public
// key file to use for plugin signature verification in dev mode. This allows
// testing enterprise plugins with custom signatures without rebuilding Vault.
DevPluginPGPKey string
PluginFileUid int
PluginFilePermissions int
@ -1358,6 +1367,25 @@ func NewCore(conf *CoreConfig) (*Core, error) {
c.pluginFilePermissions = conf.PluginFilePermissions
}
if conf.DevPluginPGPKey != "" {
// Check if it's a raw PGP key or a file path
if strings.HasPrefix(strings.TrimSpace(conf.DevPluginPGPKey), "-----BEGIN PGP PUBLIC KEY BLOCK-----") {
// It's a raw PGP key, use it directly
c.devPluginPGPKey = conf.DevPluginPGPKey
} else {
// It's a file path, validate and convert to absolute path
c.devPluginPGPKey, err = filepath.Abs(conf.DevPluginPGPKey)
if err != nil {
return nil, fmt.Errorf("core setup failed, could not verify dev plugin PGP key path: %w", err)
}
// Validate file exists at startup
if _, err := os.Stat(c.devPluginPGPKey); err != nil {
return nil, fmt.Errorf("core setup failed, dev plugin PGP key file does not exist: %w", err)
}
}
}
// Create secondaries (this will only impact Enterprise versions of Vault)
c.createSecondaries(conf.Logger)
@ -2749,6 +2777,7 @@ func (c *Core) setupPluginCatalog(ctx context.Context) error {
Tmpdir: c.containerPluginTmpdir,
EnableMlock: c.enableMlock,
PluginRuntimeCatalog: c.pluginRuntimeCatalog,
PluginPGPKey: c.devPluginPGPKey,
})
if err != nil {
return err

View File

@ -26,6 +26,7 @@ const (
var (
once sync.Once
verifier crypto.PGPVerify
initErr error
errExtractedArtifactDirNotFound = errors.New("extracted artifact directory not found")
errReadMetadata = errors.New("failed to read metadata")
@ -37,13 +38,10 @@ var (
errVerifyPluginSig = errors.New("failed to verify plugin binary PGP signature")
)
func load() error {
var errs error
once.Do(func() {
// hashiCorpPGPPubKey is HashiCorp's PGP public key
// at https://www.hashicorp.com/.well-known/pgp-key.txt.
// This key is used to verify the authenticity of HashiCorp plugins.
const hashiCorpPGPPubKey = `
// hashiCorpPGPPubKey is HashiCorp's PGP public key
// at https://www.hashicorp.com/.well-known/pgp-key.txt.
// This key is used to verify the authenticity of HashiCorp plugins.
var hashiCorpPGPPubKey = `
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGB9+xkBEACabYZOWKmgZsHTdRDiyPJxhbuUiKX65GUWkyRMJKi/1dviVxOX
@ -168,23 +166,78 @@ ZF5q4h4I33PSGDdSvGXn9UMY5Isjpg==
-----END PGP PUBLIC KEY BLOCK-----
`
func load() error {
return loadWithKey("")
}
func loadWithKey(customKeyOrPath string) error {
once.Do(func() {
pgp := crypto.PGP()
key, err := crypto.NewKeyFromArmored(hashiCorpPGPPubKey)
// Start with the HashiCorp key as the base of the allowlist
hashiCorpKey, err := crypto.NewKeyFromArmored(hashiCorpPGPPubKey)
if err != nil {
errs = errors.Join(errs, err)
initErr = fmt.Errorf("failed to parse HashiCorp PGP key: %w", err)
return
}
verifier, err = pgp.Verify().VerificationKey(key).New()
// Create a keyring starting with the HashiCorp key
keyring, err := crypto.NewKeyRing(hashiCorpKey)
if err != nil {
errs = errors.Join(errs, err)
initErr = fmt.Errorf("failed to create keyring: %w", err)
return
}
// If a custom key or path is provided, add it to the allowlist
if customKeyOrPath != "" {
var customKeyData string
// Check if the input is a raw PGP key (starts with PGP header)
if strings.HasPrefix(strings.TrimSpace(customKeyOrPath), "-----BEGIN PGP PUBLIC KEY BLOCK-----") {
// It's a raw PGP key, use it directly
customKeyData = customKeyOrPath
} else {
// It's a file path, read the key from the file
keyBytes, err := os.ReadFile(customKeyOrPath)
if err != nil {
initErr = fmt.Errorf("failed to read custom PGP key from file: %w", err)
return
}
customKeyData = string(keyBytes)
}
// Parse and add the custom key to the keyring
customKey, err := crypto.NewKeyFromArmored(customKeyData)
if err != nil {
initErr = fmt.Errorf("failed to parse custom PGP key: %w", err)
return
}
err = keyring.AddKey(customKey)
if err != nil {
initErr = fmt.Errorf("failed to add custom key to keyring: %w", err)
return
}
}
// Create verifier with the keyring (allowlist of keys)
v, err := pgp.Verify().VerificationKeys(keyring).New()
if err != nil {
initErr = fmt.Errorf("failed to create verifier: %w", err)
return
}
verifier = v
})
// All callers (first or subsequent) check the result of the initialization
if initErr != nil {
return initErr
}
if verifier == nil {
errs = errors.Join(errs, errors.New("verifier is nil after initialization"))
return errors.New("verifier initialization failed unexpectedly")
}
return errs
return nil
}
func GetExtractedArtifactDir(pluginName, pluginVersion string) string {
@ -197,8 +250,13 @@ func GetExtractedArtifactDir(pluginName, pluginVersion string) string {
type verifyFunc func(data, sig []byte) error
func verifyPGPSignatureDetached(data, sig []byte) error {
if err := load(); err != nil {
// verifyPGPSignatureDetachedWithKey verifies a detached PGP signature using either
// the default HashiCorp key (when customKeyPath is empty) or a custom key.
func verifyPGPSignatureDetachedWithKey(data, sig []byte, customKeyPath string) error {
if err := loadWithKey(customKeyPath); err != nil {
if customKeyPath != "" {
return fmt.Errorf("failed to load verifier with custom key: %w", err)
}
return fmt.Errorf("failed to load verifier: %w", err)
}
@ -207,12 +265,18 @@ func verifyPGPSignatureDetached(data, sig []byte) error {
return fmt.Errorf("failed to verify data: %w", err)
}
if sigErr := verifyResult.SignatureError(); sigErr != nil {
return fmt.Errorf("unexpected signature error: %w", sigErr)
fp := hex.EncodeToString(verifyResult.SignedByFingerprint())
return fmt.Errorf("signature verification failed for key with fingerprint %s: %w", fp, sigErr)
}
return nil
}
// verifyPGPSignatureDetached verifies a detached PGP signature using the default HashiCorp key.
func verifyPGPSignatureDetached(data, sig []byte) error {
return verifyPGPSignatureDetachedWithKey(data, sig, "")
}
func VerifyPlugin(pluginDir string, pluginName string, verifyFunc verifyFunc) (*PluginMetadata, error) {
if st, err := os.Stat(pluginDir); err != nil {
if os.IsNotExist(err) {

View File

@ -69,6 +69,10 @@ type PluginCatalog struct {
wrapper pluginutil.RunnerUtil
runtimeCatalog *PluginRuntimeCatalog
// pluginPGPKey is the path to a PGP public key file to use for plugin
// signature verification.
pluginPGPKey string
}
// Only plugins running with identical PluginRunner config can be multiplexed,
@ -151,6 +155,9 @@ type PluginCatalogInput struct {
Tmpdir string
EnableMlock bool
PluginRuntimeCatalog *PluginRuntimeCatalog
// PluginPGPKey is the path to a PGP public key file to use for plugin
// signature verification.
PluginPGPKey string
}
func SetupPluginCatalog(ctx context.Context, in *PluginCatalogInput) (*PluginCatalog, error) {
@ -164,6 +171,7 @@ func SetupPluginCatalog(ctx context.Context, in *PluginCatalogInput) (*PluginCat
mlockPlugins: in.EnableMlock,
wrapper: logical.StaticSystemView{VersionString: version.GetVersion().Version},
runtimeCatalog: in.PluginRuntimeCatalog,
pluginPGPKey: in.PluginPGPKey,
}
// Run upgrade if untyped plugins exist
@ -213,6 +221,14 @@ func SetupPluginCatalog(ctx context.Context, in *PluginCatalogInput) (*PluginCat
return catalog, nil
}
// getVerifyFunc returns the appropriate verification function based on whether
// a custom plugin PGP key is configured.
func (c *PluginCatalog) getVerifyFunc() verifyFunc {
return func(data, sig []byte) error {
return verifyPGPSignatureDetachedWithKey(data, sig, c.pluginPGPKey)
}
}
func envKeys(env []string) map[string]struct{} {
keys := make(map[string]struct{}, len(env))
for _, env := range env {
@ -891,7 +907,7 @@ func (c *PluginCatalog) verifyOfficialPlugins(ctx context.Context) error {
hasOfficialPlugins = true
pluginDir := path.Join(c.directory, path.Dir(plugin.Command))
if _, err = VerifyPlugin(pluginDir, plugin.Name, verifyPGPSignatureDetached); err != nil {
if _, err = VerifyPlugin(pluginDir, plugin.Name, c.getVerifyFunc()); err != nil {
return fmt.Errorf("failed to verify plugin %q version %q: %w", plugin.Name, plugin.Version, err)
}
}
@ -1052,7 +1068,7 @@ func (c *PluginCatalog) set(ctx context.Context, plugin pluginutil.SetPluginInpu
// e.g., plugins/vault-plugin-secrets-kv_0.24.0_linux_amd64/vault-plugin-secrets-kv
plugin.Command = path.Join(extractedArtifactDir, plugin.Name)
metadata, err := VerifyPlugin(expectedPluginDir, plugin.Name, verifyPGPSignatureDetached)
metadata, err := VerifyPlugin(expectedPluginDir, plugin.Name, c.getVerifyFunc())
if err != nil {
return nil, fmt.Errorf("failed to verify plugin plugin %q version %q: %w",
plugin.Name, plugin.Version, err)

View File

@ -0,0 +1,203 @@
// Copyright IBM Corp. 2016, 2025
// SPDX-License-Identifier: BUSL-1.1
package plugincatalog
import (
"context"
"os"
"path/filepath"
"testing"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestPluginCatalog_SetupWithCustomKey tests SetupPluginCatalog with a custom PGP key
func TestPluginCatalog_SetupWithCustomKey(t *testing.T) {
t.Parallel()
// Generate a test PGP key pair
_, pubKeyArmored := generatePGPKeyPair(t)
// Create temporary directories
tmpDir := t.TempDir()
pluginDir := filepath.Join(tmpDir, "plugins")
err := os.MkdirAll(pluginDir, 0o755)
require.NoError(t, err)
keyPath := filepath.Join(tmpDir, "custom-key.asc")
err = os.WriteFile(keyPath, []byte(pubKeyArmored), 0o644)
require.NoError(t, err)
// Setup plugin catalog with custom key
catalog, err := SetupPluginCatalog(context.Background(), &PluginCatalogInput{
BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
CatalogView: &logical.InmemStorage{},
PluginDirectory: pluginDir,
Logger: log.NewNullLogger(),
PluginPGPKey: keyPath,
})
require.NoError(t, err)
assert.NotNil(t, catalog)
assert.Equal(t, keyPath, catalog.pluginPGPKey, "custom key path should be set")
// Verify that getVerifyFunc returns a function that uses the custom key
verifyFunc := catalog.getVerifyFunc()
assert.NotNil(t, verifyFunc)
}
// TestPluginCatalog_verifyOfficialPlugins_WithCustomKey tests verifyOfficialPlugins with custom key
func TestPluginCatalog_verifyOfficialPlugins_WithCustomKey(t *testing.T) {
t.Parallel()
// Generate a test PGP key pair
privKey, pubKeyArmored := generatePGPKeyPair(t)
// Create temporary directories
tmpDir := t.TempDir()
pluginDir := filepath.Join(tmpDir, "plugins")
err := os.MkdirAll(pluginDir, 0o755)
require.NoError(t, err)
keyPath := filepath.Join(tmpDir, "custom-key.asc")
err = os.WriteFile(keyPath, []byte(pubKeyArmored), 0o644)
require.NoError(t, err)
// Create a plugin artifact with proper signatures
pluginName := "vault-plugin-test"
pluginVersion := "1.0.0"
artifactDir := filepath.Join(pluginDir, GetExtractedArtifactDir(pluginName, pluginVersion))
err = os.MkdirAll(artifactDir, 0o755)
require.NoError(t, err)
contents := generatePluginArtifactContents(t, pluginName, pluginVersion, consts.PluginTypeSecrets, true, privKey)
for filename, data := range contents {
err := os.WriteFile(filepath.Join(artifactDir, filename), data, 0o644)
require.NoError(t, err)
}
storage := &logical.InmemStorage{}
// Setup plugin catalog with custom key
catalog, err := SetupPluginCatalog(context.Background(), &PluginCatalogInput{
BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
CatalogView: storage,
PluginDirectory: pluginDir,
Logger: log.NewNullLogger(),
PluginPGPKey: keyPath,
})
require.NoError(t, err)
// Register the plugin as official
pluginEntry := &pluginutil.PluginRunner{
Name: pluginName,
Type: consts.PluginTypeSecrets,
Version: pluginVersion,
Command: filepath.Join(GetExtractedArtifactDir(pluginName, pluginVersion), pluginName),
Builtin: false,
}
// Store the plugin in catalog
entry, err := logical.StorageEntryJSON(pluginEntry.Name, pluginEntry)
require.NoError(t, err)
err = storage.Put(context.Background(), entry)
require.NoError(t, err)
// Verify official plugins - should succeed with custom key
err = catalog.verifyOfficialPlugins(context.Background())
require.NoError(t, err)
}
// TestPluginCatalog_SetupWithRawCustomKey tests SetupPluginCatalog with a raw PGP key (not a file path)
func TestPluginCatalog_SetupWithRawCustomKey(t *testing.T) {
t.Parallel()
// Generate a test PGP key pair
_, pubKeyArmored := generatePGPKeyPair(t)
// Create temporary directories
tmpDir := t.TempDir()
pluginDir := filepath.Join(tmpDir, "plugins")
err := os.MkdirAll(pluginDir, 0o755)
require.NoError(t, err)
// Setup plugin catalog with raw PGP key (not a file path)
catalog, err := SetupPluginCatalog(context.Background(), &PluginCatalogInput{
BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
CatalogView: &logical.InmemStorage{},
PluginDirectory: pluginDir,
Logger: log.NewNullLogger(),
PluginPGPKey: pubKeyArmored, // Pass raw key directly
})
require.NoError(t, err)
assert.NotNil(t, catalog)
assert.Equal(t, pubKeyArmored, catalog.pluginPGPKey, "raw custom key should be set")
// Verify that getVerifyFunc returns a function that uses the custom key
verifyFunc := catalog.getVerifyFunc()
assert.NotNil(t, verifyFunc)
}
// TestPluginCatalog_verifyOfficialPlugins_WithRawCustomKey tests verifyOfficialPlugins with raw custom key
func TestPluginCatalog_verifyOfficialPlugins_WithRawCustomKey(t *testing.T) {
t.Parallel()
// Generate a test PGP key pair
privKey, pubKeyArmored := generatePGPKeyPair(t)
// Create temporary directories
tmpDir := t.TempDir()
pluginDir := filepath.Join(tmpDir, "plugins")
err := os.MkdirAll(pluginDir, 0o755)
require.NoError(t, err)
// Create a plugin artifact with proper signatures
pluginName := "vault-plugin-test-raw"
pluginVersion := "1.0.0"
artifactDir := filepath.Join(pluginDir, GetExtractedArtifactDir(pluginName, pluginVersion))
err = os.MkdirAll(artifactDir, 0o755)
require.NoError(t, err)
contents := generatePluginArtifactContents(t, pluginName, pluginVersion, consts.PluginTypeSecrets, true, privKey)
for filename, data := range contents {
err := os.WriteFile(filepath.Join(artifactDir, filename), data, 0o644)
require.NoError(t, err)
}
storage := &logical.InmemStorage{}
// Setup plugin catalog with raw PGP key (not a file path)
catalog, err := SetupPluginCatalog(context.Background(), &PluginCatalogInput{
BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
CatalogView: storage,
PluginDirectory: pluginDir,
Logger: log.NewNullLogger(),
PluginPGPKey: pubKeyArmored, // Pass raw key directly
})
require.NoError(t, err)
// Register the plugin as official
pluginEntry := &pluginutil.PluginRunner{
Name: pluginName,
Type: consts.PluginTypeSecrets,
Version: pluginVersion,
Command: filepath.Join(GetExtractedArtifactDir(pluginName, pluginVersion), pluginName),
Builtin: false,
}
// Store the plugin in catalog
entry, err := logical.StorageEntryJSON(pluginEntry.Name, pluginEntry)
require.NoError(t, err)
err = storage.Put(context.Background(), entry)
require.NoError(t, err)
// Verify official plugins - should succeed with raw custom key
err = catalog.verifyOfficialPlugins(context.Background())
require.NoError(t, err)
}