mirror of https://github.com/coder/coder.git
236 lines
7.6 KiB
Go
236 lines
7.6 KiB
Go
package rbac_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/coderd/rbac"
|
|
)
|
|
|
|
type benchmarkCase struct {
|
|
Name string
|
|
Actor rbac.Subject
|
|
}
|
|
|
|
// benchmarkUserCases builds a set of users with different roles and groups.
|
|
// The user id used as the subject and the orgs used to build the roles are
|
|
// returned.
|
|
func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.UUID) {
|
|
orgs = []uuid.UUID{
|
|
uuid.MustParse("bf7b72bd-a2b1-4ef2-962c-1d698e0483f6"),
|
|
uuid.MustParse("e4660c6f-b9de-422d-9578-cd888983a795"),
|
|
uuid.MustParse("fb13d477-06f4-42d9-b957-f6b89bd63515"),
|
|
}
|
|
|
|
user := uuid.MustParse("10d03e62-7703-4df5-a358-4f76577d4e2f")
|
|
// noiseGroups are just added to add noise to the authorize call. They
|
|
// never match an object's list of groups.
|
|
noiseGroups := []string{uuid.NewString(), uuid.NewString(), uuid.NewString()}
|
|
|
|
benchCases := []benchmarkCase{
|
|
{
|
|
Name: "NoRoles",
|
|
Actor: rbac.Subject{
|
|
ID: user.String(),
|
|
Roles: rbac.RoleNames{},
|
|
Scope: rbac.ScopeAll,
|
|
},
|
|
},
|
|
{
|
|
Name: "Admin",
|
|
Actor: rbac.Subject{
|
|
// Give some extra roles that an admin might have
|
|
Roles: rbac.RoleNames{rbac.RoleOrgMember(orgs[0]), "auditor", rbac.RoleOwner(), rbac.RoleMember()},
|
|
ID: user.String(),
|
|
Scope: rbac.ScopeAll,
|
|
Groups: noiseGroups,
|
|
},
|
|
},
|
|
{
|
|
Name: "OrgAdmin",
|
|
Actor: rbac.Subject{
|
|
Roles: rbac.RoleNames{rbac.RoleOrgMember(orgs[0]), rbac.RoleOrgAdmin(orgs[0]), rbac.RoleMember()},
|
|
ID: user.String(),
|
|
Scope: rbac.ScopeAll,
|
|
Groups: noiseGroups,
|
|
},
|
|
},
|
|
{
|
|
Name: "OrgMember",
|
|
Actor: rbac.Subject{
|
|
// Member of 2 orgs
|
|
Roles: rbac.RoleNames{rbac.RoleOrgMember(orgs[0]), rbac.RoleOrgMember(orgs[1]), rbac.RoleMember()},
|
|
ID: user.String(),
|
|
Scope: rbac.ScopeAll,
|
|
Groups: noiseGroups,
|
|
},
|
|
},
|
|
{
|
|
Name: "ManyRoles",
|
|
Actor: rbac.Subject{
|
|
// Admin of many orgs
|
|
Roles: rbac.RoleNames{
|
|
rbac.RoleOrgMember(orgs[0]), rbac.RoleOrgAdmin(orgs[0]),
|
|
rbac.RoleOrgMember(orgs[1]), rbac.RoleOrgAdmin(orgs[1]),
|
|
rbac.RoleOrgMember(orgs[2]), rbac.RoleOrgAdmin(orgs[2]),
|
|
rbac.RoleMember(),
|
|
},
|
|
ID: user.String(),
|
|
Scope: rbac.ScopeAll,
|
|
Groups: noiseGroups,
|
|
},
|
|
},
|
|
{
|
|
Name: "AdminWithScope",
|
|
Actor: rbac.Subject{
|
|
// Give some extra roles that an admin might have
|
|
Roles: rbac.RoleNames{rbac.RoleOrgMember(orgs[0]), "auditor", rbac.RoleOwner(), rbac.RoleMember()},
|
|
ID: user.String(),
|
|
Scope: rbac.ScopeApplicationConnect,
|
|
Groups: noiseGroups,
|
|
},
|
|
},
|
|
}
|
|
return benchCases, users, orgs
|
|
}
|
|
|
|
// BenchmarkRBACAuthorize benchmarks the rbac.Authorize method.
|
|
//
|
|
// go test -bench BenchmarkRBACAuthorize -benchmem -memprofile memprofile.out -cpuprofile profile.out
|
|
func BenchmarkRBACAuthorize(b *testing.B) {
|
|
benchCases, user, orgs := benchmarkUserCases()
|
|
users := append([]uuid.UUID{},
|
|
user,
|
|
uuid.MustParse("4ca78b1d-f2d2-4168-9d76-cd93b51c6c1e"),
|
|
uuid.MustParse("0632b012-49e0-4d70-a5b3-f4398f1dcd52"),
|
|
uuid.MustParse("70dbaa7a-ea9c-4f68-a781-97b08af8461d"),
|
|
)
|
|
authorizer := rbac.NewCachingAuthorizer(prometheus.NewRegistry())
|
|
// This benchmarks all the simple cases using just user permissions. Groups
|
|
// are added as noise, but do not do anything.
|
|
for _, c := range benchCases {
|
|
b.Run(c.Name, func(b *testing.B) {
|
|
objects := benchmarkSetup(orgs, users, b.N)
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
allowed := authorizer.Authorize(context.Background(), c.Actor, rbac.ActionRead, objects[b.N%len(objects)])
|
|
_ = allowed
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// BenchmarkRBACAuthorizeGroups benchmarks the rbac.Authorize method and leverages
|
|
// groups for authorizing rather than the permissions/roles.
|
|
//
|
|
// go test -bench BenchmarkRBACAuthorizeGroups -benchmem -memprofile memprofile.out -cpuprofile profile.out
|
|
func BenchmarkRBACAuthorizeGroups(b *testing.B) {
|
|
benchCases, user, orgs := benchmarkUserCases()
|
|
users := append([]uuid.UUID{},
|
|
user,
|
|
uuid.MustParse("4ca78b1d-f2d2-4168-9d76-cd93b51c6c1e"),
|
|
uuid.MustParse("0632b012-49e0-4d70-a5b3-f4398f1dcd52"),
|
|
uuid.MustParse("70dbaa7a-ea9c-4f68-a781-97b08af8461d"),
|
|
)
|
|
authorizer := rbac.NewCachingAuthorizer(prometheus.NewRegistry())
|
|
|
|
// Same benchmark cases, but this time groups will be used to match.
|
|
// Some '*' permissions will still match, but using a fake action reduces
|
|
// the chance.
|
|
neverMatchAction := rbac.Action("never-match-action")
|
|
for _, c := range benchCases {
|
|
b.Run(c.Name+"GroupACL", func(b *testing.B) {
|
|
userGroupAllow := uuid.NewString()
|
|
c.Actor.Groups = append(c.Actor.Groups, userGroupAllow)
|
|
c.Actor.Scope = rbac.ScopeAll
|
|
objects := benchmarkSetup(orgs, users, b.N, func(object rbac.Object) rbac.Object {
|
|
m := map[string][]rbac.Action{
|
|
// Add the user's group
|
|
// Noise
|
|
uuid.NewString(): {rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
|
|
uuid.NewString(): {rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate},
|
|
uuid.NewString(): {rbac.ActionCreate, rbac.ActionRead},
|
|
uuid.NewString(): {rbac.ActionCreate},
|
|
uuid.NewString(): {rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
|
|
uuid.NewString(): {rbac.ActionRead, rbac.ActionUpdate},
|
|
}
|
|
for _, g := range c.Actor.Groups {
|
|
// Every group the user is in will be added, but it will not match the perms. This makes the
|
|
// authorizer look at many groups before finding the one that matches.
|
|
m[g] = []rbac.Action{rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete}
|
|
}
|
|
// This is the only group that will give permission.
|
|
m[userGroupAllow] = []rbac.Action{neverMatchAction}
|
|
return object.WithGroupACL(m)
|
|
})
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
allowed := authorizer.Authorize(context.Background(), c.Actor, neverMatchAction, objects[b.N%len(objects)])
|
|
_ = allowed
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// BenchmarkRBACFilter benchmarks the rbac.Filter method.
|
|
//
|
|
// go test -bench BenchmarkRBACFilter -benchmem -memprofile memprofile.out -cpuprofile profile.out
|
|
func BenchmarkRBACFilter(b *testing.B) {
|
|
benchCases, user, orgs := benchmarkUserCases()
|
|
users := append([]uuid.UUID{},
|
|
user,
|
|
uuid.MustParse("4ca78b1d-f2d2-4168-9d76-cd93b51c6c1e"),
|
|
uuid.MustParse("0632b012-49e0-4d70-a5b3-f4398f1dcd52"),
|
|
uuid.MustParse("70dbaa7a-ea9c-4f68-a781-97b08af8461d"),
|
|
)
|
|
|
|
authorizer := rbac.NewCachingAuthorizer(prometheus.NewRegistry())
|
|
|
|
for _, c := range benchCases {
|
|
b.Run("PrepareOnly-"+c.Name, func(b *testing.B) {
|
|
obType := rbac.ResourceWorkspace.Type
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := authorizer.Prepare(context.Background(), c.Actor, rbac.ActionRead, obType)
|
|
require.NoError(b, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
for _, c := range benchCases {
|
|
b.Run(c.Name, func(b *testing.B) {
|
|
objects := benchmarkSetup(orgs, users, b.N)
|
|
b.ResetTimer()
|
|
allowed, err := rbac.Filter(context.Background(), authorizer, c.Actor, rbac.ActionRead, objects)
|
|
require.NoError(b, err)
|
|
_ = allowed
|
|
})
|
|
}
|
|
}
|
|
|
|
func benchmarkSetup(orgs []uuid.UUID, users []uuid.UUID, size int, opts ...func(object rbac.Object) rbac.Object) []rbac.Object {
|
|
// Create a "random" but deterministic set of objects.
|
|
aclList := map[string][]rbac.Action{
|
|
uuid.NewString(): {rbac.ActionRead, rbac.ActionUpdate},
|
|
uuid.NewString(): {rbac.ActionCreate},
|
|
}
|
|
objectList := make([]rbac.Object, size)
|
|
for i := range objectList {
|
|
objectList[i] = rbac.ResourceWorkspace.
|
|
WithID(uuid.New()).
|
|
InOrg(orgs[i%len(orgs)]).
|
|
WithOwner(users[i%len(users)].String()).
|
|
WithACLUserList(aclList).
|
|
WithGroupACL(aclList)
|
|
|
|
for _, opt := range opts {
|
|
objectList[i] = opt(objectList[i])
|
|
}
|
|
}
|
|
|
|
return objectList
|
|
}
|