diff --git a/sdk/plugin/grpc_backend_client.go b/sdk/plugin/grpc_backend_client.go index 4e92ad13ec58..ca677bc43e70 100644 --- a/sdk/plugin/grpc_backend_client.go +++ b/sdk/plugin/grpc_backend_client.go @@ -231,9 +231,6 @@ func (b *backendGRPCPluginClient) Setup(ctx context.Context, config *logical.Bac if b.metadataMode { sysViewImpl = &logical.StaticSystemView{} } - sysView := &gRPCSystemViewServer{ - impl: sysViewImpl, - } events := &GRPCEventsServer{ impl: config.EventsSender, @@ -245,7 +242,7 @@ func (b *backendGRPCPluginClient) Setup(ctx context.Context, config *logical.Bac opts = append(opts, grpc.MaxSendMsgSize(math.MaxInt32)) s := grpc.NewServer(opts...) - pb.RegisterSystemViewServer(s, sysView) + registerSystemViewServer(s, sysViewImpl, config) pb.RegisterStorageServer(s, storage) pb.RegisterEventsServer(s, events) b.server.Store(s) diff --git a/sdk/plugin/grpc_backend_client_stubs_oss.go b/sdk/plugin/grpc_backend_client_stubs_oss.go new file mode 100644 index 000000000000..40e1ec12f307 --- /dev/null +++ b/sdk/plugin/grpc_backend_client_stubs_oss.go @@ -0,0 +1,20 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build !enterprise + +package plugin + +import ( + "github.com/hashicorp/vault/sdk/logical" + "github.com/hashicorp/vault/sdk/plugin/pb" + "google.golang.org/grpc" +) + +// registerSystemViewServer (Vault Community edition) registers the SystemView server +// to the gRPC service registrar +func registerSystemViewServer(s *grpc.Server, sysView logical.SystemView, _ *logical.BackendConfig) { + pb.RegisterSystemViewServer(s, &gRPCSystemViewServer{ + impl: sysView, + }) +} diff --git a/sdk/plugin/grpc_backend_server.go b/sdk/plugin/grpc_backend_server.go index b537d41bb550..9367b735cf35 100644 --- a/sdk/plugin/grpc_backend_server.go +++ b/sdk/plugin/grpc_backend_server.go @@ -94,13 +94,12 @@ func (b *backendGRPCPluginServer) Setup(ctx context.Context, args *pb.SetupArgs) } storage := newGRPCStorageClient(brokeredClient) - sysView := newGRPCSystemView(brokeredClient) events := newGRPCEventsClient(brokeredClient) config := &logical.BackendConfig{ StorageView: storage, Logger: b.logger, - System: sysView, + System: newGRPCSystemViewFromSetupArgs(brokeredClient, args), Config: args.Config, BackendUUID: args.BackendUUID, EventsSender: events, diff --git a/sdk/plugin/grpc_backend_server_stubs_oss.go b/sdk/plugin/grpc_backend_server_stubs_oss.go new file mode 100644 index 000000000000..3ceee2918dd2 --- /dev/null +++ b/sdk/plugin/grpc_backend_server_stubs_oss.go @@ -0,0 +1,17 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build !enterprise + +package plugin + +import ( + "github.com/hashicorp/vault/sdk/logical" + "github.com/hashicorp/vault/sdk/plugin/pb" + "google.golang.org/grpc" +) + +// newGRPCSystemViewFromSetupArgs (Vault Community edition) constructs a gRPC SystemView client. +func newGRPCSystemViewFromSetupArgs(conn *grpc.ClientConn, _ *pb.SetupArgs) logical.SystemView { + return newGRPCSystemView(conn) +} diff --git a/vault/dynamic_system_view.go b/vault/dynamic_system_view.go index 3c161ec5abc8..39ef3fd6b81a 100644 --- a/vault/dynamic_system_view.go +++ b/vault/dynamic_system_view.go @@ -33,140 +33,6 @@ type dynamicSystemView struct { perfStandby bool } -type extendedSystemView interface { - logical.SystemView - logical.ExtendedSystemView - // SudoPrivilege won't work over the plugin system so we keep it here - // instead of in sdk/logical to avoid exposing to plugins - SudoPrivilege(context.Context, string, string) bool -} - -var _ logical.ExtendedSystemView = (*extendedSystemViewImpl)(nil) - -type extendedSystemViewImpl struct { - dynamicSystemView -} - -func (e extendedSystemViewImpl) Auditor() logical.Auditor { - return genericAuditor{ - mountType: e.mountEntry.Type, - namespace: e.mountEntry.Namespace(), - c: e.core, - } -} - -func (e extendedSystemViewImpl) ForwardGenericRequest(ctx context.Context, req *logical.Request) (*logical.Response, error) { - // Forward the request if allowed - if couldForward(e.core) { - ctx = namespace.ContextWithNamespace(ctx, e.mountEntry.Namespace()) - ctx = logical.IndexStateContext(ctx, &logical.WALState{}) - ctx = context.WithValue(ctx, ctxKeyForwardedRequestMountAccessor{}, e.mountEntry.Accessor) - return forward(ctx, e.core, req) - } - - return nil, logical.ErrReadOnly -} - -// SudoPrivilege returns true if given path has sudo privileges -// for the given client token -func (e extendedSystemViewImpl) SudoPrivilege(ctx context.Context, path string, token string) bool { - // Resolve the token policy - te, err := e.core.tokenStore.Lookup(ctx, token) - if err != nil { - e.core.logger.Error("failed to lookup sudo token", "error", err) - return false - } - - // Ensure the token is valid - if te == nil { - e.core.logger.Error("entry not found for given token") - return false - } - - policyNames := make(map[string][]string) - // Add token policies - policyNames[te.NamespaceID] = append(policyNames[te.NamespaceID], te.Policies...) - - tokenNS, err := NamespaceByID(ctx, te.NamespaceID, e.core) - if err != nil { - e.core.logger.Error("failed to lookup token namespace", "error", err) - return false - } - if tokenNS == nil { - e.core.logger.Error("failed to lookup token namespace", "error", namespace.ErrNoNamespace) - return false - } - - // Add identity policies from all the namespaces - entity, identityPolicies, err := e.core.fetchEntityAndDerivedPolicies(ctx, tokenNS, te.EntityID, te.NoIdentityPolicies) - if err != nil { - e.core.logger.Error("failed to fetch identity policies", "error", err) - return false - } - for nsID, nsPolicies := range identityPolicies { - policyNames[nsID] = append(policyNames[nsID], nsPolicies...) - } - - tokenCtx := namespace.ContextWithNamespace(ctx, tokenNS) - - // Add the inline policy if it's set - policies := make([]*Policy, 0) - if te.InlinePolicy != "" { - inlinePolicy, err := ParseACLPolicy(tokenNS, te.InlinePolicy) - if err != nil { - e.core.logger.Error("failed to parse the token's inline policy", "error", err) - return false - } - policies = append(policies, inlinePolicy) - } - - // Construct the corresponding ACL object. Derive and use a new context that - // uses the req.ClientToken's namespace - acl, err := e.core.policyStore.ACL(tokenCtx, entity, policyNames, policies...) - if err != nil { - e.core.logger.Error("failed to retrieve ACL for token's policies", "token_policies", te.Policies, "error", err) - return false - } - - // The operation type isn't important here as this is run from a path the - // user has already been given access to; we only care about whether they - // have sudo. Note that we use root context because the path that comes in - // must be fully-qualified already so we don't want AllowOperation to - // prepend a namespace prefix onto it. - req := new(logical.Request) - req.Operation = logical.ReadOperation - req.Path = path - authResults := acl.AllowOperation(namespace.RootContext(ctx), req, true) - return authResults.RootPrivs -} - -func (e extendedSystemViewImpl) APILockShouldBlockRequest() (bool, error) { - mountEntry := e.mountEntry - if mountEntry == nil { - return false, fmt.Errorf("no mount entry") - } - ns := mountEntry.Namespace() - - if err := e.core.entBlockRequestIfError(ns.Path, mountEntry.Path); err != nil { - return true, nil - } - - return false, nil -} - -func (e extendedSystemViewImpl) RequestWellKnownRedirect(ctx context.Context, src, dest string) error { - return e.core.WellKnownRedirects.TryRegister(ctx, e.core, e.mountEntry.UUID, src, dest) -} - -func (e extendedSystemViewImpl) DeregisterWellKnownRedirect(ctx context.Context, src string) bool { - return e.core.WellKnownRedirects.DeregisterSource(e.mountEntry.UUID, src) -} - -// GetPinnedPluginVersion implements logical.ExtendedSystemView. -func (e extendedSystemViewImpl) GetPinnedPluginVersion(ctx context.Context, pluginType consts.PluginType, pluginName string) (*pluginutil.PinnedVersion, error) { - return e.core.pluginCatalog.GetPinnedVersion(ctx, pluginType, pluginName) -} - func (d dynamicSystemView) DefaultLeaseTTL() time.Duration { def, _ := d.fetchTTLs() return def diff --git a/vault/extended_system_view.go b/vault/extended_system_view.go new file mode 100644 index 000000000000..6809d82bd3dc --- /dev/null +++ b/vault/extended_system_view.go @@ -0,0 +1,140 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package vault + +import ( + "context" + "fmt" + + "github.com/hashicorp/vault/helper/namespace" + "github.com/hashicorp/vault/sdk/helper/consts" + "github.com/hashicorp/vault/sdk/helper/pluginutil" + "github.com/hashicorp/vault/sdk/logical" +) + +var _ logical.ExtendedSystemView = (*extendedSystemViewImpl)(nil) + +type extendedSystemViewImpl struct { + dynamicSystemView +} + +func (e extendedSystemViewImpl) Auditor() logical.Auditor { + return genericAuditor{ + mountType: e.mountEntry.Type, + namespace: e.mountEntry.Namespace(), + c: e.core, + } +} + +func (e extendedSystemViewImpl) ForwardGenericRequest(ctx context.Context, req *logical.Request) (*logical.Response, error) { + // Forward the request if allowed + if couldForward(e.core) { + ctx = namespace.ContextWithNamespace(ctx, e.mountEntry.Namespace()) + ctx = logical.IndexStateContext(ctx, &logical.WALState{}) + ctx = context.WithValue(ctx, ctxKeyForwardedRequestMountAccessor{}, e.mountEntry.Accessor) + return forward(ctx, e.core, req) + } + + return nil, logical.ErrReadOnly +} + +// SudoPrivilege returns true if given path has sudo privileges +// for the given client token +func (e extendedSystemViewImpl) SudoPrivilege(ctx context.Context, path string, token string) bool { + // Resolve the token policy + te, err := e.core.tokenStore.Lookup(ctx, token) + if err != nil { + e.core.logger.Error("failed to lookup sudo token", "error", err) + return false + } + + // Ensure the token is valid + if te == nil { + e.core.logger.Error("entry not found for given token") + return false + } + + policyNames := make(map[string][]string) + // Add token policies + policyNames[te.NamespaceID] = append(policyNames[te.NamespaceID], te.Policies...) + + tokenNS, err := NamespaceByID(ctx, te.NamespaceID, e.core) + if err != nil { + e.core.logger.Error("failed to lookup token namespace", "error", err) + return false + } + if tokenNS == nil { + e.core.logger.Error("failed to lookup token namespace", "error", namespace.ErrNoNamespace) + return false + } + + // Add identity policies from all the namespaces + entity, identityPolicies, err := e.core.fetchEntityAndDerivedPolicies(ctx, tokenNS, te.EntityID, te.NoIdentityPolicies) + if err != nil { + e.core.logger.Error("failed to fetch identity policies", "error", err) + return false + } + for nsID, nsPolicies := range identityPolicies { + policyNames[nsID] = append(policyNames[nsID], nsPolicies...) + } + + tokenCtx := namespace.ContextWithNamespace(ctx, tokenNS) + + // Add the inline policy if it's set + policies := make([]*Policy, 0) + if te.InlinePolicy != "" { + inlinePolicy, err := ParseACLPolicy(tokenNS, te.InlinePolicy) + if err != nil { + e.core.logger.Error("failed to parse the token's inline policy", "error", err) + return false + } + policies = append(policies, inlinePolicy) + } + + // Construct the corresponding ACL object. Derive and use a new context that + // uses the req.ClientToken's namespace + acl, err := e.core.policyStore.ACL(tokenCtx, entity, policyNames, policies...) + if err != nil { + e.core.logger.Error("failed to retrieve ACL for token's policies", "token_policies", te.Policies, "error", err) + return false + } + + // The operation type isn't important here as this is run from a path the + // user has already been given access to; we only care about whether they + // have sudo. Note that we use root context because the path that comes in + // must be fully-qualified already so we don't want AllowOperation to + // prepend a namespace prefix onto it. + req := new(logical.Request) + req.Operation = logical.ReadOperation + req.Path = path + authResults := acl.AllowOperation(namespace.RootContext(ctx), req, true) + return authResults.RootPrivs +} + +func (e extendedSystemViewImpl) APILockShouldBlockRequest() (bool, error) { + mountEntry := e.mountEntry + if mountEntry == nil { + return false, fmt.Errorf("no mount entry") + } + ns := mountEntry.Namespace() + + if err := e.core.entBlockRequestIfError(ns.Path, mountEntry.Path); err != nil { + return true, nil + } + + return false, nil +} + +func (e extendedSystemViewImpl) RequestWellKnownRedirect(ctx context.Context, src, dest string) error { + return e.core.WellKnownRedirects.TryRegister(ctx, e.core, e.mountEntry.UUID, src, dest) +} + +func (e extendedSystemViewImpl) DeregisterWellKnownRedirect(ctx context.Context, src string) bool { + return e.core.WellKnownRedirects.DeregisterSource(e.mountEntry.UUID, src) +} + +// GetPinnedPluginVersion implements logical.ExtendedSystemView. +func (e extendedSystemViewImpl) GetPinnedPluginVersion(ctx context.Context, pluginType consts.PluginType, pluginName string) (*pluginutil.PinnedVersion, error) { + return e.core.pluginCatalog.GetPinnedVersion(ctx, pluginType, pluginName) +} diff --git a/vault/extended_system_view_stubs_oss.go b/vault/extended_system_view_stubs_oss.go new file mode 100644 index 000000000000..6ccd7768365c --- /dev/null +++ b/vault/extended_system_view_stubs_oss.go @@ -0,0 +1,22 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +//go:build !enterprise + +package vault + +import ( + "context" + + "github.com/hashicorp/vault/sdk/logical" +) + +// extendedSystemView (Vault Community edition) is a logical.SystemView +// that is extended with logical.ExtendedSystemView and SudoPrivilege +type extendedSystemView interface { + logical.SystemView + logical.ExtendedSystemView + // SudoPrivilege won't work over the plugin system so we keep it here + // instead of in sdk/logical to avoid exposing to plugins + SudoPrivilege(context.Context, string, string) bool +}