Fix mounts of external plugins that were registered before Vault v1.0.0 could not be tuned to use versioned plugins (#27881)

This commit is contained in:
Thy Ton
2024-07-29 10:01:26 -07:00
committed by GitHub
parent b689fc62f1
commit a2b4bb1b15
3 changed files with 155 additions and 0 deletions

4
changelog/27881.txt Normal file
View File

@@ -0,0 +1,4 @@
```release-note:bug
sys: Fix a bug where mounts of external plugins that were registered before Vault v1.0.0 could not be tuned to
use versioned plugins.
```

View File

@@ -2422,6 +2422,17 @@ func (b *SystemBackend) handleTuneWriteCommon(ctx context.Context, path string,
pluginType = consts.PluginTypeCredential
}
// Update MountEntry.Type of external plugins registered in Vault pre-v1.0.0 to the plugin binary name
// stored in MountEntry.Config.PluginName
// Previously, when upgrading Vault from pre-v1.0.0 to post-v1.0.0, MountEntry.Type of external plugins
// remained "plugin" where it should follow the new scheme and be updated to the plugin binary name.
// https://hashicorp.atlassian.net/browse/VAULT-21999
if mountEntry.Config.PluginName != "" {
if mountEntry.Config.PluginName != mountEntry.Type && mountEntry.Type == "plugin" {
mountEntry.Type = mountEntry.Config.PluginName
}
}
pinnedVersion, err := b.Core.pluginCatalog.GetPinnedVersion(ctx, pluginType, mountEntry.Type)
if err != nil && !errors.Is(err, pluginutil.ErrPinnedVersionNotFound) {
return nil, err

View File

@@ -2521,6 +2521,146 @@ func TestSystemBackend_tuneAuth(t *testing.T) {
}
}
// TestSystemBackend_tune_updatePreV1MountEntryType tests once Vault is migrated post-v1.0.0,
// the secret/auth mount was enabled in Vault pre-v1.0.0 has its MountEntry.Type updated
// to the plugin name when tuned with plugin_version
func TestSystemBackend_tune_updatePreV1MountEntryType(t *testing.T) {
tempDir, err := filepath.EvalSymlinks(os.TempDir())
if err != nil {
t.Fatalf("error: %v", err)
}
c, _, _ := TestCoreUnsealedWithConfig(t, &CoreConfig{
PluginDirectory: tempDir,
})
ctx := namespace.RootContext(nil)
testCases := []struct {
pluginName string
pluginType consts.PluginType
mountTable string
}{
{"consul", consts.PluginTypeSecrets, "mounts"},
{"approle", consts.PluginTypeCredential, "auth"},
}
readMountConfig := func(pluginName, mountTable string) map[string]interface{} {
t.Helper()
req := logical.TestRequest(t, logical.ReadOperation, mountTable+"/"+pluginName)
resp, err := c.systemBackend.HandleRequest(ctx, req)
if err != nil {
t.Fatalf("err: %v", err)
}
return resp.Data
}
for _, tc := range testCases {
// Enable the mount
{
req := logical.TestRequest(t, logical.UpdateOperation, tc.mountTable+"/"+tc.pluginName)
req.Data["type"] = tc.pluginName
resp, err := c.systemBackend.HandleRequest(ctx, req)
if err != nil {
t.Fatalf("err: %v, resp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
config := readMountConfig(tc.pluginName, tc.mountTable)
pluginVersion, ok := config["plugin_version"]
if !ok || pluginVersion != "" {
t.Fatalf("expected empty plugin version in config: %#v", config)
}
}
// Directly set MountEntry's Type to "plugin" and Config.PluginName like Vault pre-v1.0.0 does
const mountEntryTypeExternalPlugin = "plugin"
{
var mountEntry *MountEntry
if tc.mountTable == "mounts" {
mountEntry, err = c.mounts.find(ctx, tc.pluginName+"/")
} else {
mountEntry, err = c.auth.find(ctx, tc.pluginName+"/")
}
if err != nil {
t.Fatal(err)
}
if mountEntry == nil {
t.Fatal()
}
mountEntry.Type = mountEntryTypeExternalPlugin
mountEntry.Config.PluginName = tc.pluginName
err := c.persistMounts(ctx, c.mounts, &mountEntry.Local)
if err != nil {
t.Fatal(err)
}
}
// Verify MountEntry.Type is still "plugin" (Vault pre-v1.0.0)
config := readMountConfig(tc.pluginName, tc.mountTable)
mountEntryType, ok := config["type"]
if !ok || mountEntryType != mountEntryTypeExternalPlugin {
t.Fatalf("expected mount type %s but was %s, config: %#v", mountEntryTypeExternalPlugin, mountEntryType, config)
}
// Register the plugin in the catalog, and then try the same request again.
const externalPluginVersion string = "v0.0.7"
{
file, err := os.Create(filepath.Join(tempDir, tc.pluginName))
if err != nil {
t.Fatal(err)
}
if err := file.Close(); err != nil {
t.Fatal(err)
}
err = c.pluginCatalog.Set(context.Background(), pluginutil.SetPluginInput{
Name: tc.pluginName,
Type: tc.pluginType,
Version: externalPluginVersion,
Command: tc.pluginName,
Args: []string{},
Env: []string{},
Sha256: []byte{},
})
if err != nil {
t.Fatal(err)
}
}
// Tune mount with plugin_version
{
req := logical.TestRequest(t, logical.UpdateOperation, tc.mountTable+"/"+tc.pluginName+"/tune")
req.Data["plugin_version"] = externalPluginVersion
resp, err := c.systemBackend.HandleRequest(ctx, req)
if err != nil {
t.Fatalf("err: %v, resp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
}
// Verify that plugin_version and MountEntry.Type were updated properly
config = readMountConfig(tc.pluginName, tc.mountTable)
pluginVersion, ok := config["plugin_version"]
if !ok || pluginVersion == "" {
t.Fatalf("expected non-empty plugin version in config: %#v", config)
}
if pluginVersion != externalPluginVersion {
t.Fatalf("expected plugin version %#v, got %v", externalPluginVersion, pluginVersion)
}
mountEntryType, ok = config["type"]
if !ok || mountEntryType != tc.pluginName {
t.Fatalf("expected mount type %s, got %s, config: %#v", tc.pluginName, mountEntryType, config)
}
}
}
func TestSystemBackend_policyList(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.ReadOperation, "policy")