enable recovery user to list targets and sessions (#2576)
* enable recovery user to list targets and sessions
This commit is contained in:
1 parent
0c18dc0729
commit
946dab487e
10 files changed
+161
-52
No files matched your search
@@ -849,13 +849,13 @@ func (r *VerifyResults) ScopesAuthorizedForList(ctx context.Context, rootScopeId
|
||||
|
||||
// We only expect the action set to be nothing, or list. In case
|
||||
// this is not the case, we bail out.
|
||||
switch len(aSet) {
|
||||
case 0:
|
||||
switch {
|
||||
case len(aSet) == 0:
|
||||
// Defer until we've read all scopes. We do this because if the
|
||||
// ordering coming back isn't in parent-first ordering our map
|
||||
// lookup might fail.
|
||||
deferredScopes = append(deferredScopes, scp)
|
||||
case 1:
|
||||
case len(aSet) == 1 || r.UserId == perms.RecoveryUserId:
|
||||
if aSet[0] != action.List {
|
||||
return nil, errors.New(ctx, errors.Internal, op, "unexpected action in set")
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/common"
|
||||
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
)
|
||||
|
||||
@@ -20,7 +21,7 @@ func DisabledAuthTestContext(iamRepoFn common.IamRepoFactory, scopeId string, op
|
||||
}
|
||||
reqInfo.UserIdOverride = opts.withUserId
|
||||
if reqInfo.UserIdOverride == "" {
|
||||
reqInfo.UserIdOverride = "u_auth"
|
||||
reqInfo.UserIdOverride = perms.AnyAuthenticatedUserId
|
||||
}
|
||||
requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
|
||||
return NewVerifierContext(requestContext, iamRepoFn, nil, nil, opts.withKms, &reqInfo)
|
||||
|
||||
@@ -710,7 +710,7 @@ func validateAddGroupMembersRequest(req *pbs.AddGroupMembersRequest) error {
|
||||
badFields["member_ids"] = fmt.Sprintf("Must only contain valid user ids but found %q.", id)
|
||||
break
|
||||
}
|
||||
if id == "u_recovery" {
|
||||
if id == perms.RecoveryUserId {
|
||||
badFields["member_ids"] = "u_recovery cannot be assigned to a group."
|
||||
break
|
||||
}
|
||||
@@ -734,7 +734,7 @@ func validateSetGroupMembersRequest(req *pbs.SetGroupMembersRequest) error {
|
||||
badFields["member_ids"] = fmt.Sprintf("Must only contain valid user ids but found %q.", id)
|
||||
break
|
||||
}
|
||||
if id == "u_recovery" {
|
||||
if id == perms.RecoveryUserId {
|
||||
badFields["member_ids"] = "u_recovery cannot be assigned to a group."
|
||||
break
|
||||
}
|
||||
|
||||
@@ -975,7 +975,7 @@ func validateAddRolePrincipalsRequest(req *pbs.AddRolePrincipalsRequest) error {
|
||||
badFields["principal_ids"] = "Must only have valid user, group, and/or managed group ids."
|
||||
break
|
||||
}
|
||||
if id == "u_recovery" {
|
||||
if id == perms.RecoveryUserId {
|
||||
badFields["principal_ids"] = "u_recovery cannot be assigned to a role"
|
||||
break
|
||||
}
|
||||
@@ -1001,7 +1001,7 @@ func validateSetRolePrincipalsRequest(req *pbs.SetRolePrincipalsRequest) error {
|
||||
badFields["principal_ids"] = "Must only have valid user, group, and/or managed group ids."
|
||||
break
|
||||
}
|
||||
if id == "u_recovery" {
|
||||
if id == perms.RecoveryUserId {
|
||||
badFields["principal_ids"] = "u_recovery cannot be assigned to a role"
|
||||
break
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ func (s Service) ListSessions(ctx context.Context, req *pbs.ListSessionsRequest)
|
||||
scopeIds, err = authResults.ScopesAuthorizedForList(ctx, req.GetScopeId(), resource.Session)
|
||||
}
|
||||
|
||||
listPerms := authResults.ACL().ListPermissions(scopeIds, resource.Session, IdActions)
|
||||
listPerms := authResults.ACL().ListPermissions(scopeIds, resource.Session, IdActions, authResults.UserId)
|
||||
|
||||
repo, err := s.repoFn(session.WithPermissions(&perms.UserPermissions{
|
||||
UserId: authResults.UserId,
|
||||
|
||||
@@ -314,6 +314,7 @@ func TestList(t *testing.T) {
|
||||
|
||||
var wantSession []*pb.Session
|
||||
var wantOtherSession []*pb.Session
|
||||
var wantAllSessions []*pb.Session
|
||||
var wantIncludeTerminatedSessions []*pb.Session
|
||||
for i := 0; i < 10; i++ {
|
||||
sess := session.TestSession(t, conn, wrap, session.ComposedOf{
|
||||
@@ -330,7 +331,7 @@ func TestList(t *testing.T) {
|
||||
|
||||
status, states := convertStates(sess.States)
|
||||
|
||||
wantSession = append(wantSession, &pb.Session{
|
||||
firstOrgSession := &pb.Session{
|
||||
Id: sess.GetPublicId(),
|
||||
ScopeId: pWithSessions.GetPublicId(),
|
||||
AuthTokenId: at.GetPublicId(),
|
||||
@@ -350,7 +351,9 @@ func TestList(t *testing.T) {
|
||||
Type: tcp.Subtype.String(),
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
Connections: []*pb.Connection{}, // connections should not be returned for list
|
||||
})
|
||||
}
|
||||
wantSession = append(wantSession, firstOrgSession)
|
||||
wantAllSessions = append(wantAllSessions, firstOrgSession)
|
||||
|
||||
wantIncludeTerminatedSessions = append(wantIncludeTerminatedSessions, wantSession[i])
|
||||
|
||||
@@ -368,7 +371,7 @@ func TestList(t *testing.T) {
|
||||
|
||||
status, states = convertStates(sess.States)
|
||||
|
||||
wantOtherSession = append(wantOtherSession, &pb.Session{
|
||||
otherOrgSession := &pb.Session{
|
||||
Id: sess.GetPublicId(),
|
||||
ScopeId: pWithSessions.GetPublicId(),
|
||||
AuthTokenId: at.GetPublicId(),
|
||||
@@ -388,7 +391,10 @@ func TestList(t *testing.T) {
|
||||
Type: tcp.Subtype.String(),
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
Connections: []*pb.Connection{}, // connections should not be returned for list
|
||||
})
|
||||
}
|
||||
wantOtherSession = append(wantOtherSession, otherOrgSession)
|
||||
|
||||
wantAllSessions = append(wantAllSessions, otherOrgSession)
|
||||
}
|
||||
|
||||
{
|
||||
@@ -439,41 +445,47 @@ func TestList(t *testing.T) {
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
req *pbs.ListSessionsRequest
|
||||
res *pbs.ListSessionsResponse
|
||||
otherRes *pbs.ListSessionsResponse
|
||||
err error
|
||||
name string
|
||||
req *pbs.ListSessionsRequest
|
||||
res *pbs.ListSessionsResponse
|
||||
otherRes *pbs.ListSessionsResponse
|
||||
allSessionRes *pbs.ListSessionsResponse
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "List Many Sessions",
|
||||
req: &pbs.ListSessionsRequest{ScopeId: pWithSessions.GetPublicId()},
|
||||
res: &pbs.ListSessionsResponse{Items: wantSession},
|
||||
otherRes: &pbs.ListSessionsResponse{Items: []*pb.Session{}},
|
||||
name: "List Many Sessions",
|
||||
req: &pbs.ListSessionsRequest{ScopeId: pWithSessions.GetPublicId()},
|
||||
res: &pbs.ListSessionsResponse{Items: wantSession},
|
||||
otherRes: &pbs.ListSessionsResponse{Items: []*pb.Session{}},
|
||||
allSessionRes: &pbs.ListSessionsResponse{Items: wantSession},
|
||||
},
|
||||
{
|
||||
name: "List Many Include Terminated",
|
||||
req: &pbs.ListSessionsRequest{ScopeId: pWithSessions.GetPublicId(), IncludeTerminated: true},
|
||||
res: &pbs.ListSessionsResponse{Items: wantIncludeTerminatedSessions},
|
||||
otherRes: &pbs.ListSessionsResponse{Items: []*pb.Session{}},
|
||||
name: "List Many Include Terminated",
|
||||
req: &pbs.ListSessionsRequest{ScopeId: pWithSessions.GetPublicId(), IncludeTerminated: true},
|
||||
res: &pbs.ListSessionsResponse{Items: wantIncludeTerminatedSessions},
|
||||
otherRes: &pbs.ListSessionsResponse{Items: []*pb.Session{}},
|
||||
allSessionRes: &pbs.ListSessionsResponse{Items: wantIncludeTerminatedSessions},
|
||||
},
|
||||
{
|
||||
name: "List No Sessions",
|
||||
req: &pbs.ListSessionsRequest{ScopeId: pNoSessions.GetPublicId()},
|
||||
res: &pbs.ListSessionsResponse{},
|
||||
otherRes: &pbs.ListSessionsResponse{Items: []*pb.Session{}},
|
||||
name: "List No Sessions",
|
||||
req: &pbs.ListSessionsRequest{ScopeId: pNoSessions.GetPublicId()},
|
||||
res: &pbs.ListSessionsResponse{},
|
||||
otherRes: &pbs.ListSessionsResponse{Items: []*pb.Session{}},
|
||||
allSessionRes: &pbs.ListSessionsResponse{},
|
||||
},
|
||||
{
|
||||
name: "List Sessions Recursively",
|
||||
req: &pbs.ListSessionsRequest{ScopeId: scope.Global.String(), Recursive: true},
|
||||
res: &pbs.ListSessionsResponse{Items: wantSession},
|
||||
otherRes: &pbs.ListSessionsResponse{Items: wantOtherSession},
|
||||
name: "List Sessions Recursively",
|
||||
req: &pbs.ListSessionsRequest{ScopeId: scope.Global.String(), Recursive: true},
|
||||
res: &pbs.ListSessionsResponse{Items: wantSession},
|
||||
otherRes: &pbs.ListSessionsResponse{Items: wantOtherSession},
|
||||
allSessionRes: &pbs.ListSessionsResponse{Items: wantAllSessions},
|
||||
},
|
||||
{
|
||||
name: "Filter To Single Sessions",
|
||||
req: &pbs.ListSessionsRequest{ScopeId: pWithSessions.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantSession[4].Id)},
|
||||
res: &pbs.ListSessionsResponse{Items: wantSession[4:5]},
|
||||
otherRes: &pbs.ListSessionsResponse{Items: []*pb.Session{}},
|
||||
name: "Filter To Single Sessions",
|
||||
req: &pbs.ListSessionsRequest{ScopeId: pWithSessions.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantSession[4].Id)},
|
||||
res: &pbs.ListSessionsResponse{Items: wantSession[4:5]},
|
||||
otherRes: &pbs.ListSessionsResponse{Items: []*pb.Session{}},
|
||||
allSessionRes: &pbs.ListSessionsResponse{Items: wantSession[4:5]},
|
||||
},
|
||||
{
|
||||
name: "Filter To Many Sessions",
|
||||
@@ -481,14 +493,16 @@ func TestList(t *testing.T) {
|
||||
ScopeId: scope.Global.String(), Recursive: true,
|
||||
Filter: fmt.Sprintf(`"/item/scope/id" matches "^%s"`, pWithSessions.GetPublicId()[:8]),
|
||||
},
|
||||
res: &pbs.ListSessionsResponse{Items: wantSession},
|
||||
otherRes: &pbs.ListSessionsResponse{Items: []*pb.Session{}},
|
||||
res: &pbs.ListSessionsResponse{Items: wantSession},
|
||||
otherRes: &pbs.ListSessionsResponse{Items: []*pb.Session{}},
|
||||
allSessionRes: &pbs.ListSessionsResponse{Items: wantSession},
|
||||
},
|
||||
{
|
||||
name: "Filter To Nothing",
|
||||
req: &pbs.ListSessionsRequest{ScopeId: pWithSessions.GetPublicId(), Filter: `"/item/id" == ""`},
|
||||
res: &pbs.ListSessionsResponse{},
|
||||
otherRes: &pbs.ListSessionsResponse{Items: []*pb.Session{}},
|
||||
name: "Filter To Nothing",
|
||||
req: &pbs.ListSessionsRequest{ScopeId: pWithSessions.GetPublicId(), Filter: `"/item/id" == ""`},
|
||||
res: &pbs.ListSessionsResponse{},
|
||||
otherRes: &pbs.ListSessionsResponse{Items: []*pb.Session{}},
|
||||
allSessionRes: &pbs.ListSessionsResponse{},
|
||||
},
|
||||
{
|
||||
name: "Filter Bad Format",
|
||||
@@ -544,6 +558,30 @@ func TestList(t *testing.T) {
|
||||
assert.Equal(0, len(wantSess.GetConnections())) // no connections on list
|
||||
wantSess.ExpirationTime = got.GetItems()[i].GetExpirationTime()
|
||||
}
|
||||
|
||||
// Test with recovery user
|
||||
recoveryRequestInfo := authpb.RequestInfo{
|
||||
TokenFormat: uint32(auth.AuthTokenTypeRecoveryKms),
|
||||
PublicId: at.GetPublicId(),
|
||||
Token: at.GetToken(),
|
||||
}
|
||||
recoveryRequestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
|
||||
recoveryCtx := auth.NewVerifierContext(recoveryRequestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &recoveryRequestInfo)
|
||||
recoveryGot, gErr := s.ListSessions(recoveryCtx, tc.req)
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListSessions(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
||||
return
|
||||
}
|
||||
require.NoError(gErr)
|
||||
if tc.allSessionRes != nil {
|
||||
require.Equal(len(tc.allSessionRes.GetItems()), len(recoveryGot.GetItems()), "Didn't get expected number of sessions: %v", recoveryGot.GetItems())
|
||||
for i, wantSess := range tc.allSessionRes.GetItems() {
|
||||
assert.True(recoveryGot.GetItems()[i].GetExpirationTime().AsTime().Sub(wantSess.GetExpirationTime().AsTime()) < 10*time.Millisecond)
|
||||
assert.Equal(0, len(wantSess.GetConnections())) // no connections on list
|
||||
wantSess.ExpirationTime = recoveryGot.GetItems()[i].GetExpirationTime()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ func (s Service) ListTargets(ctx context.Context, req *pbs.ListTargetsRequest) (
|
||||
}
|
||||
|
||||
// Get all user permissions for the requested scope(s).
|
||||
userPerms := authResults.ACL().ListPermissions(authzScopes, resource.Target, IdActions)
|
||||
userPerms := authResults.ACL().ListPermissions(authzScopes, resource.Target, IdActions, authResults.UserId)
|
||||
if len(userPerms) == 0 {
|
||||
return &pbs.ListTargetsResponse{}, nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/hashicorp/go-dbw"
|
||||
@@ -85,9 +86,9 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
|
||||
var adminRoleRaw any
|
||||
switch {
|
||||
case userId == "",
|
||||
userId == "u_anon",
|
||||
userId == "u_auth",
|
||||
userId == "u_recovery",
|
||||
userId == perms.AnonymousUserId,
|
||||
userId == perms.AnyAuthenticatedUserId,
|
||||
userId == perms.RecoveryUserId,
|
||||
opts.withSkipAdminRoleCreation:
|
||||
// TODO: Cause a log entry. The repo doesn't have a logger right now,
|
||||
// and ideally we will be using context to pass around log info scoped
|
||||
@@ -337,7 +338,7 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
|
||||
principals := []any{}
|
||||
userId := "u_anon"
|
||||
if s.Type == scope.Project.String() {
|
||||
userId = "u_auth"
|
||||
userId = perms.AnyAuthenticatedUserId
|
||||
}
|
||||
rolePrincipal, err := NewUserRole(defaultRolePublicId, userId)
|
||||
if err != nil {
|
||||
|
||||
+16
-2
@@ -8,7 +8,11 @@ import (
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
|
||||
)
|
||||
|
||||
const AnonymousUserId = "u_anon"
|
||||
const (
|
||||
AnyAuthenticatedUserId = "u_auth"
|
||||
AnonymousUserId = "u_anon"
|
||||
RecoveryUserId = "u_recovery"
|
||||
)
|
||||
|
||||
// ACL provides an entry point into the permissions engine for determining if an
|
||||
// action is allowed on a resource based on a principal's (user or group) grants.
|
||||
@@ -234,7 +238,11 @@ func (a ACL) Allowed(r Resource, aType action.Type, userId string, opt ...Option
|
||||
// or for action.All in order for a Permission to be created for the scope.
|
||||
// The set of "id actions" is resource dependant, but will generally include all
|
||||
// actions that can be taken on an individual resource.
|
||||
func (a ACL) ListPermissions(requestedScopes map[string]*scopes.ScopeInfo, requestedType resource.Type, idActions action.ActionSet) []Permission {
|
||||
func (a ACL) ListPermissions(requestedScopes map[string]*scopes.ScopeInfo,
|
||||
requestedType resource.Type,
|
||||
idActions action.ActionSet,
|
||||
userId string,
|
||||
) []Permission {
|
||||
perms := make([]Permission, 0, len(requestedScopes))
|
||||
for scopeId := range requestedScopes {
|
||||
p := Permission{
|
||||
@@ -243,6 +251,12 @@ func (a ACL) ListPermissions(requestedScopes map[string]*scopes.ScopeInfo, reque
|
||||
Action: action.List,
|
||||
OnlySelf: true, // default to only self to be restrictive
|
||||
}
|
||||
if userId == RecoveryUserId {
|
||||
p.All = true
|
||||
p.OnlySelf = false
|
||||
perms = append(perms, p)
|
||||
continue
|
||||
}
|
||||
|
||||
// Get grants for a specific scope id from the source of truth.
|
||||
grants := a.scopeMap[scopeId]
|
||||
|
||||
@@ -377,6 +377,7 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
func TestACL_ListPermissions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
userId string
|
||||
aclGrants []scopeGrant
|
||||
scopes map[string]*scopes.ScopeInfo // *scopes.ScopeInfo isn't used at the moment.
|
||||
resourceType resource.Type
|
||||
@@ -720,10 +721,64 @@ func TestACL_ListPermissions(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Allow recovery user full access to sessions",
|
||||
userId: RecoveryUserId,
|
||||
scopes: map[string]*scopes.ScopeInfo{"o_1": nil, "o_2": nil},
|
||||
resourceType: resource.Session,
|
||||
actionSet: action.ActionSet{action.List, action.Read, action.Create, action.Delete},
|
||||
expPermissions: []Permission{
|
||||
{
|
||||
ScopeId: "o_1",
|
||||
Resource: resource.Session,
|
||||
Action: action.List,
|
||||
ResourceIds: nil,
|
||||
OnlySelf: false,
|
||||
All: true,
|
||||
},
|
||||
{
|
||||
ScopeId: "o_2",
|
||||
Resource: resource.Session,
|
||||
Action: action.List,
|
||||
ResourceIds: nil,
|
||||
OnlySelf: false,
|
||||
All: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Allow recovery user full access to targets",
|
||||
userId: RecoveryUserId,
|
||||
scopes: map[string]*scopes.ScopeInfo{"o_1": nil, "o_2": nil},
|
||||
resourceType: resource.Target,
|
||||
actionSet: action.ActionSet{action.List, action.Read, action.Create, action.Delete},
|
||||
expPermissions: []Permission{
|
||||
{
|
||||
ScopeId: "o_1",
|
||||
Resource: resource.Target,
|
||||
Action: action.List,
|
||||
ResourceIds: nil,
|
||||
OnlySelf: false,
|
||||
All: true,
|
||||
},
|
||||
{
|
||||
ScopeId: "o_2",
|
||||
Resource: resource.Target,
|
||||
Action: action.List,
|
||||
ResourceIds: nil,
|
||||
OnlySelf: false,
|
||||
All: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
userId := tt.userId
|
||||
if userId == "" {
|
||||
userId = "u_1234567890"
|
||||
}
|
||||
var grants []Grant
|
||||
for _, sg := range tt.aclGrants {
|
||||
for _, g := range sg.grants {
|
||||
@@ -734,7 +789,7 @@ func TestACL_ListPermissions(t *testing.T) {
|
||||
}
|
||||
|
||||
acl := NewACL(grants...)
|
||||
perms := acl.ListPermissions(tt.scopes, tt.resourceType, tt.actionSet)
|
||||
perms := acl.ListPermissions(tt.scopes, tt.resourceType, tt.actionSet, userId)
|
||||
require.ElementsMatch(t, tt.expPermissions, perms)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user