From a2b4bb1b15f10fe509c2d25f30028d3879f33984 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Mon, 29 Jul 2024 10:01:26 -0700 Subject: [PATCH] Fix mounts of external plugins that were registered before Vault v1.0.0 could not be tuned to use versioned plugins (#27881) --- changelog/27881.txt | 4 + vault/logical_system.go | 11 +++ vault/logical_system_test.go | 140 +++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 changelog/27881.txt diff --git a/changelog/27881.txt b/changelog/27881.txt new file mode 100644 index 0000000000..0b3f9aa7fd --- /dev/null +++ b/changelog/27881.txt @@ -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. +``` \ No newline at end of file diff --git a/vault/logical_system.go b/vault/logical_system.go index 57efed1e60..cbb0f37ade 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -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 diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go index 3c9725e211..484684e93a 100644 --- a/vault/logical_system_test.go +++ b/vault/logical_system_test.go @@ -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")