package userinfo import ( "reflect" "testing" authenticationv1 "k8s.io/api/authentication/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Test_getRoleRefByRoleBindings(t *testing.T) { roleInSameNsImplicit := &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "same-ns", Namespace: "ns-1", }, Subjects: []rbacv1.Subject{{ Kind: "ServiceAccount", Name: "sa", }}, RoleRef: rbacv1.RoleRef{ Kind: "Role", Name: "role-1", }, } roleInSameNsExplicit := &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "same-ns", Namespace: "ns-1", }, Subjects: []rbacv1.Subject{{ Kind: "ServiceAccount", Name: "sa", Namespace: "ns-1", }}, RoleRef: rbacv1.RoleRef{ Kind: "Role", Name: "role-1", }, } roleInAnotherNs := &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "different-ns", Namespace: "ns-2", }, Subjects: []rbacv1.Subject{{ Kind: "ServiceAccount", Name: "sa", Namespace: "ns-1", }}, RoleRef: rbacv1.RoleRef{ Kind: "Role", Name: "role-1", }, } clusterRole := &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "different-ns", Namespace: "ns-2", }, Subjects: []rbacv1.Subject{{ Kind: "ServiceAccount", Name: "sa", Namespace: "ns-1", }}, RoleRef: rbacv1.RoleRef{ Kind: "ClusterRole", Name: "role-1", }, } userInfo := authenticationv1.UserInfo{ Username: "system:serviceaccount:ns-1:sa", } type args struct { roleBindings []*rbacv1.RoleBinding userInfo authenticationv1.UserInfo } tests := []struct { name string args args wantRoles []string wantClusterRoles []string }{{ name: "service account and role binding explicitely in the same namespace", args: args{ roleBindings: []*rbacv1.RoleBinding{ roleInSameNsExplicit, }, userInfo: userInfo, }, wantRoles: []string{ "ns-1:role-1", }, }, { name: "service account and role binding implicitely in the same namespace", args: args{ roleBindings: []*rbacv1.RoleBinding{ roleInSameNsImplicit, }, userInfo: userInfo, }, wantRoles: []string{ "ns-1:role-1", }, }, { name: "service account and role binding in the different namespaces", args: args{ roleBindings: []*rbacv1.RoleBinding{ roleInAnotherNs, }, userInfo: userInfo, }, wantRoles: []string{ "ns-2:role-1", }, }, { name: "cluster role", args: args{ roleBindings: []*rbacv1.RoleBinding{ clusterRole, }, userInfo: userInfo, }, wantClusterRoles: []string{ "role-1", }, }, { name: "all together", args: args{ roleBindings: []*rbacv1.RoleBinding{ roleInSameNsExplicit, roleInSameNsImplicit, roleInAnotherNs, clusterRole, }, userInfo: userInfo, }, wantRoles: []string{ "ns-1:role-1", "ns-1:role-1", "ns-2:role-1", }, wantClusterRoles: []string{ "role-1", }, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotRoles, gotClusterRoles := getRoleRefByRoleBindings(tt.args.roleBindings, tt.args.userInfo) if !reflect.DeepEqual(gotRoles, tt.wantRoles) { t.Errorf("getRoleRefByRoleBindings() gotRoles = %v, want %v", gotRoles, tt.wantRoles) } if !reflect.DeepEqual(gotClusterRoles, tt.wantClusterRoles) { t.Errorf("getRoleRefByRoleBindings() gotClusterRoles = %v, want %v", gotClusterRoles, tt.wantClusterRoles) } }) } } func Test_matchBindingSubjects(t *testing.T) { type args struct { subjects []rbacv1.Subject userInfo authenticationv1.UserInfo namespace string } tests := []struct { name string args args want bool }{{ name: "empty subjects", args: args{ subjects: []rbacv1.Subject{}, userInfo: authenticationv1.UserInfo{ Username: "system:serviceaccount:foo:test", }, namespace: "", }, want: false, }, { name: "nil subjects", args: args{ subjects: nil, userInfo: authenticationv1.UserInfo{ Username: "system:serviceaccount:foo:test", }, namespace: "", }, want: false, }, { name: "match service account", args: args{ subjects: []rbacv1.Subject{{ Kind: rbacv1.ServiceAccountKind, Name: "test", Namespace: "foo", }}, userInfo: authenticationv1.UserInfo{ Username: "system:serviceaccount:foo:test", }, namespace: "", }, want: true, }, { name: "match service account with fallback namespace", args: args{ subjects: []rbacv1.Subject{{ Kind: rbacv1.ServiceAccountKind, Name: "test", }}, userInfo: authenticationv1.UserInfo{ Username: "system:serviceaccount:foo:test", }, namespace: "foo", }, want: true, }, { name: "don't match service account", args: args{ subjects: []rbacv1.Subject{{ Kind: rbacv1.ServiceAccountKind, Name: "test", Namespace: "foo", }}, userInfo: authenticationv1.UserInfo{ Username: "system:serviceaccount:bar:test", }, namespace: "", }, want: false, }, { name: "don't match service account with fallback namespace", args: args{ subjects: []rbacv1.Subject{{ Kind: rbacv1.ServiceAccountKind, Name: "test", }}, userInfo: authenticationv1.UserInfo{ Username: "system:serviceaccount:bar:test", }, namespace: "foo", }, want: false, }, { name: "don't match service account with no namespace and no fallback namespace", args: args{ subjects: []rbacv1.Subject{{ Kind: rbacv1.ServiceAccountKind, Name: "test", }}, userInfo: authenticationv1.UserInfo{ Username: "system:serviceaccount::test", }, namespace: "", }, want: false, }, { name: "match user", args: args{ subjects: []rbacv1.Subject{{ Kind: rbacv1.UserKind, Name: "someone@company.org", }}, userInfo: authenticationv1.UserInfo{ Username: "someone@company.org", }, namespace: "", }, want: true, }, { name: "don't match user", args: args{ subjects: []rbacv1.Subject{{ Kind: rbacv1.UserKind, Name: "someone@company.org", }}, userInfo: authenticationv1.UserInfo{ Username: "someone-else@company.org", }, namespace: "", }, want: false, }, { name: "match group", args: args{ subjects: []rbacv1.Subject{{ Kind: rbacv1.GroupKind, Name: "admin", }}, userInfo: authenticationv1.UserInfo{ Username: "someone@company.org", Groups: []string{ "user", "dev", "admin", }, }, namespace: "", }, want: true, }, { name: "don't match group", args: args{ subjects: []rbacv1.Subject{{ Kind: rbacv1.GroupKind, Name: "marketing", }}, userInfo: authenticationv1.UserInfo{ Username: "someone@company.org", Groups: []string{ "user", "dev", "admin", }, }, namespace: "", }, want: false, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := matchBindingSubjects(tt.args.subjects, tt.args.userInfo, tt.args.namespace); got != tt.want { t.Errorf("matchBindingSubjects() = %v, want %v", got, tt.want) } }) } } func Test_getRoleRefByClusterRoleBindings(t *testing.T) { clusterRole1 := &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "cluster-role", }, Subjects: []rbacv1.Subject{{ Kind: "ServiceAccount", Name: "sa", Namespace: "foo", }}, RoleRef: rbacv1.RoleRef{ Kind: "ClusterRole", Name: "role-1", }, } clusterRole2 := &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "cluster-role", }, Subjects: []rbacv1.Subject{{ Kind: "ServiceAccount", Name: "sa", Namespace: "bar", }}, RoleRef: rbacv1.RoleRef{ Kind: "ClusterRole", Name: "role-2", }, } userInfo := authenticationv1.UserInfo{ Username: "system:serviceaccount:foo:sa", } type args struct { clusterroleBindings []*rbacv1.ClusterRoleBinding userInfo authenticationv1.UserInfo } tests := []struct { name string args args wantClusterRoles []string }{{ name: "match service account", args: args{ clusterroleBindings: []*rbacv1.ClusterRoleBinding{ clusterRole1, }, userInfo: userInfo, }, wantClusterRoles: []string{ "role-1", }, }, { name: "sa in another namespace", args: args{ clusterroleBindings: []*rbacv1.ClusterRoleBinding{ clusterRole2, }, userInfo: userInfo, }, }, { name: "match service account", args: args{ clusterroleBindings: []*rbacv1.ClusterRoleBinding{ clusterRole1, clusterRole2, }, userInfo: userInfo, }, wantClusterRoles: []string{ "role-1", }, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if gotClusterRoles := getRoleRefByClusterRoleBindings(tt.args.clusterroleBindings, tt.args.userInfo); !reflect.DeepEqual(gotClusterRoles, tt.wantClusterRoles) { t.Errorf("getRoleRefByClusterRoleBindings() = %v, want %v", gotClusterRoles, tt.wantClusterRoles) } }) } }