Skip to content

Commit

Permalink
return bad request instead of server error for identity group cycle d…
Browse files Browse the repository at this point in the history
…etection (#15912)

* return bad request for identity group cycle detection

* add changelog entry

* use change release note instead of improvement

* fix err reference

* fix TestIdentityStore_GroupHierarchyCases
  • Loading branch information
ccapurso authored Jun 10, 2022
1 parent 797d779 commit efdf2f6
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 3 deletions.
3 changes: 3 additions & 0 deletions changelog/15912.txt
Original file line number Diff line number Diff line change
@@ -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
```
4 changes: 4 additions & 0 deletions vault/identity_store_groups.go
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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

0 comments on commit efdf2f6

Please sign in to comment.