2023-03-15 19:00:52 +03:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
2024-01-02 21:36:20 +03:00
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
2023-03-15 19:00:52 +03:00
|
|
|
|
2017-10-11 20:21:20 +03:00
|
|
|
package vault
|
|
|
|
|
|
|
|
import (
|
2018-01-19 09:44:44 +03:00
|
|
|
"context"
|
2017-10-11 20:21:20 +03:00
|
|
|
"fmt"
|
|
|
|
"strings"
|
2020-04-23 21:31:22 +03:00
|
|
|
"time"
|
2017-10-11 20:21:20 +03:00
|
|
|
|
2022-02-18 00:08:51 +03:00
|
|
|
"github.com/armon/go-metrics"
|
2017-10-11 20:21:20 +03:00
|
|
|
"github.com/golang/protobuf/ptypes"
|
2018-04-03 03:46:59 +03:00
|
|
|
log "github.com/hashicorp/go-hclog"
|
2019-06-21 20:23:39 +03:00
|
|
|
"github.com/hashicorp/go-memdb"
|
2021-07-16 03:17:31 +03:00
|
|
|
"github.com/hashicorp/go-secure-stdlib/strutil"
|
2017-10-11 20:21:20 +03:00
|
|
|
"github.com/hashicorp/vault/helper/identity"
|
2020-10-14 02:38:21 +03:00
|
|
|
"github.com/hashicorp/vault/helper/metricsutil"
|
2018-09-18 06:03:00 +03:00
|
|
|
"github.com/hashicorp/vault/helper/namespace"
|
2017-10-11 20:21:20 +03:00
|
|
|
"github.com/hashicorp/vault/helper/storagepacker"
|
Add plugin version to GRPC interface (#17088)
Add plugin version to GRPC interface
Added a version interface in the sdk/logical so that it can be shared between all plugin types, and then wired it up to RunningVersion in the mounts, auth list, and database systems.
I've tested that this works with auth, database, and secrets plugin types, with the following logic to populate RunningVersion:
If a plugin has a PluginVersion() method implemented, then that is used
If not, and the plugin is built into the Vault binary, then the go.mod version is used
Otherwise, the it will be the empty string.
My apologies for the length of this PR.
* Placeholder backend should be external
We use a placeholder backend (previously a framework.Backend) before a
GRPC plugin is lazy-loaded. This makes us later think the plugin is a
builtin plugin.
So we added a `placeholderBackend` type that overrides the
`IsExternal()` method so that later we know that the plugin is external,
and don't give it a default builtin version.
2022-09-16 02:37:59 +03:00
|
|
|
"github.com/hashicorp/vault/helper/versions"
|
2019-04-13 10:44:06 +03:00
|
|
|
"github.com/hashicorp/vault/sdk/framework"
|
|
|
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
2019-04-13 00:54:35 +03:00
|
|
|
"github.com/hashicorp/vault/sdk/logical"
|
2021-09-27 20:55:29 +03:00
|
|
|
"github.com/patrickmn/go-cache"
|
2017-10-11 20:21:20 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2021-10-15 22:20:00 +03:00
|
|
|
groupBucketsPrefix = "packer/group/buckets/"
|
|
|
|
localAliasesBucketsPrefix = "packer/local-aliases/buckets/"
|
2017-10-11 20:21:20 +03:00
|
|
|
)
|
|
|
|
|
2018-09-18 06:03:00 +03:00
|
|
|
var (
|
2019-09-30 17:27:25 +03:00
|
|
|
caseSensitivityKey = "casesensitivity"
|
2018-09-18 06:03:00 +03:00
|
|
|
parseExtraEntityFromBucket = func(context.Context, *IdentityStore, *identity.Entity) (bool, error) { return false, nil }
|
|
|
|
addExtraEntityDataToResponse = func(*identity.Entity, map[string]interface{}) {}
|
|
|
|
)
|
|
|
|
|
2017-11-02 23:05:48 +03:00
|
|
|
func (c *Core) IdentityStore() *IdentityStore {
|
|
|
|
return c.identityStore
|
|
|
|
}
|
|
|
|
|
2018-10-19 22:47:26 +03:00
|
|
|
func (i *IdentityStore) resetDB(ctx context.Context) error {
|
2017-10-11 20:21:20 +03:00
|
|
|
var err error
|
|
|
|
|
2018-10-19 22:47:26 +03:00
|
|
|
i.db, err = memdb.NewMemDB(identityStoreSchema(!i.disableLowerCasedNames))
|
2017-10-11 20:21:20 +03:00
|
|
|
if err != nil {
|
2018-10-19 22:47:26 +03:00
|
|
|
return err
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
2018-10-19 22:47:26 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewIdentityStore(ctx context.Context, core *Core, config *logical.BackendConfig, logger log.Logger) (*IdentityStore, error) {
|
2017-10-11 20:21:20 +03:00
|
|
|
iStore := &IdentityStore{
|
2021-08-30 22:31:11 +03:00
|
|
|
view: config.StorageView,
|
|
|
|
logger: logger,
|
|
|
|
router: core.router,
|
|
|
|
redirectAddr: core.redirectAddr,
|
|
|
|
localNode: core,
|
|
|
|
namespacer: core,
|
|
|
|
metrics: core.MetricSink(),
|
|
|
|
totpPersister: core,
|
|
|
|
groupUpdater: core,
|
2021-09-27 20:55:29 +03:00
|
|
|
tokenStorer: core,
|
2021-10-15 22:20:00 +03:00
|
|
|
entityCreator: core,
|
2022-02-18 00:08:51 +03:00
|
|
|
mfaBackend: core.loginMFABackend,
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
2018-10-19 22:47:26 +03:00
|
|
|
// Create a memdb instance, which by default, operates on lower cased
|
|
|
|
// identity names
|
|
|
|
err := iStore.resetDB(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-09-05 22:52:54 +03:00
|
|
|
entitiesPackerLogger := iStore.logger.Named("storagepacker").Named("entities")
|
2023-11-04 00:40:17 +03:00
|
|
|
core.AddLogger(entitiesPackerLogger)
|
2021-10-15 22:20:00 +03:00
|
|
|
localAliasesPackerLogger := iStore.logger.Named("storagepacker").Named("local-aliases")
|
2023-11-04 00:40:17 +03:00
|
|
|
core.AddLogger(localAliasesPackerLogger)
|
2018-09-05 22:52:54 +03:00
|
|
|
groupsPackerLogger := iStore.logger.Named("storagepacker").Named("groups")
|
2023-11-04 00:40:17 +03:00
|
|
|
core.AddLogger(groupsPackerLogger)
|
2021-10-15 22:20:00 +03:00
|
|
|
|
2018-09-05 22:52:54 +03:00
|
|
|
iStore.entityPacker, err = storagepacker.NewStoragePacker(iStore.view, entitiesPackerLogger, "")
|
2017-10-11 20:21:20 +03:00
|
|
|
if err != nil {
|
2021-05-11 20:12:54 +03:00
|
|
|
return nil, fmt.Errorf("failed to create entity packer: %w", err)
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
2021-10-15 22:20:00 +03:00
|
|
|
iStore.localAliasPacker, err = storagepacker.NewStoragePacker(iStore.view, localAliasesPackerLogger, localAliasesBucketsPrefix)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to create local alias packer: %w", err)
|
|
|
|
}
|
|
|
|
|
2018-09-05 22:52:54 +03:00
|
|
|
iStore.groupPacker, err = storagepacker.NewStoragePacker(iStore.view, groupsPackerLogger, groupBucketsPrefix)
|
2017-10-11 20:21:20 +03:00
|
|
|
if err != nil {
|
2021-05-11 20:12:54 +03:00
|
|
|
return nil, fmt.Errorf("failed to create group packer: %w", err)
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
iStore.Backend = &framework.Backend{
|
2019-09-30 17:27:25 +03:00
|
|
|
BackendType: logical.TypeLogical,
|
|
|
|
Paths: iStore.paths(),
|
|
|
|
Invalidate: iStore.Invalidate,
|
|
|
|
InitializeFunc: iStore.initialize,
|
2019-06-21 20:23:39 +03:00
|
|
|
PathsSpecial: &logical.Paths{
|
|
|
|
Unauthenticated: []string{
|
|
|
|
"oidc/.well-known/*",
|
2021-10-13 19:51:20 +03:00
|
|
|
"oidc/provider/+/.well-known/*",
|
|
|
|
"oidc/provider/+/token",
|
2019-06-21 20:23:39 +03:00
|
|
|
},
|
2021-10-15 22:20:00 +03:00
|
|
|
LocalStorage: []string{
|
|
|
|
localAliasesBucketsPrefix,
|
|
|
|
},
|
2019-06-21 20:23:39 +03:00
|
|
|
},
|
|
|
|
PeriodicFunc: func(ctx context.Context, req *logical.Request) error {
|
2019-07-03 06:15:43 +03:00
|
|
|
iStore.oidcPeriodicFunc(ctx)
|
2019-06-21 20:23:39 +03:00
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
Add plugin version to GRPC interface (#17088)
Add plugin version to GRPC interface
Added a version interface in the sdk/logical so that it can be shared between all plugin types, and then wired it up to RunningVersion in the mounts, auth list, and database systems.
I've tested that this works with auth, database, and secrets plugin types, with the following logic to populate RunningVersion:
If a plugin has a PluginVersion() method implemented, then that is used
If not, and the plugin is built into the Vault binary, then the go.mod version is used
Otherwise, the it will be the empty string.
My apologies for the length of this PR.
* Placeholder backend should be external
We use a placeholder backend (previously a framework.Backend) before a
GRPC plugin is lazy-loaded. This makes us later think the plugin is a
builtin plugin.
So we added a `placeholderBackend` type that overrides the
`IsExternal()` method so that later we know that the plugin is external,
and don't give it a default builtin version.
2022-09-16 02:37:59 +03:00
|
|
|
RunningVersion: versions.DefaultBuiltinVersion,
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
2021-09-27 20:55:29 +03:00
|
|
|
iStore.oidcCache = newOIDCCache(cache.NoExpiration, cache.NoExpiration)
|
|
|
|
iStore.oidcAuthCodeCache = newOIDCCache(5*time.Minute, 5*time.Minute)
|
2019-06-21 20:23:39 +03:00
|
|
|
|
2018-01-19 09:44:44 +03:00
|
|
|
err = iStore.Setup(ctx, config)
|
2017-10-11 20:21:20 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return iStore, nil
|
|
|
|
}
|
|
|
|
|
2018-09-18 06:03:00 +03:00
|
|
|
func (i *IdentityStore) paths() []*framework.Path {
|
|
|
|
return framework.PathAppend(
|
|
|
|
entityPaths(i),
|
|
|
|
aliasPaths(i),
|
|
|
|
groupAliasPaths(i),
|
|
|
|
groupPaths(i),
|
|
|
|
lookupPaths(i),
|
|
|
|
upgradePaths(i),
|
2019-06-21 20:23:39 +03:00
|
|
|
oidcPaths(i),
|
2021-08-17 23:55:06 +03:00
|
|
|
oidcProviderPaths(i),
|
2022-02-18 00:08:51 +03:00
|
|
|
mfaPaths(i),
|
2018-09-18 06:03:00 +03:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-02-18 00:08:51 +03:00
|
|
|
func mfaPaths(i *IdentityStore) []*framework.Path {
|
|
|
|
return []*framework.Path{
|
2022-05-17 21:54:16 +03:00
|
|
|
{
|
|
|
|
Pattern: "mfa/method" + genericOptionalUUIDRegex("method_id"),
|
2023-04-11 23:00:35 +03:00
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationPrefix: "mfa",
|
|
|
|
OperationVerb: "read",
|
|
|
|
OperationSuffix: "method-configuration|method-configuration",
|
|
|
|
},
|
2022-05-17 21:54:16 +03:00
|
|
|
Fields: map[string]*framework.FieldSchema{
|
|
|
|
"method_id": {
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `The unique identifier for this MFA method.`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Operations: map[logical.Operation]framework.OperationHandler{
|
|
|
|
logical.ReadOperation: &framework.PathOperation{
|
|
|
|
Callback: i.handleMFAMethodReadGlobal,
|
|
|
|
Summary: "Read the current configuration for the given ID regardless of the MFA method type",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Pattern: "mfa/method/?$",
|
2023-04-11 23:00:35 +03:00
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationPrefix: "mfa",
|
|
|
|
OperationVerb: "list",
|
|
|
|
OperationSuffix: "methods",
|
|
|
|
},
|
2022-05-17 21:54:16 +03:00
|
|
|
Operations: map[logical.Operation]framework.OperationHandler{
|
|
|
|
logical.ListOperation: &framework.PathOperation{
|
|
|
|
Callback: i.handleMFAMethodListGlobal,
|
|
|
|
Summary: "List MFA method configurations for all MFA methods",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2022-02-18 00:08:51 +03:00
|
|
|
{
|
|
|
|
Pattern: "mfa/method/totp" + genericOptionalUUIDRegex("method_id"),
|
2023-04-11 23:00:35 +03:00
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationPrefix: "mfa",
|
|
|
|
},
|
2022-02-18 00:08:51 +03:00
|
|
|
Fields: map[string]*framework.FieldSchema{
|
2023-01-23 23:51:22 +03:00
|
|
|
"method_name": {
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `The unique name identifier for this MFA method.`,
|
|
|
|
},
|
2022-02-18 00:08:51 +03:00
|
|
|
"method_id": {
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `The unique identifier for this MFA method.`,
|
|
|
|
},
|
2022-04-14 20:48:24 +03:00
|
|
|
"max_validation_attempts": {
|
|
|
|
Type: framework.TypeInt,
|
|
|
|
Description: `Max number of allowed validation attempts.`,
|
|
|
|
},
|
2022-02-18 00:08:51 +03:00
|
|
|
"issuer": {
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `The name of the key's issuing organization.`,
|
|
|
|
},
|
|
|
|
"period": {
|
|
|
|
Type: framework.TypeDurationSecond,
|
|
|
|
Default: 30,
|
|
|
|
Description: `The length of time used to generate a counter for the TOTP token calculation.`,
|
|
|
|
},
|
|
|
|
"key_size": {
|
|
|
|
Type: framework.TypeInt,
|
|
|
|
Default: 20,
|
|
|
|
Description: "Determines the size in bytes of the generated key.",
|
|
|
|
},
|
|
|
|
"qr_size": {
|
|
|
|
Type: framework.TypeInt,
|
|
|
|
Default: 200,
|
|
|
|
Description: `The pixel size of the generated square QR code.`,
|
|
|
|
},
|
|
|
|
"algorithm": {
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Default: "SHA1",
|
|
|
|
Description: `The hashing algorithm used to generate the TOTP token. Options include SHA1, SHA256 and SHA512.`,
|
|
|
|
},
|
|
|
|
"digits": {
|
|
|
|
Type: framework.TypeInt,
|
|
|
|
Default: 6,
|
|
|
|
Description: `The number of digits in the generated TOTP token. This value can either be 6 or 8.`,
|
|
|
|
},
|
|
|
|
"skew": {
|
|
|
|
Type: framework.TypeInt,
|
|
|
|
Default: 1,
|
|
|
|
Description: `The number of delay periods that are allowed when validating a TOTP token. This value can either be 0 or 1.`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Operations: map[logical.Operation]framework.OperationHandler{
|
|
|
|
logical.ReadOperation: &framework.PathOperation{
|
2022-05-17 21:54:16 +03:00
|
|
|
Callback: i.handleMFAMethodTOTPRead,
|
2023-04-11 23:00:35 +03:00
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationVerb: "read",
|
|
|
|
OperationSuffix: "totp-method-configuration|totp-method-configuration",
|
|
|
|
},
|
|
|
|
Summary: "Read the current configuration for the given MFA method",
|
2022-02-18 00:08:51 +03:00
|
|
|
},
|
|
|
|
logical.UpdateOperation: &framework.PathOperation{
|
|
|
|
Callback: i.handleMFAMethodTOTPUpdate,
|
2023-04-11 23:00:35 +03:00
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationVerb: "configure",
|
|
|
|
OperationSuffix: "totp-method|totp-method",
|
|
|
|
},
|
|
|
|
Summary: "Update or create a configuration for the given MFA method",
|
2022-02-18 00:08:51 +03:00
|
|
|
},
|
|
|
|
logical.DeleteOperation: &framework.PathOperation{
|
2022-05-31 21:22:04 +03:00
|
|
|
Callback: i.handleMFAMethodTOTPDelete,
|
2023-04-11 23:00:35 +03:00
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationVerb: "delete",
|
|
|
|
OperationSuffix: "totp-method|totp-method",
|
|
|
|
},
|
|
|
|
Summary: "Delete a configuration for the given MFA method",
|
2022-02-18 00:08:51 +03:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Pattern: "mfa/method/totp/?$",
|
2023-04-11 23:00:35 +03:00
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationPrefix: "mfa",
|
|
|
|
OperationVerb: "list",
|
|
|
|
OperationSuffix: "totp-methods",
|
|
|
|
},
|
2022-02-18 00:08:51 +03:00
|
|
|
Operations: map[logical.Operation]framework.OperationHandler{
|
|
|
|
logical.ListOperation: &framework.PathOperation{
|
|
|
|
Callback: i.handleMFAMethodListTOTP,
|
|
|
|
Summary: "List MFA method configurations for the given MFA method",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Pattern: "mfa/method/totp/generate$",
|
2023-04-11 23:00:35 +03:00
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationPrefix: "mfa",
|
|
|
|
OperationVerb: "generate",
|
|
|
|
OperationSuffix: "totp-secret",
|
|
|
|
},
|
2022-02-18 00:08:51 +03:00
|
|
|
Fields: map[string]*framework.FieldSchema{
|
|
|
|
"method_id": {
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `The unique identifier for this MFA method.`,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Operations: map[logical.Operation]framework.OperationHandler{
|
|
|
|
logical.UpdateOperation: &framework.PathOperation{
|
|
|
|
Callback: i.handleLoginMFAGenerateUpdate,
|
|
|
|
Summary: "Update or create TOTP secret for the given method ID on the given entity.",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Pattern: "mfa/method/totp/admin-generate$",
|
2023-04-11 23:00:35 +03:00
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationPrefix: "mfa",
|
|
|
|
OperationVerb: "admin-generate",
|
|
|
|
OperationSuffix: "totp-secret",
|
|
|
|
},
|
2022-02-18 00:08:51 +03:00
|
|
|
Fields: map[string]*framework.FieldSchema{
|
|
|
|
"method_id": {
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `The unique identifier for this MFA method.`,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
"entity_id": {
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: "Entity ID on which the generated secret needs to get stored.",
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Operations: map[logical.Operation]framework.OperationHandler{
|
|
|
|
logical.UpdateOperation: &framework.PathOperation{
|
|
|
|
Callback: i.handleLoginMFAAdminGenerateUpdate,
|
|
|
|
Summary: "Update or create TOTP secret for the given method ID on the given entity.",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Pattern: "mfa/method/totp/admin-destroy$",
|
2023-04-11 23:00:35 +03:00
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationPrefix: "mfa",
|
|
|
|
OperationVerb: "admin-destroy",
|
|
|
|
OperationSuffix: "totp-secret",
|
|
|
|
},
|
2022-02-18 00:08:51 +03:00
|
|
|
Fields: map[string]*framework.FieldSchema{
|
|
|
|
"method_id": {
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: "The unique identifier for this MFA method.",
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
"entity_id": {
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: "Identifier of the entity from which the MFA method secret needs to be removed.",
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Operations: map[logical.Operation]framework.OperationHandler{
|
|
|
|
logical.UpdateOperation: &framework.PathOperation{
|
|
|
|
Callback: i.handleLoginMFAAdminDestroyUpdate,
|
|
|
|
Summary: "Destroys a TOTP secret for the given MFA method ID on the given entity",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Pattern: "mfa/login-enforcement/" + framework.GenericNameRegex("name"),
|
2023-04-11 23:00:35 +03:00
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationPrefix: "mfa",
|
|
|
|
OperationSuffix: "login-enforcement",
|
|
|
|
},
|
2022-02-18 00:08:51 +03:00
|
|
|
Fields: map[string]*framework.FieldSchema{
|
|
|
|
"name": {
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: "Name for this login enforcement configuration",
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
"mfa_method_ids": {
|
|
|
|
Type: framework.TypeStringSlice,
|
|
|
|
Description: "Array of Method IDs that determine what methods will be enforced",
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
"auth_method_accessors": {
|
|
|
|
Type: framework.TypeStringSlice,
|
|
|
|
Description: "Array of auth mount accessor IDs",
|
|
|
|
},
|
|
|
|
"auth_method_types": {
|
|
|
|
Type: framework.TypeStringSlice,
|
|
|
|
Description: "Array of auth mount types",
|
|
|
|
},
|
|
|
|
"identity_group_ids": {
|
|
|
|
Type: framework.TypeStringSlice,
|
|
|
|
Description: "Array of identity group IDs",
|
|
|
|
},
|
|
|
|
"identity_entity_ids": {
|
|
|
|
Type: framework.TypeStringSlice,
|
|
|
|
Description: "Array of identity entity IDs",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Operations: map[logical.Operation]framework.OperationHandler{
|
|
|
|
logical.ReadOperation: &framework.PathOperation{
|
|
|
|
Callback: i.handleMFALoginEnforcementRead,
|
|
|
|
Summary: "Read the current login enforcement",
|
|
|
|
},
|
|
|
|
logical.UpdateOperation: &framework.PathOperation{
|
|
|
|
Callback: i.handleMFALoginEnforcementUpdate,
|
|
|
|
Summary: "Create or update a login enforcement",
|
|
|
|
},
|
|
|
|
logical.DeleteOperation: &framework.PathOperation{
|
|
|
|
Callback: i.handleMFALoginEnforcementDelete,
|
|
|
|
Summary: "Delete a login enforcement",
|
|
|
|
},
|
2022-02-26 00:48:28 +03:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Pattern: "mfa/login-enforcement/?$",
|
2023-04-11 23:00:35 +03:00
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationPrefix: "mfa",
|
|
|
|
OperationSuffix: "login-enforcements",
|
|
|
|
},
|
2022-02-26 00:48:28 +03:00
|
|
|
Operations: map[logical.Operation]framework.OperationHandler{
|
2022-02-18 00:08:51 +03:00
|
|
|
logical.ListOperation: &framework.PathOperation{
|
|
|
|
Callback: i.handleMFALoginEnforcementList,
|
|
|
|
Summary: "List login enforcements",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-30 17:27:25 +03:00
|
|
|
func (i *IdentityStore) initialize(ctx context.Context, req *logical.InitializationRequest) error {
|
2019-11-08 20:13:53 +03:00
|
|
|
// Only primary should write the status
|
|
|
|
if i.System().ReplicationState().HasState(consts.ReplicationPerformanceSecondary | consts.ReplicationPerformanceStandby | consts.ReplicationDRSecondary) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-02-22 19:33:19 +03:00
|
|
|
if err := i.storeOIDCDefaultResources(ctx, req.Storage); err != nil {
|
2023-12-07 00:38:30 +03:00
|
|
|
i.logger.Error("failed to write OIDC default resources to storage", "error", err)
|
2022-02-22 19:33:19 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-07-17 19:46:00 +03:00
|
|
|
// if the storage entry for caseSensitivityKey exists, remove it
|
|
|
|
storageEntry, err := i.view.Get(ctx, caseSensitivityKey)
|
2019-09-30 17:27:25 +03:00
|
|
|
if err != nil {
|
2023-07-17 19:46:00 +03:00
|
|
|
i.logger.Error("could not get storage entry for case sensitivity key", "error", err)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if storageEntry != nil {
|
|
|
|
var setting casesensitivity
|
|
|
|
err := storageEntry.DecodeJSON(&setting)
|
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
i.logger.Debug("removing storage entry for case sensitivity key", "value", setting.DisableLowerCasedNames)
|
|
|
|
default:
|
|
|
|
i.logger.Error("failed to decode case sensitivity key, removing its storage entry anyway", "error", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = i.view.Delete(ctx, caseSensitivityKey)
|
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("could not delete storage entry for case sensitivity key", "error", err)
|
|
|
|
return nil
|
|
|
|
}
|
2019-09-30 17:27:25 +03:00
|
|
|
}
|
|
|
|
|
2023-07-17 19:46:00 +03:00
|
|
|
return nil
|
2019-09-30 17:27:25 +03:00
|
|
|
}
|
|
|
|
|
2017-10-11 20:21:20 +03:00
|
|
|
// Invalidate is a callback wherein the backend is informed that the value at
|
|
|
|
// the given key is updated. In identity store's case, it would be the entity
|
|
|
|
// storage entries that get updated. The value needs to be read and MemDB needs
|
|
|
|
// to be updated accordingly.
|
2018-01-19 09:44:44 +03:00
|
|
|
func (i *IdentityStore) Invalidate(ctx context.Context, key string) {
|
2018-04-03 03:46:59 +03:00
|
|
|
i.logger.Debug("invalidate notification received", "key", key)
|
2017-10-11 20:21:20 +03:00
|
|
|
|
2018-09-18 06:03:00 +03:00
|
|
|
i.lock.Lock()
|
|
|
|
defer i.lock.Unlock()
|
|
|
|
|
2017-10-11 20:21:20 +03:00
|
|
|
switch {
|
|
|
|
// Check if the key is a storage entry key for an entity bucket
|
|
|
|
case strings.HasPrefix(key, storagepacker.StoragePackerBucketsPrefix):
|
|
|
|
// Create a MemDB transaction
|
|
|
|
txn := i.db.Txn(true)
|
|
|
|
defer txn.Abort()
|
|
|
|
|
|
|
|
// Each entity object in MemDB holds the MD5 hash of the storage
|
|
|
|
// entry key of the entity bucket. Fetch all the entities that
|
|
|
|
// belong to this bucket using the hash value. Remove these entities
|
|
|
|
// from MemDB along with all the aliases of each entity.
|
2019-05-07 22:29:51 +03:00
|
|
|
entitiesFetched, err := i.MemDBEntitiesByBucketKeyInTxn(txn, key)
|
2017-10-11 20:21:20 +03:00
|
|
|
if err != nil {
|
2019-05-07 22:29:51 +03:00
|
|
|
i.logger.Error("failed to fetch entities using the bucket key", "key", key)
|
2017-10-11 20:21:20 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, entity := range entitiesFetched {
|
|
|
|
// Delete all the aliases in the entity. This function will also remove
|
|
|
|
// the corresponding alias indexes too.
|
|
|
|
err = i.deleteAliasesInEntityInTxn(txn, entity, entity.Aliases)
|
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to delete aliases in entity", "entity_id", entity.ID, "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete the entity using the same transaction
|
2017-11-02 23:05:48 +03:00
|
|
|
err = i.MemDBDeleteEntityByIDInTxn(txn, entity.ID)
|
2017-10-11 20:21:20 +03:00
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to delete entity from MemDB", "entity_id", entity.ID, "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the storage bucket entry
|
2021-02-24 14:58:10 +03:00
|
|
|
bucket, err := i.entityPacker.GetBucket(ctx, key)
|
2017-10-11 20:21:20 +03:00
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to refresh entities", "key", key, "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the underlying entry is nil, it means that this invalidation
|
|
|
|
// notification is for the deletion of the underlying storage entry. At
|
|
|
|
// this point, since all the entities belonging to this bucket are
|
|
|
|
// already removed, there is nothing else to be done. But, if the
|
|
|
|
// storage entry is non-nil, its an indication of an update. In this
|
|
|
|
// case, entities in the updated bucket needs to be reinserted into
|
|
|
|
// MemDB.
|
2021-12-02 16:23:41 +03:00
|
|
|
var entityIDs []string
|
2017-10-11 20:21:20 +03:00
|
|
|
if bucket != nil {
|
2021-12-02 16:23:41 +03:00
|
|
|
entityIDs = make([]string, 0, len(bucket.Items))
|
2017-10-11 20:21:20 +03:00
|
|
|
for _, item := range bucket.Items {
|
2018-04-03 05:17:33 +03:00
|
|
|
entity, err := i.parseEntityFromBucketItem(ctx, item)
|
2017-10-11 20:21:20 +03:00
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to parse entity from bucket entry item", "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-10-15 22:20:00 +03:00
|
|
|
localAliases, err := i.parseLocalAliases(entity.ID)
|
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to load local aliases from storage", "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if localAliases != nil {
|
|
|
|
for _, alias := range localAliases.Aliases {
|
|
|
|
entity.UpsertAlias(alias)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-11 20:21:20 +03:00
|
|
|
// Only update MemDB and don't touch the storage
|
2018-09-18 06:03:00 +03:00
|
|
|
err = i.upsertEntityInTxn(ctx, txn, entity, nil, false)
|
2017-10-11 20:21:20 +03:00
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to update entity in MemDB", "error", err)
|
|
|
|
return
|
|
|
|
}
|
2021-10-15 22:20:00 +03:00
|
|
|
|
|
|
|
// If we are a secondary, the entity created by the secondary
|
|
|
|
// via the CreateEntity RPC would have been cached. Now that the
|
|
|
|
// invalidation of the same has hit, there is no need of the
|
|
|
|
// cache. Clearing the cache. Writing to storage can't be
|
|
|
|
// performed by perf standbys. So only doing this in the active
|
|
|
|
// node of the secondary.
|
|
|
|
if i.localNode.ReplicationState().HasState(consts.ReplicationPerformanceSecondary) && i.localNode.HAState() != consts.PerfStandby {
|
|
|
|
if err := i.localAliasPacker.DeleteItem(ctx, entity.ID+tmpSuffix); err != nil {
|
|
|
|
i.logger.Error("failed to clear local alias entity cache", "error", err, "entity_id", entity.ID)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
entityIDs = append(entityIDs, entity.ID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// entitiesFetched are the entities before invalidation. entityIDs
|
|
|
|
// represent entities that are valid after invalidation. Clear the
|
|
|
|
// storage entries of local aliases for those entities that are
|
|
|
|
// indicated deleted by this invalidation.
|
|
|
|
if i.localNode.ReplicationState().HasState(consts.ReplicationPerformanceSecondary) && i.localNode.HAState() != consts.PerfStandby {
|
|
|
|
for _, entity := range entitiesFetched {
|
|
|
|
if !strutil.StrListContains(entityIDs, entity.ID) {
|
|
|
|
if err := i.localAliasPacker.DeleteItem(ctx, entity.ID); err != nil {
|
|
|
|
i.logger.Error("failed to clear local alias for entity", "error", err, "entity_id", entity.ID)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
txn.Commit()
|
|
|
|
return
|
|
|
|
|
|
|
|
// Check if the key is a storage entry key for an group bucket
|
2021-10-15 22:20:00 +03:00
|
|
|
// For those entities that are deleted, clear up the local alias entries
|
2017-10-11 20:21:20 +03:00
|
|
|
case strings.HasPrefix(key, groupBucketsPrefix):
|
|
|
|
// Create a MemDB transaction
|
|
|
|
txn := i.db.Txn(true)
|
|
|
|
defer txn.Abort()
|
|
|
|
|
2019-05-07 22:29:51 +03:00
|
|
|
groupsFetched, err := i.MemDBGroupsByBucketKeyInTxn(txn, key)
|
2017-10-11 20:21:20 +03:00
|
|
|
if err != nil {
|
2019-05-07 22:29:51 +03:00
|
|
|
i.logger.Error("failed to fetch groups using the bucket key", "key", key)
|
2017-10-11 20:21:20 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, group := range groupsFetched {
|
|
|
|
// Delete the group using the same transaction
|
2017-11-02 23:05:48 +03:00
|
|
|
err = i.MemDBDeleteGroupByIDInTxn(txn, group.ID)
|
2017-10-11 20:21:20 +03:00
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to delete group from MemDB", "group_id", group.ID, "error", err)
|
|
|
|
return
|
|
|
|
}
|
2019-04-10 23:14:31 +03:00
|
|
|
|
|
|
|
if group.Alias != nil {
|
|
|
|
err := i.MemDBDeleteAliasByIDInTxn(txn, group.Alias.ID, true)
|
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to delete group alias from MemDB", "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get the storage bucket entry
|
2021-02-24 14:58:10 +03:00
|
|
|
bucket, err := i.groupPacker.GetBucket(ctx, key)
|
2017-10-11 20:21:20 +03:00
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to refresh group", "key", key, "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if bucket != nil {
|
|
|
|
for _, item := range bucket.Items {
|
|
|
|
group, err := i.parseGroupFromBucketItem(item)
|
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to parse group from bucket entry item", "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-04-03 05:17:33 +03:00
|
|
|
// Before updating the group, check if the group exists. If it
|
|
|
|
// does, then delete the group alias from memdb, for the
|
|
|
|
// invalidation would have sent an update.
|
|
|
|
groupFetched, err := i.MemDBGroupByIDInTxn(txn, group.ID, true)
|
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to fetch group from MemDB", "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the group has an alias remove it from memdb
|
|
|
|
if groupFetched != nil && groupFetched.Alias != nil {
|
|
|
|
err := i.MemDBDeleteAliasByIDInTxn(txn, groupFetched.Alias.ID, true)
|
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to delete old group alias from MemDB", "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-11 20:21:20 +03:00
|
|
|
// Only update MemDB and don't touch the storage
|
2019-05-01 20:47:41 +03:00
|
|
|
err = i.UpsertGroupInTxn(ctx, txn, group, false)
|
2017-10-11 20:21:20 +03:00
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to update group in MemDB", "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
txn.Commit()
|
|
|
|
return
|
2019-06-21 20:23:39 +03:00
|
|
|
|
|
|
|
case strings.HasPrefix(key, oidcTokensPrefix):
|
2020-02-26 23:56:19 +03:00
|
|
|
ns, err := namespace.FromContext(ctx)
|
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("error retrieving namespace", "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wipe the cache for the requested namespace. This will also clear
|
|
|
|
// the shared namespace as well.
|
|
|
|
if err := i.oidcCache.Flush(ns); err != nil {
|
2019-12-17 21:43:38 +03:00
|
|
|
i.logger.Error("error flushing oidc cache", "error", err)
|
|
|
|
}
|
2021-09-27 20:55:29 +03:00
|
|
|
case strings.HasPrefix(key, clientPath):
|
|
|
|
name := strings.TrimPrefix(key, clientPath)
|
|
|
|
|
|
|
|
// Invalidate the cached client in memdb
|
|
|
|
if err := i.memDBDeleteClientByName(ctx, name); err != nil {
|
|
|
|
i.logger.Error("error invalidating client", "error", err, "key", key)
|
|
|
|
return
|
|
|
|
}
|
2021-10-15 22:20:00 +03:00
|
|
|
case strings.HasPrefix(key, localAliasesBucketsPrefix):
|
|
|
|
//
|
|
|
|
// This invalidation only happens on perf standbys
|
|
|
|
//
|
|
|
|
|
|
|
|
txn := i.db.Txn(true)
|
|
|
|
defer txn.Abort()
|
|
|
|
|
|
|
|
// Find all the local aliases belonging to this bucket and remove it
|
|
|
|
// both from aliases table and entities table. We will add the local
|
|
|
|
// aliases back by parsing the storage key. This way the deletion
|
|
|
|
// invalidation gets handled.
|
|
|
|
aliases, err := i.MemDBLocalAliasesByBucketKeyInTxn(txn, key)
|
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to fetch entities using the bucket key", "key", key)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, alias := range aliases {
|
|
|
|
entity, err := i.MemDBEntityByIDInTxn(txn, alias.CanonicalID, true)
|
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to fetch entity during local alias invalidation", "entity_id", alias.CanonicalID, "error", err)
|
|
|
|
return
|
|
|
|
}
|
2022-03-21 22:09:31 +03:00
|
|
|
if entity == nil {
|
|
|
|
i.logger.Error("failed to fetch entity during local alias invalidation, missing entity", "entity_id", alias.CanonicalID, "error", err)
|
|
|
|
continue
|
|
|
|
}
|
2021-10-15 22:20:00 +03:00
|
|
|
|
|
|
|
// Delete local aliases from the entity.
|
|
|
|
err = i.deleteAliasesInEntityInTxn(txn, entity, []*identity.Alias{alias})
|
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to delete aliases in entity", "entity_id", entity.ID, "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the entity with removed alias.
|
|
|
|
if err := i.MemDBUpsertEntityInTxn(txn, entity); err != nil {
|
|
|
|
i.logger.Error("failed to delete entity from MemDB", "entity_id", entity.ID, "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now read the invalidated storage key
|
|
|
|
bucket, err := i.localAliasPacker.GetBucket(ctx, key)
|
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to refresh local aliases", "key", key, "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if bucket != nil {
|
|
|
|
for _, item := range bucket.Items {
|
|
|
|
if strings.HasSuffix(item.ID, tmpSuffix) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var localAliases identity.LocalAliases
|
|
|
|
err = ptypes.UnmarshalAny(item.Message, &localAliases)
|
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to parse local aliases during invalidation", "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, alias := range localAliases.Aliases {
|
|
|
|
// Add to the aliases table
|
|
|
|
if err := i.MemDBUpsertAliasInTxn(txn, alias, false); err != nil {
|
|
|
|
i.logger.Error("failed to insert local alias to memdb during invalidation", "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch the associated entity and add the alias to that too.
|
|
|
|
entity, err := i.MemDBEntityByIDInTxn(txn, alias.CanonicalID, false)
|
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to fetch entity during local alias invalidation", "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if entity == nil {
|
|
|
|
cachedEntityItem, err := i.localAliasPacker.GetItem(alias.CanonicalID + tmpSuffix)
|
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to fetch cached entity", "key", key, "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if cachedEntityItem != nil {
|
|
|
|
entity, err = i.parseCachedEntity(cachedEntityItem)
|
|
|
|
if err != nil {
|
|
|
|
i.logger.Error("failed to parse cached entity", "key", key, "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if entity == nil {
|
|
|
|
i.logger.Error("received local alias invalidation for an invalid entity", "item.ID", item.ID)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
entity.UpsertAlias(alias)
|
|
|
|
|
|
|
|
// Update the entities table
|
|
|
|
if err := i.MemDBUpsertEntityInTxn(txn, entity); err != nil {
|
|
|
|
i.logger.Error("failed to upsert entity during local alias invalidation", "error", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
txn.Commit()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *IdentityStore) parseLocalAliases(entityID string) (*identity.LocalAliases, error) {
|
|
|
|
item, err := i.localAliasPacker.GetItem(entityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if item == nil {
|
|
|
|
return nil, nil
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
2021-10-15 22:20:00 +03:00
|
|
|
|
|
|
|
var localAliases identity.LocalAliases
|
|
|
|
err = ptypes.UnmarshalAny(item.Message, &localAliases)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &localAliases, nil
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
2018-04-03 05:17:33 +03:00
|
|
|
func (i *IdentityStore) parseEntityFromBucketItem(ctx context.Context, item *storagepacker.Item) (*identity.Entity, error) {
|
2017-10-11 20:21:20 +03:00
|
|
|
if item == nil {
|
|
|
|
return nil, fmt.Errorf("nil item")
|
|
|
|
}
|
|
|
|
|
2018-09-18 06:03:00 +03:00
|
|
|
persistNeeded := false
|
|
|
|
|
2017-10-11 20:21:20 +03:00
|
|
|
var entity identity.Entity
|
|
|
|
err := ptypes.UnmarshalAny(item.Message, &entity)
|
|
|
|
if err != nil {
|
2018-09-18 06:03:00 +03:00
|
|
|
// If we encounter an error, it would mean that the format of the
|
|
|
|
// entity is an older one. Try decoding using the older format and if
|
|
|
|
// successful, upgrage the storage with the newer format.
|
|
|
|
var oldEntity identity.EntityStorageEntry
|
|
|
|
oldEntityErr := ptypes.UnmarshalAny(item.Message, &oldEntity)
|
|
|
|
if oldEntityErr != nil {
|
2021-05-11 20:12:54 +03:00
|
|
|
return nil, fmt.Errorf("failed to decode entity from storage bucket item: %w", err)
|
2018-09-18 06:03:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
i.logger.Debug("upgrading the entity using patch introduced with vault 0.8.2.1", "entity_id", oldEntity.ID)
|
|
|
|
|
|
|
|
// Successfully decoded entity using older format. Entity is stored
|
|
|
|
// with older format. Upgrade it.
|
|
|
|
entity.ID = oldEntity.ID
|
|
|
|
entity.Name = oldEntity.Name
|
|
|
|
entity.Metadata = oldEntity.Metadata
|
|
|
|
entity.CreationTime = oldEntity.CreationTime
|
|
|
|
entity.LastUpdateTime = oldEntity.LastUpdateTime
|
|
|
|
entity.MergedEntityIDs = oldEntity.MergedEntityIDs
|
|
|
|
entity.Policies = oldEntity.Policies
|
2019-05-07 22:29:51 +03:00
|
|
|
entity.BucketKey = oldEntity.BucketKeyHash
|
2018-09-18 06:03:00 +03:00
|
|
|
entity.MFASecrets = oldEntity.MFASecrets
|
|
|
|
// Copy each alias individually since the format of aliases were
|
|
|
|
// also different
|
|
|
|
for _, oldAlias := range oldEntity.Personas {
|
|
|
|
var newAlias identity.Alias
|
|
|
|
newAlias.ID = oldAlias.ID
|
|
|
|
newAlias.Name = oldAlias.Name
|
|
|
|
newAlias.CanonicalID = oldAlias.EntityID
|
|
|
|
newAlias.MountType = oldAlias.MountType
|
|
|
|
newAlias.MountAccessor = oldAlias.MountAccessor
|
|
|
|
newAlias.MountPath = oldAlias.MountPath
|
|
|
|
newAlias.Metadata = oldAlias.Metadata
|
|
|
|
newAlias.CreationTime = oldAlias.CreationTime
|
|
|
|
newAlias.LastUpdateTime = oldAlias.LastUpdateTime
|
|
|
|
newAlias.MergedFromCanonicalIDs = oldAlias.MergedFromEntityIDs
|
2021-10-15 22:20:00 +03:00
|
|
|
entity.UpsertAlias(&newAlias)
|
2018-09-18 06:03:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
persistNeeded = true
|
|
|
|
}
|
|
|
|
|
|
|
|
pN, err := parseExtraEntityFromBucket(ctx, i, &entity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if pN {
|
|
|
|
persistNeeded = true
|
|
|
|
}
|
|
|
|
|
2021-08-30 22:31:11 +03:00
|
|
|
if persistNeeded && !i.localNode.ReplicationState().HasState(consts.ReplicationPerformanceSecondary) {
|
2018-09-18 06:03:00 +03:00
|
|
|
entityAsAny, err := ptypes.MarshalAny(&entity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
item := &storagepacker.Item{
|
|
|
|
ID: entity.ID,
|
|
|
|
Message: entityAsAny,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store the entity with new format
|
2019-05-01 20:47:41 +03:00
|
|
|
err = i.entityPacker.PutItem(ctx, item)
|
2018-09-18 06:03:00 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if entity.NamespaceID == "" {
|
|
|
|
entity.NamespaceID = namespace.RootNamespaceID
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return &entity, nil
|
|
|
|
}
|
|
|
|
|
2021-10-15 22:20:00 +03:00
|
|
|
func (i *IdentityStore) parseCachedEntity(item *storagepacker.Item) (*identity.Entity, error) {
|
|
|
|
if item == nil {
|
|
|
|
return nil, fmt.Errorf("nil item")
|
|
|
|
}
|
|
|
|
|
|
|
|
var entity identity.Entity
|
|
|
|
err := ptypes.UnmarshalAny(item.Message, &entity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to decode cached entity from storage bucket item: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if entity.NamespaceID == "" {
|
|
|
|
entity.NamespaceID = namespace.RootNamespaceID
|
|
|
|
}
|
|
|
|
|
|
|
|
return &entity, nil
|
|
|
|
}
|
|
|
|
|
2017-10-11 20:21:20 +03:00
|
|
|
func (i *IdentityStore) parseGroupFromBucketItem(item *storagepacker.Item) (*identity.Group, error) {
|
|
|
|
if item == nil {
|
|
|
|
return nil, fmt.Errorf("nil item")
|
|
|
|
}
|
|
|
|
|
|
|
|
var group identity.Group
|
|
|
|
err := ptypes.UnmarshalAny(item.Message, &group)
|
|
|
|
if err != nil {
|
2021-05-11 20:12:54 +03:00
|
|
|
return nil, fmt.Errorf("failed to decode group from storage bucket item: %w", err)
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
2018-09-18 06:03:00 +03:00
|
|
|
if group.NamespaceID == "" {
|
|
|
|
group.NamespaceID = namespace.RootNamespaceID
|
|
|
|
}
|
|
|
|
|
2017-10-11 20:21:20 +03:00
|
|
|
return &group, nil
|
|
|
|
}
|
|
|
|
|
2017-11-02 23:05:48 +03:00
|
|
|
// entityByAliasFactors fetches the entity based on factors of alias, i.e mount
|
2017-10-11 20:21:20 +03:00
|
|
|
// accessor and the alias name.
|
2017-11-02 23:05:48 +03:00
|
|
|
func (i *IdentityStore) entityByAliasFactors(mountAccessor, aliasName string, clone bool) (*identity.Entity, error) {
|
2017-10-11 20:21:20 +03:00
|
|
|
if mountAccessor == "" {
|
|
|
|
return nil, fmt.Errorf("missing mount accessor")
|
|
|
|
}
|
|
|
|
|
|
|
|
if aliasName == "" {
|
|
|
|
return nil, fmt.Errorf("missing alias name")
|
|
|
|
}
|
|
|
|
|
2018-02-09 18:40:56 +03:00
|
|
|
txn := i.db.Txn(false)
|
|
|
|
|
|
|
|
return i.entityByAliasFactorsInTxn(txn, mountAccessor, aliasName, clone)
|
|
|
|
}
|
|
|
|
|
2021-07-21 23:21:45 +03:00
|
|
|
// entityByAliasFactorsInTxn fetches the entity based on factors of alias, i.e
|
2018-02-09 18:40:56 +03:00
|
|
|
// mount accessor and the alias name.
|
|
|
|
func (i *IdentityStore) entityByAliasFactorsInTxn(txn *memdb.Txn, mountAccessor, aliasName string, clone bool) (*identity.Entity, error) {
|
|
|
|
if txn == nil {
|
|
|
|
return nil, fmt.Errorf("nil txn")
|
|
|
|
}
|
|
|
|
|
|
|
|
if mountAccessor == "" {
|
|
|
|
return nil, fmt.Errorf("missing mount accessor")
|
|
|
|
}
|
|
|
|
|
|
|
|
if aliasName == "" {
|
|
|
|
return nil, fmt.Errorf("missing alias name")
|
|
|
|
}
|
|
|
|
|
|
|
|
alias, err := i.MemDBAliasByFactorsInTxn(txn, mountAccessor, aliasName, false, false)
|
2017-10-11 20:21:20 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if alias == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2018-02-09 18:40:56 +03:00
|
|
|
return i.MemDBEntityByAliasIDInTxn(txn, alias.ID, clone)
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
2021-10-15 22:20:00 +03:00
|
|
|
// CreateEntity creates a new entity.
|
|
|
|
func (i *IdentityStore) CreateEntity(ctx context.Context) (*identity.Entity, error) {
|
|
|
|
defer metrics.MeasureSince([]string{"identity", "create_entity"}, time.Now())
|
|
|
|
|
|
|
|
entity := new(identity.Entity)
|
|
|
|
err := i.sanitizeEntity(ctx, entity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err := i.upsertEntity(ctx, entity, nil, true); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit a metric for the new entity
|
|
|
|
ns, err := i.namespacer.NamespaceByID(ctx, entity.NamespaceID)
|
|
|
|
var nsLabel metrics.Label
|
|
|
|
if err != nil {
|
|
|
|
nsLabel = metrics.Label{"namespace", "unknown"}
|
|
|
|
} else {
|
|
|
|
nsLabel = metricsutil.NamespaceLabel(ns)
|
|
|
|
}
|
|
|
|
i.metrics.IncrCounterWithLabels(
|
|
|
|
[]string{"identity", "entity", "creation"},
|
|
|
|
1,
|
|
|
|
[]metrics.Label{
|
|
|
|
nsLabel,
|
|
|
|
})
|
|
|
|
|
2022-07-28 16:43:24 +03:00
|
|
|
return entity.Clone()
|
2021-10-15 22:20:00 +03:00
|
|
|
}
|
|
|
|
|
2018-02-09 18:40:56 +03:00
|
|
|
// CreateOrFetchEntity creates a new entity. This is used by core to
|
2017-11-02 23:05:48 +03:00
|
|
|
// associate each login attempt by an alias to a unified entity in Vault.
|
2022-05-18 19:16:13 +03:00
|
|
|
func (i *IdentityStore) CreateOrFetchEntity(ctx context.Context, alias *logical.Alias) (*identity.Entity, bool, error) {
|
2020-04-23 21:31:22 +03:00
|
|
|
defer metrics.MeasureSince([]string{"identity", "create_or_fetch_entity"}, time.Now())
|
|
|
|
|
2017-10-11 20:21:20 +03:00
|
|
|
var entity *identity.Entity
|
|
|
|
var err error
|
2019-01-23 19:26:50 +03:00
|
|
|
var update bool
|
2022-05-18 19:16:13 +03:00
|
|
|
var entityCreated bool
|
2017-10-11 20:21:20 +03:00
|
|
|
|
|
|
|
if alias == nil {
|
2022-05-18 19:16:13 +03:00
|
|
|
return nil, false, fmt.Errorf("alias is nil")
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if alias.Name == "" {
|
2022-05-18 19:16:13 +03:00
|
|
|
return nil, false, fmt.Errorf("empty alias name")
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
2021-08-30 22:31:11 +03:00
|
|
|
mountValidationResp := i.router.ValidateMountByAccessor(alias.MountAccessor)
|
2017-10-11 20:21:20 +03:00
|
|
|
if mountValidationResp == nil {
|
2022-05-18 19:16:13 +03:00
|
|
|
return nil, false, fmt.Errorf("invalid mount accessor %q", alias.MountAccessor)
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if mountValidationResp.MountType != alias.MountType {
|
2022-05-18 19:16:13 +03:00
|
|
|
return nil, false, fmt.Errorf("mount accessor %q is not a mount of type %q", alias.MountAccessor, alias.MountType)
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
2019-01-23 19:26:50 +03:00
|
|
|
// Check if an entity already exists for the given alias
|
2021-12-22 17:51:13 +03:00
|
|
|
entity, err = i.entityByAliasFactors(alias.MountAccessor, alias.Name, true)
|
2017-10-11 20:21:20 +03:00
|
|
|
if err != nil {
|
2022-05-18 19:16:13 +03:00
|
|
|
return nil, false, err
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
2019-01-23 19:26:50 +03:00
|
|
|
if entity != nil && changedAliasIndex(entity, alias) == -1 {
|
2022-05-18 19:16:13 +03:00
|
|
|
return entity, false, nil
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
2018-07-25 05:01:58 +03:00
|
|
|
i.lock.Lock()
|
|
|
|
defer i.lock.Unlock()
|
|
|
|
|
2018-02-09 18:40:56 +03:00
|
|
|
// Create a MemDB transaction to update both alias and entity
|
|
|
|
txn := i.db.Txn(true)
|
|
|
|
defer txn.Abort()
|
|
|
|
|
|
|
|
// Check if an entity was created before acquiring the lock
|
2019-01-23 19:26:50 +03:00
|
|
|
entity, err = i.entityByAliasFactorsInTxn(txn, alias.MountAccessor, alias.Name, true)
|
2018-02-09 18:40:56 +03:00
|
|
|
if err != nil {
|
2022-05-18 19:16:13 +03:00
|
|
|
return nil, false, err
|
2018-02-09 18:40:56 +03:00
|
|
|
}
|
|
|
|
if entity != nil {
|
2019-01-23 19:26:50 +03:00
|
|
|
idx := changedAliasIndex(entity, alias)
|
|
|
|
if idx == -1 {
|
2022-05-18 19:16:13 +03:00
|
|
|
return entity, false, nil
|
2019-01-23 19:26:50 +03:00
|
|
|
}
|
|
|
|
a := entity.Aliases[idx]
|
|
|
|
a.Metadata = alias.Metadata
|
|
|
|
a.LastUpdateTime = ptypes.TimestampNow()
|
2018-02-09 18:40:56 +03:00
|
|
|
|
2019-01-23 19:26:50 +03:00
|
|
|
update = true
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
2019-01-23 19:26:50 +03:00
|
|
|
if !update {
|
|
|
|
entity = new(identity.Entity)
|
|
|
|
err = i.sanitizeEntity(ctx, entity)
|
|
|
|
if err != nil {
|
2022-05-18 19:16:13 +03:00
|
|
|
return nil, false, err
|
2019-01-23 19:26:50 +03:00
|
|
|
}
|
2017-10-11 20:21:20 +03:00
|
|
|
|
2019-01-23 19:26:50 +03:00
|
|
|
// Create a new alias
|
|
|
|
newAlias := &identity.Alias{
|
|
|
|
CanonicalID: entity.ID,
|
|
|
|
Name: alias.Name,
|
|
|
|
MountAccessor: alias.MountAccessor,
|
|
|
|
Metadata: alias.Metadata,
|
|
|
|
MountPath: mountValidationResp.MountPath,
|
|
|
|
MountType: mountValidationResp.MountType,
|
2021-10-15 22:20:00 +03:00
|
|
|
Local: alias.Local,
|
2019-01-23 19:26:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
err = i.sanitizeAlias(ctx, newAlias)
|
|
|
|
if err != nil {
|
2022-05-18 19:16:13 +03:00
|
|
|
return nil, false, err
|
2019-01-23 19:26:50 +03:00
|
|
|
}
|
2017-10-11 20:21:20 +03:00
|
|
|
|
2019-01-23 19:26:50 +03:00
|
|
|
i.logger.Debug("creating a new entity", "alias", newAlias)
|
2018-08-26 20:26:34 +03:00
|
|
|
|
2019-01-23 19:26:50 +03:00
|
|
|
// Append the new alias to the new entity
|
|
|
|
entity.Aliases = []*identity.Alias{
|
|
|
|
newAlias,
|
|
|
|
}
|
2020-06-20 02:24:05 +03:00
|
|
|
|
|
|
|
// Emit a metric for the new entity
|
2021-08-30 22:31:11 +03:00
|
|
|
ns, err := i.namespacer.NamespaceByID(ctx, entity.NamespaceID)
|
2020-06-20 02:24:05 +03:00
|
|
|
var nsLabel metrics.Label
|
|
|
|
if err != nil {
|
|
|
|
nsLabel = metrics.Label{"namespace", "unknown"}
|
|
|
|
} else {
|
2020-10-14 02:38:21 +03:00
|
|
|
nsLabel = metricsutil.NamespaceLabel(ns)
|
2020-06-20 02:24:05 +03:00
|
|
|
}
|
2021-08-30 22:31:11 +03:00
|
|
|
i.metrics.IncrCounterWithLabels(
|
2020-06-20 02:24:05 +03:00
|
|
|
[]string{"identity", "entity", "creation"},
|
|
|
|
1,
|
|
|
|
[]metrics.Label{
|
|
|
|
nsLabel,
|
|
|
|
{"auth_method", newAlias.MountType},
|
|
|
|
{"mount_point", newAlias.MountPath},
|
|
|
|
})
|
2022-05-18 19:16:13 +03:00
|
|
|
entityCreated = true
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Update MemDB and persist entity object
|
2018-09-18 06:03:00 +03:00
|
|
|
err = i.upsertEntityInTxn(ctx, txn, entity, nil, true)
|
2017-10-11 20:21:20 +03:00
|
|
|
if err != nil {
|
2022-07-21 20:53:42 +03:00
|
|
|
return entity, entityCreated, err
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
|
|
|
|
2018-02-09 18:40:56 +03:00
|
|
|
txn.Commit()
|
2022-05-18 19:16:13 +03:00
|
|
|
clonedEntity, err := entity.Clone()
|
|
|
|
return clonedEntity, entityCreated, err
|
2017-10-11 20:21:20 +03:00
|
|
|
}
|
2019-01-23 19:26:50 +03:00
|
|
|
|
|
|
|
// changedAliasIndex searches an entity for changed alias metadata.
|
|
|
|
//
|
|
|
|
// If a match is found, the changed alias's index is returned. If no alias
|
|
|
|
// names match or no metadata is different, -1 is returned.
|
|
|
|
func changedAliasIndex(entity *identity.Entity, alias *logical.Alias) int {
|
|
|
|
for i, a := range entity.Aliases {
|
2022-08-23 22:39:45 +03:00
|
|
|
if a.Name == alias.Name && a.MountAccessor == alias.MountAccessor && !strutil.EqualStringMaps(a.Metadata, alias.Metadata) {
|
2019-01-23 19:26:50 +03:00
|
|
|
return i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1
|
|
|
|
}
|