1
0
mirror of https://github.com/rancher/steve.git synced 2025-09-17 07:48:52 +00:00

Fix namespace access control in steve (#568)

* Fix adding namespace resource access

* Add tests for addResourceAccess func
This commit is contained in:
Sakala Venkata Krishna Rohit
2025-04-07 17:40:43 -07:00
committed by GitHub
parent e1061a86cd
commit 5ae8585e5d
2 changed files with 171 additions and 4 deletions

View File

@@ -98,14 +98,31 @@ func addResourceAccess(accessSet *AccessSet, namespace string, rule rbacv1.Polic
}
for _, resourceName := range names {
for _, verb := range rule.Verbs {
access := Access{
Namespace: namespace,
ResourceName: resourceName,
}
// The first condition namespace != All is to determine if it is a RoleBinding.
// The second and third conditions are to check if the resource is for "namespaces" in core group.
// In kubernetes, rule are valid if they satisfy the following
// - Should be `namespaces` GR
// - From RoleBindings in `namespace`
// - From Rule with ResourceName `*`` or the `namespace` itself.
// Ref: https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go#L194
// If the ResourceName is `All` || namespace itself then only the current namespace is considered as Resourcename
// In the case of Rolebinding for the resource "namespaces" in core group, access.Namespace
// is set to All since namespace on the resource "namespaces" is not valid.
if namespace != All && resource == "namespaces" && group == "" && (resourceName == All || resourceName == namespace) {
access.Namespace = All
access.ResourceName = namespace
}
accessSet.Add(verb,
schema.GroupResource{
Group: group,
Resource: resource,
}, Access{
Namespace: namespace,
ResourceName: resourceName,
})
}, access)
}
}
}

View File

@@ -1,11 +1,13 @@
package accesscontrol
import (
"reflect"
"slices"
"testing"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func Test_policyRuleIndex_roleBindingBySubject(t *testing.T) {
@@ -236,3 +238,151 @@ func makeCRB(name string, roleRef rbacv1.RoleRef, subjects []rbacv1.Subject) *rb
Subjects: subjects,
}
}
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)
}
})
}
}