blob: d6e7593bcc7aa7e6333464b01760b9f7a874c2fb [file] [log] [blame]
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rbac_test
import (
"reflect"
"testing"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/v1"
// install RBAC types
_ "k8s.io/kubernetes/pkg/apis/rbac/install"
)
// TestHelpersRoundTrip confirms that the rbac.New* helper functions produce RBAC objects that match objects
// that have gone through conversion and defaulting. This is required because these helper functions are
// used to create the bootstrap RBAC policy which is used during reconciliation. If they produced objects
// that did not match, reconciliation would incorrectly add duplicate data to the cluster's RBAC policy.
func TestHelpersRoundTrip(t *testing.T) {
rb := rbac.NewRoleBinding("role", "ns").Groups("g").SAs("ns", "sa").Users("u").BindingOrDie()
rbcr := rbac.NewRoleBindingForClusterRole("role", "ns").Groups("g").SAs("ns", "sa").Users("u").BindingOrDie()
crb := rbac.NewClusterBinding("role").Groups("g").SAs("ns", "sa").Users("u").BindingOrDie()
role := &rbac.Role{
Rules: []rbac.PolicyRule{
rbac.NewRule("verb").Groups("g").Resources("foo").RuleOrDie(),
rbac.NewRule("verb").URLs("/foo").RuleOrDie(),
},
}
clusterRole := &rbac.ClusterRole{
Rules: []rbac.PolicyRule{
rbac.NewRule("verb").Groups("g").Resources("foo").RuleOrDie(),
rbac.NewRule("verb").URLs("/foo").RuleOrDie(),
},
}
for _, internalObj := range []runtime.Object{&rb, &rbcr, &crb, role, clusterRole} {
v1Obj, err := legacyscheme.Scheme.ConvertToVersion(internalObj, v1.SchemeGroupVersion)
if err != nil {
t.Errorf("err on %T: %v", internalObj, err)
continue
}
legacyscheme.Scheme.Default(v1Obj)
roundTrippedObj, err := legacyscheme.Scheme.ConvertToVersion(v1Obj, rbac.SchemeGroupVersion)
if err != nil {
t.Errorf("err on %T: %v", internalObj, err)
continue
}
if !reflect.DeepEqual(internalObj, roundTrippedObj) {
t.Errorf("err on %T: got difference:\n%s", internalObj, diff.ObjectDiff(internalObj, roundTrippedObj))
continue
}
}
}
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)
}
})
}
}
func TestPolicyRuleBuilder(t *testing.T) {
tests := []struct {
testName string
verbs []string
groups []string
resources []string
names []string
urls []string
expected bool
policyRule rbac.PolicyRule
}{
{
testName: "all empty",
verbs: nil,
groups: nil,
resources: nil,
names: nil,
urls: nil,
expected: false,
policyRule: rbac.PolicyRule{},
},
{
testName: "normal resource case",
verbs: []string{"get"},
groups: []string{""},
resources: []string{"pod"},
names: []string{"gakki"},
urls: nil,
expected: true,
policyRule: rbac.PolicyRule{
Verbs: []string{"get"},
APIGroups: []string{""},
Resources: []string{"pod"},
ResourceNames: []string{"gakki"},
NonResourceURLs: []string{},
},
},
{
testName: "normal noResourceURLs case",
verbs: []string{"get"},
groups: nil,
resources: nil,
names: nil,
urls: []string{"/api/registry/healthz"},
expected: true,
policyRule: rbac.PolicyRule{
Verbs: []string{"get"},
APIGroups: []string{},
Resources: []string{},
ResourceNames: []string{},
NonResourceURLs: []string{"/api/registry/healthz"},
},
},
{
testName: "nonResourceURLs with no-empty groups",
verbs: []string{"get"},
groups: []string{""},
resources: nil,
names: nil,
urls: []string{"/api/registry/healthz"},
expected: false,
policyRule: rbac.PolicyRule{},
},
{
testName: "nonResourceURLs with no-empty resources",
verbs: []string{"get"},
groups: nil,
resources: []string{"deployments", "secrets"},
names: nil,
urls: []string{"/api/registry/healthz"},
expected: false,
policyRule: rbac.PolicyRule{},
},
{
testName: "nonResourceURLs with no-empty resourceNames",
verbs: []string{"get"},
groups: nil,
resources: nil,
names: []string{"gakki"},
urls: []string{"/api/registry/healthz"},
expected: false,
policyRule: rbac.PolicyRule{},
},
{
testName: "resource without apiGroups",
verbs: []string{"get"},
groups: nil,
resources: []string{"pod"},
names: []string{""},
urls: nil,
expected: false,
policyRule: rbac.PolicyRule{},
},
{
testName: "resourceNames with illegal verb",
verbs: []string{"list", "watch", "create", "deletecollection"},
groups: []string{""},
resources: []string{"pod"},
names: []string{"gakki"},
urls: nil,
expected: false,
policyRule: rbac.PolicyRule{},
},
{
testName: "no nonResourceURLs nor resources",
verbs: []string{"get"},
groups: []string{"rbac.authorization.k8s.io"},
resources: nil,
names: []string{"gakki"},
urls: nil,
expected: false,
policyRule: rbac.PolicyRule{},
},
}
for _, tc := range tests {
actual, err := rbac.NewRule(tc.verbs...).Groups(tc.groups...).Resources(tc.resources...).Names(tc.names...).URLs(tc.urls...).Rule()
if err != nil {
if tc.expected {
t.Error(err)
} else {
continue
}
}
if !reflect.DeepEqual(actual, tc.policyRule) {
t.Errorf("Expected %s got %s.", tc.policyRule, actual)
}
}
}