mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-22 02:18:51 +00:00
Merge pull request #53722 from deads2k/rbac-01-allow-star
Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. allow */subresource in rbac policy rules xref #29698 xref #38756 xref #49504 xref #38810 Allow `*/subresource` format in RBAC policy rules to support polymorphic subresources like `*/scale` for HPA. @DirectXMan12 fyi ```release-note RBAC PolicyRules now allow resource=`*/<subresource>` to cover `any-resource/<subresource>`. For example, `*/scale` covers `replicationcontroller/scale`. ```
This commit is contained in:
@@ -55,14 +55,29 @@ func APIGroupMatches(rule *PolicyRule, requestedGroup string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func ResourceMatches(rule *PolicyRule, requestedResource string) bool {
|
||||
func ResourceMatches(rule *PolicyRule, combinedRequestedResource, requestedSubresource string) bool {
|
||||
for _, ruleResource := range rule.Resources {
|
||||
// if everything is allowed, we match
|
||||
if ruleResource == ResourceAll {
|
||||
return true
|
||||
}
|
||||
if ruleResource == requestedResource {
|
||||
// if we have an exact match, we match
|
||||
if ruleResource == combinedRequestedResource {
|
||||
return true
|
||||
}
|
||||
|
||||
// We can also match a */subresource.
|
||||
// if there isn't a subresource, then continue
|
||||
if len(requestedSubresource) == 0 {
|
||||
continue
|
||||
}
|
||||
// if the rule isn't in the format */subresource, then we don't match, continue
|
||||
if len(ruleResource) == len(requestedSubresource)+2 &&
|
||||
strings.HasPrefix(ruleResource, "*/") &&
|
||||
strings.HasSuffix(ruleResource, requestedSubresource) {
|
||||
return true
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
|
@@ -70,3 +70,108 @@ func TestHelpersRoundTrip(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceMatches(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ruleResources []string
|
||||
combinedRequestedResource string
|
||||
requestedSubresource string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "all matches 01",
|
||||
ruleResources: []string{"*"},
|
||||
combinedRequestedResource: "foo",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "checks all rules",
|
||||
ruleResources: []string{"doesn't match", "*"},
|
||||
combinedRequestedResource: "foo",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "matches exact rule",
|
||||
ruleResources: []string{"foo/bar"},
|
||||
combinedRequestedResource: "foo/bar",
|
||||
requestedSubresource: "bar",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "matches exact rule 02",
|
||||
ruleResources: []string{"foo/bar"},
|
||||
combinedRequestedResource: "foo",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "matches subresource",
|
||||
ruleResources: []string{"*/scale"},
|
||||
combinedRequestedResource: "foo/scale",
|
||||
requestedSubresource: "scale",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "doesn't match partial subresource hit",
|
||||
ruleResources: []string{"foo/bar", "*/other"},
|
||||
combinedRequestedResource: "foo/other/segment",
|
||||
requestedSubresource: "other/segment",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "matches subresource with multiple slashes",
|
||||
ruleResources: []string{"*/other/segment"},
|
||||
combinedRequestedResource: "foo/other/segment",
|
||||
requestedSubresource: "other/segment",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "doesn't fail on empty",
|
||||
ruleResources: []string{""},
|
||||
combinedRequestedResource: "foo/other/segment",
|
||||
requestedSubresource: "other/segment",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "doesn't fail on slash",
|
||||
ruleResources: []string{"/"},
|
||||
combinedRequestedResource: "foo/other/segment",
|
||||
requestedSubresource: "other/segment",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "doesn't fail on missing subresource",
|
||||
ruleResources: []string{"*/"},
|
||||
combinedRequestedResource: "foo/other/segment",
|
||||
requestedSubresource: "other/segment",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "doesn't match on not star",
|
||||
ruleResources: []string{"*something/other/segment"},
|
||||
combinedRequestedResource: "foo/other/segment",
|
||||
requestedSubresource: "other/segment",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "doesn't match on something else",
|
||||
ruleResources: []string{"something/other/segment"},
|
||||
combinedRequestedResource: "foo/other/segment",
|
||||
requestedSubresource: "other/segment",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rule := &rbac.PolicyRule{
|
||||
Resources: tc.ruleResources,
|
||||
}
|
||||
actual := rbac.ResourceMatches(rule, tc.combinedRequestedResource, tc.requestedSubresource)
|
||||
if tc.expected != actual {
|
||||
t.Errorf("expected %v, got %v", tc.expected, actual)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -48,7 +48,8 @@ type PolicyRule struct {
|
||||
// APIGroups is the name of the APIGroup that contains the resources.
|
||||
// If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed.
|
||||
APIGroups []string
|
||||
// Resources is a list of resources this rule applies to. ResourceAll represents all resources.
|
||||
// Resources is a list of resources this rule applies to. '*' represents all resources in the specified apiGroups.
|
||||
// '*/foo' represents the subresource 'foo' for all resources in the specified apiGroups.
|
||||
Resources []string
|
||||
// ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.
|
||||
ResourceNames []string
|
||||
|
Reference in New Issue
Block a user