2024-10-28 08:35:59 +00:00
|
|
|
package accesscontrol
|
|
|
|
|
|
|
|
import (
|
2025-04-08 00:40:43 +00:00
|
|
|
"reflect"
|
2024-10-28 08:35:59 +00:00
|
|
|
"slices"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
rbacv1 "k8s.io/api/rbac/v1"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2025-04-08 00:40:43 +00:00
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
2024-10-28 08:35:59 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func Test_policyRuleIndex_roleBindingBySubject(t *testing.T) {
|
|
|
|
roleRef := rbacv1.RoleRef{Kind: "Role", Name: "testrole"}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
kind string
|
|
|
|
rb *rbacv1.RoleBinding
|
|
|
|
want []string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "indexes users",
|
|
|
|
kind: "User",
|
|
|
|
rb: makeRB("testns", "testrb", roleRef, []rbacv1.Subject{
|
|
|
|
{
|
|
|
|
APIGroup: rbacGroup,
|
|
|
|
Kind: "User",
|
|
|
|
Name: "myuser",
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
want: []string{"myuser"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "indexes multiple subjects",
|
|
|
|
kind: "Group",
|
|
|
|
rb: makeRB("testns", "testrb", roleRef, []rbacv1.Subject{
|
|
|
|
{
|
|
|
|
APIGroup: rbacGroup,
|
|
|
|
Kind: "Group",
|
|
|
|
Name: "mygroup1",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
APIGroup: rbacGroup,
|
|
|
|
Kind: "Group",
|
|
|
|
Name: "mygroup2",
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
want: []string{"mygroup1", "mygroup2"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "indexes svcaccounts in user mode",
|
|
|
|
kind: "User",
|
|
|
|
rb: makeRB("testns", "testrb", roleRef, []rbacv1.Subject{
|
|
|
|
{
|
|
|
|
APIGroup: "",
|
|
|
|
Kind: "ServiceAccount",
|
|
|
|
Name: "mysvcaccount",
|
|
|
|
Namespace: "testns",
|
|
|
|
},
|
|
|
|
}),
|
2025-03-14 15:52:28 +00:00
|
|
|
want: []string{"system:serviceaccount:testns:mysvcaccount"},
|
2024-10-28 08:35:59 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "ignores svcaccounts in group mode",
|
|
|
|
kind: "Group",
|
|
|
|
rb: makeRB("testns", "testrb", roleRef, []rbacv1.Subject{
|
|
|
|
{
|
|
|
|
APIGroup: "",
|
|
|
|
Kind: "ServiceAccount",
|
|
|
|
Name: "mysvcaccount",
|
|
|
|
Namespace: "testns",
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
want: []string{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "ignores unknown subjects",
|
|
|
|
kind: "Group",
|
|
|
|
rb: makeRB("testns", "testrb", roleRef, []rbacv1.Subject{
|
|
|
|
{
|
|
|
|
APIGroup: rbacGroup,
|
|
|
|
Kind: "User",
|
|
|
|
Name: "myuser",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
APIGroup: rbacGroup,
|
|
|
|
Kind: "Group",
|
|
|
|
Name: "mygroup1",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
APIGroup: "custom.api.group",
|
|
|
|
Kind: "CustomGroup",
|
|
|
|
Name: "mygroup2",
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
want: []string{"mygroup1"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
indexFunc := roleBindingBySubjectIndexer(tt.kind)
|
|
|
|
if got, err := indexFunc(tt.rb); err != nil {
|
|
|
|
t.Error(err)
|
|
|
|
} else if !slices.Equal(got, tt.want) {
|
|
|
|
t.Errorf("roleBindingBySubjectIndexer() got = %v, want %v", got, tt.want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func Test_policyRuleIndex_clusterRoleBindingBySubject(t *testing.T) {
|
|
|
|
roleRef := rbacv1.RoleRef{Kind: "ClusterRole", Name: "testclusterrole"}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
kind string
|
|
|
|
crb *rbacv1.ClusterRoleBinding
|
|
|
|
want []string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "ignores if RoleRef is a Role",
|
|
|
|
kind: "User",
|
|
|
|
crb: makeCRB("testcrb", rbacv1.RoleRef{Kind: "Role", Name: "testrole"}, []rbacv1.Subject{
|
|
|
|
{
|
|
|
|
APIGroup: rbacGroup,
|
|
|
|
Kind: "User",
|
|
|
|
Name: "myuser",
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
want: []string{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "indexes users",
|
|
|
|
kind: "User",
|
|
|
|
crb: makeCRB("testcrb", roleRef, []rbacv1.Subject{
|
|
|
|
{
|
|
|
|
APIGroup: rbacGroup,
|
|
|
|
Kind: "User",
|
|
|
|
Name: "myuser",
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
want: []string{"myuser"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "indexes multiple subjects",
|
|
|
|
kind: "Group",
|
|
|
|
crb: makeCRB("testcrb", roleRef, []rbacv1.Subject{
|
|
|
|
{
|
|
|
|
APIGroup: rbacGroup,
|
|
|
|
Kind: "Group",
|
|
|
|
Name: "mygroup1",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
APIGroup: rbacGroup,
|
|
|
|
Kind: "Group",
|
|
|
|
Name: "mygroup2",
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
want: []string{"mygroup1", "mygroup2"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "indexes svcaccounts in user mode",
|
|
|
|
kind: "User",
|
|
|
|
crb: makeCRB("testcrb", roleRef, []rbacv1.Subject{
|
|
|
|
{
|
|
|
|
APIGroup: "",
|
|
|
|
Kind: "ServiceAccount",
|
|
|
|
Name: "mysvcaccount",
|
|
|
|
Namespace: "testns",
|
|
|
|
},
|
|
|
|
}),
|
2025-03-14 15:52:28 +00:00
|
|
|
want: []string{"system:serviceaccount:testns:mysvcaccount"},
|
2024-10-28 08:35:59 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "ignores svcaccounts in group mode",
|
|
|
|
kind: "Group",
|
|
|
|
crb: makeCRB("testcrb", roleRef, []rbacv1.Subject{
|
|
|
|
{
|
|
|
|
APIGroup: "",
|
|
|
|
Kind: "ServiceAccount",
|
|
|
|
Name: "mysvcaccount",
|
|
|
|
Namespace: "testns",
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
want: []string{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "ignores unknown subjects",
|
|
|
|
kind: "Group",
|
|
|
|
crb: makeCRB("testcrb", roleRef, []rbacv1.Subject{
|
|
|
|
{
|
|
|
|
APIGroup: rbacGroup,
|
|
|
|
Kind: "User",
|
|
|
|
Name: "myuser",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
APIGroup: rbacGroup,
|
|
|
|
Kind: "Group",
|
|
|
|
Name: "mygroup1",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
APIGroup: "custom.api.group",
|
|
|
|
Kind: "CustomGroup",
|
|
|
|
Name: "mygroup2",
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
want: []string{"mygroup1"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
indexFunc := clusterRoleBindingBySubjectIndexer(tt.kind)
|
|
|
|
if got, err := indexFunc(tt.crb); err != nil {
|
|
|
|
t.Error(err)
|
|
|
|
} else if !slices.Equal(got, tt.want) {
|
|
|
|
t.Errorf("clusterRoleBindingBySubjectIndexer() got = %v, want %v", got, tt.want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeRB(namespace, name string, roleRef rbacv1.RoleRef, subjects []rbacv1.Subject) *rbacv1.RoleBinding {
|
|
|
|
return &rbacv1.RoleBinding{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Namespace: namespace,
|
|
|
|
Name: name,
|
|
|
|
},
|
|
|
|
RoleRef: roleRef,
|
|
|
|
Subjects: subjects,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeCRB(name string, roleRef rbacv1.RoleRef, subjects []rbacv1.Subject) *rbacv1.ClusterRoleBinding {
|
|
|
|
return &rbacv1.ClusterRoleBinding{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: name,
|
|
|
|
},
|
|
|
|
RoleRef: roleRef,
|
|
|
|
Subjects: subjects,
|
|
|
|
}
|
|
|
|
}
|
2025-04-08 00:40:43 +00:00
|
|
|
|
|
|
|
func Test_addResourceAccess(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
namespace string
|
|
|
|
rule rbacv1.PolicyRule
|
|
|
|
want AccessSet
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "RoleBinding namespaces resource with empty names",
|
|
|
|
namespace: "test-ns",
|
|
|
|
rule: rbacv1.PolicyRule{
|
|
|
|
APIGroups: []string{""},
|
|
|
|
Resources: []string{"namespaces", "deployments"},
|
|
|
|
ResourceNames: []string{},
|
|
|
|
Verbs: []string{"get"},
|
|
|
|
},
|
|
|
|
want: AccessSet{
|
|
|
|
set: map[key]resourceAccessSet{
|
|
|
|
{
|
|
|
|
verb: "get", gr: schema.GroupResource{Group: "", Resource: "namespaces"}}: {
|
|
|
|
Access{Namespace: "*", ResourceName: "test-ns"}: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
verb: "get", gr: schema.GroupResource{Group: "", Resource: "deployments"}}: {
|
|
|
|
Access{Namespace: "test-ns", ResourceName: "*"}: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "ClusterRoleBinding namespaces resource with empty names",
|
|
|
|
namespace: "*",
|
|
|
|
rule: rbacv1.PolicyRule{
|
|
|
|
APIGroups: []string{""},
|
|
|
|
Resources: []string{"namespaces", "deployments"},
|
|
|
|
ResourceNames: []string{},
|
|
|
|
Verbs: []string{"get"},
|
|
|
|
},
|
|
|
|
want: AccessSet{
|
|
|
|
set: map[key]resourceAccessSet{
|
|
|
|
{verb: "get", gr: schema.GroupResource{Group: "", Resource: "namespaces"}}: {
|
|
|
|
Access{Namespace: "*", ResourceName: "*"}: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
verb: "get", gr: schema.GroupResource{Group: "", Resource: "deployments"}}: {
|
|
|
|
Access{Namespace: "*", ResourceName: "*"}: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "RoleBinding namespaces resource with specific names",
|
|
|
|
namespace: "test-ns",
|
|
|
|
rule: rbacv1.PolicyRule{
|
|
|
|
APIGroups: []string{""},
|
|
|
|
Resources: []string{"namespaces"},
|
|
|
|
ResourceNames: []string{"specific-ns"},
|
|
|
|
Verbs: []string{"get"},
|
|
|
|
},
|
|
|
|
want: AccessSet{
|
|
|
|
set: map[key]resourceAccessSet{
|
|
|
|
{verb: "get", gr: schema.GroupResource{Group: "", Resource: "namespaces"}}: {
|
|
|
|
Access{Namespace: "test-ns", ResourceName: "specific-ns"}: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "RoleBinding namespaces resource with its own namespace",
|
|
|
|
namespace: "test-ns",
|
|
|
|
rule: rbacv1.PolicyRule{
|
|
|
|
APIGroups: []string{""},
|
|
|
|
Resources: []string{"namespaces"},
|
|
|
|
ResourceNames: []string{"test-ns"},
|
|
|
|
Verbs: []string{"get"},
|
|
|
|
},
|
|
|
|
want: AccessSet{
|
|
|
|
set: map[key]resourceAccessSet{
|
|
|
|
{verb: "get", gr: schema.GroupResource{Group: "", Resource: "namespaces"}}: {
|
|
|
|
Access{Namespace: "*", ResourceName: "test-ns"}: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "RoleBinding other resource with empty names",
|
|
|
|
namespace: "test-ns",
|
|
|
|
rule: rbacv1.PolicyRule{
|
|
|
|
APIGroups: []string{"apps"},
|
|
|
|
Resources: []string{"deployments"},
|
|
|
|
ResourceNames: []string{},
|
|
|
|
Verbs: []string{"get"},
|
|
|
|
},
|
|
|
|
want: AccessSet{
|
|
|
|
set: map[key]resourceAccessSet{
|
|
|
|
{verb: "get", gr: schema.GroupResource{Group: "apps", Resource: "deployments"}}: {
|
|
|
|
Access{Namespace: "test-ns", ResourceName: "*"}: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "ClusterRoleBinding other resource with empty names",
|
|
|
|
namespace: "*",
|
|
|
|
rule: rbacv1.PolicyRule{
|
|
|
|
APIGroups: []string{"apps"},
|
|
|
|
Resources: []string{"deployments"},
|
|
|
|
ResourceNames: []string{},
|
|
|
|
Verbs: []string{"get"},
|
|
|
|
},
|
|
|
|
want: AccessSet{
|
|
|
|
set: map[key]resourceAccessSet{
|
|
|
|
{verb: "get", gr: schema.GroupResource{Group: "apps", Resource: "deployments"}}: {
|
|
|
|
Access{Namespace: "*", ResourceName: "*"}: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "RoleBinding other resource with specific names",
|
|
|
|
namespace: "test-ns",
|
|
|
|
rule: rbacv1.PolicyRule{
|
|
|
|
APIGroups: []string{"apps"},
|
|
|
|
Resources: []string{"deployments"},
|
|
|
|
ResourceNames: []string{"my-deploy"},
|
|
|
|
Verbs: []string{"get"},
|
|
|
|
},
|
|
|
|
want: AccessSet{
|
|
|
|
set: map[key]resourceAccessSet{
|
|
|
|
{verb: "get", gr: schema.GroupResource{Group: "apps", Resource: "deployments"}}: {
|
|
|
|
Access{Namespace: "test-ns", ResourceName: "my-deploy"}: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
accessSet := &AccessSet{}
|
|
|
|
addResourceAccess(accessSet, tt.namespace, tt.rule)
|
|
|
|
if !reflect.DeepEqual(*accessSet, tt.want) {
|
|
|
|
t.Errorf("addResourceAccess() got = %v, want %v", *accessSet, tt.want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|