Support clearing an identity alias' custom_metadata (#13395)

* Support clearing an identity alias' custom_metadata

Previously, an update to an entity alias supported updating the
custom_metadata as long as the update was not empty, which makes it
impossible to clear the metadata values completely.

Fixes:
- empty custom_metadata parameters are honoured on entity alias update
- update related tests
- drop dependency on mapstructure
- reformat with gofumpt
This commit is contained in:
Ben Ash 2021-12-10 18:07:47 -05:00 committed by GitHub
parent fe718e99d4
commit 6ec3367648
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 172 additions and 57 deletions

3
changelog/13395.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
core/identity: Support updating an alias' `custom_metadata` to be empty.
```

View File

@ -8,18 +8,20 @@ import (
"github.com/golang/protobuf/ptypes" "github.com/golang/protobuf/ptypes"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-secure-stdlib/strutil" "github.com/hashicorp/go-secure-stdlib/strutil"
"github.com/hashicorp/vault/helper/identity" "github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/helper/storagepacker" "github.com/hashicorp/vault/helper/storagepacker"
"github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/logical"
"github.com/mitchellh/mapstructure"
) )
const maxCustomMetadataKeys = 64 const (
const maxCustomMetadataKeyLength = 128 maxCustomMetadataKeys = 64
const maxCustomMetadataValueLength = 512 maxCustomMetadataKeyLength = 128
const customMetadataValidationErrorPrefix = "custom_metadata validation failed" maxCustomMetadataValueLength = 512
customMetadataValidationErrorPrefix = "custom_metadata validation failed"
)
// aliasPaths returns the API endpoints to operate on aliases. // aliasPaths returns the API endpoints to operate on aliases.
// Following are the paths supported: // Following are the paths supported:
@ -138,10 +140,7 @@ func (i *IdentityStore) handleAliasCreateUpdate() framework.OperationFunc {
customMetadata := make(map[string]string) customMetadata := make(map[string]string)
data, customMetadataExists := d.GetOk("custom_metadata") data, customMetadataExists := d.GetOk("custom_metadata")
if customMetadataExists { if customMetadataExists {
err = mapstructure.Decode(data, &customMetadata) customMetadata = data.(map[string]string)
if err != nil {
return nil, err
}
} }
// Get entity id // Get entity id
@ -151,11 +150,9 @@ func (i *IdentityStore) handleAliasCreateUpdate() framework.OperationFunc {
canonicalID = d.Get("entity_id").(string) canonicalID = d.Get("entity_id").(string)
} }
//validate customMetadata if provided // validate customMetadata if provided
if len(customMetadata) != 0 { if len(customMetadata) != 0 {
if err := validateCustomMetadata(customMetadata); err != nil {
err := validateCustomMetadata(customMetadata)
if err != nil {
return nil, err return nil, err
} }
} }
@ -178,8 +175,11 @@ func (i *IdentityStore) handleAliasCreateUpdate() framework.OperationFunc {
if alias.NamespaceID != ns.ID { if alias.NamespaceID != ns.ID {
return logical.ErrorResponse("cannot modify aliases across namespaces"), logical.ErrPermissionDenied return logical.ErrorResponse("cannot modify aliases across namespaces"), logical.ErrPermissionDenied
} }
if !customMetadataExists {
customMetadata = alias.CustomMetadata
}
switch { switch {
case mountAccessor == "" && name == "" && len(customMetadata) == 0: case mountAccessor == "" && name == "":
// Just a canonical ID update, maybe // Just a canonical ID update, maybe
if canonicalID == "" { if canonicalID == "" {
// Nothing to do, so be idempotent // Nothing to do, so be idempotent
@ -187,16 +187,12 @@ func (i *IdentityStore) handleAliasCreateUpdate() framework.OperationFunc {
} }
name = alias.Name name = alias.Name
mountAccessor = alias.MountAccessor mountAccessor = alias.MountAccessor
customMetadata = alias.CustomMetadata
case mountAccessor == "": case mountAccessor == "":
// No change to mount accessor // No change to mount accessor
mountAccessor = alias.MountAccessor mountAccessor = alias.MountAccessor
case name == "": case name == "":
// No change to mount name // No change to mount name
name = alias.Name name = alias.Name
case len(customMetadata) == 0:
// No change to custom metadata
customMetadata = alias.CustomMetadata
default: default:
// mountAccessor, name and customMetadata provided // mountAccessor, name and customMetadata provided
} }

View File

@ -355,55 +355,173 @@ func TestIdentityStore_AliasRegister(t *testing.T) {
} }
func TestIdentityStore_AliasUpdate(t *testing.T) { func TestIdentityStore_AliasUpdate(t *testing.T) {
var err error
var resp *logical.Response
ctx := namespace.RootContext(nil) ctx := namespace.RootContext(nil)
is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t) is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t)
aliasData := map[string]interface{}{ tests := []struct {
"name": "testaliasname", name string
"mount_accessor": githubAccessor, createData map[string]interface{}
updateData map[string]interface{}
}{
{
name: "noop",
createData: map[string]interface{}{
"name": "noop",
"mount_accessor": githubAccessor,
"custom_metadata": map[string]string{
"foo": "baz",
"bar": "qux",
},
},
updateData: map[string]interface{}{
"name": "noop",
"mount_accessor": githubAccessor,
"custom_metadata": map[string]string{
"foo": "baz",
"bar": "qux",
},
},
},
{
name: "no-custom-metadata",
createData: map[string]interface{}{
"name": "no-custom-metadata",
"mount_accessor": githubAccessor,
},
updateData: map[string]interface{}{
"name": "no-custom-metadata",
"mount_accessor": githubAccessor,
},
},
{
name: "update-name-custom-metadata",
createData: map[string]interface{}{
"name": "update-name-custom-metadata",
"mount_accessor": githubAccessor,
"custom_metadata": make(map[string]string),
},
updateData: map[string]interface{}{
"name": "update-name-custom-metadata-2",
"mount_accessor": githubAccessor,
"custom_metadata": map[string]string{
"bar": "qux",
},
},
},
{
name: "update-name",
createData: map[string]interface{}{
"name": "update-name",
"mount_accessor": githubAccessor,
"custom_metadata": map[string]string{
"foo": "baz",
},
},
updateData: map[string]interface{}{
"name": "update-name-2",
"mount_accessor": githubAccessor,
"custom_metadata": map[string]string{
"foo": "baz",
},
},
},
{
name: "update-metadata",
createData: map[string]interface{}{
"name": "update-metadata",
"mount_accessor": githubAccessor,
"custom_metadata": map[string]string{
"foo": "bar",
},
},
updateData: map[string]interface{}{
"name": "update-metadata",
"mount_accessor": githubAccessor,
"custom_metadata": map[string]string{
"foo": "baz",
"bar": "qux",
},
},
},
{
name: "clear-metadata",
createData: map[string]interface{}{
"name": "clear",
"mount_accessor": githubAccessor,
"custom_metadata": map[string]string{
"foo": "bar",
},
},
updateData: map[string]interface{}{
"name": "clear",
"mount_accessor": githubAccessor,
"custom_metadata": map[string]string{},
},
},
} }
aliasReq := &logical.Request{ handleRequest := func(t *testing.T, req *logical.Request) *logical.Response {
Operation: logical.UpdateOperation, t.Helper()
Path: "entity-alias", resp, err := is.HandleRequest(ctx, req)
Data: aliasData, if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
return resp
} }
// This will create an alias and a corresponding entity respDefaults := map[string]interface{}{
resp, err = is.HandleRequest(ctx, aliasReq) "custom_metadata": map[string]string{},
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
aliasID := resp.Data["id"].(string)
customMetadata := make(map[string]string)
customMetadata["foo"] = "abc"
updateData := map[string]interface{}{
"name": "updatedaliasname",
"mount_accessor": githubAccessor,
"custom_metadata": customMetadata,
} }
aliasReq.Data = updateData checkValues := func(t *testing.T, reqData, respData map[string]interface{}) {
aliasReq.Path = "entity-alias/id/" + aliasID t.Helper()
resp, err = is.HandleRequest(ctx, aliasReq) expected := make(map[string]interface{})
if err != nil || (resp != nil && resp.IsError()) { for k := range reqData {
t.Fatalf("err:%v resp:%#v", err, resp) expected[k] = reqData[k]
}
for k := range respDefaults {
if _, ok := expected[k]; !ok {
expected[k] = respDefaults[k]
}
}
actual := make(map[string]interface{}, len(expected))
for k := range expected {
actual[k] = respData[k]
}
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("expected data %#v, actual %#v", expected, actual)
}
} }
aliasReq.Operation = logical.ReadOperation for _, tt := range tests {
resp, err = is.HandleRequest(ctx, aliasReq) t.Run(tt.name, func(t *testing.T) {
if err != nil || (resp != nil && resp.IsError()) { resp := handleRequest(t, &logical.Request{
t.Fatalf("err:%v resp:%#v", err, resp) Operation: logical.UpdateOperation,
} Path: "entity-alias",
Data: tt.createData,
})
if resp.Data["name"] != "updatedaliasname" { readReq := &logical.Request{
t.Fatalf("failed to update alias information; \n response data: %#v\n", resp.Data) Operation: logical.ReadOperation,
} Path: "entity-alias/id/" + resp.Data["id"].(string),
if !reflect.DeepEqual(resp.Data["custom_metadata"], customMetadata) { }
t.Fatalf("failed to update alias information; \n response data: %#v\n", resp.Data)
// check create
resp = handleRequest(t, readReq)
checkValues(t, tt.createData, resp.Data)
// check update
resp = handleRequest(t, &logical.Request{
Operation: logical.UpdateOperation,
Path: readReq.Path,
Data: tt.updateData,
})
resp = handleRequest(t, readReq)
checkValues(t, tt.updateData, resp.Data)
})
} }
} }
@ -489,7 +607,6 @@ func TestIdentityStore_AliasMove_DuplicateAccessor(t *testing.T) {
if resp == nil || !resp.IsError() { if resp == nil || !resp.IsError() {
t.Fatalf("expected an error as alias on the github accessor exists for testentity1") t.Fatalf("expected an error as alias on the github accessor exists for testentity1")
} }
} }
// Test that the alias cannot be changed to a mount for which // Test that the alias cannot be changed to a mount for which
@ -563,7 +680,6 @@ func TestIdentityStore_AliasUpdate_DuplicateAccessor(t *testing.T) {
if resp == nil || !resp.IsError() { if resp == nil || !resp.IsError() {
t.Fatalf("expected an error as an alias on the github accessor already exists for testentity") t.Fatalf("expected an error as an alias on the github accessor already exists for testentity")
} }
} }
// Test that alias creation fails if an alias for the specified mount // Test that alias creation fails if an alias for the specified mount