// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package vault import ( "context" "errors" "fmt" "strings" "time" "github.com/hashicorp/go-memdb" "github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/logical" ) type enterprisePathStub struct { parameters []string operations []logical.Operation } var ( invalidateMFAConfig = func(context.Context, *SystemBackend, string) {} invalidateLoginMFAConfig = func(context.Context, *SystemBackend, string) {} invalidateLoginMFALoginEnforcementConfig = func(context.Context, *SystemBackend, string) {} sysInvalidate = func(b *SystemBackend) func(context.Context, string) { return nil } getSystemSchemas = func() []func() *memdb.TableSchema { return nil } getEGPListResponseKeyInfo = func(*SystemBackend, *namespace.Namespace) map[string]interface{} { return nil } addSentinelPolicyData = func(map[string]interface{}, *Policy) {} inputSentinelPolicyData = func(*framework.FieldData, *Policy) *logical.Response { return nil } controlGroupUnwrap = func(context.Context, *SystemBackend, string, bool) (string, error) { return "", errors.New("control groups unavailable") } pathInternalUINamespacesRead = func(b *SystemBackend) framework.OperationFunc { return func(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) { // Short-circuit here if there's no client token provided if req.ClientToken == "" { return nil, fmt.Errorf("client token empty") } // Load the ACL policies so we can check for access and filter namespaces _, te, entity, _, err := b.Core.fetchACLTokenEntryAndEntity(ctx, req) if err != nil { return nil, err } if entity != nil && entity.Disabled { b.logger.Warn("permission denied as the entity on the token is disabled") return nil, logical.ErrPermissionDenied } if te != nil && te.EntityID != "" && entity == nil { b.logger.Warn("permission denied as the entity on the token is invalid") return nil, logical.ErrPermissionDenied } return logical.ListResponse([]string{""}), nil } } entPaths = entStubPaths entStubPaths = func(b *SystemBackend) []*framework.Path { buildEnterpriseOnlyPaths := func(paths map[string]enterprisePathStub) []*framework.Path { var results []*framework.Path for pattern, pathSpec := range paths { path := &framework.Path{ Pattern: pattern, Operations: make(map[logical.Operation]framework.OperationHandler), Fields: make(map[string]*framework.FieldSchema), } for _, parameter := range pathSpec.parameters { path.Fields[parameter] = &framework.FieldSchema{ Type: framework.TypeString, Required: true, } } for _, operation := range pathSpec.operations { path.Operations[operation] = &framework.PathOperation{ Callback: func(context.Context, *logical.Request, *framework.FieldData) (*logical.Response, error) { return logical.ErrorResponse("enterprise-only feature"), logical.ErrUnsupportedPath }, } } results = append(results, path) } return results } var paths []*framework.Path // license paths paths = append(paths, buildEnterpriseOnlyPaths(map[string]enterprisePathStub{ "license/status$": {operations: []logical.Operation{logical.ReadOperation}}, })...) // group-policy-application paths paths = append(paths, buildEnterpriseOnlyPaths(map[string]enterprisePathStub{ "config/group-policy-application$": {operations: []logical.Operation{logical.ReadOperation, logical.UpdateOperation}}, })...) // namespaces paths paths = append(paths, buildEnterpriseOnlyPaths(map[string]enterprisePathStub{ "namespaces/?$": {operations: []logical.Operation{logical.ListOperation}}, "namespaces/api-lock/lock" + framework.OptionalParamRegex("path"): {parameters: []string{"path"}, operations: []logical.Operation{logical.UpdateOperation}}, "namespaces/api-lock/unlock" + framework.OptionalParamRegex("path"): {parameters: []string{"path"}, operations: []logical.Operation{logical.UpdateOperation}}, "namespaces/(?P.+?)": {parameters: []string{"path"}, operations: []logical.Operation{logical.DeleteOperation, logical.PatchOperation, logical.ReadOperation, logical.UpdateOperation}}, })...) // replication paths paths = append(paths, buildEnterpriseOnlyPaths(map[string]enterprisePathStub{ "replication/performance/primary/enable": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/dr/primary/enable": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/performance/primary/demote": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/dr/primary/demote": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/performance/primary/disable": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/dr/primary/disable": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/performance/primary/secondary-token": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/dr/primary/secondary-token": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/performance/primary/revoke-secondary": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/dr/primary/revoke-secondary": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/performance/secondary/generate-public-key": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/dr/secondary/generate-public-key": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/performance/secondary/enable": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/dr/secondary/enable": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/performance/secondary/promote": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/dr/secondary/promote": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/performance/secondary/disable": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/dr/secondary/disable": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/dr/secondary/operation-token/delete": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/performance/secondary/update-primary": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/dr/secondary/update-primary": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/dr/secondary/license/status": {operations: []logical.Operation{logical.ReadOperation}}, "replication/dr/secondary/config/reload/(?P.+)": {parameters: []string{"subsystem"}, operations: []logical.Operation{logical.UpdateOperation}}, "replication/recover": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/dr/secondary/recover": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/dr/secondary/reindex": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/reindex": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/dr/status": {operations: []logical.Operation{logical.ReadOperation}}, "replication/performance/status": {operations: []logical.Operation{logical.ReadOperation}}, "replication/primary/enable": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/primary/demote": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/primary/disable": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/primary/secondary-token": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/performance/primary/paths-filter/" + framework.GenericNameRegex("id"): {parameters: []string{"id"}, operations: []logical.Operation{logical.DeleteOperation, logical.ReadOperation, logical.UpdateOperation}}, "replication/performance/primary/dynamic-filter/" + framework.GenericNameRegex("id"): {parameters: []string{"id"}, operations: []logical.Operation{logical.ReadOperation}}, "replication/primary/revoke-secondary": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/secondary/enable": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/secondary/promote": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/secondary/disable": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/secondary/update-primary": {operations: []logical.Operation{logical.UpdateOperation}}, "replication/performance/secondary/dynamic-filter/" + framework.GenericNameRegex("id"): {parameters: []string{"id"}, operations: []logical.Operation{logical.ReadOperation}}, })...) // This path, though an enterprise path, has always been handled in OSS. paths = append(paths, &framework.Path{ Pattern: "replication/status", Callbacks: map[logical.Operation]framework.OperationFunc{ logical.ReadOperation: func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { resp := &logical.Response{ Data: map[string]interface{}{ "mode": "disabled", }, } return resp, nil }, }, }) // seal paths paths = append(paths, buildEnterpriseOnlyPaths(map[string]enterprisePathStub{ "sealwrap/rewrap": {operations: []logical.Operation{logical.ReadOperation, logical.UpdateOperation}}, })...) // mfa paths paths = append(paths, buildEnterpriseOnlyPaths(map[string]enterprisePathStub{ "mfa/method/?": {operations: []logical.Operation{logical.ListOperation}}, "mfa/method/totp/" + framework.GenericNameRegex("name") + "/generate$": {parameters: []string{"name"}, operations: []logical.Operation{logical.ReadOperation}}, "mfa/method/totp/" + framework.GenericNameRegex("name") + "/admin-generate$": {parameters: []string{"name"}, operations: []logical.Operation{logical.UpdateOperation}}, "mfa/method/totp/" + framework.GenericNameRegex("name") + "/admin-destroy$": {parameters: []string{"name"}, operations: []logical.Operation{logical.UpdateOperation}}, "mfa/method/totp/" + framework.GenericNameRegex("name"): {parameters: []string{"name"}, operations: []logical.Operation{logical.DeleteOperation, logical.ReadOperation, logical.UpdateOperation}}, "mfa/method/okta/" + framework.GenericNameRegex("name"): {parameters: []string{"name"}, operations: []logical.Operation{logical.DeleteOperation, logical.ReadOperation, logical.UpdateOperation}}, "mfa/method/duo/" + framework.GenericNameRegex("name"): {parameters: []string{"name"}, operations: []logical.Operation{logical.DeleteOperation, logical.ReadOperation, logical.UpdateOperation}}, "mfa/method/pingid/" + framework.GenericNameRegex("name"): {parameters: []string{"name"}, operations: []logical.Operation{logical.DeleteOperation, logical.ReadOperation, logical.UpdateOperation}}, })...) // control-group paths paths = append(paths, buildEnterpriseOnlyPaths(map[string]enterprisePathStub{ "control-group/authorize": {operations: []logical.Operation{logical.UpdateOperation}}, "control-group/request": {operations: []logical.Operation{logical.UpdateOperation}}, "config/control-group": {operations: []logical.Operation{logical.DeleteOperation, logical.ReadOperation, logical.UpdateOperation}}, })...) // sentinel paths paths = append(paths, buildEnterpriseOnlyPaths(map[string]enterprisePathStub{ "policies/rgp/?$": {operations: []logical.Operation{logical.ListOperation}}, "policies/rgp/(?P.+)": {parameters: []string{"name"}, operations: []logical.Operation{logical.DeleteOperation, logical.ReadOperation, logical.UpdateOperation}}, "policies/egp/?$": {operations: []logical.Operation{logical.ListOperation}}, "policies/egp/(?P.+)": {parameters: []string{"name"}, operations: []logical.Operation{logical.DeleteOperation, logical.ReadOperation, logical.UpdateOperation}}, })...) // plugins reload status paths paths = append(paths, buildEnterpriseOnlyPaths(map[string]enterprisePathStub{ "plugins/reload/backend/status$": {operations: []logical.Operation{logical.ReadOperation}}, })...) // quotas paths paths = append(paths, buildEnterpriseOnlyPaths(map[string]enterprisePathStub{ "quotas/lease-count/?$": {operations: []logical.Operation{logical.ListOperation}}, "quotas/lease-count/" + framework.GenericNameRegex("name"): {parameters: []string{"name"}, operations: []logical.Operation{logical.DeleteOperation, logical.ReadOperation, logical.UpdateOperation}}, })...) // raft auto-snapshot paths paths = append(paths, buildEnterpriseOnlyPaths(map[string]enterprisePathStub{ "storage/raft/snapshot-auto/config/": {operations: []logical.Operation{logical.ListOperation}}, "storage/raft/snapshot-auto/config/" + framework.GenericNameRegex("name"): {parameters: []string{"name"}, operations: []logical.Operation{logical.DeleteOperation, logical.ReadOperation, logical.UpdateOperation}}, "storage/raft/snapshot-auto/status/" + framework.GenericNameRegex("name"): {parameters: []string{"name"}, operations: []logical.Operation{logical.ReadOperation}}, })...) paths = append(paths, buildEnterpriseOnlyPaths(map[string]enterprisePathStub{ "managed-keys/" + framework.GenericNameRegex("type") + "/?": {parameters: []string{"type"}, operations: []logical.Operation{logical.ListOperation}}, "managed-keys/" + framework.GenericNameRegex("type") + "/" + framework.GenericNameRegex("name"): {parameters: []string{"type", "name"}, operations: []logical.Operation{logical.CreateOperation, logical.DeleteOperation, logical.ReadOperation, logical.UpdateOperation}}, "managed-keys/" + framework.GenericNameRegex("type") + "/" + framework.GenericNameRegex("name") + "/test/sign": {parameters: []string{"type", "name"}, operations: []logical.Operation{logical.CreateOperation, logical.UpdateOperation}}, })...) return paths } handleGlobalPluginReload = func(context.Context, *Core, string, string, []string) error { return nil } handleSetupPluginReload = func(*Core) error { return nil } handleLicenseReload = func(b *SystemBackend) framework.OperationFunc { return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { return nil, nil } } checkRaw = func(b *SystemBackend, path string) error { return nil } ) // tuneMount is used to set config on a mount point func (b *SystemBackend) tuneMountTTLs(ctx context.Context, path string, me *MountEntry, newDefault, newMax time.Duration) error { zero := time.Duration(0) switch { case newDefault == zero && newMax == zero: // No checks needed case newDefault == zero && newMax != zero: // No default/max conflict, no checks needed case newDefault != zero && newMax == zero: // No default/max conflict, no checks needed case newDefault != zero && newMax != zero: if newMax < newDefault { return fmt.Errorf("backend max lease TTL of %d would be less than backend default lease TTL of %d", int(newMax.Seconds()), int(newDefault.Seconds())) } } origMax := me.Config.MaxLeaseTTL origDefault := me.Config.DefaultLeaseTTL me.Config.MaxLeaseTTL = newMax me.Config.DefaultLeaseTTL = newDefault // Update the mount table var err error switch { case strings.HasPrefix(path, credentialRoutePrefix): err = b.Core.persistAuth(ctx, b.Core.auth, &me.Local) default: err = b.Core.persistMounts(ctx, b.Core.mounts, &me.Local) } if err != nil { me.Config.MaxLeaseTTL = origMax me.Config.DefaultLeaseTTL = origDefault return fmt.Errorf("failed to update mount table, rolling back TTL changes") } if b.Core.logger.IsInfo() { b.Core.logger.Info("mount tuning of leases successful", "path", path) } return nil }