Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

return bad request instead of server error for identity group cycle detection #15912

Merged
merged 5 commits into from Jun 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/15912.txt
@@ -0,0 +1,3 @@
```release-note:change
identity: a request to `/identity/group` that includes `member_group_ids` that contains a cycle will now be responded to with a 400 rather than 500
hghaf099 marked this conversation as resolved.
Show resolved Hide resolved
```
4 changes: 4 additions & 0 deletions vault/identity_store_groups.go
Expand Up @@ -255,6 +255,10 @@ func (i *IdentityStore) handleGroupUpdateCommon(ctx context.Context, req *logica

err = i.sanitizeAndUpsertGroup(ctx, group, nil, memberGroupIDs)
if err != nil {
if errStr := err.Error(); strings.HasPrefix(errStr, errCycleDetectedPrefix) {
return logical.ErrorResponse(errStr), nil
}

return nil, err
}

Expand Down
91 changes: 90 additions & 1 deletion vault/identity_store_groups_test.go
@@ -1,6 +1,7 @@
package vault

import (
"fmt"
"reflect"
"sort"
"strings"
Expand Down Expand Up @@ -1252,7 +1253,7 @@ func TestIdentityStore_GroupHierarchyCases(t *testing.T) {
groupUpdateReq.Data = kubeGroupData
kubeGroupData["member_group_ids"] = []string{engGroupID}
resp, err = is.HandleRequest(ctx, groupUpdateReq)
if err == nil {
if err != nil || resp == nil || !resp.IsError() {
t.Fatalf("expected an error response")
}

Expand Down Expand Up @@ -1391,3 +1392,91 @@ func TestIdentityStore_GroupHierarchyCases(t *testing.T) {
t.Fatalf("bad: length of inheritedGroups; expected: 0, actual: %d", len(inheritedGroups))
}
}

func TestIdentityStore_GroupCycleDetection(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ctx := namespace.RootContext(nil)

group1Name := "group1"
group2Name := "group2"
group3Name := "group3"

resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": group1Name,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("failed to create group %q, err: %v, resp: %#v", group1Name, err, resp)
}

group1Id := resp.Data["id"].(string)

resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": group2Name,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("failed to create group %q, err: %v, resp: %#v", group2Name, err, resp)
}

group2Id := resp.Data["id"].(string)

resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": group3Name,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("failed to create group %q, err: %v, resp: %#v", group3Name, err, resp)
}

group3Id := resp.Data["id"].(string)

resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": group1Name,
"member_group_ids": []string{},
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("failed to update group %q, err: %v, resp: %#v", group1Name, err, resp)
}

resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": group2Name,
"member_group_ids": []string{group3Id},
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("failed to update group %q, err: %v, resp: %#v", group2Name, err, resp)
}

resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": group3Name,
"member_group_ids": []string{group1Id, group2Id},
},
})

if err != nil || resp == nil {
t.Fatalf("unexpected group update error for group %q, err: %v, resp: %#v", group3Name, err, resp)
}
if !resp.IsError() || resp.Error().Error() != fmt.Sprintf("%s %q", errCycleDetectedPrefix, group2Id) {
t.Fatalf("expected update to group %q to fail due to cycle, resp: %#v", group3Id, resp)
}
}
5 changes: 3 additions & 2 deletions vault/identity_store_util.go
Expand Up @@ -24,6 +24,7 @@ import (

var (
errDuplicateIdentityName = errors.New("duplicate identity name")
errCycleDetectedPrefix = "cyclic relationship detected for member group ID"
tmpSuffix = ".tmp"
)

Expand Down Expand Up @@ -1578,7 +1579,7 @@ func (i *IdentityStore) sanitizeAndUpsertGroup(ctx context.Context, group *ident
return fmt.Errorf("failed to perform cyclic relationship detection for member group ID %q", memberGroupID)
}
if cycleDetected {
return fmt.Errorf("cyclic relationship detected for member group ID %q", memberGroupID)
return fmt.Errorf("%s %q", errCycleDetectedPrefix, memberGroupID)
}
}

Expand Down Expand Up @@ -2157,7 +2158,7 @@ func (i *IdentityStore) detectCycleDFS(visited map[string]bool, startingGroupID,
return false, fmt.Errorf("failed to perform cycle detection at member group ID %q", memberGroup.ID)
}
if cycleDetected {
return true, fmt.Errorf("cycle detected at member group ID %q", memberGroup.ID)
return true, nil
}
}

Expand Down