From c5024cd3d1836f74f4c6d0a84da43cc9552f473d Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Mon, 20 Apr 2020 14:19:09 -0700 Subject: [PATCH 01/18] add aliasmetadata sdk helper and add to aws auth --- .../credential/aws/path_config_identity.go | 46 +- builtin/credential/aws/path_login.go | 36 +- builtin/credential/aws/path_login_test.go | 29 + sdk/helper/aliasmetadata/alias_metadata.go | 194 +++++++ .../aliasmetadata/alias_metadata_acc_test.go | 496 ++++++++++++++++++ .../aliasmetadata/alias_metadata_test.go | 103 ++++ sdk/logical/identity.pb.go | 59 ++- sdk/logical/identity.proto | 7 +- .../helper/aliasmetadata/alias_metadata.go | 194 +++++++ .../vault/sdk/logical/identity.pb.go | 59 ++- .../vault/sdk/logical/identity.proto | 7 +- 11 files changed, 1153 insertions(+), 77 deletions(-) create mode 100644 sdk/helper/aliasmetadata/alias_metadata.go create mode 100644 sdk/helper/aliasmetadata/alias_metadata_acc_test.go create mode 100644 sdk/helper/aliasmetadata/alias_metadata_test.go create mode 100644 vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go diff --git a/builtin/credential/aws/path_config_identity.go b/builtin/credential/aws/path_config_identity.go index 28e5aa11c6dc2..3c94184341e66 100644 --- a/builtin/credential/aws/path_config_identity.go +++ b/builtin/credential/aws/path_config_identity.go @@ -5,10 +5,34 @@ import ( "fmt" "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/aliasmetadata" "github.com/hashicorp/vault/sdk/helper/strutil" "github.com/hashicorp/vault/sdk/logical" ) +// aliasMetadataFields is a list of the default alias metadata +// added to tokens during login. The default alias type used +// by this back-end is the role ID. Subsequently, the default +// fields included are expected to have a low rate of change +// when the role ID is in use. +var aliasMetadataFields = &aliasmetadata.Fields{ + Default: []string{ + "account_id", + "auth_type", + }, + AvailableToAdd: []string{ + "ami_id", + "canonical_arn", + "client_arn", + "client_user_id", + "inferred_aws_region", + "inferred_entity_id", + "inferred_entity_type", + "instance_id", + "region", + }, +} + func (b *backend) pathConfigIdentity() *framework.Path { return &framework.Path{ Pattern: "config/identity$", @@ -23,6 +47,7 @@ func (b *backend) pathConfigIdentity() *framework.Path { Default: identityAliasEC2InstanceID, Description: fmt.Sprintf("Configure how the AWS auth method generates entity alias when using EC2 auth. Valid values are %q, %q, and %q. Defaults to %q.", identityAliasRoleID, identityAliasEC2InstanceID, identityAliasEC2ImageID, identityAliasRoleID), }, + aliasmetadata.FieldName: aliasmetadata.FieldSchema(aliasMetadataFields), }, Operations: map[logical.Operation]framework.OperationHandler{ @@ -45,9 +70,11 @@ func identityConfigEntry(ctx context.Context, s logical.Storage) (*identityConfi return nil, err } - var entry identityConfig + entry := &identityConfig{ + Handler: aliasmetadata.NewHandler(aliasMetadataFields), + } if entryRaw != nil { - if err := entryRaw.DecodeJSON(&entry); err != nil { + if err := entryRaw.DecodeJSON(entry); err != nil { return nil, err } } @@ -60,7 +87,7 @@ func identityConfigEntry(ctx context.Context, s logical.Storage) (*identityConfi entry.EC2Alias = identityAliasRoleID } - return &entry, nil + return entry, nil } func pathConfigIdentityRead(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) { @@ -71,8 +98,9 @@ func pathConfigIdentityRead(ctx context.Context, req *logical.Request, _ *framew return &logical.Response{ Data: map[string]interface{}{ - "iam_alias": config.IAMAlias, - "ec2_alias": config.EC2Alias, + "iam_alias": config.IAMAlias, + "ec2_alias": config.EC2Alias, + aliasmetadata.FieldName: config.GetAliasMetadata(), }, }, nil } @@ -102,6 +130,9 @@ func pathConfigIdentityUpdate(ctx context.Context, req *logical.Request, data *f } config.EC2Alias = ec2Alias } + if err := config.ParseAliasMetadata(data); err != nil { + return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest + } entry, err := logical.StorageEntryJSON("config/identity", config) if err != nil { @@ -117,8 +148,9 @@ func pathConfigIdentityUpdate(ctx context.Context, req *logical.Request, data *f } type identityConfig struct { - IAMAlias string `json:"iam_alias"` - EC2Alias string `json:"ec2_alias"` + IAMAlias string `json:"iam_alias"` + EC2Alias string `json:"ec2_alias"` + aliasmetadata.Handler `json:"alias_metadata_handler"` } const identityAliasIAMUniqueID = "unique_id" diff --git a/builtin/credential/aws/path_login.go b/builtin/credential/aws/path_login.go index e0ce98dc45080..b9c31dfb19e45 100644 --- a/builtin/credential/aws/path_login.go +++ b/builtin/credential/aws/path_login.go @@ -845,15 +845,15 @@ func (b *backend) pathLoginUpdateEc2(ctx context.Context, req *logical.Request, }, Alias: &logical.Alias{ Name: identityAlias, - Metadata: map[string]string{ - "instance_id": identityDocParsed.InstanceID, - "region": identityDocParsed.Region, - "account_id": identityDocParsed.AccountID, - "ami_id": identityDocParsed.AmiID, - }, }, } roleEntry.PopulateTokenAuth(auth) + identityConfigEntry.PopulateDesiredAliasMetadata(auth, map[string]string{ + "instance_id": identityDocParsed.InstanceID, + "region": identityDocParsed.Region, + "account_id": identityDocParsed.AccountID, + "ami_id": identityDocParsed.AmiID, + }) resp := &logical.Response{ Auth: auth, @@ -1375,19 +1375,23 @@ func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request, DisplayName: entity.FriendlyName, Alias: &logical.Alias{ Name: identityAlias, - Metadata: map[string]string{ - "client_arn": callerID.Arn, - "canonical_arn": entity.canonicalArn(), - "client_user_id": callerUniqueId, - "auth_type": iamAuthType, - "inferred_entity_type": inferredEntityType, - "inferred_entity_id": inferredEntityID, - "inferred_aws_region": roleEntry.InferredAWSRegion, - "account_id": entity.AccountNumber, - }, }, } roleEntry.PopulateTokenAuth(auth) + if err := identityConfigEntry.PopulateDesiredAliasMetadata(auth, map[string]string{ + "client_arn": callerID.Arn, + "canonical_arn": entity.canonicalArn(), + "client_user_id": callerUniqueId, + "auth_type": iamAuthType, + "inferred_entity_type": inferredEntityType, + "inferred_entity_id": inferredEntityID, + "inferred_aws_region": roleEntry.InferredAWSRegion, + "account_id": entity.AccountNumber, + }); err != nil { + if b.Logger().IsWarn() { + b.Logger().Warn(fmt.Sprintf("unable to set alias metadata due to %s", err)) + } + } return &logical.Response{ Auth: auth, diff --git a/builtin/credential/aws/path_login_test.go b/builtin/credential/aws/path_login_test.go index 59d120d53d418..e204f6afc1d2a 100644 --- a/builtin/credential/aws/path_login_test.go +++ b/builtin/credential/aws/path_login_test.go @@ -11,6 +11,8 @@ import ( "strings" "testing" + "github.com/hashicorp/vault/sdk/helper/aliasmetadata" + "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/sts" "github.com/hashicorp/vault/sdk/logical" @@ -214,8 +216,35 @@ func TestBackend_pathLogin_IAMHeaders(t *testing.T) { t.Fatal(err) } + // Configure identity. + _, err = b.HandleRequest(context.Background(), &logical.Request{ + Operation: logical.UpdateOperation, + Path: "config/identity", + Storage: storage, + Data: map[string]interface{}{ + aliasmetadata.FieldName: []string{ + "default", + "ami_id", + "canonical_arn", + "client_arn", + "client_user_id", + "inferred_aws_region", + "inferred_entity_id", + "inferred_entity_type", + "instance_id", + "region", + }, + "iam_alias": "role_id", + "ec2_alias": "role_id", + }, + }) + if err != nil { + t.Fatal(err) + } + // create a role entry roleEntry := &awsRoleEntry{ + RoleID: "foo", Version: currentRoleStorageVersion, AuthType: iamAuthType, } diff --git a/sdk/helper/aliasmetadata/alias_metadata.go b/sdk/helper/aliasmetadata/alias_metadata.go new file mode 100644 index 0000000000000..1b9cf1cc29aef --- /dev/null +++ b/sdk/helper/aliasmetadata/alias_metadata.go @@ -0,0 +1,194 @@ +package aliasmetadata + +/* + aliasmetadata is a package offering convenience and + standardization when supporting an `alias_metadata` + field in a plugin's configuration. This then controls + what alias metadata is added to an Auth during login. + + To see an example of how to add and use it, check out + how these structs and fields are used in the AWS auth + method. + + Or, check out its acceptance test in this package to + see its integration points. +*/ + +import ( + "errors" + "fmt" + "strings" + + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/strutil" + "github.com/hashicorp/vault/sdk/logical" +) + +// FieldName is the user-facing name for the field. +const FieldName = "alias_metadata" + +// Fields is for configuring a back-end's available +// default and additional fields. These are used for +// providing a verbose field description, and for parsing +// user input. +type Fields struct { + // Default is a list of the default fields that should + // be included if a user sends "default" in their list + // of desired fields. These fields should all have a + // low rate of change because each change can incur a + // write to storage. + Default []string + + // AvailableToAdd is a list of fields not included by + // default, that the user may include. + AvailableToAdd []string +} + +func (f *Fields) all() []string { + return append(f.Default, f.AvailableToAdd...) +} + +// FieldSchema takes the default and additionally available +// fields, and uses them to generate a verbose description +// regarding how to use the "alias_metadata" field. +func FieldSchema(fields *Fields) *framework.FieldSchema { + return &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: description(fields), + DisplayAttrs: &framework.DisplayAttributes{ + Name: FieldName, + Value: "default,field1,field2", + }, + Default: []string{"default"}, + } +} + +// NewHandler instantiates a Handler to be embedded in your config. +func NewHandler(fields *Fields) Handler { + return &handler{ + fields: fields, + } +} + +// Handler is an interface for the helper methods you get on your +// config when you embed the Handler. +type Handler interface { + GetAliasMetadata() []string + ParseAliasMetadata(data *framework.FieldData) error + PopulateDesiredAliasMetadata(auth *logical.Auth, fieldValues map[string]string) error +} + +type handler struct { + // AliasMetadata is an explicit list of all the user's configured + // fields that are being added to alias metadata. It will never + // include the "default" parameter, and instead includes the actual + // fields behind "default", if selected. If it has never been set, + // the pointer will be nil. + AliasMetadata *[]string `json:"alias_metadata"` + + // fields is a list of the configured default and available + // fields. It's intentionally not jsonified. + fields *Fields +} + +// GetAliasMetadata is intended to be used on config reads. +// It gets an explicit list of all the user's configured +// fields that are being added to alias metadata. It will never +// include the "default" parameter, and instead includes the actual +// fields behind "default", if selected. +func (h *handler) GetAliasMetadata() []string { + if h.AliasMetadata == nil { + return h.fields.Default + } + return *h.AliasMetadata +} + +// ParseAliasMetadata is intended to be used on config create/update. +// It takes a user's selected fields (or lack thereof), +// converts it to a list of explicit fields, and adds it to the handler +// for later storage. +func (h *handler) ParseAliasMetadata(data *framework.FieldData) error { + userProvided, ok := data.GetOk(FieldName) + if !ok { + // Nothing further to do here. + return nil + } + + // uniqueFields protects against weird edge cases like if + // a user provided "default,field1,field2,default". + uniqueFields := make(map[string]bool) + for _, field := range userProvided.([]string) { + if field == "default" { + // Add the fields that "default" represents, rather + // than the explicit field. + for _, dfltField := range h.fields.Default { + uniqueFields[dfltField] = true + } + } else { + // Make sure they've sent a supported field so we can + // error early if not. + if !strutil.StrListContains(h.fields.all(), field) { + return fmt.Errorf("%q is not an available field, please select from: %s", field, strings.Join(h.fields.all(), ", ")) + } + uniqueFields[field] = true + } + } + // Attach the fields we've received so they'll be stored. + aliasMetadata := make([]string, len(uniqueFields)) + i := 0 + for fieldName := range uniqueFields { + aliasMetadata[i] = fieldName + i++ + } + // Fulfilling the pointer here flags that the user has made + // an explicit selection so we shouldn't just fall back to + // our defaults. + h.AliasMetadata = &aliasMetadata + return nil +} + +// PopulateDesiredAliasMetadata is intended to be used during login +// just before returning an auth. +// It takes the available alias metadata and, +// if the auth should have it, adds it to the auth's alias metadata. +func (h *handler) PopulateDesiredAliasMetadata(auth *logical.Auth, available map[string]string) error { + if auth == nil { + return errors.New("auth is nil") + } + if auth.Alias == nil { + return errors.New("auth alias is nil") + } + if auth.Alias.Name == "" { + // We need the caller to set the alias name or there will + // be nothing for these fields to operate upon. + return errors.New("auth alias name must be set") + } + if auth.Alias.Metadata == nil { + auth.Alias.Metadata = make(map[string]string) + } + fieldsToInclude := h.fields.Default + if h.AliasMetadata != nil { + fieldsToInclude = *h.AliasMetadata + } + for availableField, itsValue := range available { + if strutil.StrListContains(fieldsToInclude, availableField) { + auth.Alias.Metadata[availableField] = itsValue + } + } + return nil +} + +func description(fields *Fields) string { + desc := "The metadata to include on the aliases generated by this plugin." + if len(fields.Default) > 0 { + desc += fmt.Sprintf(" When set to 'default', includes: %s.", strings.Join(fields.Default, ", ")) + } + if len(fields.AvailableToAdd) > 0 { + desc += fmt.Sprintf(" These fields are available to add: %s.", strings.Join(fields.AvailableToAdd, ", ")) + } + desc += " Not editing this field means the 'default' fields are included." + + " Explicitly setting this field to empty overrides the 'default' and means no alias metadata will be included." + + " Add fields by sending, 'default,field1,field2'." + + " We advise only including fields that change rarely because each change triggers a storage write." + return desc +} diff --git a/sdk/helper/aliasmetadata/alias_metadata_acc_test.go b/sdk/helper/aliasmetadata/alias_metadata_acc_test.go new file mode 100644 index 0000000000000..26ea1342186c3 --- /dev/null +++ b/sdk/helper/aliasmetadata/alias_metadata_acc_test.go @@ -0,0 +1,496 @@ +package aliasmetadata + +import ( + "context" + "fmt" + "reflect" + "sort" + "testing" + + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/logical" +) + +func TestAcceptance(t *testing.T) { + ctx := context.Background() + storage := &logical.InmemStorage{} + + b := &fakeBackend{ + Backend: &framework.Backend{ + Paths: []*framework.Path{ + configPath(), + loginPath(), + }, + }, + } + if err := b.Setup(ctx, &logical.BackendConfig{ + StorageView: storage, + Logger: hclog.Default(), + }); err != nil { + t.Fatal(err) + } + + // On the first read of alias_metadata, when nothing has been touched, + // we should receive the default field(s) if a read is performed. + resp, err := b.HandleRequest(ctx, &logical.Request{ + Operation: logical.ReadOperation, + Path: "config", + Storage: storage, + Connection: &logical.Connection{ + RemoteAddr: "http://foo.com", + }, + }) + if err != nil { + t.Fatal(err) + } + if resp == nil || resp.Data == nil { + t.Fatal("expected non-nil response") + } + if !reflect.DeepEqual(resp.Data[FieldName], []string{"role_name"}) { + t.Fatal("expected default field of role_name to be returned") + } + + // The auth should only have the default metadata. + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.UpdateOperation, + Path: "login", + Storage: storage, + Connection: &logical.Connection{ + RemoteAddr: "http://foo.com", + }, + Data: map[string]interface{}{ + "role_name": "something", + }, + }) + if err != nil { + t.Fatal(err) + } + if resp == nil || resp.Auth == nil || resp.Auth.Alias == nil || resp.Auth.Alias.Metadata == nil { + t.Fatalf("expected alias metadata") + } + if len(resp.Auth.Alias.Metadata) != 1 { + t.Fatal("expected only 1 field") + } + if resp.Auth.Alias.Metadata["role_name"] != "something" { + t.Fatal("expected role_name to be something") + } + + // We should be able to set the alias_metadata to empty by sending an + // explicitly empty array. + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.UpdateOperation, + Path: "config", + Storage: storage, + Connection: &logical.Connection{ + RemoteAddr: "http://foo.com", + }, + Data: map[string]interface{}{ + FieldName: []string{}, + }, + }) + if err != nil { + t.Fatal(err) + } + if resp != nil { + t.Fatal("expected nil response") + } + + // Now we should receive no fields for alias_metadata. + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.ReadOperation, + Path: "config", + Storage: storage, + Connection: &logical.Connection{ + RemoteAddr: "http://foo.com", + }, + }) + if err != nil { + t.Fatal(err) + } + if resp == nil || resp.Data == nil { + t.Fatal("expected non-nil response") + } + if !reflect.DeepEqual(resp.Data[FieldName], []string{}) { + t.Fatal("expected no fields to be returned") + } + + // The auth should have no metadata. + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.UpdateOperation, + Path: "login", + Storage: storage, + Connection: &logical.Connection{ + RemoteAddr: "http://foo.com", + }, + Data: map[string]interface{}{ + "role_name": "something", + }, + }) + if err != nil { + t.Fatal(err) + } + if resp == nil || resp.Auth == nil || resp.Auth.Alias == nil || resp.Auth.Alias.Metadata == nil { + t.Fatal("expected alias metadata") + } + if len(resp.Auth.Alias.Metadata) != 0 { + t.Fatal("expected 0 fields") + } + + // Now if we set it to "default", the default fields should + // be restored. + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.UpdateOperation, + Path: "config", + Storage: storage, + Connection: &logical.Connection{ + RemoteAddr: "http://foo.com", + }, + Data: map[string]interface{}{ + FieldName: []string{"default"}, + }, + }) + if err != nil { + t.Fatal(err) + } + if resp != nil { + t.Fatal("expected nil response") + } + + // Let's make sure we've returned to the default fields. + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.ReadOperation, + Path: "config", + Storage: storage, + Connection: &logical.Connection{ + RemoteAddr: "http://foo.com", + }, + }) + if err != nil { + t.Fatal(err) + } + if resp == nil || resp.Data == nil { + t.Fatal("expected non-nil response") + } + if !reflect.DeepEqual(resp.Data[FieldName], []string{"role_name"}) { + t.Fatal("expected default field of role_name to be returned") + } + + // We should again only receive the default field on the login. + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.UpdateOperation, + Path: "login", + Storage: storage, + Connection: &logical.Connection{ + RemoteAddr: "http://foo.com", + }, + Data: map[string]interface{}{ + "role_name": "something", + }, + }) + if err != nil { + t.Fatal(err) + } + if resp == nil || resp.Auth == nil || resp.Auth.Alias == nil || resp.Auth.Alias.Metadata == nil { + t.Fatal("expected alias metadata") + } + if len(resp.Auth.Alias.Metadata) != 1 { + t.Fatal("expected only 1 field") + } + if resp.Auth.Alias.Metadata["role_name"] != "something" { + t.Fatal("expected role_name to be something") + } + + // We should be able to set it to "default" plus 1 optional field. + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.UpdateOperation, + Path: "config", + Storage: storage, + Connection: &logical.Connection{ + RemoteAddr: "http://foo.com", + }, + Data: map[string]interface{}{ + FieldName: []string{"default", "remote_addr"}, + }, + }) + if err != nil { + t.Fatal(err) + } + if resp != nil { + t.Fatal("expected nil response") + } + + // Let's make sure the default and optional field are being stored + // correctly. + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.ReadOperation, + Path: "config", + Storage: storage, + Connection: &logical.Connection{ + RemoteAddr: "http://foo.com", + }, + }) + if err != nil { + t.Fatal(err) + } + if resp == nil || resp.Data == nil { + t.Fatal("expected non-nil response") + } + expected := []string{"role_name", "remote_addr"} + sort.Strings(expected) + actual := resp.Data[FieldName].([]string) + sort.Strings(actual) + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("unexpectedly received %s", resp.Data[FieldName]) + } + + // They both should now appear on the login. + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.UpdateOperation, + Path: "login", + Storage: storage, + Connection: &logical.Connection{ + RemoteAddr: "http://foo.com", + }, + Data: map[string]interface{}{ + "role_name": "something", + }, + }) + if err != nil { + t.Fatal(err) + } + if resp == nil || resp.Auth == nil || resp.Auth.Alias == nil || resp.Auth.Alias.Metadata == nil { + t.Fatal("expected alias metadata") + } + if len(resp.Auth.Alias.Metadata) != 2 { + t.Fatal("expected 2 fields") + } + if resp.Auth.Alias.Metadata["role_name"] != "something" { + t.Fatal("expected role_name to be something") + } + if resp.Auth.Alias.Metadata["remote_addr"] != "http://foo.com" { + t.Fatal("expected remote_addr to be http://foo.com") + } + + // Omit all default fields and just select one. + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.UpdateOperation, + Path: "config", + Storage: storage, + Connection: &logical.Connection{ + RemoteAddr: "http://foo.com", + }, + Data: map[string]interface{}{ + FieldName: []string{"remote_addr"}, + }, + }) + if err != nil { + t.Fatal(err) + } + if resp != nil { + t.Fatal("expected nil response") + } + + // Make sure that worked. + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.ReadOperation, + Path: "config", + Storage: storage, + Connection: &logical.Connection{ + RemoteAddr: "http://foo.com", + }, + }) + if err != nil { + t.Fatal(err) + } + if resp == nil || resp.Data == nil { + t.Fatal("expected non-nil response") + } + if !reflect.DeepEqual(resp.Data[FieldName], []string{"remote_addr"}) { + t.Fatal("expected remote_addr to be returned") + } + + // Ensure only the selected one is on logins. + // They both should now appear on the login. + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.UpdateOperation, + Path: "login", + Storage: storage, + Connection: &logical.Connection{ + RemoteAddr: "http://foo.com", + }, + Data: map[string]interface{}{ + "role_name": "something", + }, + }) + if err != nil { + t.Fatal(err) + } + if resp == nil || resp.Auth == nil || resp.Auth.Alias == nil || resp.Auth.Alias.Metadata == nil { + t.Fatal("expected alias metadata") + } + if len(resp.Auth.Alias.Metadata) != 1 { + t.Fatal("expected only 1 field") + } + if resp.Auth.Alias.Metadata["remote_addr"] != "http://foo.com" { + t.Fatal("expected remote_addr to be http://foo.com") + } + + // Try adding an unsupported field. + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.UpdateOperation, + Path: "config", + Storage: storage, + Connection: &logical.Connection{ + RemoteAddr: "http://foo.com", + }, + Data: map[string]interface{}{ + FieldName: []string{"asl;dfkj"}, + }, + }) + if err == nil { + t.Fatal("expected err") + } + if resp == nil { + t.Fatal("expected non-nil response") + } + if !resp.IsError() { + t.Fatal("expected error response") + } +} + +// We expect people to embed the handler on their +// config so it automatically makes its helper methods +// available and easy to find wherever the config is +// needed. Explicitly naming it in json avoids it +// automatically being named "Handler" by Go's JSON +// marshalling library. +type fakeConfig struct { + Handler `json:"alias_metadata_handler"` +} + +type fakeBackend struct { + *framework.Backend +} + +// We expect each back-end to explicitly define the fields that +// will be included by default, and optionally available. +var aliasMetadataFields = &Fields{ + Default: []string{ + "role_name", // This would likely never change because the alias is the role name. + }, + AvailableToAdd: []string{ + "remote_addr", // This would likely change with every new caller. + }, +} + +func configPath() *framework.Path { + return &framework.Path{ + Pattern: "config", + Fields: map[string]*framework.FieldSchema{ + FieldName: FieldSchema(aliasMetadataFields), + }, + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ReadOperation: &framework.PathOperation{ + Callback: func(ctx context.Context, req *logical.Request, fd *framework.FieldData) (*logical.Response, error) { + entryRaw, err := req.Storage.Get(ctx, "config") + if err != nil { + return nil, err + } + conf := &fakeConfig{ + Handler: NewHandler(aliasMetadataFields), + } + if entryRaw != nil { + if err := entryRaw.DecodeJSON(conf); err != nil { + return nil, err + } + } + // Note that even if the config entry was nil, we return + // a populated response to give info on what the default + // alias metadata is when unconfigured. + return &logical.Response{ + Data: map[string]interface{}{ + FieldName: conf.GetAliasMetadata(), + }, + }, nil + }, + }, + logical.UpdateOperation: &framework.PathOperation{ + Callback: func(ctx context.Context, req *logical.Request, fd *framework.FieldData) (*logical.Response, error) { + entryRaw, err := req.Storage.Get(ctx, "config") + if err != nil { + return nil, err + } + conf := &fakeConfig{ + Handler: NewHandler(aliasMetadataFields), + } + if entryRaw != nil { + if err := entryRaw.DecodeJSON(conf); err != nil { + return nil, err + } + } + // This is where we read in the user's given alias metadata. + if err := conf.ParseAliasMetadata(fd); err != nil { + // Since this will only error on bad input, it's best to give + // a 400 response with the explicit problem included. + return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest + } + entry, err := logical.StorageEntryJSON("config", conf) + if err != nil { + return nil, err + } + if err = req.Storage.Put(ctx, entry); err != nil { + return nil, err + } + return nil, nil + }, + }, + }, + } +} + +func loginPath() *framework.Path { + return &framework.Path{ + Pattern: "login", + Fields: map[string]*framework.FieldSchema{ + "role_name": { + Type: framework.TypeString, + Required: true, + }, + }, + Operations: map[logical.Operation]framework.OperationHandler{ + logical.UpdateOperation: &framework.PathOperation{ + Callback: func(ctx context.Context, req *logical.Request, fd *framework.FieldData) (*logical.Response, error) { + entryRaw, err := req.Storage.Get(ctx, "config") + if err != nil { + return nil, err + } + conf := &fakeConfig{ + Handler: NewHandler(aliasMetadataFields), + } + if entryRaw != nil { + if err := entryRaw.DecodeJSON(conf); err != nil { + return nil, err + } + } + auth := &logical.Auth{ + Alias: &logical.Alias{ + Name: fd.Get("role_name").(string), + }, + } + // Here we provide everything and let the method strip out + // the undesired stuff. + if err := conf.PopulateDesiredAliasMetadata(auth, map[string]string{ + "role_name": fd.Get("role_name").(string), + "remote_addr": req.Connection.RemoteAddr, + }); err != nil { + fmt.Println("unable to populate due to " + err.Error()) + } + return &logical.Response{ + Auth: auth, + }, nil + }, + }, + }, + } +} diff --git a/sdk/helper/aliasmetadata/alias_metadata_test.go b/sdk/helper/aliasmetadata/alias_metadata_test.go new file mode 100644 index 0000000000000..df23e80bd1e94 --- /dev/null +++ b/sdk/helper/aliasmetadata/alias_metadata_test.go @@ -0,0 +1,103 @@ +package aliasmetadata + +import ( + "reflect" + "sort" + "testing" + + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/logical" +) + +var testFields = &Fields{ + Default: []string{"fizz", "buzz"}, + AvailableToAdd: []string{"foo", "bar"}, +} + +func TestFieldSchema(t *testing.T) { + schema := FieldSchema(testFields) + if schema.Type != framework.TypeCommaStringSlice { + t.Fatal("expected TypeCommaStringSlice") + } + if schema.Description != `The metadata to include on the aliases generated by this plugin. When set to 'default', includes: fizz, buzz. These fields are available to add: foo, bar. Not editing this field means the 'default' fields are included. Explicitly setting this field to empty overrides the 'default' and means no alias metadata will be included. Add fields by sending, 'default,field1,field2'. We advise only including fields that change rarely because each change triggers a storage write.` { + t.Fatal("received unexpected description: " + schema.Description) + } + if schema.DisplayAttrs == nil { + t.Fatal("expected display attributes") + } + if schema.DisplayAttrs.Name != FieldName { + t.Fatalf("expected name of %s", FieldName) + } + if schema.DisplayAttrs.Value != "default,field1,field2" { + t.Fatal("expected default,field1,field2") + } + if !reflect.DeepEqual(schema.Default, []string{"default"}) { + t.Fatal("expected default") + } +} + +func TestGetAliasMetadata(t *testing.T) { + h := NewHandler(testFields) + expected := []string{"fizz", "buzz"} + sort.Strings(expected) + actual := h.GetAliasMetadata() + sort.Strings(actual) + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %s but received %s", expected, actual) + } +} + +func TestParseAliasMetadata(t *testing.T) { + h := NewHandler(testFields) + data := &framework.FieldData{ + Raw: map[string]interface{}{ + FieldName: []string{"default"}, + }, + Schema: map[string]*framework.FieldSchema{ + FieldName: FieldSchema(testFields), + }, + } + if err := h.ParseAliasMetadata(data); err != nil { + t.Fatal(err) + } + expected := []string{"fizz", "buzz"} + sort.Strings(expected) + actual := h.GetAliasMetadata() + sort.Strings(actual) + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %s but received %s", expected, actual) + } +} + +func TestPopulateDesiredAliasMetadata(t *testing.T) { + h := NewHandler(testFields) + data := &framework.FieldData{ + Raw: map[string]interface{}{ + FieldName: []string{"foo"}, + }, + Schema: map[string]*framework.FieldSchema{ + FieldName: FieldSchema(testFields), + }, + } + if err := h.ParseAliasMetadata(data); err != nil { + t.Fatal(err) + } + auth := &logical.Auth{ + Alias: &logical.Alias{ + Name: "foo", + }, + } + if err := h.PopulateDesiredAliasMetadata(auth, map[string]string{ + "fizz": "fizzval", + "buzz": "buzzval", + "foo": "fooval", + }); err != nil { + t.Fatal(err) + } + if len(auth.Alias.Metadata) != 1 { + t.Fatal("expected only 1 configured field to be populated") + } + if auth.Alias.Metadata["foo"] != "fooval" { + t.Fatal("expected foova;") + } +} diff --git a/sdk/logical/identity.pb.go b/sdk/logical/identity.pb.go index 94102de78d625..26ba18a4e2b0e 100644 --- a/sdk/logical/identity.pb.go +++ b/sdk/logical/identity.pb.go @@ -114,7 +114,12 @@ type Alias struct { MountAccessor string `sentinel:"" protobuf:"bytes,2,opt,name=mount_accessor,json=mountAccessor,proto3" json:"mount_accessor,omitempty"` // Name is the identifier of this identity in its authentication source Name string `sentinel:"" protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` - // Metadata represents the custom data tied to this alias + // Metadata represents the custom data tied to this alias. Fields added + // to it should have a low rate of change (or no change) because each + // change incurs a storage write, so quickly-changing fields can have + // a significant performance impact at scale. See the SDK's + // "aliasmetadata" package for a helper that eases and standardizes + // using this safely. Metadata map[string]string `sentinel:"" protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // ID is the unique identifier for the alias ID string `sentinel:"" protobuf:"bytes,5,opt,name=ID,proto3" json:"ID,omitempty"` @@ -270,31 +275,33 @@ func init() { proto.RegisterMapType((map[string]string)(nil), "logical.Group.MetadataEntry") } -func init() { proto.RegisterFile("sdk/logical/identity.proto", fileDescriptor_4a34d35719c603a1) } +func init() { + proto.RegisterFile("sdk/logical/identity.proto", fileDescriptor_4a34d35719c603a1) +} var fileDescriptor_4a34d35719c603a1 = []byte{ - // 365 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x93, 0x4f, 0x6b, 0xfa, 0x30, - 0x18, 0xc7, 0x69, 0x6b, 0xfd, 0xf3, 0xf8, 0x53, 0x7e, 0x84, 0x1d, 0x8a, 0x4c, 0x70, 0xc2, 0x46, - 0x4f, 0x2d, 0x6c, 0x17, 0xb7, 0x9d, 0x1c, 0xca, 0xf0, 0xb0, 0x4b, 0xd9, 0x69, 0x17, 0x89, 0x4d, - 0xd0, 0x60, 0xdb, 0x94, 0x26, 0x15, 0xfa, 0x1a, 0xc6, 0x5e, 0xd6, 0xde, 0xd7, 0x30, 0x8d, 0xa5, - 0x3a, 0x84, 0x1d, 0xb6, 0x5b, 0xf2, 0x7d, 0x9e, 0x7e, 0x9b, 0xcf, 0x37, 0x79, 0x60, 0x20, 0xc8, - 0xd6, 0x8f, 0xf8, 0x9a, 0x85, 0x38, 0xf2, 0x19, 0xa1, 0x89, 0x64, 0xb2, 0xf0, 0xd2, 0x8c, 0x4b, - 0x8e, 0x5a, 0x5a, 0x1f, 0x7f, 0x98, 0xd0, 0x9c, 0xab, 0x0a, 0xea, 0x83, 0xb9, 0x98, 0x39, 0xc6, - 0xc8, 0x70, 0x3b, 0x81, 0xb9, 0x98, 0x21, 0x04, 0x8d, 0x04, 0xc7, 0xd4, 0x31, 0x95, 0xa2, 0xd6, - 0xc8, 0x85, 0x16, 0x8e, 0x18, 0x16, 0x54, 0x38, 0xd6, 0xc8, 0x72, 0xbb, 0xb7, 0x7d, 0x4f, 0x3b, - 0x79, 0xd3, 0xbd, 0x1e, 0x1c, 0xca, 0xe8, 0x1e, 0xda, 0x31, 0x95, 0x98, 0x60, 0x89, 0x9d, 0x86, - 0x6a, 0x1d, 0x56, 0xad, 0xe5, 0x0f, 0xbd, 0x17, 0x5d, 0x9f, 0x27, 0x32, 0x2b, 0x82, 0xaa, 0x1d, - 0x0d, 0xa0, 0x4d, 0x98, 0xc0, 0xab, 0x88, 0x12, 0xc7, 0x1e, 0x19, 0x6e, 0x3b, 0xa8, 0xf6, 0xe8, - 0x0a, 0xfe, 0xed, 0x0f, 0x22, 0x52, 0x1c, 0xd2, 0x25, 0x23, 0x4e, 0x53, 0x1d, 0xae, 0x5b, 0x69, - 0x0b, 0x32, 0x78, 0x84, 0xde, 0x91, 0x33, 0xfa, 0x0f, 0xd6, 0x96, 0x16, 0x9a, 0x6c, 0xbf, 0x44, - 0x17, 0x60, 0xef, 0x70, 0x94, 0x1f, 0xd8, 0xca, 0xcd, 0x83, 0x39, 0x31, 0xc6, 0xef, 0x26, 0xd8, - 0x8a, 0x04, 0x0d, 0x01, 0x62, 0x9e, 0x27, 0x72, 0x29, 0x8b, 0x94, 0xea, 0x8f, 0x3b, 0x4a, 0x79, - 0x2d, 0x52, 0x8a, 0xae, 0xa1, 0x5f, 0x96, 0x71, 0x18, 0x52, 0x21, 0x78, 0xa6, 0xbd, 0x7a, 0x4a, - 0x9d, 0x6a, 0xb1, 0x0a, 0xd1, 0xaa, 0x85, 0x38, 0xf9, 0x16, 0xcd, 0xe5, 0x71, 0x8a, 0x67, 0x93, - 0x29, 0xaf, 0xc8, 0xae, 0xae, 0xe8, 0xaf, 0xd3, 0xf8, 0x34, 0xc0, 0x7e, 0xce, 0x78, 0x9e, 0xfe, - 0xe8, 0x71, 0xd4, 0xb9, 0xac, 0x13, 0x2e, 0xe5, 0x72, 0x96, 0xeb, 0x94, 0xa3, 0xf1, 0xbb, 0x1c, - 0x4f, 0xee, 0xdb, 0xcd, 0x9a, 0xc9, 0x4d, 0xbe, 0xf2, 0x42, 0x1e, 0xfb, 0x1b, 0x2c, 0x36, 0x2c, - 0xe4, 0x59, 0xea, 0xef, 0x70, 0x1e, 0x49, 0xbf, 0x36, 0x27, 0xab, 0xa6, 0x9a, 0x8f, 0xbb, 0xaf, - 0x00, 0x00, 0x00, 0xff, 0xff, 0xa8, 0xe2, 0x28, 0xc0, 0x3d, 0x03, 0x00, 0x00, + // 363 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x93, 0x4f, 0x4b, 0xf3, 0x40, + 0x10, 0xc6, 0x49, 0xd2, 0xf4, 0xcf, 0xf4, 0x6d, 0x79, 0x59, 0x3c, 0x84, 0x62, 0xa1, 0x16, 0x94, + 0x9c, 0x12, 0xd0, 0x4b, 0xd5, 0x53, 0x45, 0x91, 0x1e, 0xbc, 0x04, 0x4f, 0x5e, 0xca, 0x36, 0xbb, + 0xb4, 0x4b, 0x93, 0x6e, 0xc8, 0x6e, 0x0a, 0xf9, 0x0c, 0xe2, 0xc7, 0xf2, 0x7b, 0x99, 0x6e, 0xb6, + 0x21, 0xad, 0x14, 0x3c, 0xe8, 0x6d, 0xe6, 0x99, 0xc9, 0xec, 0xfe, 0x9e, 0xcd, 0xc0, 0x40, 0x90, + 0xb5, 0x1f, 0xf1, 0x25, 0x0b, 0x71, 0xe4, 0x33, 0x42, 0x37, 0x92, 0xc9, 0xdc, 0x4b, 0x52, 0x2e, + 0x39, 0x6a, 0x69, 0x7d, 0xfc, 0x61, 0x42, 0xf3, 0x49, 0x55, 0x50, 0x1f, 0xcc, 0xd9, 0xa3, 0x63, + 0x8c, 0x0c, 0xb7, 0x13, 0x14, 0x11, 0x42, 0xd0, 0xd8, 0xe0, 0x98, 0x3a, 0xa6, 0x52, 0x54, 0x8c, + 0x5c, 0x68, 0xe1, 0x88, 0x61, 0x41, 0x85, 0x63, 0x8d, 0x2c, 0xb7, 0x7b, 0xdd, 0xf7, 0xf4, 0x24, + 0x6f, 0xba, 0xd3, 0x83, 0x7d, 0x19, 0xdd, 0x42, 0x3b, 0xa6, 0x12, 0x13, 0x2c, 0xb1, 0xd3, 0x50, + 0xad, 0xc3, 0xaa, 0xb5, 0x3c, 0xd0, 0x7b, 0xd1, 0xf5, 0x22, 0x4d, 0xf3, 0xa0, 0x6a, 0x47, 0x03, + 0x68, 0x13, 0x26, 0xf0, 0x22, 0xa2, 0xc4, 0xb1, 0x8b, 0xc3, 0xdb, 0x41, 0x95, 0xa3, 0x0b, 0xf8, + 0xb7, 0xbb, 0x88, 0x48, 0x70, 0x48, 0xe7, 0x8c, 0x38, 0x4d, 0x75, 0xb9, 0x6e, 0xa5, 0xcd, 0xc8, + 0xe0, 0x1e, 0x7a, 0x07, 0x93, 0xd1, 0x7f, 0xb0, 0xd6, 0x34, 0xd7, 0x64, 0xbb, 0x10, 0x9d, 0x81, + 0xbd, 0xc5, 0x51, 0xb6, 0x67, 0x2b, 0x93, 0x3b, 0x73, 0x62, 0x8c, 0xdf, 0x4d, 0xb0, 0x15, 0x09, + 0x1a, 0x02, 0xc4, 0x3c, 0xdb, 0xc8, 0xb9, 0xcc, 0x13, 0xaa, 0x3f, 0xee, 0x28, 0xe5, 0xb5, 0x10, + 0xd0, 0x25, 0xf4, 0xcb, 0x32, 0x0e, 0x43, 0x2a, 0x04, 0x4f, 0xf5, 0xac, 0x9e, 0x52, 0xa7, 0x5a, + 0xac, 0x4c, 0xb4, 0x6a, 0x26, 0x4e, 0xbe, 0x59, 0x73, 0x7e, 0xe8, 0xe2, 0x49, 0x67, 0xca, 0x27, + 0xb2, 0xab, 0x27, 0xfa, 0x6b, 0x37, 0x3e, 0x0d, 0xb0, 0x9f, 0x53, 0x9e, 0x25, 0x3f, 0xfa, 0x39, + 0xea, 0x5c, 0xd6, 0x11, 0x97, 0x9a, 0x72, 0x92, 0xeb, 0x98, 0xa3, 0xf1, 0xbb, 0x1c, 0x0f, 0xee, + 0xdb, 0xd5, 0x92, 0xc9, 0x55, 0xb6, 0xf0, 0x42, 0x1e, 0xfb, 0x2b, 0x2c, 0x56, 0x2c, 0xe4, 0x69, + 0xe2, 0x6f, 0x71, 0x16, 0x49, 0xbf, 0xb6, 0x27, 0x8b, 0xa6, 0xda, 0x8f, 0x9b, 0xaf, 0x00, 0x00, + 0x00, 0xff, 0xff, 0xa8, 0xe2, 0x28, 0xc0, 0x3d, 0x03, 0x00, 0x00, } diff --git a/sdk/logical/identity.proto b/sdk/logical/identity.proto index 34af579b4d7a9..78c3758f8510f 100644 --- a/sdk/logical/identity.proto +++ b/sdk/logical/identity.proto @@ -36,7 +36,12 @@ message Alias { // Name is the identifier of this identity in its authentication source string name = 3; - // Metadata represents the custom data tied to this alias + // Metadata represents the custom data tied to this alias. Fields added + // to it should have a low rate of change (or no change) because each + // change incurs a storage write, so quickly-changing fields can have + // a significant performance impact at scale. See the SDK's + // "aliasmetadata" package for a helper that eases and standardizes + // using this safely. map metadata = 4; // ID is the unique identifier for the alias diff --git a/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go b/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go new file mode 100644 index 0000000000000..1b9cf1cc29aef --- /dev/null +++ b/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go @@ -0,0 +1,194 @@ +package aliasmetadata + +/* + aliasmetadata is a package offering convenience and + standardization when supporting an `alias_metadata` + field in a plugin's configuration. This then controls + what alias metadata is added to an Auth during login. + + To see an example of how to add and use it, check out + how these structs and fields are used in the AWS auth + method. + + Or, check out its acceptance test in this package to + see its integration points. +*/ + +import ( + "errors" + "fmt" + "strings" + + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/strutil" + "github.com/hashicorp/vault/sdk/logical" +) + +// FieldName is the user-facing name for the field. +const FieldName = "alias_metadata" + +// Fields is for configuring a back-end's available +// default and additional fields. These are used for +// providing a verbose field description, and for parsing +// user input. +type Fields struct { + // Default is a list of the default fields that should + // be included if a user sends "default" in their list + // of desired fields. These fields should all have a + // low rate of change because each change can incur a + // write to storage. + Default []string + + // AvailableToAdd is a list of fields not included by + // default, that the user may include. + AvailableToAdd []string +} + +func (f *Fields) all() []string { + return append(f.Default, f.AvailableToAdd...) +} + +// FieldSchema takes the default and additionally available +// fields, and uses them to generate a verbose description +// regarding how to use the "alias_metadata" field. +func FieldSchema(fields *Fields) *framework.FieldSchema { + return &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: description(fields), + DisplayAttrs: &framework.DisplayAttributes{ + Name: FieldName, + Value: "default,field1,field2", + }, + Default: []string{"default"}, + } +} + +// NewHandler instantiates a Handler to be embedded in your config. +func NewHandler(fields *Fields) Handler { + return &handler{ + fields: fields, + } +} + +// Handler is an interface for the helper methods you get on your +// config when you embed the Handler. +type Handler interface { + GetAliasMetadata() []string + ParseAliasMetadata(data *framework.FieldData) error + PopulateDesiredAliasMetadata(auth *logical.Auth, fieldValues map[string]string) error +} + +type handler struct { + // AliasMetadata is an explicit list of all the user's configured + // fields that are being added to alias metadata. It will never + // include the "default" parameter, and instead includes the actual + // fields behind "default", if selected. If it has never been set, + // the pointer will be nil. + AliasMetadata *[]string `json:"alias_metadata"` + + // fields is a list of the configured default and available + // fields. It's intentionally not jsonified. + fields *Fields +} + +// GetAliasMetadata is intended to be used on config reads. +// It gets an explicit list of all the user's configured +// fields that are being added to alias metadata. It will never +// include the "default" parameter, and instead includes the actual +// fields behind "default", if selected. +func (h *handler) GetAliasMetadata() []string { + if h.AliasMetadata == nil { + return h.fields.Default + } + return *h.AliasMetadata +} + +// ParseAliasMetadata is intended to be used on config create/update. +// It takes a user's selected fields (or lack thereof), +// converts it to a list of explicit fields, and adds it to the handler +// for later storage. +func (h *handler) ParseAliasMetadata(data *framework.FieldData) error { + userProvided, ok := data.GetOk(FieldName) + if !ok { + // Nothing further to do here. + return nil + } + + // uniqueFields protects against weird edge cases like if + // a user provided "default,field1,field2,default". + uniqueFields := make(map[string]bool) + for _, field := range userProvided.([]string) { + if field == "default" { + // Add the fields that "default" represents, rather + // than the explicit field. + for _, dfltField := range h.fields.Default { + uniqueFields[dfltField] = true + } + } else { + // Make sure they've sent a supported field so we can + // error early if not. + if !strutil.StrListContains(h.fields.all(), field) { + return fmt.Errorf("%q is not an available field, please select from: %s", field, strings.Join(h.fields.all(), ", ")) + } + uniqueFields[field] = true + } + } + // Attach the fields we've received so they'll be stored. + aliasMetadata := make([]string, len(uniqueFields)) + i := 0 + for fieldName := range uniqueFields { + aliasMetadata[i] = fieldName + i++ + } + // Fulfilling the pointer here flags that the user has made + // an explicit selection so we shouldn't just fall back to + // our defaults. + h.AliasMetadata = &aliasMetadata + return nil +} + +// PopulateDesiredAliasMetadata is intended to be used during login +// just before returning an auth. +// It takes the available alias metadata and, +// if the auth should have it, adds it to the auth's alias metadata. +func (h *handler) PopulateDesiredAliasMetadata(auth *logical.Auth, available map[string]string) error { + if auth == nil { + return errors.New("auth is nil") + } + if auth.Alias == nil { + return errors.New("auth alias is nil") + } + if auth.Alias.Name == "" { + // We need the caller to set the alias name or there will + // be nothing for these fields to operate upon. + return errors.New("auth alias name must be set") + } + if auth.Alias.Metadata == nil { + auth.Alias.Metadata = make(map[string]string) + } + fieldsToInclude := h.fields.Default + if h.AliasMetadata != nil { + fieldsToInclude = *h.AliasMetadata + } + for availableField, itsValue := range available { + if strutil.StrListContains(fieldsToInclude, availableField) { + auth.Alias.Metadata[availableField] = itsValue + } + } + return nil +} + +func description(fields *Fields) string { + desc := "The metadata to include on the aliases generated by this plugin." + if len(fields.Default) > 0 { + desc += fmt.Sprintf(" When set to 'default', includes: %s.", strings.Join(fields.Default, ", ")) + } + if len(fields.AvailableToAdd) > 0 { + desc += fmt.Sprintf(" These fields are available to add: %s.", strings.Join(fields.AvailableToAdd, ", ")) + } + desc += " Not editing this field means the 'default' fields are included." + + " Explicitly setting this field to empty overrides the 'default' and means no alias metadata will be included." + + " Add fields by sending, 'default,field1,field2'." + + " We advise only including fields that change rarely because each change triggers a storage write." + return desc +} diff --git a/vendor/github.com/hashicorp/vault/sdk/logical/identity.pb.go b/vendor/github.com/hashicorp/vault/sdk/logical/identity.pb.go index 94102de78d625..26ba18a4e2b0e 100644 --- a/vendor/github.com/hashicorp/vault/sdk/logical/identity.pb.go +++ b/vendor/github.com/hashicorp/vault/sdk/logical/identity.pb.go @@ -114,7 +114,12 @@ type Alias struct { MountAccessor string `sentinel:"" protobuf:"bytes,2,opt,name=mount_accessor,json=mountAccessor,proto3" json:"mount_accessor,omitempty"` // Name is the identifier of this identity in its authentication source Name string `sentinel:"" protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` - // Metadata represents the custom data tied to this alias + // Metadata represents the custom data tied to this alias. Fields added + // to it should have a low rate of change (or no change) because each + // change incurs a storage write, so quickly-changing fields can have + // a significant performance impact at scale. See the SDK's + // "aliasmetadata" package for a helper that eases and standardizes + // using this safely. Metadata map[string]string `sentinel:"" protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // ID is the unique identifier for the alias ID string `sentinel:"" protobuf:"bytes,5,opt,name=ID,proto3" json:"ID,omitempty"` @@ -270,31 +275,33 @@ func init() { proto.RegisterMapType((map[string]string)(nil), "logical.Group.MetadataEntry") } -func init() { proto.RegisterFile("sdk/logical/identity.proto", fileDescriptor_4a34d35719c603a1) } +func init() { + proto.RegisterFile("sdk/logical/identity.proto", fileDescriptor_4a34d35719c603a1) +} var fileDescriptor_4a34d35719c603a1 = []byte{ - // 365 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x93, 0x4f, 0x6b, 0xfa, 0x30, - 0x18, 0xc7, 0x69, 0x6b, 0xfd, 0xf3, 0xf8, 0x53, 0x7e, 0x84, 0x1d, 0x8a, 0x4c, 0x70, 0xc2, 0x46, - 0x4f, 0x2d, 0x6c, 0x17, 0xb7, 0x9d, 0x1c, 0xca, 0xf0, 0xb0, 0x4b, 0xd9, 0x69, 0x17, 0x89, 0x4d, - 0xd0, 0x60, 0xdb, 0x94, 0x26, 0x15, 0xfa, 0x1a, 0xc6, 0x5e, 0xd6, 0xde, 0xd7, 0x30, 0x8d, 0xa5, - 0x3a, 0x84, 0x1d, 0xb6, 0x5b, 0xf2, 0x7d, 0x9e, 0x7e, 0x9b, 0xcf, 0x37, 0x79, 0x60, 0x20, 0xc8, - 0xd6, 0x8f, 0xf8, 0x9a, 0x85, 0x38, 0xf2, 0x19, 0xa1, 0x89, 0x64, 0xb2, 0xf0, 0xd2, 0x8c, 0x4b, - 0x8e, 0x5a, 0x5a, 0x1f, 0x7f, 0x98, 0xd0, 0x9c, 0xab, 0x0a, 0xea, 0x83, 0xb9, 0x98, 0x39, 0xc6, - 0xc8, 0x70, 0x3b, 0x81, 0xb9, 0x98, 0x21, 0x04, 0x8d, 0x04, 0xc7, 0xd4, 0x31, 0x95, 0xa2, 0xd6, - 0xc8, 0x85, 0x16, 0x8e, 0x18, 0x16, 0x54, 0x38, 0xd6, 0xc8, 0x72, 0xbb, 0xb7, 0x7d, 0x4f, 0x3b, - 0x79, 0xd3, 0xbd, 0x1e, 0x1c, 0xca, 0xe8, 0x1e, 0xda, 0x31, 0x95, 0x98, 0x60, 0x89, 0x9d, 0x86, - 0x6a, 0x1d, 0x56, 0xad, 0xe5, 0x0f, 0xbd, 0x17, 0x5d, 0x9f, 0x27, 0x32, 0x2b, 0x82, 0xaa, 0x1d, - 0x0d, 0xa0, 0x4d, 0x98, 0xc0, 0xab, 0x88, 0x12, 0xc7, 0x1e, 0x19, 0x6e, 0x3b, 0xa8, 0xf6, 0xe8, - 0x0a, 0xfe, 0xed, 0x0f, 0x22, 0x52, 0x1c, 0xd2, 0x25, 0x23, 0x4e, 0x53, 0x1d, 0xae, 0x5b, 0x69, - 0x0b, 0x32, 0x78, 0x84, 0xde, 0x91, 0x33, 0xfa, 0x0f, 0xd6, 0x96, 0x16, 0x9a, 0x6c, 0xbf, 0x44, - 0x17, 0x60, 0xef, 0x70, 0x94, 0x1f, 0xd8, 0xca, 0xcd, 0x83, 0x39, 0x31, 0xc6, 0xef, 0x26, 0xd8, - 0x8a, 0x04, 0x0d, 0x01, 0x62, 0x9e, 0x27, 0x72, 0x29, 0x8b, 0x94, 0xea, 0x8f, 0x3b, 0x4a, 0x79, - 0x2d, 0x52, 0x8a, 0xae, 0xa1, 0x5f, 0x96, 0x71, 0x18, 0x52, 0x21, 0x78, 0xa6, 0xbd, 0x7a, 0x4a, - 0x9d, 0x6a, 0xb1, 0x0a, 0xd1, 0xaa, 0x85, 0x38, 0xf9, 0x16, 0xcd, 0xe5, 0x71, 0x8a, 0x67, 0x93, - 0x29, 0xaf, 0xc8, 0xae, 0xae, 0xe8, 0xaf, 0xd3, 0xf8, 0x34, 0xc0, 0x7e, 0xce, 0x78, 0x9e, 0xfe, - 0xe8, 0x71, 0xd4, 0xb9, 0xac, 0x13, 0x2e, 0xe5, 0x72, 0x96, 0xeb, 0x94, 0xa3, 0xf1, 0xbb, 0x1c, - 0x4f, 0xee, 0xdb, 0xcd, 0x9a, 0xc9, 0x4d, 0xbe, 0xf2, 0x42, 0x1e, 0xfb, 0x1b, 0x2c, 0x36, 0x2c, - 0xe4, 0x59, 0xea, 0xef, 0x70, 0x1e, 0x49, 0xbf, 0x36, 0x27, 0xab, 0xa6, 0x9a, 0x8f, 0xbb, 0xaf, - 0x00, 0x00, 0x00, 0xff, 0xff, 0xa8, 0xe2, 0x28, 0xc0, 0x3d, 0x03, 0x00, 0x00, + // 363 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x93, 0x4f, 0x4b, 0xf3, 0x40, + 0x10, 0xc6, 0x49, 0xd2, 0xf4, 0xcf, 0xf4, 0x6d, 0x79, 0x59, 0x3c, 0x84, 0x62, 0xa1, 0x16, 0x94, + 0x9c, 0x12, 0xd0, 0x4b, 0xd5, 0x53, 0x45, 0x91, 0x1e, 0xbc, 0x04, 0x4f, 0x5e, 0xca, 0x36, 0xbb, + 0xb4, 0x4b, 0x93, 0x6e, 0xc8, 0x6e, 0x0a, 0xf9, 0x0c, 0xe2, 0xc7, 0xf2, 0x7b, 0x99, 0x6e, 0xb6, + 0x21, 0xad, 0x14, 0x3c, 0xe8, 0x6d, 0xe6, 0x99, 0xc9, 0xec, 0xfe, 0x9e, 0xcd, 0xc0, 0x40, 0x90, + 0xb5, 0x1f, 0xf1, 0x25, 0x0b, 0x71, 0xe4, 0x33, 0x42, 0x37, 0x92, 0xc9, 0xdc, 0x4b, 0x52, 0x2e, + 0x39, 0x6a, 0x69, 0x7d, 0xfc, 0x61, 0x42, 0xf3, 0x49, 0x55, 0x50, 0x1f, 0xcc, 0xd9, 0xa3, 0x63, + 0x8c, 0x0c, 0xb7, 0x13, 0x14, 0x11, 0x42, 0xd0, 0xd8, 0xe0, 0x98, 0x3a, 0xa6, 0x52, 0x54, 0x8c, + 0x5c, 0x68, 0xe1, 0x88, 0x61, 0x41, 0x85, 0x63, 0x8d, 0x2c, 0xb7, 0x7b, 0xdd, 0xf7, 0xf4, 0x24, + 0x6f, 0xba, 0xd3, 0x83, 0x7d, 0x19, 0xdd, 0x42, 0x3b, 0xa6, 0x12, 0x13, 0x2c, 0xb1, 0xd3, 0x50, + 0xad, 0xc3, 0xaa, 0xb5, 0x3c, 0xd0, 0x7b, 0xd1, 0xf5, 0x22, 0x4d, 0xf3, 0xa0, 0x6a, 0x47, 0x03, + 0x68, 0x13, 0x26, 0xf0, 0x22, 0xa2, 0xc4, 0xb1, 0x8b, 0xc3, 0xdb, 0x41, 0x95, 0xa3, 0x0b, 0xf8, + 0xb7, 0xbb, 0x88, 0x48, 0x70, 0x48, 0xe7, 0x8c, 0x38, 0x4d, 0x75, 0xb9, 0x6e, 0xa5, 0xcd, 0xc8, + 0xe0, 0x1e, 0x7a, 0x07, 0x93, 0xd1, 0x7f, 0xb0, 0xd6, 0x34, 0xd7, 0x64, 0xbb, 0x10, 0x9d, 0x81, + 0xbd, 0xc5, 0x51, 0xb6, 0x67, 0x2b, 0x93, 0x3b, 0x73, 0x62, 0x8c, 0xdf, 0x4d, 0xb0, 0x15, 0x09, + 0x1a, 0x02, 0xc4, 0x3c, 0xdb, 0xc8, 0xb9, 0xcc, 0x13, 0xaa, 0x3f, 0xee, 0x28, 0xe5, 0xb5, 0x10, + 0xd0, 0x25, 0xf4, 0xcb, 0x32, 0x0e, 0x43, 0x2a, 0x04, 0x4f, 0xf5, 0xac, 0x9e, 0x52, 0xa7, 0x5a, + 0xac, 0x4c, 0xb4, 0x6a, 0x26, 0x4e, 0xbe, 0x59, 0x73, 0x7e, 0xe8, 0xe2, 0x49, 0x67, 0xca, 0x27, + 0xb2, 0xab, 0x27, 0xfa, 0x6b, 0x37, 0x3e, 0x0d, 0xb0, 0x9f, 0x53, 0x9e, 0x25, 0x3f, 0xfa, 0x39, + 0xea, 0x5c, 0xd6, 0x11, 0x97, 0x9a, 0x72, 0x92, 0xeb, 0x98, 0xa3, 0xf1, 0xbb, 0x1c, 0x0f, 0xee, + 0xdb, 0xd5, 0x92, 0xc9, 0x55, 0xb6, 0xf0, 0x42, 0x1e, 0xfb, 0x2b, 0x2c, 0x56, 0x2c, 0xe4, 0x69, + 0xe2, 0x6f, 0x71, 0x16, 0x49, 0xbf, 0xb6, 0x27, 0x8b, 0xa6, 0xda, 0x8f, 0x9b, 0xaf, 0x00, 0x00, + 0x00, 0xff, 0xff, 0xa8, 0xe2, 0x28, 0xc0, 0x3d, 0x03, 0x00, 0x00, } diff --git a/vendor/github.com/hashicorp/vault/sdk/logical/identity.proto b/vendor/github.com/hashicorp/vault/sdk/logical/identity.proto index 34af579b4d7a9..78c3758f8510f 100644 --- a/vendor/github.com/hashicorp/vault/sdk/logical/identity.proto +++ b/vendor/github.com/hashicorp/vault/sdk/logical/identity.proto @@ -36,7 +36,12 @@ message Alias { // Name is the identifier of this identity in its authentication source string name = 3; - // Metadata represents the custom data tied to this alias + // Metadata represents the custom data tied to this alias. Fields added + // to it should have a low rate of change (or no change) because each + // change incurs a storage write, so quickly-changing fields can have + // a significant performance impact at scale. See the SDK's + // "aliasmetadata" package for a helper that eases and standardizes + // using this safely. map metadata = 4; // ID is the unique identifier for the alias From 3c2ed56cc59d832cde197f1a0ad21dd18545ad38 Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Tue, 21 Apr 2020 13:37:29 -0700 Subject: [PATCH 02/18] split into ec2_metadata and iam_metadata fields --- .../credential/aws/path_config_identity.go | 73 ++++++++++++------- builtin/credential/aws/path_login.go | 10 ++- sdk/helper/aliasmetadata/alias_metadata.go | 11 +-- .../helper/aliasmetadata/alias_metadata.go | 11 +-- 4 files changed, 65 insertions(+), 40 deletions(-) diff --git a/builtin/credential/aws/path_config_identity.go b/builtin/credential/aws/path_config_identity.go index 3c94184341e66..b271b1c1202fd 100644 --- a/builtin/credential/aws/path_config_identity.go +++ b/builtin/credential/aws/path_config_identity.go @@ -10,28 +10,40 @@ import ( "github.com/hashicorp/vault/sdk/logical" ) -// aliasMetadataFields is a list of the default alias metadata +// iamAliasMetadataFields is a list of the default alias metadata // added to tokens during login. The default alias type used // by this back-end is the role ID. Subsequently, the default // fields included are expected to have a low rate of change // when the role ID is in use. -var aliasMetadataFields = &aliasmetadata.Fields{ - Default: []string{ - "account_id", - "auth_type", - }, - AvailableToAdd: []string{ - "ami_id", - "canonical_arn", - "client_arn", - "client_user_id", - "inferred_aws_region", - "inferred_entity_id", - "inferred_entity_type", - "instance_id", - "region", - }, -} +var ( + iamAliasMetadataFields = &aliasmetadata.Fields{ + FieldName: "iam_metadata", + Default: []string{ + "account_id", + "auth_type", + }, + AvailableToAdd: []string{ + "canonical_arn", + "client_arn", + "client_user_id", + "inferred_aws_region", + "inferred_entity_id", + "inferred_entity_type", + }, + } + + ec2AliasMetadataFields = &aliasmetadata.Fields{ + FieldName: "ec2_metadata", + Default: []string{ + "account_id", + }, + AvailableToAdd: []string{ + "ami_id", + "instance_id", + "region", + }, + } +) func (b *backend) pathConfigIdentity() *framework.Path { return &framework.Path{ @@ -47,7 +59,8 @@ func (b *backend) pathConfigIdentity() *framework.Path { Default: identityAliasEC2InstanceID, Description: fmt.Sprintf("Configure how the AWS auth method generates entity alias when using EC2 auth. Valid values are %q, %q, and %q. Defaults to %q.", identityAliasRoleID, identityAliasEC2InstanceID, identityAliasEC2ImageID, identityAliasRoleID), }, - aliasmetadata.FieldName: aliasmetadata.FieldSchema(aliasMetadataFields), + iamAliasMetadataFields.FieldName: aliasmetadata.FieldSchema(iamAliasMetadataFields), + ec2AliasMetadataFields.FieldName: aliasmetadata.FieldSchema(ec2AliasMetadataFields), }, Operations: map[logical.Operation]framework.OperationHandler{ @@ -71,7 +84,8 @@ func identityConfigEntry(ctx context.Context, s logical.Storage) (*identityConfi } entry := &identityConfig{ - Handler: aliasmetadata.NewHandler(aliasMetadataFields), + IAMAliasMetadataHandler: aliasmetadata.NewHandler(iamAliasMetadataFields), + EC2AliasMetadataHandler: aliasmetadata.NewHandler(ec2AliasMetadataFields), } if entryRaw != nil { if err := entryRaw.DecodeJSON(entry); err != nil { @@ -98,9 +112,10 @@ func pathConfigIdentityRead(ctx context.Context, req *logical.Request, _ *framew return &logical.Response{ Data: map[string]interface{}{ - "iam_alias": config.IAMAlias, - "ec2_alias": config.EC2Alias, - aliasmetadata.FieldName: config.GetAliasMetadata(), + "iam_alias": config.IAMAlias, + iamAliasMetadataFields.FieldName: config.IAMAliasMetadataHandler.GetAliasMetadata(), + "ec2_alias": config.EC2Alias, + ec2AliasMetadataFields.FieldName: config.EC2AliasMetadataHandler.GetAliasMetadata(), }, }, nil } @@ -130,7 +145,10 @@ func pathConfigIdentityUpdate(ctx context.Context, req *logical.Request, data *f } config.EC2Alias = ec2Alias } - if err := config.ParseAliasMetadata(data); err != nil { + if err := config.IAMAliasMetadataHandler.ParseAliasMetadata(data); err != nil { + return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest + } + if err := config.EC2AliasMetadataHandler.ParseAliasMetadata(data); err != nil { return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest } @@ -148,9 +166,10 @@ func pathConfigIdentityUpdate(ctx context.Context, req *logical.Request, data *f } type identityConfig struct { - IAMAlias string `json:"iam_alias"` - EC2Alias string `json:"ec2_alias"` - aliasmetadata.Handler `json:"alias_metadata_handler"` + IAMAlias string `json:"iam_alias"` + IAMAliasMetadataHandler aliasmetadata.Handler `json:"iam_alias_metadata_handler"` + EC2Alias string `json:"ec2_alias"` + EC2AliasMetadataHandler aliasmetadata.Handler `json:"ec2_alias_metadata_handler"` } const identityAliasIAMUniqueID = "unique_id" diff --git a/builtin/credential/aws/path_login.go b/builtin/credential/aws/path_login.go index b9c31dfb19e45..cb98c5b8a06a8 100644 --- a/builtin/credential/aws/path_login.go +++ b/builtin/credential/aws/path_login.go @@ -848,12 +848,16 @@ func (b *backend) pathLoginUpdateEc2(ctx context.Context, req *logical.Request, }, } roleEntry.PopulateTokenAuth(auth) - identityConfigEntry.PopulateDesiredAliasMetadata(auth, map[string]string{ + if err := identityConfigEntry.EC2AliasMetadataHandler.PopulateDesiredAliasMetadata(auth, map[string]string{ "instance_id": identityDocParsed.InstanceID, "region": identityDocParsed.Region, "account_id": identityDocParsed.AccountID, "ami_id": identityDocParsed.AmiID, - }) + }); err != nil { + if b.Logger().IsWarn() { + b.Logger().Warn(fmt.Sprintf("unable to set alias metadata due to %s", err)) + } + } resp := &logical.Response{ Auth: auth, @@ -1378,7 +1382,7 @@ func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request, }, } roleEntry.PopulateTokenAuth(auth) - if err := identityConfigEntry.PopulateDesiredAliasMetadata(auth, map[string]string{ + if err := identityConfigEntry.IAMAliasMetadataHandler.PopulateDesiredAliasMetadata(auth, map[string]string{ "client_arn": callerID.Arn, "canonical_arn": entity.canonicalArn(), "client_user_id": callerUniqueId, diff --git a/sdk/helper/aliasmetadata/alias_metadata.go b/sdk/helper/aliasmetadata/alias_metadata.go index 1b9cf1cc29aef..56bfa3b00076b 100644 --- a/sdk/helper/aliasmetadata/alias_metadata.go +++ b/sdk/helper/aliasmetadata/alias_metadata.go @@ -24,14 +24,15 @@ import ( "github.com/hashicorp/vault/sdk/logical" ) -// FieldName is the user-facing name for the field. -const FieldName = "alias_metadata" - // Fields is for configuring a back-end's available // default and additional fields. These are used for // providing a verbose field description, and for parsing // user input. type Fields struct { + // The field name as it'll be reflected in the user-facing + // schema. + FieldName string + // Default is a list of the default fields that should // be included if a user sends "default" in their list // of desired fields. These fields should all have a @@ -56,7 +57,7 @@ func FieldSchema(fields *Fields) *framework.FieldSchema { Type: framework.TypeCommaStringSlice, Description: description(fields), DisplayAttrs: &framework.DisplayAttributes{ - Name: FieldName, + Name: fields.FieldName, Value: "default,field1,field2", }, Default: []string{"default"}, @@ -108,7 +109,7 @@ func (h *handler) GetAliasMetadata() []string { // converts it to a list of explicit fields, and adds it to the handler // for later storage. func (h *handler) ParseAliasMetadata(data *framework.FieldData) error { - userProvided, ok := data.GetOk(FieldName) + userProvided, ok := data.GetOk(h.fields.FieldName) if !ok { // Nothing further to do here. return nil diff --git a/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go b/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go index 1b9cf1cc29aef..56bfa3b00076b 100644 --- a/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go +++ b/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go @@ -24,14 +24,15 @@ import ( "github.com/hashicorp/vault/sdk/logical" ) -// FieldName is the user-facing name for the field. -const FieldName = "alias_metadata" - // Fields is for configuring a back-end's available // default and additional fields. These are used for // providing a verbose field description, and for parsing // user input. type Fields struct { + // The field name as it'll be reflected in the user-facing + // schema. + FieldName string + // Default is a list of the default fields that should // be included if a user sends "default" in their list // of desired fields. These fields should all have a @@ -56,7 +57,7 @@ func FieldSchema(fields *Fields) *framework.FieldSchema { Type: framework.TypeCommaStringSlice, Description: description(fields), DisplayAttrs: &framework.DisplayAttributes{ - Name: FieldName, + Name: fields.FieldName, Value: "default,field1,field2", }, Default: []string{"default"}, @@ -108,7 +109,7 @@ func (h *handler) GetAliasMetadata() []string { // converts it to a list of explicit fields, and adds it to the handler // for later storage. func (h *handler) ParseAliasMetadata(data *framework.FieldData) error { - userProvided, ok := data.GetOk(FieldName) + userProvided, ok := data.GetOk(h.fields.FieldName) if !ok { // Nothing further to do here. return nil From d4bfe709d7cceb68caa8e016b8a20bf70318b44a Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Tue, 21 Apr 2020 13:50:25 -0700 Subject: [PATCH 03/18] fix tests --- .../credential/aws/path_config_identity.go | 17 +++++++----- builtin/credential/aws/path_login_test.go | 13 ++++----- .../aliasmetadata/alias_metadata_acc_test.go | 27 ++++++++++--------- .../aliasmetadata/alias_metadata_test.go | 13 ++++----- 4 files changed, 39 insertions(+), 31 deletions(-) diff --git a/builtin/credential/aws/path_config_identity.go b/builtin/credential/aws/path_config_identity.go index b271b1c1202fd..a932a2d1a2084 100644 --- a/builtin/credential/aws/path_config_identity.go +++ b/builtin/credential/aws/path_config_identity.go @@ -10,12 +10,12 @@ import ( "github.com/hashicorp/vault/sdk/logical" ) -// iamAliasMetadataFields is a list of the default alias metadata -// added to tokens during login. The default alias type used -// by this back-end is the role ID. Subsequently, the default -// fields included are expected to have a low rate of change -// when the role ID is in use. var ( + // iamAliasMetadataFields is a list of the default alias metadata + // added to tokens during login. The default alias type used + // by this back-end is the role ID. Subsequently, the default + // fields included are expected to have a low rate of change + // when the role ID is in use. iamAliasMetadataFields = &aliasmetadata.Fields{ FieldName: "iam_metadata", Default: []string{ @@ -32,6 +32,11 @@ var ( }, } + // ec2AliasMetadataFields is a list of the default alias metadata + // added to tokens during login. The default alias type used + // by this back-end is the role ID. Subsequently, the default + // fields included are expected to have a low rate of change + // when the role ID is in use. ec2AliasMetadataFields = &aliasmetadata.Fields{ FieldName: "ec2_metadata", Default: []string{ @@ -54,12 +59,12 @@ func (b *backend) pathConfigIdentity() *framework.Path { Default: identityAliasIAMUniqueID, Description: fmt.Sprintf("Configure how the AWS auth method generates entity aliases when using IAM auth. Valid values are %q, %q, and %q. Defaults to %q.", identityAliasRoleID, identityAliasIAMUniqueID, identityAliasIAMFullArn, identityAliasRoleID), }, + iamAliasMetadataFields.FieldName: aliasmetadata.FieldSchema(iamAliasMetadataFields), "ec2_alias": { Type: framework.TypeString, Default: identityAliasEC2InstanceID, Description: fmt.Sprintf("Configure how the AWS auth method generates entity alias when using EC2 auth. Valid values are %q, %q, and %q. Defaults to %q.", identityAliasRoleID, identityAliasEC2InstanceID, identityAliasEC2ImageID, identityAliasRoleID), }, - iamAliasMetadataFields.FieldName: aliasmetadata.FieldSchema(iamAliasMetadataFields), ec2AliasMetadataFields.FieldName: aliasmetadata.FieldSchema(ec2AliasMetadataFields), }, diff --git a/builtin/credential/aws/path_login_test.go b/builtin/credential/aws/path_login_test.go index e204f6afc1d2a..743aba8f361b3 100644 --- a/builtin/credential/aws/path_login_test.go +++ b/builtin/credential/aws/path_login_test.go @@ -11,8 +11,6 @@ import ( "strings" "testing" - "github.com/hashicorp/vault/sdk/helper/aliasmetadata" - "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/sts" "github.com/hashicorp/vault/sdk/logical" @@ -222,20 +220,23 @@ func TestBackend_pathLogin_IAMHeaders(t *testing.T) { Path: "config/identity", Storage: storage, Data: map[string]interface{}{ - aliasmetadata.FieldName: []string{ + "iam_alias": "role_id", + "iam_metadata": []string{ "default", - "ami_id", "canonical_arn", "client_arn", "client_user_id", "inferred_aws_region", "inferred_entity_id", "inferred_entity_type", + }, + "ec2_alias": "role_id", + "ec2_metadata": []string{ + "default", + "ami_id", "instance_id", "region", }, - "iam_alias": "role_id", - "ec2_alias": "role_id", }, }) if err != nil { diff --git a/sdk/helper/aliasmetadata/alias_metadata_acc_test.go b/sdk/helper/aliasmetadata/alias_metadata_acc_test.go index 26ea1342186c3..a5b9eef8577e5 100644 --- a/sdk/helper/aliasmetadata/alias_metadata_acc_test.go +++ b/sdk/helper/aliasmetadata/alias_metadata_acc_test.go @@ -47,7 +47,7 @@ func TestAcceptance(t *testing.T) { if resp == nil || resp.Data == nil { t.Fatal("expected non-nil response") } - if !reflect.DeepEqual(resp.Data[FieldName], []string{"role_name"}) { + if !reflect.DeepEqual(resp.Data[aliasMetadataFields.FieldName], []string{"role_name"}) { t.Fatal("expected default field of role_name to be returned") } @@ -86,7 +86,7 @@ func TestAcceptance(t *testing.T) { RemoteAddr: "http://foo.com", }, Data: map[string]interface{}{ - FieldName: []string{}, + aliasMetadataFields.FieldName: []string{}, }, }) if err != nil { @@ -111,7 +111,7 @@ func TestAcceptance(t *testing.T) { if resp == nil || resp.Data == nil { t.Fatal("expected non-nil response") } - if !reflect.DeepEqual(resp.Data[FieldName], []string{}) { + if !reflect.DeepEqual(resp.Data[aliasMetadataFields.FieldName], []string{}) { t.Fatal("expected no fields to be returned") } @@ -147,7 +147,7 @@ func TestAcceptance(t *testing.T) { RemoteAddr: "http://foo.com", }, Data: map[string]interface{}{ - FieldName: []string{"default"}, + aliasMetadataFields.FieldName: []string{"default"}, }, }) if err != nil { @@ -172,7 +172,7 @@ func TestAcceptance(t *testing.T) { if resp == nil || resp.Data == nil { t.Fatal("expected non-nil response") } - if !reflect.DeepEqual(resp.Data[FieldName], []string{"role_name"}) { + if !reflect.DeepEqual(resp.Data[aliasMetadataFields.FieldName], []string{"role_name"}) { t.Fatal("expected default field of role_name to be returned") } @@ -210,7 +210,7 @@ func TestAcceptance(t *testing.T) { RemoteAddr: "http://foo.com", }, Data: map[string]interface{}{ - FieldName: []string{"default", "remote_addr"}, + aliasMetadataFields.FieldName: []string{"default", "remote_addr"}, }, }) if err != nil { @@ -238,10 +238,10 @@ func TestAcceptance(t *testing.T) { } expected := []string{"role_name", "remote_addr"} sort.Strings(expected) - actual := resp.Data[FieldName].([]string) + actual := resp.Data[aliasMetadataFields.FieldName].([]string) sort.Strings(actual) if !reflect.DeepEqual(expected, actual) { - t.Fatalf("unexpectedly received %s", resp.Data[FieldName]) + t.Fatalf("unexpectedly received %s", resp.Data[aliasMetadataFields.FieldName]) } // They both should now appear on the login. @@ -281,7 +281,7 @@ func TestAcceptance(t *testing.T) { RemoteAddr: "http://foo.com", }, Data: map[string]interface{}{ - FieldName: []string{"remote_addr"}, + aliasMetadataFields.FieldName: []string{"remote_addr"}, }, }) if err != nil { @@ -306,7 +306,7 @@ func TestAcceptance(t *testing.T) { if resp == nil || resp.Data == nil { t.Fatal("expected non-nil response") } - if !reflect.DeepEqual(resp.Data[FieldName], []string{"remote_addr"}) { + if !reflect.DeepEqual(resp.Data[aliasMetadataFields.FieldName], []string{"remote_addr"}) { t.Fatal("expected remote_addr to be returned") } @@ -345,7 +345,7 @@ func TestAcceptance(t *testing.T) { RemoteAddr: "http://foo.com", }, Data: map[string]interface{}{ - FieldName: []string{"asl;dfkj"}, + aliasMetadataFields.FieldName: []string{"asl;dfkj"}, }, }) if err == nil { @@ -376,6 +376,7 @@ type fakeBackend struct { // We expect each back-end to explicitly define the fields that // will be included by default, and optionally available. var aliasMetadataFields = &Fields{ + FieldName: "some_field_name", Default: []string{ "role_name", // This would likely never change because the alias is the role name. }, @@ -388,7 +389,7 @@ func configPath() *framework.Path { return &framework.Path{ Pattern: "config", Fields: map[string]*framework.FieldSchema{ - FieldName: FieldSchema(aliasMetadataFields), + aliasMetadataFields.FieldName: FieldSchema(aliasMetadataFields), }, Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ @@ -410,7 +411,7 @@ func configPath() *framework.Path { // alias metadata is when unconfigured. return &logical.Response{ Data: map[string]interface{}{ - FieldName: conf.GetAliasMetadata(), + aliasMetadataFields.FieldName: conf.GetAliasMetadata(), }, }, nil }, diff --git a/sdk/helper/aliasmetadata/alias_metadata_test.go b/sdk/helper/aliasmetadata/alias_metadata_test.go index df23e80bd1e94..efc094b67b582 100644 --- a/sdk/helper/aliasmetadata/alias_metadata_test.go +++ b/sdk/helper/aliasmetadata/alias_metadata_test.go @@ -10,6 +10,7 @@ import ( ) var testFields = &Fields{ + FieldName: "some-field-name", Default: []string{"fizz", "buzz"}, AvailableToAdd: []string{"foo", "bar"}, } @@ -25,8 +26,8 @@ func TestFieldSchema(t *testing.T) { if schema.DisplayAttrs == nil { t.Fatal("expected display attributes") } - if schema.DisplayAttrs.Name != FieldName { - t.Fatalf("expected name of %s", FieldName) + if schema.DisplayAttrs.Name != testFields.FieldName { + t.Fatalf("expected name of %s", testFields.FieldName) } if schema.DisplayAttrs.Value != "default,field1,field2" { t.Fatal("expected default,field1,field2") @@ -51,10 +52,10 @@ func TestParseAliasMetadata(t *testing.T) { h := NewHandler(testFields) data := &framework.FieldData{ Raw: map[string]interface{}{ - FieldName: []string{"default"}, + testFields.FieldName: []string{"default"}, }, Schema: map[string]*framework.FieldSchema{ - FieldName: FieldSchema(testFields), + testFields.FieldName: FieldSchema(testFields), }, } if err := h.ParseAliasMetadata(data); err != nil { @@ -73,10 +74,10 @@ func TestPopulateDesiredAliasMetadata(t *testing.T) { h := NewHandler(testFields) data := &framework.FieldData{ Raw: map[string]interface{}{ - FieldName: []string{"foo"}, + testFields.FieldName: []string{"foo"}, }, Schema: map[string]*framework.FieldSchema{ - FieldName: FieldSchema(testFields), + testFields.FieldName: FieldSchema(testFields), }, } if err := h.ParseAliasMetadata(data); err != nil { From 5507b57b88bcfc0b87b23d43d732191ef9ce9cef Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Wed, 22 Apr 2020 10:50:17 -0700 Subject: [PATCH 04/18] strip pointer --- sdk/helper/aliasmetadata/alias_metadata.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/helper/aliasmetadata/alias_metadata.go b/sdk/helper/aliasmetadata/alias_metadata.go index 56bfa3b00076b..0f48ee3261930 100644 --- a/sdk/helper/aliasmetadata/alias_metadata.go +++ b/sdk/helper/aliasmetadata/alias_metadata.go @@ -85,7 +85,7 @@ type handler struct { // include the "default" parameter, and instead includes the actual // fields behind "default", if selected. If it has never been set, // the pointer will be nil. - AliasMetadata *[]string `json:"alias_metadata"` + AliasMetadata []string `json:"alias_metadata"` // fields is a list of the configured default and available // fields. It's intentionally not jsonified. @@ -101,7 +101,7 @@ func (h *handler) GetAliasMetadata() []string { if h.AliasMetadata == nil { return h.fields.Default } - return *h.AliasMetadata + return h.AliasMetadata } // ParseAliasMetadata is intended to be used on config create/update. @@ -144,7 +144,7 @@ func (h *handler) ParseAliasMetadata(data *framework.FieldData) error { // Fulfilling the pointer here flags that the user has made // an explicit selection so we shouldn't just fall back to // our defaults. - h.AliasMetadata = &aliasMetadata + h.AliasMetadata = aliasMetadata return nil } @@ -169,7 +169,7 @@ func (h *handler) PopulateDesiredAliasMetadata(auth *logical.Auth, available map } fieldsToInclude := h.fields.Default if h.AliasMetadata != nil { - fieldsToInclude = *h.AliasMetadata + fieldsToInclude = h.AliasMetadata } for availableField, itsValue := range available { if strutil.StrListContains(fieldsToInclude, availableField) { From 343c97abd59ab0b59643ada2d9e315b1e4d656c8 Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Wed, 22 Apr 2020 11:02:04 -0700 Subject: [PATCH 05/18] add test of default metadata --- builtin/credential/aws/path_login_test.go | 272 ++++++++++++++++++++++ 1 file changed, 272 insertions(+) diff --git a/builtin/credential/aws/path_login_test.go b/builtin/credential/aws/path_login_test.go index 743aba8f361b3..04e5a62ccda52 100644 --- a/builtin/credential/aws/path_login_test.go +++ b/builtin/credential/aws/path_login_test.go @@ -376,6 +376,278 @@ func TestBackend_pathLogin_IAMHeaders(t *testing.T) { } } +func TestBackend_defaultAliasMetadataIAM(t *testing.T) { + storage := &logical.InmemStorage{} + config := logical.TestBackendConfig() + config.StorageView = storage + b, err := Backend(config) + if err != nil { + t.Fatal(err) + } + + err = b.Setup(context.Background(), config) + if err != nil { + t.Fatal(err) + } + + // sets up a test server to stand in for STS service + ts := setupIAMTestServer() + defer ts.Close() + + clientConfigData := map[string]interface{}{ + "iam_server_id_header_value": testVaultHeaderValue, + "sts_endpoint": ts.URL, + } + clientRequest := &logical.Request{ + Operation: logical.UpdateOperation, + Path: "config/client", + Storage: storage, + Data: clientConfigData, + } + _, err = b.HandleRequest(context.Background(), clientRequest) + if err != nil { + t.Fatal(err) + } + + // Configure identity. + _, err = b.HandleRequest(context.Background(), &logical.Request{ + Operation: logical.UpdateOperation, + Path: "config/identity", + Storage: storage, + Data: map[string]interface{}{ + "iam_alias": "role_id", + "ec2_alias": "role_id", + }, + }) + if err != nil { + t.Fatal(err) + } + + // create a role entry + roleEntry := &awsRoleEntry{ + RoleID: "foo", + Version: currentRoleStorageVersion, + AuthType: iamAuthType, + } + + if err := b.setRole(context.Background(), storage, testValidRoleName, roleEntry); err != nil { + t.Fatalf("failed to set entry: %s", err) + } + + // create a baseline loginData map structure, including iam_request_headers + // already base64encoded. This is the "Default" loginData used for all tests. + // Each sub test can override the map's iam_request_headers entry + loginData, err := defaultLoginData() + if err != nil { + t.Fatal(err) + } + + expectedAliasMetadata := map[string]string{ + "account_id": "123456789012", + "auth_type": "iam", + } + + testCases := []struct { + Name string + Header interface{} + ExpectErr error + }{ + { + Name: "Default", + }, + { + Name: "Map-complete", + Header: map[string]interface{}{ + "Content-Length": "43", + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + "User-Agent": "aws-sdk-go/1.14.24 (go1.11; darwin; amd64)", + "X-Amz-Date": "20180910T203328Z", + "X-Vault-Aws-Iam-Server-Id": "VaultAcceptanceTesting", + "Authorization": "AWS4-HMAC-SHA256 Credential=AKIAJPQ466AIIQW4LPSQ/20180910/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=cdef5819b2e97f1ff0f3e898fd2621aa03af00a4ec3e019122c20e5482534bf4", + }, + }, + { + Name: "JSON-complete", + Header: `{ + "Content-Length":"43", + "Content-Type":"application/x-www-form-urlencoded; charset=utf-8", + "User-Agent":"aws-sdk-go/1.14.24 (go1.11; darwin; amd64)", + "X-Amz-Date":"20180910T203328Z", + "X-Vault-Aws-Iam-Server-Id": "VaultAcceptanceTesting", + "Authorization":"AWS4-HMAC-SHA256 Credential=AKIAJPQ466AIIQW4LPSQ/20180910/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=cdef5819b2e97f1ff0f3e898fd2621aa03af00a4ec3e019122c20e5482534bf4" + }`, + }, + { + Name: "Base64-complete", + Header: base64Complete(), + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + if tc.Header != nil { + loginData["iam_request_headers"] = tc.Header + } + + loginRequest := &logical.Request{ + Operation: logical.UpdateOperation, + Path: "login", + Storage: storage, + Data: loginData, + Connection: &logical.Connection{}, + } + + resp, err := b.HandleRequest(context.Background(), loginRequest) + if err != nil || resp == nil || resp.IsError() { + if tc.ExpectErr != nil && tc.ExpectErr.Error() == resp.Error().Error() { + return + } + t.Errorf("un expected failed login:\nresp: %#v\n\nerr: %v", resp, err) + } + + if !reflect.DeepEqual(expectedAliasMetadata, resp.Auth.Alias.Metadata) { + t.Errorf("expected metadata (%#v) to match (%#v)", expectedAliasMetadata, resp.Auth.Alias.Metadata) + } + }) + } +} + +func TestBackend_defaultAliasMetadata(t *testing.T) { + storage := &logical.InmemStorage{} + config := logical.TestBackendConfig() + config.StorageView = storage + b, err := Backend(config) + if err != nil { + t.Fatal(err) + } + + err = b.Setup(context.Background(), config) + if err != nil { + t.Fatal(err) + } + + // sets up a test server to stand in for STS service + ts := setupIAMTestServer() + defer ts.Close() + + clientConfigData := map[string]interface{}{ + "iam_server_id_header_value": testVaultHeaderValue, + "sts_endpoint": ts.URL, + } + clientRequest := &logical.Request{ + Operation: logical.UpdateOperation, + Path: "config/client", + Storage: storage, + Data: clientConfigData, + } + _, err = b.HandleRequest(context.Background(), clientRequest) + if err != nil { + t.Fatal(err) + } + + // Configure identity. + _, err = b.HandleRequest(context.Background(), &logical.Request{ + Operation: logical.UpdateOperation, + Path: "config/identity", + Storage: storage, + Data: map[string]interface{}{ + "iam_alias": "role_id", + "ec2_alias": "role_id", + }, + }) + if err != nil { + t.Fatal(err) + } + + // create a role entry + roleEntry := &awsRoleEntry{ + RoleID: "foo", + Version: currentRoleStorageVersion, + AuthType: iamAuthType, + } + + if err := b.setRole(context.Background(), storage, testValidRoleName, roleEntry); err != nil { + t.Fatalf("failed to set entry: %s", err) + } + + // create a baseline loginData map structure, including iam_request_headers + // already base64encoded. This is the "Default" loginData used for all tests. + // Each sub test can override the map's iam_request_headers entry + loginData, err := defaultLoginData() + if err != nil { + t.Fatal(err) + } + + expectedAliasMetadata := map[string]string{ + "account_id": "123456789012", + "auth_type": "iam", + } + + testCases := []struct { + Name string + Header interface{} + ExpectErr error + }{ + { + Name: "Default", + }, + { + Name: "Map-complete", + Header: map[string]interface{}{ + "Content-Length": "43", + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + "User-Agent": "aws-sdk-go/1.14.24 (go1.11; darwin; amd64)", + "X-Amz-Date": "20180910T203328Z", + "X-Vault-Aws-Iam-Server-Id": "VaultAcceptanceTesting", + "Authorization": "AWS4-HMAC-SHA256 Credential=AKIAJPQ466AIIQW4LPSQ/20180910/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=cdef5819b2e97f1ff0f3e898fd2621aa03af00a4ec3e019122c20e5482534bf4", + }, + }, + { + Name: "JSON-complete", + Header: `{ + "Content-Length":"43", + "Content-Type":"application/x-www-form-urlencoded; charset=utf-8", + "User-Agent":"aws-sdk-go/1.14.24 (go1.11; darwin; amd64)", + "X-Amz-Date":"20180910T203328Z", + "X-Vault-Aws-Iam-Server-Id": "VaultAcceptanceTesting", + "Authorization":"AWS4-HMAC-SHA256 Credential=AKIAJPQ466AIIQW4LPSQ/20180910/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=cdef5819b2e97f1ff0f3e898fd2621aa03af00a4ec3e019122c20e5482534bf4" + }`, + }, + { + Name: "Base64-complete", + Header: base64Complete(), + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + if tc.Header != nil { + loginData["iam_request_headers"] = tc.Header + } + + loginRequest := &logical.Request{ + Operation: logical.UpdateOperation, + Path: "login", + Storage: storage, + Data: loginData, + Connection: &logical.Connection{}, + } + + resp, err := b.HandleRequest(context.Background(), loginRequest) + if err != nil || resp == nil || resp.IsError() { + if tc.ExpectErr != nil && tc.ExpectErr.Error() == resp.Error().Error() { + return + } + t.Errorf("un expected failed login:\nresp: %#v\n\nerr: %v", resp, err) + } + + if !reflect.DeepEqual(expectedAliasMetadata, resp.Auth.Alias.Metadata) { + t.Errorf("expected metadata (%#v) to match (%#v)", expectedAliasMetadata, resp.Auth.Alias.Metadata) + } + }) + } +} + func defaultLoginData() (map[string]interface{}, error) { awsSession, err := session.NewSession() if err != nil { From 1f70a5e9d97208bbf0496ecbbbfef5b51bb0ccf0 Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Wed, 22 Apr 2020 11:04:28 -0700 Subject: [PATCH 06/18] more test <3 --- builtin/credential/aws/path_login_test.go | 136 ---------------------- 1 file changed, 136 deletions(-) diff --git a/builtin/credential/aws/path_login_test.go b/builtin/credential/aws/path_login_test.go index 04e5a62ccda52..2f0c0529f5f2b 100644 --- a/builtin/credential/aws/path_login_test.go +++ b/builtin/credential/aws/path_login_test.go @@ -376,142 +376,6 @@ func TestBackend_pathLogin_IAMHeaders(t *testing.T) { } } -func TestBackend_defaultAliasMetadataIAM(t *testing.T) { - storage := &logical.InmemStorage{} - config := logical.TestBackendConfig() - config.StorageView = storage - b, err := Backend(config) - if err != nil { - t.Fatal(err) - } - - err = b.Setup(context.Background(), config) - if err != nil { - t.Fatal(err) - } - - // sets up a test server to stand in for STS service - ts := setupIAMTestServer() - defer ts.Close() - - clientConfigData := map[string]interface{}{ - "iam_server_id_header_value": testVaultHeaderValue, - "sts_endpoint": ts.URL, - } - clientRequest := &logical.Request{ - Operation: logical.UpdateOperation, - Path: "config/client", - Storage: storage, - Data: clientConfigData, - } - _, err = b.HandleRequest(context.Background(), clientRequest) - if err != nil { - t.Fatal(err) - } - - // Configure identity. - _, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "config/identity", - Storage: storage, - Data: map[string]interface{}{ - "iam_alias": "role_id", - "ec2_alias": "role_id", - }, - }) - if err != nil { - t.Fatal(err) - } - - // create a role entry - roleEntry := &awsRoleEntry{ - RoleID: "foo", - Version: currentRoleStorageVersion, - AuthType: iamAuthType, - } - - if err := b.setRole(context.Background(), storage, testValidRoleName, roleEntry); err != nil { - t.Fatalf("failed to set entry: %s", err) - } - - // create a baseline loginData map structure, including iam_request_headers - // already base64encoded. This is the "Default" loginData used for all tests. - // Each sub test can override the map's iam_request_headers entry - loginData, err := defaultLoginData() - if err != nil { - t.Fatal(err) - } - - expectedAliasMetadata := map[string]string{ - "account_id": "123456789012", - "auth_type": "iam", - } - - testCases := []struct { - Name string - Header interface{} - ExpectErr error - }{ - { - Name: "Default", - }, - { - Name: "Map-complete", - Header: map[string]interface{}{ - "Content-Length": "43", - "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", - "User-Agent": "aws-sdk-go/1.14.24 (go1.11; darwin; amd64)", - "X-Amz-Date": "20180910T203328Z", - "X-Vault-Aws-Iam-Server-Id": "VaultAcceptanceTesting", - "Authorization": "AWS4-HMAC-SHA256 Credential=AKIAJPQ466AIIQW4LPSQ/20180910/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=cdef5819b2e97f1ff0f3e898fd2621aa03af00a4ec3e019122c20e5482534bf4", - }, - }, - { - Name: "JSON-complete", - Header: `{ - "Content-Length":"43", - "Content-Type":"application/x-www-form-urlencoded; charset=utf-8", - "User-Agent":"aws-sdk-go/1.14.24 (go1.11; darwin; amd64)", - "X-Amz-Date":"20180910T203328Z", - "X-Vault-Aws-Iam-Server-Id": "VaultAcceptanceTesting", - "Authorization":"AWS4-HMAC-SHA256 Credential=AKIAJPQ466AIIQW4LPSQ/20180910/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=cdef5819b2e97f1ff0f3e898fd2621aa03af00a4ec3e019122c20e5482534bf4" - }`, - }, - { - Name: "Base64-complete", - Header: base64Complete(), - }, - } - - for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - if tc.Header != nil { - loginData["iam_request_headers"] = tc.Header - } - - loginRequest := &logical.Request{ - Operation: logical.UpdateOperation, - Path: "login", - Storage: storage, - Data: loginData, - Connection: &logical.Connection{}, - } - - resp, err := b.HandleRequest(context.Background(), loginRequest) - if err != nil || resp == nil || resp.IsError() { - if tc.ExpectErr != nil && tc.ExpectErr.Error() == resp.Error().Error() { - return - } - t.Errorf("un expected failed login:\nresp: %#v\n\nerr: %v", resp, err) - } - - if !reflect.DeepEqual(expectedAliasMetadata, resp.Auth.Alias.Metadata) { - t.Errorf("expected metadata (%#v) to match (%#v)", expectedAliasMetadata, resp.Auth.Alias.Metadata) - } - }) - } -} - func TestBackend_defaultAliasMetadata(t *testing.T) { storage := &logical.InmemStorage{} config := logical.TestBackendConfig() From f180042327b68ba3c6971e098478317348902aef Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Wed, 22 Apr 2020 16:56:11 -0700 Subject: [PATCH 07/18] switch from interface to custom marshallers --- .../credential/aws/path_config_identity.go | 12 ++-- sdk/helper/aliasmetadata/alias_metadata.go | 61 +++++++++++-------- .../aliasmetadata/alias_metadata_acc_test.go | 6 +- .../aliasmetadata/alias_metadata_test.go | 4 +- .../helper/aliasmetadata/alias_metadata.go | 61 +++++++++++-------- 5 files changed, 85 insertions(+), 59 deletions(-) diff --git a/builtin/credential/aws/path_config_identity.go b/builtin/credential/aws/path_config_identity.go index a932a2d1a2084..fdaac0b16dd93 100644 --- a/builtin/credential/aws/path_config_identity.go +++ b/builtin/credential/aws/path_config_identity.go @@ -118,9 +118,9 @@ func pathConfigIdentityRead(ctx context.Context, req *logical.Request, _ *framew return &logical.Response{ Data: map[string]interface{}{ "iam_alias": config.IAMAlias, - iamAliasMetadataFields.FieldName: config.IAMAliasMetadataHandler.GetAliasMetadata(), + iamAliasMetadataFields.FieldName: config.IAMAliasMetadataHandler.AliasMetadata(), "ec2_alias": config.EC2Alias, - ec2AliasMetadataFields.FieldName: config.EC2AliasMetadataHandler.GetAliasMetadata(), + ec2AliasMetadataFields.FieldName: config.EC2AliasMetadataHandler.AliasMetadata(), }, }, nil } @@ -171,10 +171,10 @@ func pathConfigIdentityUpdate(ctx context.Context, req *logical.Request, data *f } type identityConfig struct { - IAMAlias string `json:"iam_alias"` - IAMAliasMetadataHandler aliasmetadata.Handler `json:"iam_alias_metadata_handler"` - EC2Alias string `json:"ec2_alias"` - EC2AliasMetadataHandler aliasmetadata.Handler `json:"ec2_alias_metadata_handler"` + IAMAlias string `json:"iam_alias"` + IAMAliasMetadataHandler *aliasmetadata.Handler `json:"iam_alias_metadata_handler"` + EC2Alias string `json:"ec2_alias"` + EC2AliasMetadataHandler *aliasmetadata.Handler `json:"ec2_alias_metadata_handler"` } const identityAliasIAMUniqueID = "unique_id" diff --git a/sdk/helper/aliasmetadata/alias_metadata.go b/sdk/helper/aliasmetadata/alias_metadata.go index 0f48ee3261930..ae66e2356adfb 100644 --- a/sdk/helper/aliasmetadata/alias_metadata.go +++ b/sdk/helper/aliasmetadata/alias_metadata.go @@ -15,6 +15,7 @@ package aliasmetadata */ import ( + "encoding/json" "errors" "fmt" "strings" @@ -64,51 +65,42 @@ func FieldSchema(fields *Fields) *framework.FieldSchema { } } -// NewHandler instantiates a Handler to be embedded in your config. -func NewHandler(fields *Fields) Handler { - return &handler{ +func NewHandler(fields *Fields) *Handler { + return &Handler{ fields: fields, } } -// Handler is an interface for the helper methods you get on your -// config when you embed the Handler. -type Handler interface { - GetAliasMetadata() []string - ParseAliasMetadata(data *framework.FieldData) error - PopulateDesiredAliasMetadata(auth *logical.Auth, fieldValues map[string]string) error -} - -type handler struct { - // AliasMetadata is an explicit list of all the user's configured +type Handler struct { + // aliasMetadata is an explicit list of all the user's configured // fields that are being added to alias metadata. It will never // include the "default" parameter, and instead includes the actual // fields behind "default", if selected. If it has never been set, // the pointer will be nil. - AliasMetadata []string `json:"alias_metadata"` + aliasMetadata []string // fields is a list of the configured default and available // fields. It's intentionally not jsonified. fields *Fields } -// GetAliasMetadata is intended to be used on config reads. +// AliasMetadata is intended to be used on config reads. // It gets an explicit list of all the user's configured // fields that are being added to alias metadata. It will never // include the "default" parameter, and instead includes the actual // fields behind "default", if selected. -func (h *handler) GetAliasMetadata() []string { - if h.AliasMetadata == nil { +func (h *Handler) AliasMetadata() []string { + if h.aliasMetadata == nil { return h.fields.Default } - return h.AliasMetadata + return h.aliasMetadata } // ParseAliasMetadata is intended to be used on config create/update. // It takes a user's selected fields (or lack thereof), -// converts it to a list of explicit fields, and adds it to the handler +// converts it to a list of explicit fields, and adds it to the Handler // for later storage. -func (h *handler) ParseAliasMetadata(data *framework.FieldData) error { +func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { userProvided, ok := data.GetOk(h.fields.FieldName) if !ok { // Nothing further to do here. @@ -144,7 +136,7 @@ func (h *handler) ParseAliasMetadata(data *framework.FieldData) error { // Fulfilling the pointer here flags that the user has made // an explicit selection so we shouldn't just fall back to // our defaults. - h.AliasMetadata = aliasMetadata + h.aliasMetadata = aliasMetadata return nil } @@ -152,7 +144,7 @@ func (h *handler) ParseAliasMetadata(data *framework.FieldData) error { // just before returning an auth. // It takes the available alias metadata and, // if the auth should have it, adds it to the auth's alias metadata. -func (h *handler) PopulateDesiredAliasMetadata(auth *logical.Auth, available map[string]string) error { +func (h *Handler) PopulateDesiredAliasMetadata(auth *logical.Auth, available map[string]string) error { if auth == nil { return errors.New("auth is nil") } @@ -168,8 +160,8 @@ func (h *handler) PopulateDesiredAliasMetadata(auth *logical.Auth, available map auth.Alias.Metadata = make(map[string]string) } fieldsToInclude := h.fields.Default - if h.AliasMetadata != nil { - fieldsToInclude = h.AliasMetadata + if h.aliasMetadata != nil { + fieldsToInclude = h.aliasMetadata } for availableField, itsValue := range available { if strutil.StrListContains(fieldsToInclude, availableField) { @@ -179,6 +171,27 @@ func (h *handler) PopulateDesiredAliasMetadata(auth *logical.Auth, available map return nil } +func (h *Handler) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + AliasMetadata []string `json:"alias_metadata"` + }{ + AliasMetadata: h.aliasMetadata, + }) +} + +func (h *Handler) UnmarshalJSON(data []byte) error { + jsonable := &struct { + AliasMetadata []string `json:"alias_metadata"` + }{ + AliasMetadata: h.aliasMetadata, + } + if err := json.Unmarshal(data, jsonable); err != nil { + return err + } + h.aliasMetadata = jsonable.AliasMetadata + return nil +} + func description(fields *Fields) string { desc := "The metadata to include on the aliases generated by this plugin." if len(fields.Default) > 0 { diff --git a/sdk/helper/aliasmetadata/alias_metadata_acc_test.go b/sdk/helper/aliasmetadata/alias_metadata_acc_test.go index a5b9eef8577e5..8890004f1c3e9 100644 --- a/sdk/helper/aliasmetadata/alias_metadata_acc_test.go +++ b/sdk/helper/aliasmetadata/alias_metadata_acc_test.go @@ -359,14 +359,14 @@ func TestAcceptance(t *testing.T) { } } -// We expect people to embed the handler on their +// We expect people to embed the Handler on their // config so it automatically makes its helper methods // available and easy to find wherever the config is // needed. Explicitly naming it in json avoids it // automatically being named "Handler" by Go's JSON // marshalling library. type fakeConfig struct { - Handler `json:"alias_metadata_handler"` + *Handler `json:"alias_metadata_handler"` } type fakeBackend struct { @@ -411,7 +411,7 @@ func configPath() *framework.Path { // alias metadata is when unconfigured. return &logical.Response{ Data: map[string]interface{}{ - aliasMetadataFields.FieldName: conf.GetAliasMetadata(), + aliasMetadataFields.FieldName: conf.AliasMetadata(), }, }, nil }, diff --git a/sdk/helper/aliasmetadata/alias_metadata_test.go b/sdk/helper/aliasmetadata/alias_metadata_test.go index efc094b67b582..6bec531a8dfd3 100644 --- a/sdk/helper/aliasmetadata/alias_metadata_test.go +++ b/sdk/helper/aliasmetadata/alias_metadata_test.go @@ -41,7 +41,7 @@ func TestGetAliasMetadata(t *testing.T) { h := NewHandler(testFields) expected := []string{"fizz", "buzz"} sort.Strings(expected) - actual := h.GetAliasMetadata() + actual := h.AliasMetadata() sort.Strings(actual) if !reflect.DeepEqual(expected, actual) { t.Fatalf("expected %s but received %s", expected, actual) @@ -63,7 +63,7 @@ func TestParseAliasMetadata(t *testing.T) { } expected := []string{"fizz", "buzz"} sort.Strings(expected) - actual := h.GetAliasMetadata() + actual := h.AliasMetadata() sort.Strings(actual) if !reflect.DeepEqual(expected, actual) { t.Fatalf("expected %s but received %s", expected, actual) diff --git a/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go b/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go index 56bfa3b00076b..ae66e2356adfb 100644 --- a/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go +++ b/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go @@ -15,6 +15,7 @@ package aliasmetadata */ import ( + "encoding/json" "errors" "fmt" "strings" @@ -64,51 +65,42 @@ func FieldSchema(fields *Fields) *framework.FieldSchema { } } -// NewHandler instantiates a Handler to be embedded in your config. -func NewHandler(fields *Fields) Handler { - return &handler{ +func NewHandler(fields *Fields) *Handler { + return &Handler{ fields: fields, } } -// Handler is an interface for the helper methods you get on your -// config when you embed the Handler. -type Handler interface { - GetAliasMetadata() []string - ParseAliasMetadata(data *framework.FieldData) error - PopulateDesiredAliasMetadata(auth *logical.Auth, fieldValues map[string]string) error -} - -type handler struct { - // AliasMetadata is an explicit list of all the user's configured +type Handler struct { + // aliasMetadata is an explicit list of all the user's configured // fields that are being added to alias metadata. It will never // include the "default" parameter, and instead includes the actual // fields behind "default", if selected. If it has never been set, // the pointer will be nil. - AliasMetadata *[]string `json:"alias_metadata"` + aliasMetadata []string // fields is a list of the configured default and available // fields. It's intentionally not jsonified. fields *Fields } -// GetAliasMetadata is intended to be used on config reads. +// AliasMetadata is intended to be used on config reads. // It gets an explicit list of all the user's configured // fields that are being added to alias metadata. It will never // include the "default" parameter, and instead includes the actual // fields behind "default", if selected. -func (h *handler) GetAliasMetadata() []string { - if h.AliasMetadata == nil { +func (h *Handler) AliasMetadata() []string { + if h.aliasMetadata == nil { return h.fields.Default } - return *h.AliasMetadata + return h.aliasMetadata } // ParseAliasMetadata is intended to be used on config create/update. // It takes a user's selected fields (or lack thereof), -// converts it to a list of explicit fields, and adds it to the handler +// converts it to a list of explicit fields, and adds it to the Handler // for later storage. -func (h *handler) ParseAliasMetadata(data *framework.FieldData) error { +func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { userProvided, ok := data.GetOk(h.fields.FieldName) if !ok { // Nothing further to do here. @@ -144,7 +136,7 @@ func (h *handler) ParseAliasMetadata(data *framework.FieldData) error { // Fulfilling the pointer here flags that the user has made // an explicit selection so we shouldn't just fall back to // our defaults. - h.AliasMetadata = &aliasMetadata + h.aliasMetadata = aliasMetadata return nil } @@ -152,7 +144,7 @@ func (h *handler) ParseAliasMetadata(data *framework.FieldData) error { // just before returning an auth. // It takes the available alias metadata and, // if the auth should have it, adds it to the auth's alias metadata. -func (h *handler) PopulateDesiredAliasMetadata(auth *logical.Auth, available map[string]string) error { +func (h *Handler) PopulateDesiredAliasMetadata(auth *logical.Auth, available map[string]string) error { if auth == nil { return errors.New("auth is nil") } @@ -168,8 +160,8 @@ func (h *handler) PopulateDesiredAliasMetadata(auth *logical.Auth, available map auth.Alias.Metadata = make(map[string]string) } fieldsToInclude := h.fields.Default - if h.AliasMetadata != nil { - fieldsToInclude = *h.AliasMetadata + if h.aliasMetadata != nil { + fieldsToInclude = h.aliasMetadata } for availableField, itsValue := range available { if strutil.StrListContains(fieldsToInclude, availableField) { @@ -179,6 +171,27 @@ func (h *handler) PopulateDesiredAliasMetadata(auth *logical.Auth, available map return nil } +func (h *Handler) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + AliasMetadata []string `json:"alias_metadata"` + }{ + AliasMetadata: h.aliasMetadata, + }) +} + +func (h *Handler) UnmarshalJSON(data []byte) error { + jsonable := &struct { + AliasMetadata []string `json:"alias_metadata"` + }{ + AliasMetadata: h.aliasMetadata, + } + if err := json.Unmarshal(data, jsonable); err != nil { + return err + } + h.aliasMetadata = jsonable.AliasMetadata + return nil +} + func description(fields *Fields) string { desc := "The metadata to include on the aliases generated by this plugin." if len(fields.Default) > 0 { From 5038cb932e5ce6d649a533cc7e1a95585cdee732 Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Wed, 22 Apr 2020 17:02:54 -0700 Subject: [PATCH 08/18] add tests for marshalling --- .../aliasmetadata/alias_metadata_test.go | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/sdk/helper/aliasmetadata/alias_metadata_test.go b/sdk/helper/aliasmetadata/alias_metadata_test.go index 6bec531a8dfd3..dd0f488eb204b 100644 --- a/sdk/helper/aliasmetadata/alias_metadata_test.go +++ b/sdk/helper/aliasmetadata/alias_metadata_test.go @@ -1,6 +1,8 @@ package aliasmetadata import ( + "encoding/json" + "fmt" "reflect" "sort" "testing" @@ -102,3 +104,25 @@ func TestPopulateDesiredAliasMetadata(t *testing.T) { t.Fatal("expected foova;") } } + +func TestMarshalJSON(t *testing.T) { + h := NewHandler(&Fields{}) + h.aliasMetadata = []string{"fizz", "buzz"} + b, err := json.Marshal(h) + if err != nil { + t.Fatal(err) + } + if string(b) != `{"alias_metadata":["fizz","buzz"]}` { + t.Fatal(`expected {"alias_metadata":["fizz","buzz"]}`) + } +} + +func TestUnmarshalJSON(t *testing.T) { + h := NewHandler(&Fields{}) + if err := json.Unmarshal([]byte(`{"alias_metadata":["fizz","buzz"]}`), h); err != nil { + t.Fatal(err) + } + if fmt.Sprintf("%s", h.aliasMetadata) != `[fizz buzz]` { + t.Fatal(`expected [fizz buzz]`) + } +} From 7c5cae53093a7aef21fcd5be9c4363446d6c546a Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Thu, 23 Apr 2020 08:34:50 -0700 Subject: [PATCH 09/18] store nil when selected fields are default --- sdk/helper/aliasmetadata/alias_metadata.go | 25 +++++++++++++------ .../helper/aliasmetadata/alias_metadata.go | 25 +++++++++++++------ 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/sdk/helper/aliasmetadata/alias_metadata.go b/sdk/helper/aliasmetadata/alias_metadata.go index ae66e2356adfb..5d5df37ee0b84 100644 --- a/sdk/helper/aliasmetadata/alias_metadata.go +++ b/sdk/helper/aliasmetadata/alias_metadata.go @@ -75,8 +75,8 @@ type Handler struct { // aliasMetadata is an explicit list of all the user's configured // fields that are being added to alias metadata. It will never // include the "default" parameter, and instead includes the actual - // fields behind "default", if selected. If it has never been set, - // the pointer will be nil. + // fields behind "default", if selected. If it has never been set + // or only the default fields are desired, it is nil. aliasMetadata []string // fields is a list of the configured default and available @@ -101,18 +101,30 @@ func (h *Handler) AliasMetadata() []string { // converts it to a list of explicit fields, and adds it to the Handler // for later storage. func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { - userProvided, ok := data.GetOk(h.fields.FieldName) + userProvidedRaw, ok := data.GetOk(h.fields.FieldName) if !ok { // Nothing further to do here. return nil } + userProvided, ok := userProvidedRaw.([]string) + if !ok { + return fmt.Errorf("%s is an unexpected type of %T", userProvidedRaw, userProvidedRaw) + } + + // If the only field the user has chosen was the default field, + // we don't store anything so we won't have to do a storage + // migration if the default changes. + if len(userProvided) == 1 && userProvided[0] == "default" { + h.aliasMetadata = nil + return nil + } // uniqueFields protects against weird edge cases like if // a user provided "default,field1,field2,default". uniqueFields := make(map[string]bool) - for _, field := range userProvided.([]string) { + for _, field := range userProvided { if field == "default" { - // Add the fields that "default" represents, rather + // Add the field that "default" represents, rather // than the explicit field. for _, dfltField := range h.fields.Default { uniqueFields[dfltField] = true @@ -134,8 +146,7 @@ func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { i++ } // Fulfilling the pointer here flags that the user has made - // an explicit selection so we shouldn't just fall back to - // our defaults. + // a non-default selection. h.aliasMetadata = aliasMetadata return nil } diff --git a/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go b/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go index ae66e2356adfb..5d5df37ee0b84 100644 --- a/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go +++ b/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go @@ -75,8 +75,8 @@ type Handler struct { // aliasMetadata is an explicit list of all the user's configured // fields that are being added to alias metadata. It will never // include the "default" parameter, and instead includes the actual - // fields behind "default", if selected. If it has never been set, - // the pointer will be nil. + // fields behind "default", if selected. If it has never been set + // or only the default fields are desired, it is nil. aliasMetadata []string // fields is a list of the configured default and available @@ -101,18 +101,30 @@ func (h *Handler) AliasMetadata() []string { // converts it to a list of explicit fields, and adds it to the Handler // for later storage. func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { - userProvided, ok := data.GetOk(h.fields.FieldName) + userProvidedRaw, ok := data.GetOk(h.fields.FieldName) if !ok { // Nothing further to do here. return nil } + userProvided, ok := userProvidedRaw.([]string) + if !ok { + return fmt.Errorf("%s is an unexpected type of %T", userProvidedRaw, userProvidedRaw) + } + + // If the only field the user has chosen was the default field, + // we don't store anything so we won't have to do a storage + // migration if the default changes. + if len(userProvided) == 1 && userProvided[0] == "default" { + h.aliasMetadata = nil + return nil + } // uniqueFields protects against weird edge cases like if // a user provided "default,field1,field2,default". uniqueFields := make(map[string]bool) - for _, field := range userProvided.([]string) { + for _, field := range userProvided { if field == "default" { - // Add the fields that "default" represents, rather + // Add the field that "default" represents, rather // than the explicit field. for _, dfltField := range h.fields.Default { uniqueFields[dfltField] = true @@ -134,8 +146,7 @@ func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { i++ } // Fulfilling the pointer here flags that the user has made - // an explicit selection so we shouldn't just fall back to - // our defaults. + // a non-default selection. h.aliasMetadata = aliasMetadata return nil } From 4022bd1e164e98576266a6d23747e50a4250b321 Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Thu, 23 Apr 2020 08:51:20 -0700 Subject: [PATCH 10/18] separate loop into pieces --- sdk/helper/aliasmetadata/alias_metadata.go | 47 ++++++++----------- .../helper/aliasmetadata/alias_metadata.go | 47 ++++++++----------- 2 files changed, 40 insertions(+), 54 deletions(-) diff --git a/sdk/helper/aliasmetadata/alias_metadata.go b/sdk/helper/aliasmetadata/alias_metadata.go index 5d5df37ee0b84..f0c2ee236605e 100644 --- a/sdk/helper/aliasmetadata/alias_metadata.go +++ b/sdk/helper/aliasmetadata/alias_metadata.go @@ -119,36 +119,29 @@ func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { return nil } - // uniqueFields protects against weird edge cases like if - // a user provided "default,field1,field2,default". - uniqueFields := make(map[string]bool) + userProvided = h.expandDefaultField(userProvided) + userProvided = strutil.RemoveDuplicates(userProvided, true) + if !strutil.StrListSubset(h.fields.all(), userProvided) { + return fmt.Errorf("%q contains an unavailable field, please select from %q", + strings.Join(userProvided, ", "), strings.Join(h.fields.all(), ", ")) + } + h.aliasMetadata = userProvided + return nil +} + +// expandDefaultField looks for the field "default" and, if exists, +// replaces it with the actual default fields signified. +func (h *Handler) expandDefaultField(userProvided []string) (expanded []string) { for _, field := range userProvided { - if field == "default" { - // Add the field that "default" represents, rather - // than the explicit field. - for _, dfltField := range h.fields.Default { - uniqueFields[dfltField] = true - } - } else { - // Make sure they've sent a supported field so we can - // error early if not. - if !strutil.StrListContains(h.fields.all(), field) { - return fmt.Errorf("%q is not an available field, please select from: %s", field, strings.Join(h.fields.all(), ", ")) - } - uniqueFields[field] = true + if field != "default" { + expanded = append(expanded, field) + continue + } + for _, dfltField := range h.fields.Default { + expanded = append(expanded, dfltField) } } - // Attach the fields we've received so they'll be stored. - aliasMetadata := make([]string, len(uniqueFields)) - i := 0 - for fieldName := range uniqueFields { - aliasMetadata[i] = fieldName - i++ - } - // Fulfilling the pointer here flags that the user has made - // a non-default selection. - h.aliasMetadata = aliasMetadata - return nil + return expanded } // PopulateDesiredAliasMetadata is intended to be used during login diff --git a/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go b/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go index 5d5df37ee0b84..f0c2ee236605e 100644 --- a/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go +++ b/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go @@ -119,36 +119,29 @@ func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { return nil } - // uniqueFields protects against weird edge cases like if - // a user provided "default,field1,field2,default". - uniqueFields := make(map[string]bool) + userProvided = h.expandDefaultField(userProvided) + userProvided = strutil.RemoveDuplicates(userProvided, true) + if !strutil.StrListSubset(h.fields.all(), userProvided) { + return fmt.Errorf("%q contains an unavailable field, please select from %q", + strings.Join(userProvided, ", "), strings.Join(h.fields.all(), ", ")) + } + h.aliasMetadata = userProvided + return nil +} + +// expandDefaultField looks for the field "default" and, if exists, +// replaces it with the actual default fields signified. +func (h *Handler) expandDefaultField(userProvided []string) (expanded []string) { for _, field := range userProvided { - if field == "default" { - // Add the field that "default" represents, rather - // than the explicit field. - for _, dfltField := range h.fields.Default { - uniqueFields[dfltField] = true - } - } else { - // Make sure they've sent a supported field so we can - // error early if not. - if !strutil.StrListContains(h.fields.all(), field) { - return fmt.Errorf("%q is not an available field, please select from: %s", field, strings.Join(h.fields.all(), ", ")) - } - uniqueFields[field] = true + if field != "default" { + expanded = append(expanded, field) + continue + } + for _, dfltField := range h.fields.Default { + expanded = append(expanded, dfltField) } } - // Attach the fields we've received so they'll be stored. - aliasMetadata := make([]string, len(uniqueFields)) - i := 0 - for fieldName := range uniqueFields { - aliasMetadata[i] = fieldName - i++ - } - // Fulfilling the pointer here flags that the user has made - // a non-default selection. - h.aliasMetadata = aliasMetadata - return nil + return expanded } // PopulateDesiredAliasMetadata is intended to be used during login From 6d19459b96ae327fb1040049a9591b81562d4551 Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Thu, 23 Apr 2020 09:46:41 -0700 Subject: [PATCH 11/18] separate acc test into multiple --- .../aliasmetadata/alias_metadata_acc_test.go | 122 ++++++++++++------ 1 file changed, 79 insertions(+), 43 deletions(-) diff --git a/sdk/helper/aliasmetadata/alias_metadata_acc_test.go b/sdk/helper/aliasmetadata/alias_metadata_acc_test.go index 8890004f1c3e9..e4404292bce4d 100644 --- a/sdk/helper/aliasmetadata/alias_metadata_acc_test.go +++ b/sdk/helper/aliasmetadata/alias_metadata_acc_test.go @@ -12,31 +12,39 @@ import ( "github.com/hashicorp/vault/sdk/logical" ) +type environment struct { + ctx context.Context + storage logical.Storage + backend logical.Backend +} + func TestAcceptance(t *testing.T) { ctx := context.Background() storage := &logical.InmemStorage{} - - b := &fakeBackend{ - Backend: &framework.Backend{ - Paths: []*framework.Path{ - configPath(), - loginPath(), - }, - }, - } - if err := b.Setup(ctx, &logical.BackendConfig{ - StorageView: storage, - Logger: hclog.Default(), - }); err != nil { + b, err := backend(ctx, storage) + if err != nil { t.Fatal(err) } + env := &environment{ + ctx: ctx, + storage: storage, + backend: b, + } + t.Run("test initial fields are default", env.TestInitialFieldsAreDefault) + t.Run("test fields can be unset", env.TestAliasMetadataCanBeUnset) + t.Run("test defaults can be restored", env.TestDefaultCanBeReused) + t.Run("test default plus more can be selected", env.TestDefaultPlusMoreCanBeSelected) + t.Run("test only non-defaults can be selected", env.TestOnlyNonDefaultsCanBeSelected) + t.Run("test bad field results in useful error", env.TestAddingBadField) +} +func (e *environment) TestInitialFieldsAreDefault(t *testing.T) { // On the first read of alias_metadata, when nothing has been touched, // we should receive the default field(s) if a read is performed. - resp, err := b.HandleRequest(ctx, &logical.Request{ + resp, err := e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.ReadOperation, Path: "config", - Storage: storage, + Storage: e.storage, Connection: &logical.Connection{ RemoteAddr: "http://foo.com", }, @@ -52,10 +60,10 @@ func TestAcceptance(t *testing.T) { } // The auth should only have the default metadata. - resp, err = b.HandleRequest(ctx, &logical.Request{ + resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.UpdateOperation, Path: "login", - Storage: storage, + Storage: e.storage, Connection: &logical.Connection{ RemoteAddr: "http://foo.com", }, @@ -75,13 +83,15 @@ func TestAcceptance(t *testing.T) { if resp.Auth.Alias.Metadata["role_name"] != "something" { t.Fatal("expected role_name to be something") } +} +func (e *environment) TestAliasMetadataCanBeUnset(t *testing.T) { // We should be able to set the alias_metadata to empty by sending an // explicitly empty array. - resp, err = b.HandleRequest(ctx, &logical.Request{ + resp, err := e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.UpdateOperation, Path: "config", - Storage: storage, + Storage: e.storage, Connection: &logical.Connection{ RemoteAddr: "http://foo.com", }, @@ -97,10 +107,10 @@ func TestAcceptance(t *testing.T) { } // Now we should receive no fields for alias_metadata. - resp, err = b.HandleRequest(ctx, &logical.Request{ + resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.ReadOperation, Path: "config", - Storage: storage, + Storage: e.storage, Connection: &logical.Connection{ RemoteAddr: "http://foo.com", }, @@ -116,10 +126,10 @@ func TestAcceptance(t *testing.T) { } // The auth should have no metadata. - resp, err = b.HandleRequest(ctx, &logical.Request{ + resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.UpdateOperation, Path: "login", - Storage: storage, + Storage: e.storage, Connection: &logical.Connection{ RemoteAddr: "http://foo.com", }, @@ -136,13 +146,15 @@ func TestAcceptance(t *testing.T) { if len(resp.Auth.Alias.Metadata) != 0 { t.Fatal("expected 0 fields") } +} +func (e *environment) TestDefaultCanBeReused(t *testing.T) { // Now if we set it to "default", the default fields should // be restored. - resp, err = b.HandleRequest(ctx, &logical.Request{ + resp, err := e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.UpdateOperation, Path: "config", - Storage: storage, + Storage: e.storage, Connection: &logical.Connection{ RemoteAddr: "http://foo.com", }, @@ -158,10 +170,10 @@ func TestAcceptance(t *testing.T) { } // Let's make sure we've returned to the default fields. - resp, err = b.HandleRequest(ctx, &logical.Request{ + resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.ReadOperation, Path: "config", - Storage: storage, + Storage: e.storage, Connection: &logical.Connection{ RemoteAddr: "http://foo.com", }, @@ -177,10 +189,10 @@ func TestAcceptance(t *testing.T) { } // We should again only receive the default field on the login. - resp, err = b.HandleRequest(ctx, &logical.Request{ + resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.UpdateOperation, Path: "login", - Storage: storage, + Storage: e.storage, Connection: &logical.Connection{ RemoteAddr: "http://foo.com", }, @@ -200,12 +212,14 @@ func TestAcceptance(t *testing.T) { if resp.Auth.Alias.Metadata["role_name"] != "something" { t.Fatal("expected role_name to be something") } +} +func (e *environment) TestDefaultPlusMoreCanBeSelected(t *testing.T) { // We should be able to set it to "default" plus 1 optional field. - resp, err = b.HandleRequest(ctx, &logical.Request{ + resp, err := e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.UpdateOperation, Path: "config", - Storage: storage, + Storage: e.storage, Connection: &logical.Connection{ RemoteAddr: "http://foo.com", }, @@ -222,10 +236,10 @@ func TestAcceptance(t *testing.T) { // Let's make sure the default and optional field are being stored // correctly. - resp, err = b.HandleRequest(ctx, &logical.Request{ + resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.ReadOperation, Path: "config", - Storage: storage, + Storage: e.storage, Connection: &logical.Connection{ RemoteAddr: "http://foo.com", }, @@ -245,10 +259,10 @@ func TestAcceptance(t *testing.T) { } // They both should now appear on the login. - resp, err = b.HandleRequest(ctx, &logical.Request{ + resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.UpdateOperation, Path: "login", - Storage: storage, + Storage: e.storage, Connection: &logical.Connection{ RemoteAddr: "http://foo.com", }, @@ -271,12 +285,14 @@ func TestAcceptance(t *testing.T) { if resp.Auth.Alias.Metadata["remote_addr"] != "http://foo.com" { t.Fatal("expected remote_addr to be http://foo.com") } +} +func (e *environment) TestOnlyNonDefaultsCanBeSelected(t *testing.T) { // Omit all default fields and just select one. - resp, err = b.HandleRequest(ctx, &logical.Request{ + resp, err := e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.UpdateOperation, Path: "config", - Storage: storage, + Storage: e.storage, Connection: &logical.Connection{ RemoteAddr: "http://foo.com", }, @@ -292,10 +308,10 @@ func TestAcceptance(t *testing.T) { } // Make sure that worked. - resp, err = b.HandleRequest(ctx, &logical.Request{ + resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.ReadOperation, Path: "config", - Storage: storage, + Storage: e.storage, Connection: &logical.Connection{ RemoteAddr: "http://foo.com", }, @@ -312,10 +328,10 @@ func TestAcceptance(t *testing.T) { // Ensure only the selected one is on logins. // They both should now appear on the login. - resp, err = b.HandleRequest(ctx, &logical.Request{ + resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.UpdateOperation, Path: "login", - Storage: storage, + Storage: e.storage, Connection: &logical.Connection{ RemoteAddr: "http://foo.com", }, @@ -335,12 +351,14 @@ func TestAcceptance(t *testing.T) { if resp.Auth.Alias.Metadata["remote_addr"] != "http://foo.com" { t.Fatal("expected remote_addr to be http://foo.com") } +} +func (e *environment) TestAddingBadField(t *testing.T) { // Try adding an unsupported field. - resp, err = b.HandleRequest(ctx, &logical.Request{ + resp, err := e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.UpdateOperation, Path: "config", - Storage: storage, + Storage: e.storage, Connection: &logical.Connection{ RemoteAddr: "http://foo.com", }, @@ -495,3 +513,21 @@ func loginPath() *framework.Path { }, } } + +func backend(ctx context.Context, storage logical.Storage) (logical.Backend, error) { + b := &fakeBackend{ + Backend: &framework.Backend{ + Paths: []*framework.Path{ + configPath(), + loginPath(), + }, + }, + } + if err := b.Setup(ctx, &logical.BackendConfig{ + StorageView: storage, + Logger: hclog.Default(), + }); err != nil { + return nil, err + } + return b, nil +} From 379ae98512d48af07be4c1eecfd6c939ac3c5b68 Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Fri, 24 Apr 2020 09:38:02 -0700 Subject: [PATCH 12/18] Update builtin/credential/aws/path_login.go Co-Authored-By: Jim Kalafut --- builtin/credential/aws/path_login.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/credential/aws/path_login.go b/builtin/credential/aws/path_login.go index cb98c5b8a06a8..4e60f5bf38886 100644 --- a/builtin/credential/aws/path_login.go +++ b/builtin/credential/aws/path_login.go @@ -855,7 +855,7 @@ func (b *backend) pathLoginUpdateEc2(ctx context.Context, req *logical.Request, "ami_id": identityDocParsed.AmiID, }); err != nil { if b.Logger().IsWarn() { - b.Logger().Warn(fmt.Sprintf("unable to set alias metadata due to %s", err)) + b.Logger().Warn("unable to set alias metadata", "err", err)) } } From a7987073e3b3b9ba96c23a8d9f95de99914d2a49 Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Fri, 24 Apr 2020 09:58:21 -0700 Subject: [PATCH 13/18] changes from feedback --- builtin/credential/aws/path_login.go | 8 +-- sdk/helper/aliasmetadata/alias_metadata.go | 8 ++- .../aliasmetadata/alias_metadata_acc_test.go | 68 ++----------------- .../helper/aliasmetadata/alias_metadata.go | 8 ++- 4 files changed, 20 insertions(+), 72 deletions(-) diff --git a/builtin/credential/aws/path_login.go b/builtin/credential/aws/path_login.go index 4e60f5bf38886..e9cafd7308963 100644 --- a/builtin/credential/aws/path_login.go +++ b/builtin/credential/aws/path_login.go @@ -854,9 +854,7 @@ func (b *backend) pathLoginUpdateEc2(ctx context.Context, req *logical.Request, "account_id": identityDocParsed.AccountID, "ami_id": identityDocParsed.AmiID, }); err != nil { - if b.Logger().IsWarn() { - b.Logger().Warn("unable to set alias metadata", "err", err)) - } + b.Logger().Warn("unable to set alias metadata", "err", err) } resp := &logical.Response{ @@ -1392,9 +1390,7 @@ func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request, "inferred_aws_region": roleEntry.InferredAWSRegion, "account_id": entity.AccountNumber, }); err != nil { - if b.Logger().IsWarn() { - b.Logger().Warn(fmt.Sprintf("unable to set alias metadata due to %s", err)) - } + b.Logger().Warn(fmt.Sprintf("unable to set alias metadata due to %s", err)) } return &logical.Response{ diff --git a/sdk/helper/aliasmetadata/alias_metadata.go b/sdk/helper/aliasmetadata/alias_metadata.go index f0c2ee236605e..aeeaab9d7f74e 100644 --- a/sdk/helper/aliasmetadata/alias_metadata.go +++ b/sdk/helper/aliasmetadata/alias_metadata.go @@ -110,6 +110,7 @@ func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { if !ok { return fmt.Errorf("%s is an unexpected type of %T", userProvidedRaw, userProvidedRaw) } + userProvided = strutil.RemoveDuplicates(userProvided, true) // If the only field the user has chosen was the default field, // we don't store anything so we won't have to do a storage @@ -119,8 +120,11 @@ func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { return nil } - userProvided = h.expandDefaultField(userProvided) - userProvided = strutil.RemoveDuplicates(userProvided, true) + // Validate and store the input. + if strutil.StrListContains(userProvided, "default") { + return fmt.Errorf("%q contains default - default can't be used in combination with other fields", + userProvided) + } if !strutil.StrListSubset(h.fields.all(), userProvided) { return fmt.Errorf("%q contains an unavailable field, please select from %q", strings.Join(userProvided, ", "), strings.Join(h.fields.all(), ", ")) diff --git a/sdk/helper/aliasmetadata/alias_metadata_acc_test.go b/sdk/helper/aliasmetadata/alias_metadata_acc_test.go index e4404292bce4d..c6498dc8821f7 100644 --- a/sdk/helper/aliasmetadata/alias_metadata_acc_test.go +++ b/sdk/helper/aliasmetadata/alias_metadata_acc_test.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "reflect" - "sort" "testing" "github.com/hashicorp/go-hclog" @@ -33,7 +32,7 @@ func TestAcceptance(t *testing.T) { t.Run("test initial fields are default", env.TestInitialFieldsAreDefault) t.Run("test fields can be unset", env.TestAliasMetadataCanBeUnset) t.Run("test defaults can be restored", env.TestDefaultCanBeReused) - t.Run("test default plus more can be selected", env.TestDefaultPlusMoreCanBeSelected) + t.Run("test default plus more cannot be selected", env.TestDefaultPlusMoreCannotBeSelected) t.Run("test only non-defaults can be selected", env.TestOnlyNonDefaultsCanBeSelected) t.Run("test bad field results in useful error", env.TestAddingBadField) } @@ -214,9 +213,9 @@ func (e *environment) TestDefaultCanBeReused(t *testing.T) { } } -func (e *environment) TestDefaultPlusMoreCanBeSelected(t *testing.T) { - // We should be able to set it to "default" plus 1 optional field. - resp, err := e.backend.HandleRequest(e.ctx, &logical.Request{ +func (e *environment) TestDefaultPlusMoreCannotBeSelected(t *testing.T) { + // We should not be able to set it to "default" plus 1 optional field. + _, err := e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.UpdateOperation, Path: "config", Storage: e.storage, @@ -227,63 +226,8 @@ func (e *environment) TestDefaultPlusMoreCanBeSelected(t *testing.T) { aliasMetadataFields.FieldName: []string{"default", "remote_addr"}, }, }) - if err != nil { - t.Fatal(err) - } - if resp != nil { - t.Fatal("expected nil response") - } - - // Let's make sure the default and optional field are being stored - // correctly. - resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ - Operation: logical.ReadOperation, - Path: "config", - Storage: e.storage, - Connection: &logical.Connection{ - RemoteAddr: "http://foo.com", - }, - }) - if err != nil { - t.Fatal(err) - } - if resp == nil || resp.Data == nil { - t.Fatal("expected non-nil response") - } - expected := []string{"role_name", "remote_addr"} - sort.Strings(expected) - actual := resp.Data[aliasMetadataFields.FieldName].([]string) - sort.Strings(actual) - if !reflect.DeepEqual(expected, actual) { - t.Fatalf("unexpectedly received %s", resp.Data[aliasMetadataFields.FieldName]) - } - - // They both should now appear on the login. - resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ - Operation: logical.UpdateOperation, - Path: "login", - Storage: e.storage, - Connection: &logical.Connection{ - RemoteAddr: "http://foo.com", - }, - Data: map[string]interface{}{ - "role_name": "something", - }, - }) - if err != nil { - t.Fatal(err) - } - if resp == nil || resp.Auth == nil || resp.Auth.Alias == nil || resp.Auth.Alias.Metadata == nil { - t.Fatal("expected alias metadata") - } - if len(resp.Auth.Alias.Metadata) != 2 { - t.Fatal("expected 2 fields") - } - if resp.Auth.Alias.Metadata["role_name"] != "something" { - t.Fatal("expected role_name to be something") - } - if resp.Auth.Alias.Metadata["remote_addr"] != "http://foo.com" { - t.Fatal("expected remote_addr to be http://foo.com") + if err == nil { + t.Fatal("expected err") } } diff --git a/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go b/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go index f0c2ee236605e..aeeaab9d7f74e 100644 --- a/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go +++ b/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go @@ -110,6 +110,7 @@ func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { if !ok { return fmt.Errorf("%s is an unexpected type of %T", userProvidedRaw, userProvidedRaw) } + userProvided = strutil.RemoveDuplicates(userProvided, true) // If the only field the user has chosen was the default field, // we don't store anything so we won't have to do a storage @@ -119,8 +120,11 @@ func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { return nil } - userProvided = h.expandDefaultField(userProvided) - userProvided = strutil.RemoveDuplicates(userProvided, true) + // Validate and store the input. + if strutil.StrListContains(userProvided, "default") { + return fmt.Errorf("%q contains default - default can't be used in combination with other fields", + userProvided) + } if !strutil.StrListSubset(h.fields.all(), userProvided) { return fmt.Errorf("%q contains an unavailable field, please select from %q", strings.Join(userProvided, ", "), strings.Join(h.fields.all(), ", ")) From 3cbce692163f49a2594270360b9236a943a75c9d Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Fri, 24 Apr 2020 10:02:15 -0700 Subject: [PATCH 14/18] update aws test --- builtin/credential/aws/path_login_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/builtin/credential/aws/path_login_test.go b/builtin/credential/aws/path_login_test.go index 2f0c0529f5f2b..7cdcf0b0f66db 100644 --- a/builtin/credential/aws/path_login_test.go +++ b/builtin/credential/aws/path_login_test.go @@ -222,7 +222,8 @@ func TestBackend_pathLogin_IAMHeaders(t *testing.T) { Data: map[string]interface{}{ "iam_alias": "role_id", "iam_metadata": []string{ - "default", + "account_id", + "auth_type", "canonical_arn", "client_arn", "client_user_id", @@ -232,7 +233,7 @@ func TestBackend_pathLogin_IAMHeaders(t *testing.T) { }, "ec2_alias": "role_id", "ec2_metadata": []string{ - "default", + "account_id", "ami_id", "instance_id", "region", From ae3ba7f04ddcac3eb4f03036e4a6c2ce4028081e Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Fri, 24 Apr 2020 11:44:58 -0700 Subject: [PATCH 15/18] refactor to also populate auth metadata --- .../credential/aws/path_config_identity.go | 38 +++---- builtin/credential/aws/path_login.go | 18 +--- builtin/credential/aws/path_login_test.go | 6 +- .../auth_metadata.go} | 100 +++++++----------- .../auth_metadata_acc_test.go} | 52 ++++----- .../auth_metadata_test.go} | 34 +++--- .../auth_metadata.go} | 100 +++++++----------- 7 files changed, 148 insertions(+), 200 deletions(-) rename sdk/helper/{aliasmetadata/alias_metadata.go => authmetadata/auth_metadata.go} (61%) rename sdk/helper/{aliasmetadata/alias_metadata_acc_test.go => authmetadata/auth_metadata_acc_test.go} (88%) rename sdk/helper/{aliasmetadata/alias_metadata_test.go => authmetadata/auth_metadata_test.go} (66%) rename vendor/github.com/hashicorp/vault/sdk/helper/{aliasmetadata/alias_metadata.go => authmetadata/auth_metadata.go} (61%) diff --git a/builtin/credential/aws/path_config_identity.go b/builtin/credential/aws/path_config_identity.go index fdaac0b16dd93..6447ec29119b4 100644 --- a/builtin/credential/aws/path_config_identity.go +++ b/builtin/credential/aws/path_config_identity.go @@ -5,18 +5,18 @@ import ( "fmt" "github.com/hashicorp/vault/sdk/framework" - "github.com/hashicorp/vault/sdk/helper/aliasmetadata" + "github.com/hashicorp/vault/sdk/helper/authmetadata" "github.com/hashicorp/vault/sdk/helper/strutil" "github.com/hashicorp/vault/sdk/logical" ) var ( - // iamAliasMetadataFields is a list of the default alias metadata + // iamAuthMetadataFields is a list of the default auth metadata // added to tokens during login. The default alias type used // by this back-end is the role ID. Subsequently, the default // fields included are expected to have a low rate of change // when the role ID is in use. - iamAliasMetadataFields = &aliasmetadata.Fields{ + iamAuthMetadataFields = &authmetadata.Fields{ FieldName: "iam_metadata", Default: []string{ "account_id", @@ -32,12 +32,12 @@ var ( }, } - // ec2AliasMetadataFields is a list of the default alias metadata + // ec2AuthMetadataFields is a list of the default auth metadata // added to tokens during login. The default alias type used // by this back-end is the role ID. Subsequently, the default // fields included are expected to have a low rate of change // when the role ID is in use. - ec2AliasMetadataFields = &aliasmetadata.Fields{ + ec2AuthMetadataFields = &authmetadata.Fields{ FieldName: "ec2_metadata", Default: []string{ "account_id", @@ -59,13 +59,13 @@ func (b *backend) pathConfigIdentity() *framework.Path { Default: identityAliasIAMUniqueID, Description: fmt.Sprintf("Configure how the AWS auth method generates entity aliases when using IAM auth. Valid values are %q, %q, and %q. Defaults to %q.", identityAliasRoleID, identityAliasIAMUniqueID, identityAliasIAMFullArn, identityAliasRoleID), }, - iamAliasMetadataFields.FieldName: aliasmetadata.FieldSchema(iamAliasMetadataFields), + iamAuthMetadataFields.FieldName: authmetadata.FieldSchema(iamAuthMetadataFields), "ec2_alias": { Type: framework.TypeString, Default: identityAliasEC2InstanceID, Description: fmt.Sprintf("Configure how the AWS auth method generates entity alias when using EC2 auth. Valid values are %q, %q, and %q. Defaults to %q.", identityAliasRoleID, identityAliasEC2InstanceID, identityAliasEC2ImageID, identityAliasRoleID), }, - ec2AliasMetadataFields.FieldName: aliasmetadata.FieldSchema(ec2AliasMetadataFields), + ec2AuthMetadataFields.FieldName: authmetadata.FieldSchema(ec2AuthMetadataFields), }, Operations: map[logical.Operation]framework.OperationHandler{ @@ -89,8 +89,8 @@ func identityConfigEntry(ctx context.Context, s logical.Storage) (*identityConfi } entry := &identityConfig{ - IAMAliasMetadataHandler: aliasmetadata.NewHandler(iamAliasMetadataFields), - EC2AliasMetadataHandler: aliasmetadata.NewHandler(ec2AliasMetadataFields), + IAMAuthMetadataHandler: authmetadata.NewHandler(iamAuthMetadataFields), + EC2AuthMetadataHandler: authmetadata.NewHandler(ec2AuthMetadataFields), } if entryRaw != nil { if err := entryRaw.DecodeJSON(entry); err != nil { @@ -117,10 +117,10 @@ func pathConfigIdentityRead(ctx context.Context, req *logical.Request, _ *framew return &logical.Response{ Data: map[string]interface{}{ - "iam_alias": config.IAMAlias, - iamAliasMetadataFields.FieldName: config.IAMAliasMetadataHandler.AliasMetadata(), - "ec2_alias": config.EC2Alias, - ec2AliasMetadataFields.FieldName: config.EC2AliasMetadataHandler.AliasMetadata(), + "iam_alias": config.IAMAlias, + iamAuthMetadataFields.FieldName: config.IAMAuthMetadataHandler.AuthMetadata(), + "ec2_alias": config.EC2Alias, + ec2AuthMetadataFields.FieldName: config.EC2AuthMetadataHandler.AuthMetadata(), }, }, nil } @@ -150,10 +150,10 @@ func pathConfigIdentityUpdate(ctx context.Context, req *logical.Request, data *f } config.EC2Alias = ec2Alias } - if err := config.IAMAliasMetadataHandler.ParseAliasMetadata(data); err != nil { + if err := config.IAMAuthMetadataHandler.ParseAuthMetadata(data); err != nil { return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest } - if err := config.EC2AliasMetadataHandler.ParseAliasMetadata(data); err != nil { + if err := config.EC2AuthMetadataHandler.ParseAuthMetadata(data); err != nil { return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest } @@ -171,10 +171,10 @@ func pathConfigIdentityUpdate(ctx context.Context, req *logical.Request, data *f } type identityConfig struct { - IAMAlias string `json:"iam_alias"` - IAMAliasMetadataHandler *aliasmetadata.Handler `json:"iam_alias_metadata_handler"` - EC2Alias string `json:"ec2_alias"` - EC2AliasMetadataHandler *aliasmetadata.Handler `json:"ec2_alias_metadata_handler"` + IAMAlias string `json:"iam_alias"` + IAMAuthMetadataHandler *authmetadata.Handler `json:"iam_auth_metadata_handler"` + EC2Alias string `json:"ec2_alias"` + EC2AuthMetadataHandler *authmetadata.Handler `json:"ec2_auth_metadata_handler"` } const identityAliasIAMUniqueID = "unique_id" diff --git a/builtin/credential/aws/path_login.go b/builtin/credential/aws/path_login.go index e9cafd7308963..3007ccd2c18c6 100644 --- a/builtin/credential/aws/path_login.go +++ b/builtin/credential/aws/path_login.go @@ -836,19 +836,15 @@ func (b *backend) pathLoginUpdateEc2(ctx context.Context, req *logical.Request, auth := &logical.Auth{ Metadata: map[string]string{ - "instance_id": identityDocParsed.InstanceID, - "region": identityDocParsed.Region, - "account_id": identityDocParsed.AccountID, "role_tag_max_ttl": rTagMaxTTL.String(), "role": roleName, - "ami_id": identityDocParsed.AmiID, }, Alias: &logical.Alias{ Name: identityAlias, }, } roleEntry.PopulateTokenAuth(auth) - if err := identityConfigEntry.EC2AliasMetadataHandler.PopulateDesiredAliasMetadata(auth, map[string]string{ + if err := identityConfigEntry.EC2AuthMetadataHandler.PopulateDesiredMetadata(auth, map[string]string{ "instance_id": identityDocParsed.InstanceID, "region": identityDocParsed.Region, "account_id": identityDocParsed.AccountID, @@ -1360,15 +1356,7 @@ func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request, auth := &logical.Auth{ Metadata: map[string]string{ - "client_arn": callerID.Arn, - "canonical_arn": entity.canonicalArn(), - "client_user_id": callerUniqueId, - "auth_type": iamAuthType, - "inferred_entity_type": inferredEntityType, - "inferred_entity_id": inferredEntityID, - "inferred_aws_region": roleEntry.InferredAWSRegion, - "account_id": entity.AccountNumber, - "role_id": roleEntry.RoleID, + "role_id": roleEntry.RoleID, }, InternalData: map[string]interface{}{ "role_name": roleName, @@ -1380,7 +1368,7 @@ func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request, }, } roleEntry.PopulateTokenAuth(auth) - if err := identityConfigEntry.IAMAliasMetadataHandler.PopulateDesiredAliasMetadata(auth, map[string]string{ + if err := identityConfigEntry.IAMAuthMetadataHandler.PopulateDesiredMetadata(auth, map[string]string{ "client_arn": callerID.Arn, "canonical_arn": entity.canonicalArn(), "client_user_id": callerUniqueId, diff --git a/builtin/credential/aws/path_login_test.go b/builtin/credential/aws/path_login_test.go index 7cdcf0b0f66db..ac29f1d298646 100644 --- a/builtin/credential/aws/path_login_test.go +++ b/builtin/credential/aws/path_login_test.go @@ -263,7 +263,7 @@ func TestBackend_pathLogin_IAMHeaders(t *testing.T) { t.Fatal(err) } - expectedAliasMetadata := map[string]string{ + expectedAuthMetadata := map[string]string{ "account_id": "123456789012", "auth_type": "iam", "canonical_arn": "arn:aws:iam::123456789012:user/valid-role", @@ -370,8 +370,8 @@ func TestBackend_pathLogin_IAMHeaders(t *testing.T) { t.Errorf("un expected failed login:\nresp: %#v\n\nerr: %v", resp, err) } - if !reflect.DeepEqual(expectedAliasMetadata, resp.Auth.Alias.Metadata) { - t.Errorf("expected metadata (%#v) to match (%#v)", expectedAliasMetadata, resp.Auth.Alias.Metadata) + if !reflect.DeepEqual(expectedAuthMetadata, resp.Auth.Alias.Metadata) { + t.Errorf("expected metadata (%#v) to match (%#v)", expectedAuthMetadata, resp.Auth.Alias.Metadata) } }) } diff --git a/sdk/helper/aliasmetadata/alias_metadata.go b/sdk/helper/authmetadata/auth_metadata.go similarity index 61% rename from sdk/helper/aliasmetadata/alias_metadata.go rename to sdk/helper/authmetadata/auth_metadata.go index aeeaab9d7f74e..a005d0e525e34 100644 --- a/sdk/helper/aliasmetadata/alias_metadata.go +++ b/sdk/helper/authmetadata/auth_metadata.go @@ -1,10 +1,10 @@ -package aliasmetadata +package authmetadata /* - aliasmetadata is a package offering convenience and - standardization when supporting an `alias_metadata` + authmetadata is a package offering convenience and + standardization when supporting an `auth_metadata` field in a plugin's configuration. This then controls - what alias metadata is added to an Auth during login. + what metadata is added to an Auth during login. To see an example of how to add and use it, check out how these structs and fields are used in the AWS auth @@ -52,14 +52,14 @@ func (f *Fields) all() []string { // FieldSchema takes the default and additionally available // fields, and uses them to generate a verbose description -// regarding how to use the "alias_metadata" field. +// regarding how to use the "auth_metadata" field. func FieldSchema(fields *Fields) *framework.FieldSchema { return &framework.FieldSchema{ Type: framework.TypeCommaStringSlice, Description: description(fields), DisplayAttrs: &framework.DisplayAttributes{ Name: fields.FieldName, - Value: "default,field1,field2", + Value: "field1,field2", }, Default: []string{"default"}, } @@ -72,35 +72,32 @@ func NewHandler(fields *Fields) *Handler { } type Handler struct { - // aliasMetadata is an explicit list of all the user's configured - // fields that are being added to alias metadata. It will never - // include the "default" parameter, and instead includes the actual - // fields behind "default", if selected. If it has never been set - // or only the default fields are desired, it is nil. - aliasMetadata []string + // authMetadata is an explicit list of all the user's configured + // fields that are being added to auth metadata. If it is set to + // default or unconfigured, it will be nil. Otherwise, it will + // hold the explicit fields set by the user. + authMetadata []string // fields is a list of the configured default and available - // fields. It's intentionally not jsonified. + // fields. fields *Fields } -// AliasMetadata is intended to be used on config reads. +// AuthMetadata is intended to be used on config reads. // It gets an explicit list of all the user's configured -// fields that are being added to alias metadata. It will never -// include the "default" parameter, and instead includes the actual -// fields behind "default", if selected. -func (h *Handler) AliasMetadata() []string { - if h.aliasMetadata == nil { +// fields that are being added to auth metadata. +func (h *Handler) AuthMetadata() []string { + if h.authMetadata == nil { return h.fields.Default } - return h.aliasMetadata + return h.authMetadata } -// ParseAliasMetadata is intended to be used on config create/update. +// ParseAuthMetadata is intended to be used on config create/update. // It takes a user's selected fields (or lack thereof), // converts it to a list of explicit fields, and adds it to the Handler // for later storage. -func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { +func (h *Handler) ParseAuthMetadata(data *framework.FieldData) error { userProvidedRaw, ok := data.GetOk(h.fields.FieldName) if !ok { // Nothing further to do here. @@ -116,7 +113,7 @@ func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { // we don't store anything so we won't have to do a storage // migration if the default changes. if len(userProvided) == 1 && userProvided[0] == "default" { - h.aliasMetadata = nil + h.authMetadata = nil return nil } @@ -129,50 +126,34 @@ func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { return fmt.Errorf("%q contains an unavailable field, please select from %q", strings.Join(userProvided, ", "), strings.Join(h.fields.all(), ", ")) } - h.aliasMetadata = userProvided + h.authMetadata = userProvided return nil } -// expandDefaultField looks for the field "default" and, if exists, -// replaces it with the actual default fields signified. -func (h *Handler) expandDefaultField(userProvided []string) (expanded []string) { - for _, field := range userProvided { - if field != "default" { - expanded = append(expanded, field) - continue - } - for _, dfltField := range h.fields.Default { - expanded = append(expanded, dfltField) - } - } - return expanded -} - -// PopulateDesiredAliasMetadata is intended to be used during login +// PopulateDesiredMetadata is intended to be used during login // just before returning an auth. -// It takes the available alias metadata and, -// if the auth should have it, adds it to the auth's alias metadata. -func (h *Handler) PopulateDesiredAliasMetadata(auth *logical.Auth, available map[string]string) error { +// It takes the available auth metadata and, +// if the auth should have it, adds it to the auth's metadata. +func (h *Handler) PopulateDesiredMetadata(auth *logical.Auth, available map[string]string) error { if auth == nil { return errors.New("auth is nil") } - if auth.Alias == nil { - return errors.New("auth alias is nil") + if auth.Metadata == nil { + auth.Metadata = make(map[string]string) } - if auth.Alias.Name == "" { - // We need the caller to set the alias name or there will - // be nothing for these fields to operate upon. - return errors.New("auth alias name must be set") + if auth.Alias == nil { + auth.Alias = &logical.Alias{} } if auth.Alias.Metadata == nil { auth.Alias.Metadata = make(map[string]string) } fieldsToInclude := h.fields.Default - if h.aliasMetadata != nil { - fieldsToInclude = h.aliasMetadata + if h.authMetadata != nil { + fieldsToInclude = h.authMetadata } for availableField, itsValue := range available { if strutil.StrListContains(fieldsToInclude, availableField) { + auth.Metadata[availableField] = itsValue auth.Alias.Metadata[availableField] = itsValue } } @@ -181,27 +162,27 @@ func (h *Handler) PopulateDesiredAliasMetadata(auth *logical.Auth, available map func (h *Handler) MarshalJSON() ([]byte, error) { return json.Marshal(&struct { - AliasMetadata []string `json:"alias_metadata"` + AuthMetadata []string `json:"auth_metadata"` }{ - AliasMetadata: h.aliasMetadata, + AuthMetadata: h.authMetadata, }) } func (h *Handler) UnmarshalJSON(data []byte) error { jsonable := &struct { - AliasMetadata []string `json:"alias_metadata"` + AuthMetadata []string `json:"auth_metadata"` }{ - AliasMetadata: h.aliasMetadata, + AuthMetadata: h.authMetadata, } if err := json.Unmarshal(data, jsonable); err != nil { return err } - h.aliasMetadata = jsonable.AliasMetadata + h.authMetadata = jsonable.AuthMetadata return nil } func description(fields *Fields) string { - desc := "The metadata to include on the aliases generated by this plugin." + desc := "The metadata to include on the aliases and audit logs generated by this plugin." if len(fields.Default) > 0 { desc += fmt.Sprintf(" When set to 'default', includes: %s.", strings.Join(fields.Default, ", ")) } @@ -209,8 +190,7 @@ func description(fields *Fields) string { desc += fmt.Sprintf(" These fields are available to add: %s.", strings.Join(fields.AvailableToAdd, ", ")) } desc += " Not editing this field means the 'default' fields are included." + - " Explicitly setting this field to empty overrides the 'default' and means no alias metadata will be included." + - " Add fields by sending, 'default,field1,field2'." + - " We advise only including fields that change rarely because each change triggers a storage write." + " Explicitly setting this field to empty overrides the 'default' and means no metadata will be included." + + " If not using 'default', explicit fields must be sent like: 'field1,field2'." return desc } diff --git a/sdk/helper/aliasmetadata/alias_metadata_acc_test.go b/sdk/helper/authmetadata/auth_metadata_acc_test.go similarity index 88% rename from sdk/helper/aliasmetadata/alias_metadata_acc_test.go rename to sdk/helper/authmetadata/auth_metadata_acc_test.go index c6498dc8821f7..39888c69a16cb 100644 --- a/sdk/helper/aliasmetadata/alias_metadata_acc_test.go +++ b/sdk/helper/authmetadata/auth_metadata_acc_test.go @@ -1,4 +1,4 @@ -package aliasmetadata +package authmetadata import ( "context" @@ -30,7 +30,7 @@ func TestAcceptance(t *testing.T) { backend: b, } t.Run("test initial fields are default", env.TestInitialFieldsAreDefault) - t.Run("test fields can be unset", env.TestAliasMetadataCanBeUnset) + t.Run("test fields can be unset", env.TestAuthMetadataCanBeUnset) t.Run("test defaults can be restored", env.TestDefaultCanBeReused) t.Run("test default plus more cannot be selected", env.TestDefaultPlusMoreCannotBeSelected) t.Run("test only non-defaults can be selected", env.TestOnlyNonDefaultsCanBeSelected) @@ -38,7 +38,7 @@ func TestAcceptance(t *testing.T) { } func (e *environment) TestInitialFieldsAreDefault(t *testing.T) { - // On the first read of alias_metadata, when nothing has been touched, + // On the first read of auth_metadata, when nothing has been touched, // we should receive the default field(s) if a read is performed. resp, err := e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.ReadOperation, @@ -54,7 +54,7 @@ func (e *environment) TestInitialFieldsAreDefault(t *testing.T) { if resp == nil || resp.Data == nil { t.Fatal("expected non-nil response") } - if !reflect.DeepEqual(resp.Data[aliasMetadataFields.FieldName], []string{"role_name"}) { + if !reflect.DeepEqual(resp.Data[authMetadataFields.FieldName], []string{"role_name"}) { t.Fatal("expected default field of role_name to be returned") } @@ -84,8 +84,8 @@ func (e *environment) TestInitialFieldsAreDefault(t *testing.T) { } } -func (e *environment) TestAliasMetadataCanBeUnset(t *testing.T) { - // We should be able to set the alias_metadata to empty by sending an +func (e *environment) TestAuthMetadataCanBeUnset(t *testing.T) { + // We should be able to set the auth_metadata to empty by sending an // explicitly empty array. resp, err := e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.UpdateOperation, @@ -95,7 +95,7 @@ func (e *environment) TestAliasMetadataCanBeUnset(t *testing.T) { RemoteAddr: "http://foo.com", }, Data: map[string]interface{}{ - aliasMetadataFields.FieldName: []string{}, + authMetadataFields.FieldName: []string{}, }, }) if err != nil { @@ -105,7 +105,7 @@ func (e *environment) TestAliasMetadataCanBeUnset(t *testing.T) { t.Fatal("expected nil response") } - // Now we should receive no fields for alias_metadata. + // Now we should receive no fields for auth_metadata. resp, err = e.backend.HandleRequest(e.ctx, &logical.Request{ Operation: logical.ReadOperation, Path: "config", @@ -120,7 +120,7 @@ func (e *environment) TestAliasMetadataCanBeUnset(t *testing.T) { if resp == nil || resp.Data == nil { t.Fatal("expected non-nil response") } - if !reflect.DeepEqual(resp.Data[aliasMetadataFields.FieldName], []string{}) { + if !reflect.DeepEqual(resp.Data[authMetadataFields.FieldName], []string{}) { t.Fatal("expected no fields to be returned") } @@ -158,7 +158,7 @@ func (e *environment) TestDefaultCanBeReused(t *testing.T) { RemoteAddr: "http://foo.com", }, Data: map[string]interface{}{ - aliasMetadataFields.FieldName: []string{"default"}, + authMetadataFields.FieldName: []string{"default"}, }, }) if err != nil { @@ -183,7 +183,7 @@ func (e *environment) TestDefaultCanBeReused(t *testing.T) { if resp == nil || resp.Data == nil { t.Fatal("expected non-nil response") } - if !reflect.DeepEqual(resp.Data[aliasMetadataFields.FieldName], []string{"role_name"}) { + if !reflect.DeepEqual(resp.Data[authMetadataFields.FieldName], []string{"role_name"}) { t.Fatal("expected default field of role_name to be returned") } @@ -223,7 +223,7 @@ func (e *environment) TestDefaultPlusMoreCannotBeSelected(t *testing.T) { RemoteAddr: "http://foo.com", }, Data: map[string]interface{}{ - aliasMetadataFields.FieldName: []string{"default", "remote_addr"}, + authMetadataFields.FieldName: []string{"default", "remote_addr"}, }, }) if err == nil { @@ -241,7 +241,7 @@ func (e *environment) TestOnlyNonDefaultsCanBeSelected(t *testing.T) { RemoteAddr: "http://foo.com", }, Data: map[string]interface{}{ - aliasMetadataFields.FieldName: []string{"remote_addr"}, + authMetadataFields.FieldName: []string{"remote_addr"}, }, }) if err != nil { @@ -266,7 +266,7 @@ func (e *environment) TestOnlyNonDefaultsCanBeSelected(t *testing.T) { if resp == nil || resp.Data == nil { t.Fatal("expected non-nil response") } - if !reflect.DeepEqual(resp.Data[aliasMetadataFields.FieldName], []string{"remote_addr"}) { + if !reflect.DeepEqual(resp.Data[authMetadataFields.FieldName], []string{"remote_addr"}) { t.Fatal("expected remote_addr to be returned") } @@ -307,7 +307,7 @@ func (e *environment) TestAddingBadField(t *testing.T) { RemoteAddr: "http://foo.com", }, Data: map[string]interface{}{ - aliasMetadataFields.FieldName: []string{"asl;dfkj"}, + authMetadataFields.FieldName: []string{"asl;dfkj"}, }, }) if err == nil { @@ -328,7 +328,7 @@ func (e *environment) TestAddingBadField(t *testing.T) { // automatically being named "Handler" by Go's JSON // marshalling library. type fakeConfig struct { - *Handler `json:"alias_metadata_handler"` + *Handler `json:"auth_metadata_handler"` } type fakeBackend struct { @@ -337,7 +337,7 @@ type fakeBackend struct { // We expect each back-end to explicitly define the fields that // will be included by default, and optionally available. -var aliasMetadataFields = &Fields{ +var authMetadataFields = &Fields{ FieldName: "some_field_name", Default: []string{ "role_name", // This would likely never change because the alias is the role name. @@ -351,7 +351,7 @@ func configPath() *framework.Path { return &framework.Path{ Pattern: "config", Fields: map[string]*framework.FieldSchema{ - aliasMetadataFields.FieldName: FieldSchema(aliasMetadataFields), + authMetadataFields.FieldName: FieldSchema(authMetadataFields), }, Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ @@ -361,7 +361,7 @@ func configPath() *framework.Path { return nil, err } conf := &fakeConfig{ - Handler: NewHandler(aliasMetadataFields), + Handler: NewHandler(authMetadataFields), } if entryRaw != nil { if err := entryRaw.DecodeJSON(conf); err != nil { @@ -370,10 +370,10 @@ func configPath() *framework.Path { } // Note that even if the config entry was nil, we return // a populated response to give info on what the default - // alias metadata is when unconfigured. + // auth metadata is when unconfigured. return &logical.Response{ Data: map[string]interface{}{ - aliasMetadataFields.FieldName: conf.AliasMetadata(), + authMetadataFields.FieldName: conf.AuthMetadata(), }, }, nil }, @@ -385,15 +385,15 @@ func configPath() *framework.Path { return nil, err } conf := &fakeConfig{ - Handler: NewHandler(aliasMetadataFields), + Handler: NewHandler(authMetadataFields), } if entryRaw != nil { if err := entryRaw.DecodeJSON(conf); err != nil { return nil, err } } - // This is where we read in the user's given alias metadata. - if err := conf.ParseAliasMetadata(fd); err != nil { + // This is where we read in the user's given auth metadata. + if err := conf.ParseAuthMetadata(fd); err != nil { // Since this will only error on bad input, it's best to give // a 400 response with the explicit problem included. return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest @@ -429,7 +429,7 @@ func loginPath() *framework.Path { return nil, err } conf := &fakeConfig{ - Handler: NewHandler(aliasMetadataFields), + Handler: NewHandler(authMetadataFields), } if entryRaw != nil { if err := entryRaw.DecodeJSON(conf); err != nil { @@ -443,7 +443,7 @@ func loginPath() *framework.Path { } // Here we provide everything and let the method strip out // the undesired stuff. - if err := conf.PopulateDesiredAliasMetadata(auth, map[string]string{ + if err := conf.PopulateDesiredMetadata(auth, map[string]string{ "role_name": fd.Get("role_name").(string), "remote_addr": req.Connection.RemoteAddr, }); err != nil { diff --git a/sdk/helper/aliasmetadata/alias_metadata_test.go b/sdk/helper/authmetadata/auth_metadata_test.go similarity index 66% rename from sdk/helper/aliasmetadata/alias_metadata_test.go rename to sdk/helper/authmetadata/auth_metadata_test.go index dd0f488eb204b..2d037aaa6bd02 100644 --- a/sdk/helper/aliasmetadata/alias_metadata_test.go +++ b/sdk/helper/authmetadata/auth_metadata_test.go @@ -1,4 +1,4 @@ -package aliasmetadata +package authmetadata import ( "encoding/json" @@ -22,7 +22,7 @@ func TestFieldSchema(t *testing.T) { if schema.Type != framework.TypeCommaStringSlice { t.Fatal("expected TypeCommaStringSlice") } - if schema.Description != `The metadata to include on the aliases generated by this plugin. When set to 'default', includes: fizz, buzz. These fields are available to add: foo, bar. Not editing this field means the 'default' fields are included. Explicitly setting this field to empty overrides the 'default' and means no alias metadata will be included. Add fields by sending, 'default,field1,field2'. We advise only including fields that change rarely because each change triggers a storage write.` { + if schema.Description != `The metadata to include on the aliases and audit logs generated by this plugin. When set to 'default', includes: fizz, buzz. These fields are available to add: foo, bar. Not editing this field means the 'default' fields are included. Explicitly setting this field to empty overrides the 'default' and means no metadata will be included. If not using 'default', explicit fields must be sent like: 'field1,field2'.` { t.Fatal("received unexpected description: " + schema.Description) } if schema.DisplayAttrs == nil { @@ -31,26 +31,26 @@ func TestFieldSchema(t *testing.T) { if schema.DisplayAttrs.Name != testFields.FieldName { t.Fatalf("expected name of %s", testFields.FieldName) } - if schema.DisplayAttrs.Value != "default,field1,field2" { - t.Fatal("expected default,field1,field2") + if schema.DisplayAttrs.Value != "field1,field2" { + t.Fatal("expected field1,field2") } if !reflect.DeepEqual(schema.Default, []string{"default"}) { t.Fatal("expected default") } } -func TestGetAliasMetadata(t *testing.T) { +func TestGetAuthMetadata(t *testing.T) { h := NewHandler(testFields) expected := []string{"fizz", "buzz"} sort.Strings(expected) - actual := h.AliasMetadata() + actual := h.AuthMetadata() sort.Strings(actual) if !reflect.DeepEqual(expected, actual) { t.Fatalf("expected %s but received %s", expected, actual) } } -func TestParseAliasMetadata(t *testing.T) { +func TestParseAuthMetadata(t *testing.T) { h := NewHandler(testFields) data := &framework.FieldData{ Raw: map[string]interface{}{ @@ -60,19 +60,19 @@ func TestParseAliasMetadata(t *testing.T) { testFields.FieldName: FieldSchema(testFields), }, } - if err := h.ParseAliasMetadata(data); err != nil { + if err := h.ParseAuthMetadata(data); err != nil { t.Fatal(err) } expected := []string{"fizz", "buzz"} sort.Strings(expected) - actual := h.AliasMetadata() + actual := h.AuthMetadata() sort.Strings(actual) if !reflect.DeepEqual(expected, actual) { t.Fatalf("expected %s but received %s", expected, actual) } } -func TestPopulateDesiredAliasMetadata(t *testing.T) { +func TestPopulateDesiredAuthMetadata(t *testing.T) { h := NewHandler(testFields) data := &framework.FieldData{ Raw: map[string]interface{}{ @@ -82,7 +82,7 @@ func TestPopulateDesiredAliasMetadata(t *testing.T) { testFields.FieldName: FieldSchema(testFields), }, } - if err := h.ParseAliasMetadata(data); err != nil { + if err := h.ParseAuthMetadata(data); err != nil { t.Fatal(err) } auth := &logical.Auth{ @@ -90,7 +90,7 @@ func TestPopulateDesiredAliasMetadata(t *testing.T) { Name: "foo", }, } - if err := h.PopulateDesiredAliasMetadata(auth, map[string]string{ + if err := h.PopulateDesiredMetadata(auth, map[string]string{ "fizz": "fizzval", "buzz": "buzzval", "foo": "fooval", @@ -107,22 +107,22 @@ func TestPopulateDesiredAliasMetadata(t *testing.T) { func TestMarshalJSON(t *testing.T) { h := NewHandler(&Fields{}) - h.aliasMetadata = []string{"fizz", "buzz"} + h.authMetadata = []string{"fizz", "buzz"} b, err := json.Marshal(h) if err != nil { t.Fatal(err) } - if string(b) != `{"alias_metadata":["fizz","buzz"]}` { - t.Fatal(`expected {"alias_metadata":["fizz","buzz"]}`) + if string(b) != `{"auth_metadata":["fizz","buzz"]}` { + t.Fatal(`expected {"auth_metadata":["fizz","buzz"]}`) } } func TestUnmarshalJSON(t *testing.T) { h := NewHandler(&Fields{}) - if err := json.Unmarshal([]byte(`{"alias_metadata":["fizz","buzz"]}`), h); err != nil { + if err := json.Unmarshal([]byte(`{"auth_metadata":["fizz","buzz"]}`), h); err != nil { t.Fatal(err) } - if fmt.Sprintf("%s", h.aliasMetadata) != `[fizz buzz]` { + if fmt.Sprintf("%s", h.authMetadata) != `[fizz buzz]` { t.Fatal(`expected [fizz buzz]`) } } diff --git a/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go b/vendor/github.com/hashicorp/vault/sdk/helper/authmetadata/auth_metadata.go similarity index 61% rename from vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go rename to vendor/github.com/hashicorp/vault/sdk/helper/authmetadata/auth_metadata.go index aeeaab9d7f74e..a005d0e525e34 100644 --- a/vendor/github.com/hashicorp/vault/sdk/helper/aliasmetadata/alias_metadata.go +++ b/vendor/github.com/hashicorp/vault/sdk/helper/authmetadata/auth_metadata.go @@ -1,10 +1,10 @@ -package aliasmetadata +package authmetadata /* - aliasmetadata is a package offering convenience and - standardization when supporting an `alias_metadata` + authmetadata is a package offering convenience and + standardization when supporting an `auth_metadata` field in a plugin's configuration. This then controls - what alias metadata is added to an Auth during login. + what metadata is added to an Auth during login. To see an example of how to add and use it, check out how these structs and fields are used in the AWS auth @@ -52,14 +52,14 @@ func (f *Fields) all() []string { // FieldSchema takes the default and additionally available // fields, and uses them to generate a verbose description -// regarding how to use the "alias_metadata" field. +// regarding how to use the "auth_metadata" field. func FieldSchema(fields *Fields) *framework.FieldSchema { return &framework.FieldSchema{ Type: framework.TypeCommaStringSlice, Description: description(fields), DisplayAttrs: &framework.DisplayAttributes{ Name: fields.FieldName, - Value: "default,field1,field2", + Value: "field1,field2", }, Default: []string{"default"}, } @@ -72,35 +72,32 @@ func NewHandler(fields *Fields) *Handler { } type Handler struct { - // aliasMetadata is an explicit list of all the user's configured - // fields that are being added to alias metadata. It will never - // include the "default" parameter, and instead includes the actual - // fields behind "default", if selected. If it has never been set - // or only the default fields are desired, it is nil. - aliasMetadata []string + // authMetadata is an explicit list of all the user's configured + // fields that are being added to auth metadata. If it is set to + // default or unconfigured, it will be nil. Otherwise, it will + // hold the explicit fields set by the user. + authMetadata []string // fields is a list of the configured default and available - // fields. It's intentionally not jsonified. + // fields. fields *Fields } -// AliasMetadata is intended to be used on config reads. +// AuthMetadata is intended to be used on config reads. // It gets an explicit list of all the user's configured -// fields that are being added to alias metadata. It will never -// include the "default" parameter, and instead includes the actual -// fields behind "default", if selected. -func (h *Handler) AliasMetadata() []string { - if h.aliasMetadata == nil { +// fields that are being added to auth metadata. +func (h *Handler) AuthMetadata() []string { + if h.authMetadata == nil { return h.fields.Default } - return h.aliasMetadata + return h.authMetadata } -// ParseAliasMetadata is intended to be used on config create/update. +// ParseAuthMetadata is intended to be used on config create/update. // It takes a user's selected fields (or lack thereof), // converts it to a list of explicit fields, and adds it to the Handler // for later storage. -func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { +func (h *Handler) ParseAuthMetadata(data *framework.FieldData) error { userProvidedRaw, ok := data.GetOk(h.fields.FieldName) if !ok { // Nothing further to do here. @@ -116,7 +113,7 @@ func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { // we don't store anything so we won't have to do a storage // migration if the default changes. if len(userProvided) == 1 && userProvided[0] == "default" { - h.aliasMetadata = nil + h.authMetadata = nil return nil } @@ -129,50 +126,34 @@ func (h *Handler) ParseAliasMetadata(data *framework.FieldData) error { return fmt.Errorf("%q contains an unavailable field, please select from %q", strings.Join(userProvided, ", "), strings.Join(h.fields.all(), ", ")) } - h.aliasMetadata = userProvided + h.authMetadata = userProvided return nil } -// expandDefaultField looks for the field "default" and, if exists, -// replaces it with the actual default fields signified. -func (h *Handler) expandDefaultField(userProvided []string) (expanded []string) { - for _, field := range userProvided { - if field != "default" { - expanded = append(expanded, field) - continue - } - for _, dfltField := range h.fields.Default { - expanded = append(expanded, dfltField) - } - } - return expanded -} - -// PopulateDesiredAliasMetadata is intended to be used during login +// PopulateDesiredMetadata is intended to be used during login // just before returning an auth. -// It takes the available alias metadata and, -// if the auth should have it, adds it to the auth's alias metadata. -func (h *Handler) PopulateDesiredAliasMetadata(auth *logical.Auth, available map[string]string) error { +// It takes the available auth metadata and, +// if the auth should have it, adds it to the auth's metadata. +func (h *Handler) PopulateDesiredMetadata(auth *logical.Auth, available map[string]string) error { if auth == nil { return errors.New("auth is nil") } - if auth.Alias == nil { - return errors.New("auth alias is nil") + if auth.Metadata == nil { + auth.Metadata = make(map[string]string) } - if auth.Alias.Name == "" { - // We need the caller to set the alias name or there will - // be nothing for these fields to operate upon. - return errors.New("auth alias name must be set") + if auth.Alias == nil { + auth.Alias = &logical.Alias{} } if auth.Alias.Metadata == nil { auth.Alias.Metadata = make(map[string]string) } fieldsToInclude := h.fields.Default - if h.aliasMetadata != nil { - fieldsToInclude = h.aliasMetadata + if h.authMetadata != nil { + fieldsToInclude = h.authMetadata } for availableField, itsValue := range available { if strutil.StrListContains(fieldsToInclude, availableField) { + auth.Metadata[availableField] = itsValue auth.Alias.Metadata[availableField] = itsValue } } @@ -181,27 +162,27 @@ func (h *Handler) PopulateDesiredAliasMetadata(auth *logical.Auth, available map func (h *Handler) MarshalJSON() ([]byte, error) { return json.Marshal(&struct { - AliasMetadata []string `json:"alias_metadata"` + AuthMetadata []string `json:"auth_metadata"` }{ - AliasMetadata: h.aliasMetadata, + AuthMetadata: h.authMetadata, }) } func (h *Handler) UnmarshalJSON(data []byte) error { jsonable := &struct { - AliasMetadata []string `json:"alias_metadata"` + AuthMetadata []string `json:"auth_metadata"` }{ - AliasMetadata: h.aliasMetadata, + AuthMetadata: h.authMetadata, } if err := json.Unmarshal(data, jsonable); err != nil { return err } - h.aliasMetadata = jsonable.AliasMetadata + h.authMetadata = jsonable.AuthMetadata return nil } func description(fields *Fields) string { - desc := "The metadata to include on the aliases generated by this plugin." + desc := "The metadata to include on the aliases and audit logs generated by this plugin." if len(fields.Default) > 0 { desc += fmt.Sprintf(" When set to 'default', includes: %s.", strings.Join(fields.Default, ", ")) } @@ -209,8 +190,7 @@ func description(fields *Fields) string { desc += fmt.Sprintf(" These fields are available to add: %s.", strings.Join(fields.AvailableToAdd, ", ")) } desc += " Not editing this field means the 'default' fields are included." + - " Explicitly setting this field to empty overrides the 'default' and means no alias metadata will be included." + - " Add fields by sending, 'default,field1,field2'." + - " We advise only including fields that change rarely because each change triggers a storage write." + " Explicitly setting this field to empty overrides the 'default' and means no metadata will be included." + + " If not using 'default', explicit fields must be sent like: 'field1,field2'." return desc } From f297a499ffe66935354a5eeb9d5dc07886a9fc4d Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Fri, 24 Apr 2020 14:46:55 -0700 Subject: [PATCH 16/18] update how jsonification is tested --- sdk/helper/authmetadata/auth_metadata_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sdk/helper/authmetadata/auth_metadata_test.go b/sdk/helper/authmetadata/auth_metadata_test.go index 2d037aaa6bd02..62341ebc85fbb 100644 --- a/sdk/helper/authmetadata/auth_metadata_test.go +++ b/sdk/helper/authmetadata/auth_metadata_test.go @@ -1,7 +1,6 @@ package authmetadata import ( - "encoding/json" "fmt" "reflect" "sort" @@ -108,7 +107,7 @@ func TestPopulateDesiredAuthMetadata(t *testing.T) { func TestMarshalJSON(t *testing.T) { h := NewHandler(&Fields{}) h.authMetadata = []string{"fizz", "buzz"} - b, err := json.Marshal(h) + b, err := h.MarshalJSON() if err != nil { t.Fatal(err) } @@ -119,7 +118,7 @@ func TestMarshalJSON(t *testing.T) { func TestUnmarshalJSON(t *testing.T) { h := NewHandler(&Fields{}) - if err := json.Unmarshal([]byte(`{"auth_metadata":["fizz","buzz"]}`), h); err != nil { + if err := h.UnmarshalJSON([]byte(`{"auth_metadata":["fizz","buzz"]}`)); err != nil { t.Fatal(err) } if fmt.Sprintf("%s", h.authMetadata) != `[fizz buzz]` { From 556f563c5b53a74e58828cea8a92a2afd86a4d76 Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Mon, 27 Apr 2020 09:53:59 -0700 Subject: [PATCH 17/18] only add populated metadata values --- sdk/helper/authmetadata/auth_metadata.go | 4 ++++ .../hashicorp/vault/sdk/helper/authmetadata/auth_metadata.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/sdk/helper/authmetadata/auth_metadata.go b/sdk/helper/authmetadata/auth_metadata.go index a005d0e525e34..c1e4e93d59369 100644 --- a/sdk/helper/authmetadata/auth_metadata.go +++ b/sdk/helper/authmetadata/auth_metadata.go @@ -152,6 +152,10 @@ func (h *Handler) PopulateDesiredMetadata(auth *logical.Auth, available map[stri fieldsToInclude = h.authMetadata } for availableField, itsValue := range available { + if itsValue == "" { + // Don't bother setting fields for which there is no value. + continue + } if strutil.StrListContains(fieldsToInclude, availableField) { auth.Metadata[availableField] = itsValue auth.Alias.Metadata[availableField] = itsValue diff --git a/vendor/github.com/hashicorp/vault/sdk/helper/authmetadata/auth_metadata.go b/vendor/github.com/hashicorp/vault/sdk/helper/authmetadata/auth_metadata.go index a005d0e525e34..c1e4e93d59369 100644 --- a/vendor/github.com/hashicorp/vault/sdk/helper/authmetadata/auth_metadata.go +++ b/vendor/github.com/hashicorp/vault/sdk/helper/authmetadata/auth_metadata.go @@ -152,6 +152,10 @@ func (h *Handler) PopulateDesiredMetadata(auth *logical.Auth, available map[stri fieldsToInclude = h.authMetadata } for availableField, itsValue := range available { + if itsValue == "" { + // Don't bother setting fields for which there is no value. + continue + } if strutil.StrListContains(fieldsToInclude, availableField) { auth.Metadata[availableField] = itsValue auth.Alias.Metadata[availableField] = itsValue From 2a6fe2ede8cbc0ed9a930dd076e97c935c3396ec Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Mon, 27 Apr 2020 10:00:20 -0700 Subject: [PATCH 18/18] add auth_type to ec2 logins --- builtin/credential/aws/path_config_identity.go | 1 + builtin/credential/aws/path_login.go | 1 + builtin/credential/aws/path_login_test.go | 4 ---- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/builtin/credential/aws/path_config_identity.go b/builtin/credential/aws/path_config_identity.go index 6447ec29119b4..581aa0ac91e52 100644 --- a/builtin/credential/aws/path_config_identity.go +++ b/builtin/credential/aws/path_config_identity.go @@ -41,6 +41,7 @@ var ( FieldName: "ec2_metadata", Default: []string{ "account_id", + "auth_type", }, AvailableToAdd: []string{ "ami_id", diff --git a/builtin/credential/aws/path_login.go b/builtin/credential/aws/path_login.go index 3007ccd2c18c6..829317c9bd6de 100644 --- a/builtin/credential/aws/path_login.go +++ b/builtin/credential/aws/path_login.go @@ -849,6 +849,7 @@ func (b *backend) pathLoginUpdateEc2(ctx context.Context, req *logical.Request, "region": identityDocParsed.Region, "account_id": identityDocParsed.AccountID, "ami_id": identityDocParsed.AmiID, + "auth_type": ec2AuthType, }); err != nil { b.Logger().Warn("unable to set alias metadata", "err", err) } diff --git a/builtin/credential/aws/path_login_test.go b/builtin/credential/aws/path_login_test.go index ac29f1d298646..ef5a5aacb48a6 100644 --- a/builtin/credential/aws/path_login_test.go +++ b/builtin/credential/aws/path_login_test.go @@ -269,10 +269,6 @@ func TestBackend_pathLogin_IAMHeaders(t *testing.T) { "canonical_arn": "arn:aws:iam::123456789012:user/valid-role", "client_arn": "arn:aws:iam::123456789012:user/valid-role", "client_user_id": "ASOMETHINGSOMETHINGSOMETHING", - // Note there is no inferred entity, so these fields should be empty - "inferred_aws_region": "", - "inferred_entity_id": "", - "inferred_entity_type": "", } // expected errors for certain tests