Skip to content

Commit

Permalink
backport of commit bcd89f8 (#29343)
Browse files Browse the repository at this point in the history
Co-authored-by: Thy Ton <[email protected]>
  • Loading branch information
hc-github-team-secure-vault-core and thyton authored Jan 10, 2025
1 parent 6f2a734 commit 818aa55
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 140 deletions.
5 changes: 1 addition & 4 deletions sdk/plugin/grpc_backend_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand Down
20 changes: 20 additions & 0 deletions sdk/plugin/grpc_backend_client_stubs_oss.go
Original file line number Diff line number Diff line change
@@ -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,
})
}
3 changes: 1 addition & 2 deletions sdk/plugin/grpc_backend_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
17 changes: 17 additions & 0 deletions sdk/plugin/grpc_backend_server_stubs_oss.go
Original file line number Diff line number Diff line change
@@ -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)
}
134 changes: 0 additions & 134 deletions vault/dynamic_system_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
140 changes: 140 additions & 0 deletions vault/extended_system_view.go
Original file line number Diff line number Diff line change
@@ -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)
}
22 changes: 22 additions & 0 deletions vault/extended_system_view_stubs_oss.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 818aa55

Please sign in to comment.