From fcf06d58748458f6b0b8518daf71dfaf7cad9785 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Thu, 6 Apr 2023 00:41:07 -0700 Subject: [PATCH] feat: add plugin metadata to audit logging (#19814) --- audit/format.go | 84 +++++--- changelog/19814.txt | 3 + http/logical_test.go | 178 ++++++++++++++++ sdk/logical/request.go | 48 +++++ .../plugin/external_plugin_test.go | 194 ++++++++++++++++++ vault/mount.go | 19 ++ vault/request_handling.go | 10 + vault/router.go | 10 + 8 files changed, 514 insertions(+), 32 deletions(-) create mode 100644 changelog/19814.txt diff --git a/audit/format.go b/audit/format.go index 83ec18ad4..64edb9083 100644 --- a/audit/format.go +++ b/audit/format.go @@ -112,13 +112,17 @@ func (f *AuditFormatter) FormatRequest(ctx context.Context, w io.Writer, config }, Request: &AuditRequest{ - ID: req.ID, - ClientID: req.ClientID, - ClientToken: req.ClientToken, - ClientTokenAccessor: req.ClientTokenAccessor, - Operation: req.Operation, - MountType: req.MountType, - MountAccessor: req.MountAccessor, + ID: req.ID, + ClientID: req.ClientID, + ClientToken: req.ClientToken, + ClientTokenAccessor: req.ClientTokenAccessor, + Operation: req.Operation, + MountType: req.MountType, + MountAccessor: req.MountAccessor, + MountRunningVersion: req.MountRunningVersion(), + MountRunningSha256: req.MountRunningSha256(), + MountIsExternalPlugin: req.MountIsExternalPlugin(), + MountClass: req.MountClass(), Namespace: &AuditNamespace{ ID: ns.ID, Path: ns.Path, @@ -311,13 +315,17 @@ func (f *AuditFormatter) FormatResponse(ctx context.Context, w io.Writer, config }, Request: &AuditRequest{ - ID: req.ID, - ClientToken: req.ClientToken, - ClientTokenAccessor: req.ClientTokenAccessor, - ClientID: req.ClientID, - Operation: req.Operation, - MountType: req.MountType, - MountAccessor: req.MountAccessor, + ID: req.ID, + ClientToken: req.ClientToken, + ClientTokenAccessor: req.ClientTokenAccessor, + ClientID: req.ClientID, + Operation: req.Operation, + MountType: req.MountType, + MountAccessor: req.MountAccessor, + MountRunningVersion: req.MountRunningVersion(), + MountRunningSha256: req.MountRunningSha256(), + MountIsExternalPlugin: req.MountIsExternalPlugin(), + MountClass: req.MountClass(), Namespace: &AuditNamespace{ ID: ns.ID, Path: ns.Path, @@ -333,15 +341,19 @@ func (f *AuditFormatter) FormatResponse(ctx context.Context, w io.Writer, config }, Response: &AuditResponse{ - MountType: req.MountType, - MountAccessor: req.MountAccessor, - Auth: respAuth, - Secret: respSecret, - Data: respData, - Warnings: resp.Warnings, - Redirect: resp.Redirect, - WrapInfo: respWrapInfo, - Headers: resp.Headers, + MountType: req.MountType, + MountAccessor: req.MountAccessor, + MountRunningVersion: req.MountRunningVersion(), + MountRunningSha256: req.MountRunningSha256(), + MountIsExternalPlugin: req.MountIsExternalPlugin(), + MountClass: req.MountClass(), + Auth: respAuth, + Secret: respSecret, + Data: respData, + Warnings: resp.Warnings, + Redirect: resp.Redirect, + WrapInfo: respWrapInfo, + Headers: resp.Headers, }, } @@ -399,6 +411,10 @@ type AuditRequest struct { Operation logical.Operation `json:"operation,omitempty"` MountType string `json:"mount_type,omitempty"` MountAccessor string `json:"mount_accessor,omitempty"` + MountRunningVersion string `json:"mount_running_version,omitempty"` + MountRunningSha256 string `json:"mount_running_sha256,omitempty"` + MountClass string `json:"mount_class,omitempty"` + MountIsExternalPlugin bool `json:"mount_is_external_plugin,omitempty"` ClientToken string `json:"client_token,omitempty"` ClientTokenAccessor string `json:"client_token_accessor,omitempty"` Namespace *AuditNamespace `json:"namespace,omitempty"` @@ -413,15 +429,19 @@ type AuditRequest struct { } type AuditResponse struct { - Auth *AuditAuth `json:"auth,omitempty"` - MountType string `json:"mount_type,omitempty"` - MountAccessor string `json:"mount_accessor,omitempty"` - Secret *AuditSecret `json:"secret,omitempty"` - Data map[string]interface{} `json:"data,omitempty"` - Warnings []string `json:"warnings,omitempty"` - Redirect string `json:"redirect,omitempty"` - WrapInfo *AuditResponseWrapInfo `json:"wrap_info,omitempty"` - Headers map[string][]string `json:"headers,omitempty"` + Auth *AuditAuth `json:"auth,omitempty"` + MountType string `json:"mount_type,omitempty"` + MountAccessor string `json:"mount_accessor,omitempty"` + MountRunningVersion string `json:"mount_running_plugin_version,omitempty"` + MountRunningSha256 string `json:"mount_running_sha256,omitempty"` + MountClass string `json:"mount_class,omitempty"` + MountIsExternalPlugin bool `json:"mount_is_external_plugin,omitempty"` + Secret *AuditSecret `json:"secret,omitempty"` + Data map[string]interface{} `json:"data,omitempty"` + Warnings []string `json:"warnings,omitempty"` + Redirect string `json:"redirect,omitempty"` + WrapInfo *AuditResponseWrapInfo `json:"wrap_info,omitempty"` + Headers map[string][]string `json:"headers,omitempty"` } type AuditAuth struct { diff --git a/changelog/19814.txt b/changelog/19814.txt new file mode 100644 index 000000000..4e5379021 --- /dev/null +++ b/changelog/19814.txt @@ -0,0 +1,3 @@ +```release-note:feature +audit: add plugin metadata, including plugin name, type, version, sha256, and whether plugin is external, to audit logging +``` \ No newline at end of file diff --git a/http/logical_test.go b/http/logical_test.go index e4b137c10..a9ccdff05 100644 --- a/http/logical_test.go +++ b/http/logical_test.go @@ -11,6 +11,7 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "os" "reflect" "strconv" "strings" @@ -760,3 +761,180 @@ func TestLogical_ErrRelativePath(t *testing.T) { t.Errorf("expected response for write to include %q", logical.ErrRelativePath.Error()) } } + +func testBuiltinPluginMetadataAuditLog(t *testing.T, log map[string]interface{}, expectedMountClass string) { + if mountClass, ok := log["mount_class"].(string); !ok { + t.Fatalf("mount_class should be a string, not %T", log["mount_class"]) + } else if mountClass != expectedMountClass { + t.Fatalf("bad: mount_class should be %s, not %s", expectedMountClass, mountClass) + } + + if _, ok := log["mount_running_version"].(string); !ok { + t.Fatalf("mount_running_version should be a string, not %T", log["mount_running_version"]) + } + + if _, ok := log["mount_running_sha256"].(string); ok { + t.Fatalf("mount_running_sha256 should be nil, not %T", log["mount_running_sha256"]) + } + + if mountIsExternalPlugin, ok := log["mount_is_external_plugin"].(bool); ok && mountIsExternalPlugin { + t.Fatalf("mount_is_external_plugin should be nil or false, not %T", log["mount_is_external_plugin"]) + } +} + +// TestLogical_AuditEnabled_ShouldLogPluginMetadata_Auth tests that we have plugin metadata of a builtin auth plugin +// in audit log when it is enabled +func TestLogical_AuditEnabled_ShouldLogPluginMetadata_Auth(t *testing.T) { + coreConfig := &vault.CoreConfig{ + AuditBackends: map[string]audit.Factory{ + "file": auditFile.Factory, + }, + } + + cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ + HandlerFunc: Handler, + }) + + cluster.Start() + defer cluster.Cleanup() + + cores := cluster.Cores + + core := cores[0].Core + c := cluster.Cores[0].Client + vault.TestWaitActive(t, core) + + // Enable the audit backend + tempDir := t.TempDir() + auditLogFile, err := os.CreateTemp(tempDir, "") + if err != nil { + t.Fatal(err) + } + + err = c.Sys().EnableAuditWithOptions("file", &api.EnableAuditOptions{ + Type: "file", + Options: map[string]string{ + "file_path": auditLogFile.Name(), + }, + }) + if err != nil { + t.Fatal(err) + } + + _, err = c.Logical().Write("auth/token/create", map[string]interface{}{ + "ttl": "10s", + }) + if err != nil { + t.Fatal(err) + } + + // Check the audit trail on request and response + decoder := json.NewDecoder(auditLogFile) + var auditRecord map[string]interface{} + for decoder.Decode(&auditRecord) == nil { + auditRequest := map[string]interface{}{} + if req, ok := auditRecord["request"]; ok { + auditRequest = req.(map[string]interface{}) + if auditRequest["path"] != "auth/token/create" { + continue + } + } + testBuiltinPluginMetadataAuditLog(t, auditRequest, consts.PluginTypeCredential.String()) + + auditResponse := map[string]interface{}{} + if req, ok := auditRecord["response"]; ok { + auditRequest = req.(map[string]interface{}) + if auditResponse["path"] != "auth/token/create" { + continue + } + } + testBuiltinPluginMetadataAuditLog(t, auditResponse, consts.PluginTypeCredential.String()) + } +} + +// TestLogical_AuditEnabled_ShouldLogPluginMetadata_Secret tests that we have plugin metadata of a builtin secret plugin +// in audit log when it is enabled +func TestLogical_AuditEnabled_ShouldLogPluginMetadata_Secret(t *testing.T) { + coreConfig := &vault.CoreConfig{ + LogicalBackends: map[string]logical.Factory{ + "kv": kv.VersionedKVFactory, + }, + AuditBackends: map[string]audit.Factory{ + "file": auditFile.Factory, + }, + } + + cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ + HandlerFunc: Handler, + }) + + cluster.Start() + defer cluster.Cleanup() + + cores := cluster.Cores + + core := cores[0].Core + c := cluster.Cores[0].Client + vault.TestWaitActive(t, core) + + if err := c.Sys().Mount("kv/", &api.MountInput{ + Type: "kv-v2", + }); err != nil { + t.Fatalf("kv-v2 mount attempt failed - err: %#v\n", err) + } + + // Enable the audit backend + tempDir := t.TempDir() + auditLogFile, err := os.CreateTemp(tempDir, "") + if err != nil { + t.Fatal(err) + } + + err = c.Sys().EnableAuditWithOptions("file", &api.EnableAuditOptions{ + Type: "file", + Options: map[string]string{ + "file_path": auditLogFile.Name(), + }, + }) + if err != nil { + t.Fatal(err) + } + + { + writeData := map[string]interface{}{ + "data": map[string]interface{}{ + "bar": "a", + }, + } + corehelpers.RetryUntil(t, 10*time.Second, func() error { + resp, err := c.Logical().Write("kv/data/foo", writeData) + if err != nil { + t.Fatalf("write request failed, err: %#v, resp: %#v\n", err, resp) + } + return nil + }) + } + + // Check the audit trail on request and response + decoder := json.NewDecoder(auditLogFile) + var auditRecord map[string]interface{} + for decoder.Decode(&auditRecord) == nil { + auditRequest := map[string]interface{}{} + if req, ok := auditRecord["request"]; ok { + auditRequest = req.(map[string]interface{}) + if auditRequest["path"] != "kv/data/foo" { + continue + } + } + testBuiltinPluginMetadataAuditLog(t, auditRequest, consts.PluginTypeSecrets.String()) + + auditResponse := map[string]interface{}{} + if req, ok := auditRecord["response"]; ok { + auditRequest = req.(map[string]interface{}) + if auditResponse["path"] != "kv/data/foo" { + continue + } + } + testBuiltinPluginMetadataAuditLog(t, auditResponse, consts.PluginTypeSecrets.String()) + } +} diff --git a/sdk/logical/request.go b/sdk/logical/request.go index 2e54af1c0..5dcd462f6 100644 --- a/sdk/logical/request.go +++ b/sdk/logical/request.go @@ -156,6 +156,22 @@ type Request struct { // backends can be tied to the mount it belongs to. MountAccessor string `json:"mount_accessor" structs:"mount_accessor" mapstructure:"mount_accessor" sentinel:""` + // mountRunningVersion is used internally to propagate the semantic version + // of the mounted plugin as reported by its vault.MountEntry to audit logging + mountRunningVersion string + + // mountRunningSha256 is used internally to propagate the encoded sha256 + // of the mounted plugin as reported its vault.MountEntry to audit logging + mountRunningSha256 string + + // mountIsExternalPlugin is used internally to propagate whether + // the backend of the mounted plugin is running externally (i.e., over GRPC) + // to audit logging + mountIsExternalPlugin bool + + // mountClass is used internally to propagate the mount class of the mounted plugin to audit logging + mountClass string + // WrapInfo contains requested response wrapping parameters WrapInfo *RequestWrapInfo `json:"wrap_info" structs:"wrap_info" mapstructure:"wrap_info" sentinel:""` @@ -283,6 +299,38 @@ func (r *Request) SentinelKeys() []string { } } +func (r *Request) MountRunningVersion() string { + return r.mountRunningVersion +} + +func (r *Request) SetMountRunningVersion(mountRunningVersion string) { + r.mountRunningVersion = mountRunningVersion +} + +func (r *Request) MountRunningSha256() string { + return r.mountRunningSha256 +} + +func (r *Request) SetMountRunningSha256(mountRunningSha256 string) { + r.mountRunningSha256 = mountRunningSha256 +} + +func (r *Request) MountIsExternalPlugin() bool { + return r.mountIsExternalPlugin +} + +func (r *Request) SetMountIsExternalPlugin(mountIsExternalPlugin bool) { + r.mountIsExternalPlugin = mountIsExternalPlugin +} + +func (r *Request) MountClass() string { + return r.mountClass +} + +func (r *Request) SetMountClass(mountClass string) { + r.mountClass = mountClass +} + func (r *Request) LastRemoteWAL() uint64 { return r.lastRemoteWAL } diff --git a/vault/external_tests/plugin/external_plugin_test.go b/vault/external_tests/plugin/external_plugin_test.go index a3ad8b59f..cf8a7b976 100644 --- a/vault/external_tests/plugin/external_plugin_test.go +++ b/vault/external_tests/plugin/external_plugin_test.go @@ -5,12 +5,16 @@ package plugin_test import ( "context" + "encoding/json" "fmt" "os" "path/filepath" "testing" "time" + "github.com/hashicorp/vault/audit" + auditFile "github.com/hashicorp/vault/builtin/audit/file" + "github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api/auth/approle" "github.com/hashicorp/vault/builtin/logical/database" @@ -27,6 +31,35 @@ import ( _ "github.com/jackc/pgx/v4/stdlib" ) +func getClusterWithFileAuditBackend(t *testing.T, typ consts.PluginType, numCores int) *vault.TestCluster { + pluginDir, cleanup := corehelpers.MakeTestPluginDir(t) + t.Cleanup(func() { cleanup(t) }) + coreConfig := &vault.CoreConfig{ + PluginDirectory: pluginDir, + LogicalBackends: map[string]logical.Factory{ + "database": database.Factory, + }, + AuditBackends: map[string]audit.Factory{ + "file": auditFile.Factory, + }, + } + + cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ + TempDir: pluginDir, + NumCores: numCores, + Plugins: &vault.TestPluginConfig{ + Typ: typ, + Versions: []string{""}, + }, + HandlerFunc: vaulthttp.Handler, + }) + + cluster.Start() + vault.TestWaitActive(t, cluster.Cores[0].Core) + + return cluster +} + func getCluster(t *testing.T, typ consts.PluginType, numCores int) *vault.TestCluster { pluginDir, cleanup := corehelpers.MakeTestPluginDir(t) t.Cleanup(func() { cleanup(t) }) @@ -826,3 +859,164 @@ CREATE ROLE "{{name}}" WITH VALID UNTIL '{{expiration}}'; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{name}}"; ` + +func testExternalPluginMetadataAuditLog(t *testing.T, log map[string]interface{}, expectedMountClass string) { + if mountClass, ok := log["mount_class"].(string); !ok { + t.Fatalf("mount_class should be a string, not %T", log["mount_class"]) + } else if mountClass != expectedMountClass { + t.Fatalf("bad: mount_class should be %s, not %s", expectedMountClass, mountClass) + } + + if mountIsExternalPlugin, ok := log["mount_is_external_plugin"].(bool); !ok { + t.Fatalf("mount_is_external_plugin should be a bool, not %T", log["mount_is_external_plugin"]) + } else if !mountIsExternalPlugin { + t.Fatalf("bad: mount_is_external_plugin should be true, not %t", mountIsExternalPlugin) + } + + if _, ok := log["mount_running_sha256"].(string); !ok { + t.Fatalf("mount_running_sha256 should be a string, not %T", log["mount_running_sha256"]) + } +} + +// TestExternalPlugin_AuditEnabled_ShouldLogPluginMetadata_Auth tests that we have plugin metadata of an auth plugin +// in audit log when it is enabled +func TestExternalPlugin_AuditEnabled_ShouldLogPluginMetadata_Auth(t *testing.T) { + cluster := getClusterWithFileAuditBackend(t, consts.PluginTypeCredential, 1) + defer cluster.Cleanup() + + plugin := cluster.Plugins[0] + client := cluster.Cores[0].Client + client.SetToken(cluster.RootToken) + + testRegisterAndEnable(t, client, plugin) + + // Enable the audit backend + tempDir := t.TempDir() + auditLogFile, err := os.CreateTemp(tempDir, "") + if err != nil { + t.Fatal(err) + } + + err = client.Sys().EnableAuditWithOptions("file", &api.EnableAuditOptions{ + Type: "file", + Options: map[string]string{ + "file_path": auditLogFile.Name(), + }, + }) + if err != nil { + t.Fatal(err) + } + + _, err = client.Logical().Write("auth/"+plugin.Name+"/role/role1", map[string]interface{}{ + "bind_secret_id": "true", + "period": "300", + }) + if err != nil { + t.Fatal(err) + } + + // Check the audit trail on request and response + decoder := json.NewDecoder(auditLogFile) + var auditRecord map[string]interface{} + for decoder.Decode(&auditRecord) == nil { + auditRequest := map[string]interface{}{} + if req, ok := auditRecord["request"]; ok { + auditRequest = req.(map[string]interface{}) + if auditRequest["path"] != "auth/"+plugin.Name+"/role/role1" { + continue + } + } + testExternalPluginMetadataAuditLog(t, auditRequest, consts.PluginTypeCredential.String()) + + auditResponse := map[string]interface{}{} + if req, ok := auditRecord["response"]; ok { + auditRequest = req.(map[string]interface{}) + if auditResponse["path"] != "auth/"+plugin.Name+"/role/role1" { + continue + } + } + testExternalPluginMetadataAuditLog(t, auditResponse, consts.PluginTypeCredential.String()) + } + + // Deregister + if err := client.Sys().DeregisterPlugin(&api.DeregisterPluginInput{ + Name: plugin.Name, + Type: api.PluginType(plugin.Typ), + Version: plugin.Version, + }); err != nil { + t.Fatal(err) + } +} + +// TestExternalPlugin_AuditEnabled_ShouldLogPluginMetadata_Secret tests that we have plugin metadata of a secret plugin +// in audit log when it is enabled +func TestExternalPlugin_AuditEnabled_ShouldLogPluginMetadata_Secret(t *testing.T) { + cluster := getClusterWithFileAuditBackend(t, consts.PluginTypeSecrets, 1) + defer cluster.Cleanup() + + plugin := cluster.Plugins[0] + client := cluster.Cores[0].Client + client.SetToken(cluster.RootToken) + + testRegisterAndEnable(t, client, plugin) + + // Enable the audit backend + tempDir := t.TempDir() + auditLogFile, err := os.CreateTemp(tempDir, "") + if err != nil { + t.Fatal(err) + } + + err = client.Sys().EnableAuditWithOptions("file", &api.EnableAuditOptions{ + Type: "file", + Options: map[string]string{ + "file_path": auditLogFile.Name(), + }, + }) + if err != nil { + t.Fatal(err) + } + + // Configure + cleanupConsul, consulConfig := consul.PrepareTestContainer(t, "", false, true) + defer cleanupConsul() + _, err = client.Logical().Write(plugin.Name+"/config/access", map[string]interface{}{ + "address": consulConfig.Address(), + "token": consulConfig.Token, + }) + if err != nil { + t.Fatal(err) + } + + // Check the audit trail on request and response + decoder := json.NewDecoder(auditLogFile) + var auditRecord map[string]interface{} + for decoder.Decode(&auditRecord) == nil { + auditRequest := map[string]interface{}{} + if req, ok := auditRecord["request"]; ok { + auditRequest = req.(map[string]interface{}) + if auditRequest["path"] != plugin.Name+"/config/access" { + continue + } + } + testExternalPluginMetadataAuditLog(t, auditRequest, consts.PluginTypeSecrets.String()) + + auditResponse := map[string]interface{}{} + if req, ok := auditRecord["response"]; ok { + auditRequest = req.(map[string]interface{}) + if auditResponse["path"] != plugin.Name+"/config/access" { + continue + } + } + testExternalPluginMetadataAuditLog(t, auditResponse, consts.PluginTypeSecrets.String()) + } + + // Deregister + if err := client.Sys().DeregisterPlugin(&api.DeregisterPluginInput{ + Name: plugin.Name, + Type: api.PluginType(plugin.Typ), + Version: plugin.Version, + }); err != nil { + t.Fatal(err) + } +} diff --git a/vault/mount.go b/vault/mount.go index 9936e6bda..48ff2df47 100644 --- a/vault/mount.go +++ b/vault/mount.go @@ -422,6 +422,25 @@ func (e *MountEntry) Clone() (*MountEntry, error) { return cp.(*MountEntry), nil } +// IsExternalPlugin returns whether the plugin is running externally +// if the RunningSha256 is non-empty, the builtin is external. Otherwise, it's builtin +func (e *MountEntry) IsExternalPlugin() bool { + return e.RunningSha256 != "" +} + +// MountClass returns the mount class based on Accessor and Path +func (e *MountEntry) MountClass() string { + if e.Accessor == "" || strings.HasPrefix(e.Path, fmt.Sprintf("%s/", systemMountPath)) { + return "" + } + + if e.Table == credentialTableType { + return consts.PluginTypeCredential.String() + } + + return consts.PluginTypeSecrets.String() +} + // Namespace returns the namespace for the mount entry func (e *MountEntry) Namespace() *namespace.Namespace { return e.namespace diff --git a/vault/request_handling.go b/vault/request_handling.go index 9dee7f9a7..b36122987 100644 --- a/vault/request_handling.go +++ b/vault/request_handling.go @@ -850,6 +850,11 @@ func (c *Core) handleRequest(ctx context.Context, req *logical.Request) (retResp if entry != nil { // Set here so the audit log has it even if authorization fails req.MountType = entry.Type + req.SetMountRunningSha256(entry.RunningSha256) + req.SetMountRunningVersion(entry.RunningVersion) + req.SetMountIsExternalPlugin(entry.IsExternalPlugin()) + req.SetMountClass(entry.MountClass()) + // Get and set ignored HMAC'd value. if rawVals, ok := entry.synthesizedConfigCache.Load("audit_non_hmac_request_keys"); ok { nonHMACReqDataKeys = rawVals.([]string) @@ -1270,6 +1275,11 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re if entry != nil { // Set here so the audit log has it even if authorization fails req.MountType = entry.Type + req.SetMountRunningSha256(entry.RunningSha256) + req.SetMountRunningVersion(entry.RunningVersion) + req.SetMountIsExternalPlugin(entry.IsExternalPlugin()) + req.SetMountClass(entry.MountClass()) + // Get and set ignored HMAC'd value. if rawVals, ok := entry.synthesizedConfigCache.Load("audit_non_hmac_request_keys"); ok { nonHMACReqDataKeys = rawVals.([]string) diff --git a/vault/router.go b/vault/router.go index 75ad01d1e..3497e8244 100644 --- a/vault/router.go +++ b/vault/router.go @@ -618,6 +618,11 @@ func (r *Router) routeCommon(ctx context.Context, req *logical.Request, existenc req.Path = strings.TrimPrefix(ns.Path+req.Path, mount) req.MountPoint = mount req.MountType = re.mountEntry.Type + req.SetMountRunningSha256(re.mountEntry.RunningSha256) + req.SetMountRunningVersion(re.mountEntry.RunningVersion) + req.SetMountIsExternalPlugin(re.mountEntry.IsExternalPlugin()) + req.SetMountClass(re.mountEntry.MountClass()) + if req.Path == "/" { req.Path = "" } @@ -733,6 +738,11 @@ func (r *Router) routeCommon(ctx context.Context, req *logical.Request, existenc req.Path = originalPath req.MountPoint = mount req.MountType = re.mountEntry.Type + req.SetMountRunningSha256(re.mountEntry.RunningSha256) + req.SetMountRunningVersion(re.mountEntry.RunningVersion) + req.SetMountIsExternalPlugin(re.mountEntry.IsExternalPlugin()) + req.SetMountClass(re.mountEntry.MountClass()) + req.Connection = originalConn req.ID = originalReqID req.Storage = nil