Add generated file

This PR adds generated files under pkg/client and vendor folder.
This commit is contained in:
xing-yang
2018-07-12 10:55:15 -07:00
parent 36b1de0341
commit e213d1890d
17729 changed files with 5090889 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"policy_compact_test.go",
"policy_comparator_test.go",
"rule_test.go",
],
embed = [":go_default_library"],
deps = [
"//pkg/apis/rbac/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"internal_version_adapter.go",
"policy_compact.go",
"policy_comparator.go",
"rule.go",
],
importpath = "k8s.io/kubernetes/pkg/registry/rbac/validation",
deps = [
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/v1:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/serviceaccount:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@@ -0,0 +1,39 @@
/*
Copyright 2018 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 validation
import (
"context"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/kubernetes/pkg/apis/rbac"
rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
)
func ConfirmNoEscalationInternal(ctx context.Context, ruleResolver AuthorizationRuleResolver, inRules []rbac.PolicyRule) error {
rules := []rbacv1.PolicyRule{}
for i := range inRules {
v1Rule := rbacv1.PolicyRule{}
err := rbacv1helpers.Convert_rbac_PolicyRule_To_v1_PolicyRule(&inRules[i], &v1Rule, nil)
if err != nil {
return err
}
rules = append(rules, v1Rule)
}
return ConfirmNoEscalation(ctx, ruleResolver, rules)
}

View File

@@ -0,0 +1,89 @@
/*
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 validation
import (
"reflect"
rbacv1 "k8s.io/api/rbac/v1"
)
type simpleResource struct {
Group string
Resource string
ResourceNameExist bool
ResourceName string
}
// CompactRules combines rules that contain a single APIGroup/Resource, differ only by verb, and contain no other attributes.
// this is a fast check, and works well with the decomposed "missing rules" list from a Covers check.
func CompactRules(rules []rbacv1.PolicyRule) ([]rbacv1.PolicyRule, error) {
compacted := make([]rbacv1.PolicyRule, 0, len(rules))
simpleRules := map[simpleResource]*rbacv1.PolicyRule{}
for _, rule := range rules {
if resource, isSimple := isSimpleResourceRule(&rule); isSimple {
if existingRule, ok := simpleRules[resource]; ok {
// Add the new verbs to the existing simple resource rule
if existingRule.Verbs == nil {
existingRule.Verbs = []string{}
}
existingRule.Verbs = append(existingRule.Verbs, rule.Verbs...)
} else {
// Copy the rule to accumulate matching simple resource rules into
simpleRules[resource] = rule.DeepCopy()
}
} else {
compacted = append(compacted, rule)
}
}
// Once we've consolidated the simple resource rules, add them to the compacted list
for _, simpleRule := range simpleRules {
compacted = append(compacted, *simpleRule)
}
return compacted, nil
}
// isSimpleResourceRule returns true if the given rule contains verbs, a single resource, a single API group, at most one Resource Name, and no other values
func isSimpleResourceRule(rule *rbacv1.PolicyRule) (simpleResource, bool) {
resource := simpleResource{}
// If we have "complex" rule attributes, return early without allocations or expensive comparisons
if len(rule.ResourceNames) > 1 || len(rule.NonResourceURLs) > 0 {
return resource, false
}
// If we have multiple api groups or resources, return early
if len(rule.APIGroups) != 1 || len(rule.Resources) != 1 {
return resource, false
}
// Test if this rule only contains APIGroups/Resources/Verbs/ResourceNames
simpleRule := &rbacv1.PolicyRule{APIGroups: rule.APIGroups, Resources: rule.Resources, Verbs: rule.Verbs, ResourceNames: rule.ResourceNames}
if !reflect.DeepEqual(simpleRule, rule) {
return resource, false
}
if len(rule.ResourceNames) == 0 {
resource = simpleResource{Group: rule.APIGroups[0], Resource: rule.Resources[0], ResourceNameExist: false}
} else {
resource = simpleResource{Group: rule.APIGroups[0], Resource: rule.Resources[0], ResourceNameExist: true, ResourceName: rule.ResourceNames[0]}
}
return resource, true
}

View File

@@ -0,0 +1,227 @@
/*
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 validation
import (
"reflect"
"sort"
"testing"
rbacv1 "k8s.io/api/rbac/v1"
rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
)
func TestCompactRules(t *testing.T) {
testcases := map[string]struct {
Rules []rbacv1.PolicyRule
Expected []rbacv1.PolicyRule
}{
"empty": {
Rules: []rbacv1.PolicyRule{},
Expected: []rbacv1.PolicyRule{},
},
"simple": {
Rules: []rbacv1.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}},
{Verbs: []string{"list"}, APIGroups: []string{""}, Resources: []string{"builds"}},
{Verbs: []string{"update", "patch"}, APIGroups: []string{""}, Resources: []string{"builds"}},
{Verbs: []string{"create"}, APIGroups: []string{"extensions"}, Resources: []string{"daemonsets"}},
{Verbs: []string{"delete"}, APIGroups: []string{"extensions"}, Resources: []string{"daemonsets"}},
{Verbs: []string{"patch"}, APIGroups: []string{"extensions"}, Resources: []string{"daemonsets"}, ResourceNames: []string{""}},
{Verbs: []string{"get"}, APIGroups: []string{"extensions"}, Resources: []string{"daemonsets"}, ResourceNames: []string{"foo"}},
{Verbs: []string{"list"}, APIGroups: []string{"extensions"}, Resources: []string{"daemonsets"}, ResourceNames: []string{"foo"}},
{Verbs: []string{"educate"}, APIGroups: []string{""}, Resources: []string{"dolphins"}},
// nil verbs are preserved in non-merge cases.
// these are the pirates who don't do anything.
{Verbs: nil, APIGroups: []string{""}, Resources: []string{"pirates"}},
// Test merging into a nil Verbs string set
{Verbs: nil, APIGroups: []string{""}, Resources: []string{"pods"}},
{Verbs: []string{"create"}, APIGroups: []string{""}, Resources: []string{"pods"}},
},
Expected: []rbacv1.PolicyRule{
{Verbs: []string{"create", "delete"}, APIGroups: []string{"extensions"}, Resources: []string{"daemonsets"}},
{Verbs: []string{"patch"}, APIGroups: []string{"extensions"}, Resources: []string{"daemonsets"}, ResourceNames: []string{""}},
{Verbs: []string{"get", "list"}, APIGroups: []string{"extensions"}, Resources: []string{"daemonsets"}, ResourceNames: []string{"foo"}},
{Verbs: []string{"get", "list", "update", "patch"}, APIGroups: []string{""}, Resources: []string{"builds"}},
{Verbs: []string{"educate"}, APIGroups: []string{""}, Resources: []string{"dolphins"}},
{Verbs: nil, APIGroups: []string{""}, Resources: []string{"pirates"}},
{Verbs: []string{"create"}, APIGroups: []string{""}, Resources: []string{"pods"}},
},
},
"complex multi-group": {
Rules: []rbacv1.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{"", "builds.openshift.io"}, Resources: []string{"builds"}},
{Verbs: []string{"list"}, APIGroups: []string{"", "builds.openshift.io"}, Resources: []string{"builds"}},
},
Expected: []rbacv1.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{"", "builds.openshift.io"}, Resources: []string{"builds"}},
{Verbs: []string{"list"}, APIGroups: []string{"", "builds.openshift.io"}, Resources: []string{"builds"}},
},
},
"complex multi-resource": {
Rules: []rbacv1.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds", "images"}},
{Verbs: []string{"list"}, APIGroups: []string{""}, Resources: []string{"builds", "images"}},
},
Expected: []rbacv1.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds", "images"}},
{Verbs: []string{"list"}, APIGroups: []string{""}, Resources: []string{"builds", "images"}},
},
},
"complex named-resource": {
Rules: []rbacv1.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, ResourceNames: []string{"mybuild"}},
{Verbs: []string{"list"}, APIGroups: []string{""}, Resources: []string{"builds"}, ResourceNames: []string{"mybuild2"}},
},
Expected: []rbacv1.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, ResourceNames: []string{"mybuild"}},
{Verbs: []string{"list"}, APIGroups: []string{""}, Resources: []string{"builds"}, ResourceNames: []string{"mybuild2"}},
},
},
"complex non-resource": {
Rules: []rbacv1.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, NonResourceURLs: []string{"/"}},
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, NonResourceURLs: []string{"/foo"}},
},
Expected: []rbacv1.PolicyRule{
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, NonResourceURLs: []string{"/"}},
{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, NonResourceURLs: []string{"/foo"}},
},
},
}
for k, tc := range testcases {
rules := tc.Rules
originalRules := make([]rbacv1.PolicyRule, len(tc.Rules))
for i := range tc.Rules {
originalRules[i] = *tc.Rules[i].DeepCopy()
}
compacted, err := CompactRules(tc.Rules)
if err != nil {
t.Errorf("%s: unexpected error: %v", k, err)
continue
}
if !reflect.DeepEqual(rules, originalRules) {
t.Errorf("%s: CompactRules mutated rules. Expected\n%#v\ngot\n%#v", k, originalRules, rules)
continue
}
if covers, missing := Covers(compacted, rules); !covers {
t.Errorf("%s: compacted rules did not cover original rules. missing: %#v", k, missing)
continue
}
if covers, missing := Covers(rules, compacted); !covers {
t.Errorf("%s: original rules did not cover compacted rules. missing: %#v", k, missing)
continue
}
sort.Stable(rbacv1helpers.SortableRuleSlice(compacted))
sort.Stable(rbacv1helpers.SortableRuleSlice(tc.Expected))
if !reflect.DeepEqual(compacted, tc.Expected) {
t.Errorf("%s: Expected\n%#v\ngot\n%#v", k, tc.Expected, compacted)
continue
}
}
}
func TestIsSimpleResourceRule(t *testing.T) {
testcases := map[string]struct {
Rule rbacv1.PolicyRule
Simple bool
Resource simpleResource
}{
"simple, no verbs": {
Rule: rbacv1.PolicyRule{Verbs: []string{}, APIGroups: []string{""}, Resources: []string{"builds"}},
Simple: true,
Resource: simpleResource{Group: "", Resource: "builds"},
},
"simple, one verb": {
Rule: rbacv1.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}},
Simple: true,
Resource: simpleResource{Group: "", Resource: "builds"},
},
"simple, one empty resource name": {
Rule: rbacv1.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, ResourceNames: []string{""}},
Simple: true,
Resource: simpleResource{Group: "", Resource: "builds", ResourceNameExist: true, ResourceName: ""},
},
"simple, one resource name": {
Rule: rbacv1.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, ResourceNames: []string{"foo"}},
Simple: true,
Resource: simpleResource{Group: "", Resource: "builds", ResourceNameExist: true, ResourceName: "foo"},
},
"simple, multi verb": {
Rule: rbacv1.PolicyRule{Verbs: []string{"get", "list"}, APIGroups: []string{""}, Resources: []string{"builds"}},
Simple: true,
Resource: simpleResource{Group: "", Resource: "builds"},
},
"complex, empty": {
Rule: rbacv1.PolicyRule{},
Simple: false,
Resource: simpleResource{},
},
"complex, no group": {
Rule: rbacv1.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{}, Resources: []string{"builds"}},
Simple: false,
Resource: simpleResource{},
},
"complex, multi group": {
Rule: rbacv1.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{"a", "b"}, Resources: []string{"builds"}},
Simple: false,
Resource: simpleResource{},
},
"complex, no resource": {
Rule: rbacv1.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{}},
Simple: false,
Resource: simpleResource{},
},
"complex, multi resource": {
Rule: rbacv1.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds", "images"}},
Simple: false,
Resource: simpleResource{},
},
"complex, resource names": {
Rule: rbacv1.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, ResourceNames: []string{"foo", "bar"}},
Simple: false,
Resource: simpleResource{},
},
"complex, non-resource urls": {
Rule: rbacv1.PolicyRule{Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"builds"}, NonResourceURLs: []string{"/"}},
Simple: false,
Resource: simpleResource{},
},
}
for k, tc := range testcases {
resource, simple := isSimpleResourceRule(&tc.Rule)
if simple != tc.Simple {
t.Errorf("%s: expected simple=%v, got simple=%v", k, tc.Simple, simple)
continue
}
if resource != tc.Resource {
t.Errorf("%s: expected resource=%v, got resource=%v", k, tc.Resource, resource)
continue
}
}
}

View File

@@ -0,0 +1,173 @@
/*
Copyright 2016 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 validation
import (
"strings"
rbacv1 "k8s.io/api/rbac/v1"
)
// Covers determines whether or not the ownerRules cover the servantRules in terms of allowed actions.
// It returns whether or not the ownerRules cover and a list of the rules that the ownerRules do not cover.
func Covers(ownerRules, servantRules []rbacv1.PolicyRule) (bool, []rbacv1.PolicyRule) {
// 1. Break every servantRule into individual rule tuples: group, verb, resource, resourceName
// 2. Compare the mini-rules against each owner rule. Because the breakdown is down to the most atomic level, we're guaranteed that each mini-servant rule will be either fully covered or not covered by a single owner rule
// 3. Any left over mini-rules means that we are not covered and we have a nice list of them.
// TODO: it might be nice to collapse the list down into something more human readable
subrules := []rbacv1.PolicyRule{}
for _, servantRule := range servantRules {
subrules = append(subrules, BreakdownRule(servantRule)...)
}
uncoveredRules := []rbacv1.PolicyRule{}
for _, subrule := range subrules {
covered := false
for _, ownerRule := range ownerRules {
if ruleCovers(ownerRule, subrule) {
covered = true
break
}
}
if !covered {
uncoveredRules = append(uncoveredRules, subrule)
}
}
return (len(uncoveredRules) == 0), uncoveredRules
}
// BreadownRule takes a rule and builds an equivalent list of rules that each have at most one verb, one
// resource, and one resource name
func BreakdownRule(rule rbacv1.PolicyRule) []rbacv1.PolicyRule {
subrules := []rbacv1.PolicyRule{}
for _, group := range rule.APIGroups {
for _, resource := range rule.Resources {
for _, verb := range rule.Verbs {
if len(rule.ResourceNames) > 0 {
for _, resourceName := range rule.ResourceNames {
subrules = append(subrules, rbacv1.PolicyRule{APIGroups: []string{group}, Resources: []string{resource}, Verbs: []string{verb}, ResourceNames: []string{resourceName}})
}
} else {
subrules = append(subrules, rbacv1.PolicyRule{APIGroups: []string{group}, Resources: []string{resource}, Verbs: []string{verb}})
}
}
}
}
// Non-resource URLs are unique because they only combine with verbs.
for _, nonResourceURL := range rule.NonResourceURLs {
for _, verb := range rule.Verbs {
subrules = append(subrules, rbacv1.PolicyRule{NonResourceURLs: []string{nonResourceURL}, Verbs: []string{verb}})
}
}
return subrules
}
func has(set []string, ele string) bool {
for _, s := range set {
if s == ele {
return true
}
}
return false
}
func hasAll(set, contains []string) bool {
owning := make(map[string]struct{}, len(set))
for _, ele := range set {
owning[ele] = struct{}{}
}
for _, ele := range contains {
if _, ok := owning[ele]; !ok {
return false
}
}
return true
}
func resourceCoversAll(setResources, coversResources []string) bool {
// if we have a star or an exact match on all resources, then we match
if has(setResources, rbacv1.ResourceAll) || hasAll(setResources, coversResources) {
return true
}
for _, path := range coversResources {
// if we have an exact match, then we match.
if has(setResources, path) {
continue
}
// if we're not a subresource, then we definitely don't match. fail.
if !strings.Contains(path, "/") {
return false
}
tokens := strings.SplitN(path, "/", 2)
resourceToCheck := "*/" + tokens[1]
if !has(setResources, resourceToCheck) {
return false
}
}
return true
}
func nonResourceURLsCoversAll(set, covers []string) bool {
for _, path := range covers {
covered := false
for _, owner := range set {
if nonResourceURLCovers(owner, path) {
covered = true
break
}
}
if !covered {
return false
}
}
return true
}
func nonResourceURLCovers(ownerPath, subPath string) bool {
if ownerPath == subPath {
return true
}
return strings.HasSuffix(ownerPath, "*") && strings.HasPrefix(subPath, strings.TrimRight(ownerPath, "*"))
}
// ruleCovers determines whether the ownerRule (which may have multiple verbs, resources, and resourceNames) covers
// the subrule (which may only contain at most one verb, resource, and resourceName)
func ruleCovers(ownerRule, subRule rbacv1.PolicyRule) bool {
verbMatches := has(ownerRule.Verbs, rbacv1.VerbAll) || hasAll(ownerRule.Verbs, subRule.Verbs)
groupMatches := has(ownerRule.APIGroups, rbacv1.APIGroupAll) || hasAll(ownerRule.APIGroups, subRule.APIGroups)
resourceMatches := resourceCoversAll(ownerRule.Resources, subRule.Resources)
nonResourceURLMatches := nonResourceURLsCoversAll(ownerRule.NonResourceURLs, subRule.NonResourceURLs)
resourceNameMatches := false
if len(subRule.ResourceNames) == 0 {
resourceNameMatches = (len(ownerRule.ResourceNames) == 0)
} else {
resourceNameMatches = (len(ownerRule.ResourceNames) == 0) || hasAll(ownerRule.ResourceNames, subRule.ResourceNames)
}
return verbMatches && groupMatches && resourceMatches && resourceNameMatches && nonResourceURLMatches
}

View File

@@ -0,0 +1,446 @@
/*
Copyright 2016 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 validation
import (
"reflect"
"testing"
rbacv1 "k8s.io/api/rbac/v1"
)
type escalationTest struct {
ownerRules []rbacv1.PolicyRule
servantRules []rbacv1.PolicyRule
expectedCovered bool
expectedUncoveredRules []rbacv1.PolicyRule
}
func TestCoversExactMatch(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"builds"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"builds"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbacv1.PolicyRule{},
}.test(t)
}
func TestCoversSubresourceWildcard(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"*/scale"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"foo/scale"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbacv1.PolicyRule{},
}.test(t)
}
func TestCoversMultipleRulesCoveringSingleRule(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"delete"}, Resources: []string{"deployments"}},
{APIGroups: []string{"v1"}, Verbs: []string{"delete"}, Resources: []string{"builds"}},
{APIGroups: []string{"v1"}, Verbs: []string{"update"}, Resources: []string{"builds", "deployments"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"delete", "update"}, Resources: []string{"builds", "deployments"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbacv1.PolicyRule{},
}.test(t)
}
func TestCoversMultipleAPIGroupsCoveringSingleRule(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"group1"}, Verbs: []string{"delete"}, Resources: []string{"deployments"}},
{APIGroups: []string{"group1"}, Verbs: []string{"delete"}, Resources: []string{"builds"}},
{APIGroups: []string{"group1"}, Verbs: []string{"update"}, Resources: []string{"builds", "deployments"}},
{APIGroups: []string{"group2"}, Verbs: []string{"delete"}, Resources: []string{"deployments"}},
{APIGroups: []string{"group2"}, Verbs: []string{"delete"}, Resources: []string{"builds"}},
{APIGroups: []string{"group2"}, Verbs: []string{"update"}, Resources: []string{"builds", "deployments"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"group1", "group2"}, Verbs: []string{"delete", "update"}, Resources: []string{"builds", "deployments"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbacv1.PolicyRule{},
}.test(t)
}
func TestCoversSingleAPIGroupsCoveringMultiple(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"group1", "group2"}, Verbs: []string{"delete", "update"}, Resources: []string{"builds", "deployments"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"group1"}, Verbs: []string{"delete"}, Resources: []string{"deployments"}},
{APIGroups: []string{"group1"}, Verbs: []string{"delete"}, Resources: []string{"builds"}},
{APIGroups: []string{"group1"}, Verbs: []string{"update"}, Resources: []string{"builds", "deployments"}},
{APIGroups: []string{"group2"}, Verbs: []string{"delete"}, Resources: []string{"deployments"}},
{APIGroups: []string{"group2"}, Verbs: []string{"delete"}, Resources: []string{"builds"}},
{APIGroups: []string{"group2"}, Verbs: []string{"update"}, Resources: []string{"builds", "deployments"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbacv1.PolicyRule{},
}.test(t)
}
func TestCoversMultipleRulesMissingSingleVerbResourceCombination(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"delete", "update"}, Resources: []string{"builds", "deployments"}},
{APIGroups: []string{"v1"}, Verbs: []string{"delete"}, Resources: []string{"pods"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"delete", "update"}, Resources: []string{"builds", "deployments", "pods"}},
},
expectedCovered: false,
expectedUncoveredRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"update"}, Resources: []string{"pods"}},
},
}.test(t)
}
func TestCoversAPIGroupStarCoveringMultiple(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"*"}, Verbs: []string{"get"}, Resources: []string{"roles"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"group1", "group2"}, Verbs: []string{"get"}, Resources: []string{"roles"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbacv1.PolicyRule{},
}.test(t)
}
func TestCoversEnumerationNotCoveringAPIGroupStar(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"dummy-group"}, Verbs: []string{"get"}, Resources: []string{"roles"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"*"}, Verbs: []string{"get"}, Resources: []string{"roles"}},
},
expectedCovered: false,
expectedUncoveredRules: []rbacv1.PolicyRule{
{APIGroups: []string{"*"}, Verbs: []string{"get"}, Resources: []string{"roles"}},
},
}.test(t)
}
func TestCoversAPIGroupStarCoveringStar(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"*"}, Verbs: []string{"get"}, Resources: []string{"roles"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"*"}, Verbs: []string{"get"}, Resources: []string{"roles"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbacv1.PolicyRule{},
}.test(t)
}
func TestCoversVerbStarCoveringMultiple(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"*"}, Resources: []string{"roles"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"watch", "list"}, Resources: []string{"roles"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbacv1.PolicyRule{},
}.test(t)
}
func TestCoversEnumerationNotCoveringVerbStar(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get", "list", "watch", "create", "update", "delete", "exec"}, Resources: []string{"roles"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"*"}, Resources: []string{"roles"}},
},
expectedCovered: false,
expectedUncoveredRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"*"}, Resources: []string{"roles"}},
},
}.test(t)
}
func TestCoversVerbStarCoveringStar(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"*"}, Resources: []string{"roles"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"*"}, Resources: []string{"roles"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbacv1.PolicyRule{},
}.test(t)
}
func TestCoversResourceStarCoveringMultiple(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"*"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"resourcegroup:deployments"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbacv1.PolicyRule{},
}.test(t)
}
func TestCoversEnumerationNotCoveringResourceStar(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"roles", "resourcegroup:deployments"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"*"}},
},
expectedCovered: false,
expectedUncoveredRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"*"}},
},
}.test(t)
}
func TestCoversResourceStarCoveringStar(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"*"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"*"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbacv1.PolicyRule{},
}.test(t)
}
func TestCoversResourceNameEmptyCoveringMultiple(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"pods"}, ResourceNames: []string{}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"pods"}, ResourceNames: []string{"foo", "bar"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbacv1.PolicyRule{},
}.test(t)
}
func TestCoversEnumerationNotCoveringResourceNameEmpty(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"pods"}, ResourceNames: []string{"foo", "bar"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"pods"}, ResourceNames: []string{}},
},
expectedCovered: false,
expectedUncoveredRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"pods"}},
},
}.test(t)
}
func TestCoversNonResourceURLs(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{NonResourceURLs: []string{"/apis"}, Verbs: []string{"*"}},
},
servantRules: []rbacv1.PolicyRule{
{NonResourceURLs: []string{"/apis"}, Verbs: []string{"*"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbacv1.PolicyRule{},
}.test(t)
}
func TestCoversNonResourceURLsStar(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{NonResourceURLs: []string{"*"}, Verbs: []string{"*"}},
},
servantRules: []rbacv1.PolicyRule{
{NonResourceURLs: []string{"/apis", "/apis/v1", "/"}, Verbs: []string{"*"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbacv1.PolicyRule{},
}.test(t)
}
func TestCoversNonResourceURLsStarAfterPrefixDoesntCover(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{NonResourceURLs: []string{"/apis/*"}, Verbs: []string{"*"}},
},
servantRules: []rbacv1.PolicyRule{
{NonResourceURLs: []string{"/apis", "/apis/v1"}, Verbs: []string{"get"}},
},
expectedCovered: false,
expectedUncoveredRules: []rbacv1.PolicyRule{
{NonResourceURLs: []string{"/apis"}, Verbs: []string{"get"}},
},
}.test(t)
}
func TestCoversNonResourceURLsStarAfterPrefix(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{NonResourceURLs: []string{"/apis/*"}, Verbs: []string{"*"}},
},
servantRules: []rbacv1.PolicyRule{
{NonResourceURLs: []string{"/apis/v1/foo", "/apis/v1"}, Verbs: []string{"get"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbacv1.PolicyRule{},
}.test(t)
}
func TestCoversNonResourceURLsWithOtherFields(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"builds"}, NonResourceURLs: []string{"/apis"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"builds"}, NonResourceURLs: []string{"/apis"}},
},
expectedCovered: true,
expectedUncoveredRules: []rbacv1.PolicyRule{},
}.test(t)
}
func TestCoversNonResourceURLsWithOtherFieldsFailure(t *testing.T) {
escalationTest{
ownerRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"builds"}},
},
servantRules: []rbacv1.PolicyRule{
{APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"builds"}, NonResourceURLs: []string{"/apis"}},
},
expectedCovered: false,
expectedUncoveredRules: []rbacv1.PolicyRule{{NonResourceURLs: []string{"/apis"}, Verbs: []string{"get"}}},
}.test(t)
}
func (test escalationTest) test(t *testing.T) {
actualCovered, actualUncoveredRules := Covers(test.ownerRules, test.servantRules)
if actualCovered != test.expectedCovered {
t.Errorf("expected %v, but got %v", test.expectedCovered, actualCovered)
}
if !rulesMatch(test.expectedUncoveredRules, actualUncoveredRules) {
t.Errorf("expected %v, but got %v", test.expectedUncoveredRules, actualUncoveredRules)
}
}
func rulesMatch(expectedRules, actualRules []rbacv1.PolicyRule) bool {
if len(expectedRules) != len(actualRules) {
return false
}
for _, expectedRule := range expectedRules {
found := false
for _, actualRule := range actualRules {
if reflect.DeepEqual(expectedRule, actualRule) {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func TestNonResourceURLCovers(t *testing.T) {
tests := []struct {
owner string
requested string
want bool
}{
{"*", "", true},
{"*", "/", true},
{"*", "/api", true},
{"/*", "", false},
{"/*", "/", true},
{"/*", "/foo", true},
{"/api", "/api", true},
{"/apis", "/api", false},
{"/api/v1", "/api", false},
{"/api/v1", "/api/v1", true},
{"/api/*", "/api/v1", true},
{"/api/*", "/api", false},
{"/api/*/*", "/api/v1", false},
{"/*/v1/*", "/api/v1", false},
}
for _, tc := range tests {
got := nonResourceURLCovers(tc.owner, tc.requested)
if got != tc.want {
t.Errorf("nonResourceURLCovers(%q, %q): want=(%t), got=(%t)", tc.owner, tc.requested, tc.want, got)
}
}
}

View File

@@ -0,0 +1,340 @@
/*
Copyright 2016 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 validation
import (
"context"
"errors"
"fmt"
"github.com/golang/glog"
rbacv1 "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apiserver/pkg/authentication/serviceaccount"
"k8s.io/apiserver/pkg/authentication/user"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
)
type AuthorizationRuleResolver interface {
// GetRoleReferenceRules attempts to resolve the role reference of a RoleBinding or ClusterRoleBinding. The passed namespace should be the namepsace
// of the role binding, the empty string if a cluster role binding.
GetRoleReferenceRules(roleRef rbacv1.RoleRef, namespace string) ([]rbacv1.PolicyRule, error)
// RulesFor returns the list of rules that apply to a given user in a given namespace and error. If an error is returned, the slice of
// PolicyRules may not be complete, but it contains all retrievable rules. This is done because policy rules are purely additive and policy determinations
// can be made on the basis of those rules that are found.
RulesFor(user user.Info, namespace string) ([]rbacv1.PolicyRule, error)
// VisitRulesFor invokes visitor() with each rule that applies to a given user in a given namespace, and each error encountered resolving those rules.
// If visitor() returns false, visiting is short-circuited.
VisitRulesFor(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool)
}
// ConfirmNoEscalation determines if the roles for a given user in a given namespace encompass the provided role.
func ConfirmNoEscalation(ctx context.Context, ruleResolver AuthorizationRuleResolver, rules []rbacv1.PolicyRule) error {
ruleResolutionErrors := []error{}
user, ok := genericapirequest.UserFrom(ctx)
if !ok {
return fmt.Errorf("no user on context")
}
namespace, _ := genericapirequest.NamespaceFrom(ctx)
ownerRules, err := ruleResolver.RulesFor(user, namespace)
if err != nil {
// As per AuthorizationRuleResolver contract, this may return a non fatal error with an incomplete list of policies. Log the error and continue.
glog.V(1).Infof("non-fatal error getting local rules for %v: %v", user, err)
ruleResolutionErrors = append(ruleResolutionErrors, err)
}
ownerRightsCover, missingRights := Covers(ownerRules, rules)
if !ownerRightsCover {
return apierrors.NewUnauthorized(fmt.Sprintf("attempt to grant extra privileges: %v user=%v ownerrules=%v ruleResolutionErrors=%v", missingRights, user, ownerRules, ruleResolutionErrors))
}
return nil
}
type DefaultRuleResolver struct {
roleGetter RoleGetter
roleBindingLister RoleBindingLister
clusterRoleGetter ClusterRoleGetter
clusterRoleBindingLister ClusterRoleBindingLister
}
func NewDefaultRuleResolver(roleGetter RoleGetter, roleBindingLister RoleBindingLister, clusterRoleGetter ClusterRoleGetter, clusterRoleBindingLister ClusterRoleBindingLister) *DefaultRuleResolver {
return &DefaultRuleResolver{roleGetter, roleBindingLister, clusterRoleGetter, clusterRoleBindingLister}
}
type RoleGetter interface {
GetRole(namespace, name string) (*rbacv1.Role, error)
}
type RoleBindingLister interface {
ListRoleBindings(namespace string) ([]*rbacv1.RoleBinding, error)
}
type ClusterRoleGetter interface {
GetClusterRole(name string) (*rbacv1.ClusterRole, error)
}
type ClusterRoleBindingLister interface {
ListClusterRoleBindings() ([]*rbacv1.ClusterRoleBinding, error)
}
func (r *DefaultRuleResolver) RulesFor(user user.Info, namespace string) ([]rbacv1.PolicyRule, error) {
visitor := &ruleAccumulator{}
r.VisitRulesFor(user, namespace, visitor.visit)
return visitor.rules, utilerrors.NewAggregate(visitor.errors)
}
type ruleAccumulator struct {
rules []rbacv1.PolicyRule
errors []error
}
func (r *ruleAccumulator) visit(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool {
if rule != nil {
r.rules = append(r.rules, *rule)
}
if err != nil {
r.errors = append(r.errors, err)
}
return true
}
func describeSubject(s *rbacv1.Subject, bindingNamespace string) string {
switch s.Kind {
case rbacv1.ServiceAccountKind:
if len(s.Namespace) > 0 {
return fmt.Sprintf("%s %q", s.Kind, s.Name+"/"+s.Namespace)
}
return fmt.Sprintf("%s %q", s.Kind, s.Name+"/"+bindingNamespace)
default:
return fmt.Sprintf("%s %q", s.Kind, s.Name)
}
}
type clusterRoleBindingDescriber struct {
binding *rbacv1.ClusterRoleBinding
subject *rbacv1.Subject
}
func (d *clusterRoleBindingDescriber) String() string {
return fmt.Sprintf("ClusterRoleBinding %q of %s %q to %s",
d.binding.Name,
d.binding.RoleRef.Kind,
d.binding.RoleRef.Name,
describeSubject(d.subject, ""),
)
}
type roleBindingDescriber struct {
binding *rbacv1.RoleBinding
subject *rbacv1.Subject
}
func (d *roleBindingDescriber) String() string {
return fmt.Sprintf("RoleBinding %q of %s %q to %s",
d.binding.Name+"/"+d.binding.Namespace,
d.binding.RoleRef.Kind,
d.binding.RoleRef.Name,
describeSubject(d.subject, d.binding.Namespace),
)
}
func (r *DefaultRuleResolver) VisitRulesFor(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) {
if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(); err != nil {
if !visitor(nil, nil, err) {
return
}
} else {
sourceDescriber := &clusterRoleBindingDescriber{}
for _, clusterRoleBinding := range clusterRoleBindings {
subjectIndex, applies := appliesTo(user, clusterRoleBinding.Subjects, "")
if !applies {
continue
}
rules, err := r.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "")
if err != nil {
if !visitor(nil, nil, err) {
return
}
continue
}
sourceDescriber.binding = clusterRoleBinding
sourceDescriber.subject = &clusterRoleBinding.Subjects[subjectIndex]
for i := range rules {
if !visitor(sourceDescriber, &rules[i], nil) {
return
}
}
}
}
if len(namespace) > 0 {
if roleBindings, err := r.roleBindingLister.ListRoleBindings(namespace); err != nil {
if !visitor(nil, nil, err) {
return
}
} else {
sourceDescriber := &roleBindingDescriber{}
for _, roleBinding := range roleBindings {
subjectIndex, applies := appliesTo(user, roleBinding.Subjects, namespace)
if !applies {
continue
}
rules, err := r.GetRoleReferenceRules(roleBinding.RoleRef, namespace)
if err != nil {
if !visitor(nil, nil, err) {
return
}
continue
}
sourceDescriber.binding = roleBinding
sourceDescriber.subject = &roleBinding.Subjects[subjectIndex]
for i := range rules {
if !visitor(sourceDescriber, &rules[i], nil) {
return
}
}
}
}
}
}
// GetRoleReferenceRules attempts to resolve the RoleBinding or ClusterRoleBinding.
func (r *DefaultRuleResolver) GetRoleReferenceRules(roleRef rbacv1.RoleRef, bindingNamespace string) ([]rbacv1.PolicyRule, error) {
switch roleRef.Kind {
case "Role":
role, err := r.roleGetter.GetRole(bindingNamespace, roleRef.Name)
if err != nil {
return nil, err
}
return role.Rules, nil
case "ClusterRole":
clusterRole, err := r.clusterRoleGetter.GetClusterRole(roleRef.Name)
if err != nil {
return nil, err
}
return clusterRole.Rules, nil
default:
return nil, fmt.Errorf("unsupported role reference kind: %q", roleRef.Kind)
}
}
// appliesTo returns whether any of the bindingSubjects applies to the specified subject,
// and if true, the index of the first subject that applies
func appliesTo(user user.Info, bindingSubjects []rbacv1.Subject, namespace string) (int, bool) {
for i, bindingSubject := range bindingSubjects {
if appliesToUser(user, bindingSubject, namespace) {
return i, true
}
}
return 0, false
}
func appliesToUser(user user.Info, subject rbacv1.Subject, namespace string) bool {
switch subject.Kind {
case rbacv1.UserKind:
return user.GetName() == subject.Name
case rbacv1.GroupKind:
return has(user.GetGroups(), subject.Name)
case rbacv1.ServiceAccountKind:
// default the namespace to namespace we're working in if its available. This allows rolebindings that reference
// SAs in th local namespace to avoid having to qualify them.
saNamespace := namespace
if len(subject.Namespace) > 0 {
saNamespace = subject.Namespace
}
if len(saNamespace) == 0 {
return false
}
return serviceaccount.MakeUsername(saNamespace, subject.Name) == user.GetName()
default:
return false
}
}
// NewTestRuleResolver returns a rule resolver from lists of role objects.
func NewTestRuleResolver(roles []*rbacv1.Role, roleBindings []*rbacv1.RoleBinding, clusterRoles []*rbacv1.ClusterRole, clusterRoleBindings []*rbacv1.ClusterRoleBinding) (AuthorizationRuleResolver, *StaticRoles) {
r := StaticRoles{
roles: roles,
roleBindings: roleBindings,
clusterRoles: clusterRoles,
clusterRoleBindings: clusterRoleBindings,
}
return newMockRuleResolver(&r), &r
}
func newMockRuleResolver(r *StaticRoles) AuthorizationRuleResolver {
return NewDefaultRuleResolver(r, r, r, r)
}
// StaticRoles is a rule resolver that resolves from lists of role objects.
type StaticRoles struct {
roles []*rbacv1.Role
roleBindings []*rbacv1.RoleBinding
clusterRoles []*rbacv1.ClusterRole
clusterRoleBindings []*rbacv1.ClusterRoleBinding
}
func (r *StaticRoles) GetRole(namespace, name string) (*rbacv1.Role, error) {
if len(namespace) == 0 {
return nil, errors.New("must provide namespace when getting role")
}
for _, role := range r.roles {
if role.Namespace == namespace && role.Name == name {
return role, nil
}
}
return nil, errors.New("role not found")
}
func (r *StaticRoles) GetClusterRole(name string) (*rbacv1.ClusterRole, error) {
for _, clusterRole := range r.clusterRoles {
if clusterRole.Name == name {
return clusterRole, nil
}
}
return nil, errors.New("clusterrole not found")
}
func (r *StaticRoles) ListRoleBindings(namespace string) ([]*rbacv1.RoleBinding, error) {
if len(namespace) == 0 {
return nil, errors.New("must provide namespace when listing role bindings")
}
roleBindingList := []*rbacv1.RoleBinding{}
for _, roleBinding := range r.roleBindings {
if roleBinding.Namespace != namespace {
continue
}
// TODO(ericchiang): need to implement label selectors?
roleBindingList = append(roleBindingList, roleBinding)
}
return roleBindingList, nil
}
func (r *StaticRoles) ListClusterRoleBindings() ([]*rbacv1.ClusterRoleBinding, error) {
return r.clusterRoleBindings, nil
}

View File

@@ -0,0 +1,277 @@
/*
Copyright 2016 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 validation
import (
"hash/fnv"
"io"
"reflect"
"sort"
"testing"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apiserver/pkg/authentication/user"
)
// compute a hash of a policy rule so we can sort in a deterministic order
func hashOf(p rbacv1.PolicyRule) string {
hash := fnv.New32()
writeStrings := func(slis ...[]string) {
for _, sli := range slis {
for _, s := range sli {
io.WriteString(hash, s)
}
}
}
writeStrings(p.Verbs, p.APIGroups, p.Resources, p.ResourceNames, p.NonResourceURLs)
return string(hash.Sum(nil))
}
// byHash sorts a set of policy rules by a hash of its fields
type byHash []rbacv1.PolicyRule
func (b byHash) Len() int { return len(b) }
func (b byHash) Less(i, j int) bool { return hashOf(b[i]) < hashOf(b[j]) }
func (b byHash) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func TestDefaultRuleResolver(t *testing.T) {
ruleReadPods := rbacv1.PolicyRule{
Verbs: []string{"GET", "WATCH"},
APIGroups: []string{"v1"},
Resources: []string{"pods"},
}
ruleReadServices := rbacv1.PolicyRule{
Verbs: []string{"GET", "WATCH"},
APIGroups: []string{"v1"},
Resources: []string{"services"},
}
ruleWriteNodes := rbacv1.PolicyRule{
Verbs: []string{"PUT", "CREATE", "UPDATE"},
APIGroups: []string{"v1"},
Resources: []string{"nodes"},
}
ruleAdmin := rbacv1.PolicyRule{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"*"},
}
staticRoles1 := StaticRoles{
roles: []*rbacv1.Role{
{
ObjectMeta: metav1.ObjectMeta{Namespace: "namespace1", Name: "readthings"},
Rules: []rbacv1.PolicyRule{ruleReadPods, ruleReadServices},
},
},
clusterRoles: []*rbacv1.ClusterRole{
{
ObjectMeta: metav1.ObjectMeta{Name: "cluster-admin"},
Rules: []rbacv1.PolicyRule{ruleAdmin},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "write-nodes"},
Rules: []rbacv1.PolicyRule{ruleWriteNodes},
},
},
roleBindings: []*rbacv1.RoleBinding{
{
ObjectMeta: metav1.ObjectMeta{Namespace: "namespace1"},
Subjects: []rbacv1.Subject{
{Kind: rbacv1.UserKind, Name: "foobar"},
{Kind: rbacv1.GroupKind, Name: "group1"},
},
RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "Role", Name: "readthings"},
},
},
clusterRoleBindings: []*rbacv1.ClusterRoleBinding{
{
Subjects: []rbacv1.Subject{
{Kind: rbacv1.UserKind, Name: "admin"},
{Kind: rbacv1.GroupKind, Name: "admin"},
},
RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: "cluster-admin"},
},
},
}
tests := []struct {
StaticRoles
// For a given context, what are the rules that apply?
user user.Info
namespace string
effectiveRules []rbacv1.PolicyRule
}{
{
StaticRoles: staticRoles1,
user: &user.DefaultInfo{Name: "foobar"},
namespace: "namespace1",
effectiveRules: []rbacv1.PolicyRule{ruleReadPods, ruleReadServices},
},
{
StaticRoles: staticRoles1,
user: &user.DefaultInfo{Name: "foobar"},
namespace: "namespace2",
effectiveRules: nil,
},
{
StaticRoles: staticRoles1,
// Same as above but without a namespace. Only cluster rules should apply.
user: &user.DefaultInfo{Name: "foobar", Groups: []string{"admin"}},
effectiveRules: []rbacv1.PolicyRule{ruleAdmin},
},
{
StaticRoles: staticRoles1,
user: &user.DefaultInfo{},
effectiveRules: nil,
},
}
for i, tc := range tests {
ruleResolver := newMockRuleResolver(&tc.StaticRoles)
rules, err := ruleResolver.RulesFor(tc.user, tc.namespace)
if err != nil {
t.Errorf("case %d: GetEffectivePolicyRules(context)=%v", i, err)
continue
}
// Sort for deep equals
sort.Sort(byHash(rules))
sort.Sort(byHash(tc.effectiveRules))
if !reflect.DeepEqual(rules, tc.effectiveRules) {
ruleDiff := diff.ObjectDiff(rules, tc.effectiveRules)
t.Errorf("case %d: %s", i, ruleDiff)
}
}
}
func TestAppliesTo(t *testing.T) {
tests := []struct {
subjects []rbacv1.Subject
user user.Info
namespace string
appliesTo bool
index int
testCase string
}{
{
subjects: []rbacv1.Subject{
{Kind: rbacv1.UserKind, Name: "foobar"},
},
user: &user.DefaultInfo{Name: "foobar"},
appliesTo: true,
index: 0,
testCase: "single subject that matches username",
},
{
subjects: []rbacv1.Subject{
{Kind: rbacv1.UserKind, Name: "barfoo"},
{Kind: rbacv1.UserKind, Name: "foobar"},
},
user: &user.DefaultInfo{Name: "foobar"},
appliesTo: true,
index: 1,
testCase: "multiple subjects, one that matches username",
},
{
subjects: []rbacv1.Subject{
{Kind: rbacv1.UserKind, Name: "barfoo"},
{Kind: rbacv1.UserKind, Name: "foobar"},
},
user: &user.DefaultInfo{Name: "zimzam"},
appliesTo: false,
testCase: "multiple subjects, none that match username",
},
{
subjects: []rbacv1.Subject{
{Kind: rbacv1.UserKind, Name: "barfoo"},
{Kind: rbacv1.GroupKind, Name: "foobar"},
},
user: &user.DefaultInfo{Name: "zimzam", Groups: []string{"foobar"}},
appliesTo: true,
index: 1,
testCase: "multiple subjects, one that match group",
},
{
subjects: []rbacv1.Subject{
{Kind: rbacv1.UserKind, Name: "barfoo"},
{Kind: rbacv1.GroupKind, Name: "foobar"},
},
user: &user.DefaultInfo{Name: "zimzam", Groups: []string{"foobar"}},
namespace: "namespace1",
appliesTo: true,
index: 1,
testCase: "multiple subjects, one that match group, should ignore namespace",
},
{
subjects: []rbacv1.Subject{
{Kind: rbacv1.UserKind, Name: "barfoo"},
{Kind: rbacv1.GroupKind, Name: "foobar"},
{Kind: rbacv1.ServiceAccountKind, Namespace: "kube-system", Name: "default"},
},
user: &user.DefaultInfo{Name: "system:serviceaccount:kube-system:default"},
namespace: "default",
appliesTo: true,
index: 2,
testCase: "multiple subjects with a service account that matches",
},
{
subjects: []rbacv1.Subject{
{Kind: rbacv1.UserKind, Name: "*"},
},
user: &user.DefaultInfo{Name: "foobar"},
namespace: "default",
appliesTo: false,
testCase: "* user subject name doesn't match all users",
},
{
subjects: []rbacv1.Subject{
{Kind: rbacv1.GroupKind, Name: user.AllAuthenticated},
{Kind: rbacv1.GroupKind, Name: user.AllUnauthenticated},
},
user: &user.DefaultInfo{Name: "foobar", Groups: []string{user.AllAuthenticated}},
namespace: "default",
appliesTo: true,
index: 0,
testCase: "binding to all authenticated and unauthenticated subjects matches authenticated user",
},
{
subjects: []rbacv1.Subject{
{Kind: rbacv1.GroupKind, Name: user.AllAuthenticated},
{Kind: rbacv1.GroupKind, Name: user.AllUnauthenticated},
},
user: &user.DefaultInfo{Name: "system:anonymous", Groups: []string{user.AllUnauthenticated}},
namespace: "default",
appliesTo: true,
index: 1,
testCase: "binding to all authenticated and unauthenticated subjects matches anonymous user",
},
}
for _, tc := range tests {
gotIndex, got := appliesTo(tc.user, tc.subjects, tc.namespace)
if got != tc.appliesTo {
t.Errorf("case %q want appliesTo=%t, got appliesTo=%t", tc.testCase, tc.appliesTo, got)
}
if gotIndex != tc.index {
t.Errorf("case %q want index %d, got %d", tc.testCase, tc.index, gotIndex)
}
}
}