vendor files

This commit is contained in:
Serguei Bezverkhi
2018-01-09 13:57:14 -05:00
parent 558bc6c02a
commit 7b24313bd6
16547 changed files with 4527373 additions and 0 deletions

29
vendor/k8s.io/kubernetes/plugin/pkg/auth/BUILD generated vendored Normal file
View File

@ -0,0 +1,29 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["doc.go"],
importpath = "k8s.io/kubernetes/plugin/pkg/auth",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//plugin/pkg/auth/authenticator/token/bootstrap:all-srcs",
"//plugin/pkg/auth/authorizer:all-srcs",
],
tags = ["automanaged"],
)

10
vendor/k8s.io/kubernetes/plugin/pkg/auth/OWNERS generated vendored Normal file
View File

@ -0,0 +1,10 @@
approvers:
- erictune
- liggitt
- deads2k
reviewers:
- erictune
- liggitt
- deads2k
- ericchiang
- enj

View File

@ -0,0 +1,51 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["bootstrap_test.go"],
importpath = "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/bootstrap",
library = ":go_default_library",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/bootstrap/api:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["bootstrap.go"],
importpath = "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/bootstrap",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/bootstrap/api:go_default_library",
"//pkg/client/listers/core/internalversion:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user: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,220 @@
/*
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 bootstrap provides a token authenticator for TLS bootstrap secrets.
*/
package bootstrap
import (
"crypto/subtle"
"fmt"
"regexp"
"strings"
"time"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/authentication/user"
api "k8s.io/kubernetes/pkg/apis/core"
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
"k8s.io/kubernetes/pkg/client/listers/core/internalversion"
)
// TODO: A few methods in this package is copied from other sources. Either
// because the existing functionality isn't exported or because it is in a
// package that shouldn't be directly imported by this packages.
// NewTokenAuthenticator initializes a bootstrap token authenticator.
//
// Lister is expected to be for the "kube-system" namespace.
func NewTokenAuthenticator(lister internalversion.SecretNamespaceLister) *TokenAuthenticator {
return &TokenAuthenticator{lister}
}
// TokenAuthenticator authenticates bootstrap tokens from secrets in the API server.
type TokenAuthenticator struct {
lister internalversion.SecretNamespaceLister
}
// tokenErrorf prints a error message for a secret that has matched a bearer
// token but fails to meet some other criteria.
//
// tokenErrorf(secret, "has invalid value for key %s", key)
//
func tokenErrorf(s *api.Secret, format string, i ...interface{}) {
format = fmt.Sprintf("Bootstrap secret %s/%s matching bearer token ", s.Namespace, s.Name) + format
glog.V(3).Infof(format, i...)
}
// AuthenticateToken tries to match the provided token to a bootstrap token secret
// in a given namespace. If found, it authenticates the token in the
// "system:bootstrappers" group and with the "system:bootstrap:(token-id)" username.
//
// All secrets must be of type "bootstrap.kubernetes.io/token". An example secret:
//
// apiVersion: v1
// kind: Secret
// metadata:
// # Name MUST be of form "bootstrap-token-( token id )".
// name: bootstrap-token-( token id )
// namespace: kube-system
// # Only secrets of this type will be evaluated.
// type: bootstrap.kubernetes.io/token
// data:
// token-secret: ( private part of token )
// token-id: ( token id )
// # Required key usage.
// usage-bootstrap-authentication: true
// auth-extra-groups: "system:bootstrappers:custom-group1,system:bootstrappers:custom-group2"
// # May also contain an expiry.
//
// Tokens are expected to be of the form:
//
// ( token-id ).( token-secret )
//
func (t *TokenAuthenticator) AuthenticateToken(token string) (user.Info, bool, error) {
tokenID, tokenSecret, err := parseToken(token)
if err != nil {
// Token isn't of the correct form, ignore it.
return nil, false, nil
}
secretName := bootstrapapi.BootstrapTokenSecretPrefix + tokenID
secret, err := t.lister.Get(secretName)
if err != nil {
if errors.IsNotFound(err) {
glog.V(3).Infof("No secret of name %s to match bootstrap bearer token", secretName)
return nil, false, nil
}
return nil, false, err
}
if secret.DeletionTimestamp != nil {
tokenErrorf(secret, "is deleted and awaiting removal")
return nil, false, nil
}
if string(secret.Type) != string(bootstrapapi.SecretTypeBootstrapToken) || secret.Data == nil {
tokenErrorf(secret, "has invalid type, expected %s.", bootstrapapi.SecretTypeBootstrapToken)
return nil, false, nil
}
ts := getSecretString(secret, bootstrapapi.BootstrapTokenSecretKey)
if subtle.ConstantTimeCompare([]byte(ts), []byte(tokenSecret)) != 1 {
tokenErrorf(secret, "has invalid value for key %s, expected %s.", bootstrapapi.BootstrapTokenSecretKey, tokenSecret)
return nil, false, nil
}
id := getSecretString(secret, bootstrapapi.BootstrapTokenIDKey)
if id != tokenID {
tokenErrorf(secret, "has invalid value for key %s, expected %s.", bootstrapapi.BootstrapTokenIDKey, tokenID)
return nil, false, nil
}
if isSecretExpired(secret) {
// logging done in isSecretExpired method.
return nil, false, nil
}
if getSecretString(secret, bootstrapapi.BootstrapTokenUsageAuthentication) != "true" {
tokenErrorf(secret, "not marked %s=true.", bootstrapapi.BootstrapTokenUsageAuthentication)
return nil, false, nil
}
groups, err := getGroups(secret)
if err != nil {
tokenErrorf(secret, "has invalid value for key %s: %v.", bootstrapapi.BootstrapTokenExtraGroupsKey, err)
return nil, false, nil
}
return &user.DefaultInfo{
Name: bootstrapapi.BootstrapUserPrefix + string(id),
Groups: groups,
}, true, nil
}
// Copied from k8s.io/kubernetes/pkg/bootstrap/api
func getSecretString(secret *api.Secret, key string) string {
if secret.Data == nil {
return ""
}
if val, ok := secret.Data[key]; ok {
return string(val)
}
return ""
}
// Copied from k8s.io/kubernetes/pkg/bootstrap/api
func isSecretExpired(secret *api.Secret) bool {
expiration := getSecretString(secret, bootstrapapi.BootstrapTokenExpirationKey)
if len(expiration) > 0 {
expTime, err2 := time.Parse(time.RFC3339, expiration)
if err2 != nil {
tokenErrorf(secret, "has unparsable expiration time (%s). Treating as expired.", expiration)
return true
}
if time.Now().After(expTime) {
tokenErrorf(secret, "has expired.", expiration)
return true
}
}
return false
}
// Copied from kubernetes/cmd/kubeadm/app/util/token
var (
tokenRegexpString = "^([a-z0-9]{6})\\.([a-z0-9]{16})$"
tokenRegexp = regexp.MustCompile(tokenRegexpString)
)
// parseToken tries and parse a valid token from a string.
// A token ID and token secret are returned in case of success, an error otherwise.
func parseToken(s string) (string, string, error) {
split := tokenRegexp.FindStringSubmatch(s)
if len(split) != 3 {
return "", "", fmt.Errorf("token [%q] was not of form [%q]", s, tokenRegexpString)
}
return split[1], split[2], nil
}
// getGroups loads and validates the bootstrapapi.BootstrapTokenExtraGroupsKey
// key from the bootstrap token secret, returning a list of group names or an
// error if any of the group names are invalid.
func getGroups(secret *api.Secret) ([]string, error) {
// always include the default group
groups := sets.NewString(bootstrapapi.BootstrapDefaultGroup)
// grab any extra groups and if there are none, return just the default
extraGroupsString := getSecretString(secret, bootstrapapi.BootstrapTokenExtraGroupsKey)
if extraGroupsString == "" {
return groups.List(), nil
}
// validate the names of the extra groups
for _, group := range strings.Split(extraGroupsString, ",") {
if err := bootstrapapi.ValidateBootstrapGroupName(group); err != nil {
return nil, err
}
groups.Insert(group)
}
// return the result as a deduplicated, sorted list
return groups.List(), nil
}

View File

@ -0,0 +1,359 @@
/*
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 bootstrap
import (
"reflect"
"testing"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authentication/user"
api "k8s.io/kubernetes/pkg/apis/core"
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
)
type lister struct {
secrets []*api.Secret
}
func (l *lister) List(selector labels.Selector) (ret []*api.Secret, err error) {
return l.secrets, nil
}
func (l *lister) Get(name string) (*api.Secret, error) {
for _, s := range l.secrets {
if s.Name == name {
return s, nil
}
}
return nil, errors.NewNotFound(schema.GroupResource{}, name)
}
const (
tokenID = "foobar" // 6 letters
tokenSecret = "circumnavigation" // 16 letters
)
func TestTokenAuthenticator(t *testing.T) {
now := metav1.Now()
tests := []struct {
name string
secrets []*api.Secret
token string
wantNotFound bool
wantUser *user.DefaultInfo
}{
{
name: "valid token",
secrets: []*api.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: bootstrapapi.BootstrapTokenSecretPrefix + tokenID,
},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
},
Type: "bootstrap.kubernetes.io/token",
},
},
token: tokenID + "." + tokenSecret,
wantUser: &user.DefaultInfo{
Name: "system:bootstrap:" + tokenID,
Groups: []string{"system:bootstrappers"},
},
},
{
name: "valid token with extra group",
secrets: []*api.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: bootstrapapi.BootstrapTokenSecretPrefix + tokenID,
},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
bootstrapapi.BootstrapTokenExtraGroupsKey: []byte("system:bootstrappers:foo"),
},
Type: "bootstrap.kubernetes.io/token",
},
},
token: tokenID + "." + tokenSecret,
wantUser: &user.DefaultInfo{
Name: "system:bootstrap:" + tokenID,
Groups: []string{"system:bootstrappers", "system:bootstrappers:foo"},
},
},
{
name: "invalid group",
secrets: []*api.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: bootstrapapi.BootstrapTokenSecretPrefix + tokenID,
},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
bootstrapapi.BootstrapTokenExtraGroupsKey: []byte("foo"),
},
Type: "bootstrap.kubernetes.io/token",
},
},
token: tokenID + "." + tokenSecret,
wantNotFound: true,
},
{
name: "invalid secret name",
secrets: []*api.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: "bad-name",
},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
},
Type: "bootstrap.kubernetes.io/token",
},
},
token: tokenID + "." + tokenSecret,
wantNotFound: true,
},
{
name: "no usage",
secrets: []*api.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: bootstrapapi.BootstrapTokenSecretPrefix + tokenID,
},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
},
Type: "bootstrap.kubernetes.io/token",
},
},
token: tokenID + "." + tokenSecret,
wantNotFound: true,
},
{
name: "wrong token",
secrets: []*api.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: bootstrapapi.BootstrapTokenSecretPrefix + tokenID,
},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
},
Type: "bootstrap.kubernetes.io/token",
},
},
token: "barfoo" + "." + tokenSecret,
wantNotFound: true,
},
{
name: "deleted token",
secrets: []*api.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: bootstrapapi.BootstrapTokenSecretPrefix + tokenID,
DeletionTimestamp: &now,
},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
},
Type: "bootstrap.kubernetes.io/token",
},
},
token: tokenID + "." + tokenSecret,
wantNotFound: true,
},
{
name: "expired token",
secrets: []*api.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: bootstrapapi.BootstrapTokenSecretPrefix + tokenID,
},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
bootstrapapi.BootstrapTokenExpirationKey: []byte("2009-11-10T23:00:00Z"),
},
Type: "bootstrap.kubernetes.io/token",
},
},
token: tokenID + "." + tokenSecret,
wantNotFound: true,
},
{
name: "not expired token",
secrets: []*api.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: bootstrapapi.BootstrapTokenSecretPrefix + tokenID,
},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
bootstrapapi.BootstrapTokenExpirationKey: []byte("2109-11-10T23:00:00Z"),
},
Type: "bootstrap.kubernetes.io/token",
},
},
token: tokenID + "." + tokenSecret,
wantUser: &user.DefaultInfo{
Name: "system:bootstrap:" + tokenID,
Groups: []string{"system:bootstrappers"},
},
},
{
name: "token id wrong length",
secrets: []*api.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: bootstrapapi.BootstrapTokenSecretPrefix + "foo",
},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte("foo"),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
},
Type: "bootstrap.kubernetes.io/token",
},
},
// Token ID must be 6 characters.
token: "foo" + "." + tokenSecret,
wantNotFound: true,
},
}
for _, test := range tests {
func() {
a := NewTokenAuthenticator(&lister{test.secrets})
u, found, err := a.AuthenticateToken(test.token)
if err != nil {
t.Errorf("test %q returned an error: %v", test.name, err)
return
}
if !found {
if !test.wantNotFound {
t.Errorf("test %q expected to get user", test.name)
}
return
}
if test.wantNotFound {
t.Errorf("test %q expected to not get a user", test.name)
return
}
gotUser := u.(*user.DefaultInfo)
if !reflect.DeepEqual(gotUser, test.wantUser) {
t.Errorf("test %q want user=%#v, got=%#v", test.name, test.wantUser, gotUser)
}
}()
}
}
func TestGetGroups(t *testing.T) {
tests := []struct {
name string
secret *api.Secret
expectResult []string
expectError bool
}{
{
name: "not set",
secret: &api.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{},
},
expectResult: []string{"system:bootstrappers"},
},
{
name: "set to empty value",
secret: &api.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenExtraGroupsKey: []byte(""),
},
},
expectResult: []string{"system:bootstrappers"},
},
{
name: "invalid prefix",
secret: &api.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenExtraGroupsKey: []byte("foo"),
},
},
expectError: true,
},
{
name: "valid",
secret: &api.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
bootstrapapi.BootstrapTokenExtraGroupsKey: []byte("system:bootstrappers:foo,system:bootstrappers:bar,system:bootstrappers:bar"),
},
},
// expect the results in deduplicated, sorted order
expectResult: []string{
"system:bootstrappers",
"system:bootstrappers:bar",
"system:bootstrappers:foo",
},
},
}
for _, test := range tests {
result, err := getGroups(test.secret)
if test.expectError {
if err == nil {
t.Errorf("test %q expected an error, but didn't get one (result: %#v)", test.name, result)
}
continue
}
if err != nil {
t.Errorf("test %q return an unexpected error: %v", test.name, err)
continue
}
if !reflect.DeepEqual(result, test.expectResult) {
t.Errorf("test %q expected %#v, got %#v", test.name, test.expectResult, result)
}
}
}

View File

@ -0,0 +1,29 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["doc.go"],
importpath = "k8s.io/kubernetes/plugin/pkg/auth/authorizer",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//plugin/pkg/auth/authorizer/node:all-srcs",
"//plugin/pkg/auth/authorizer/rbac:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -0,0 +1,18 @@
/*
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 authorizer contains implementations for pkg/auth/authorizer interfaces
package authorizer // import "k8s.io/kubernetes/plugin/pkg/auth/authorizer"

View File

@ -0,0 +1,63 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["node_authorizer_test.go"],
importpath = "k8s.io/kubernetes/plugin/pkg/auth/authorizer/node",
library = ":go_default_library",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/auth/nodeidentifier:go_default_library",
"//plugin/pkg/auth/authorizer/rbac/bootstrappolicy:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"graph.go",
"graph_populator.go",
"node_authorizer.go",
],
importpath = "k8s.io/kubernetes/plugin/pkg/auth/authorizer/node",
deps = [
"//pkg/api/persistentvolume:go_default_library",
"//pkg/api/pod:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/auth/nodeidentifier:go_default_library",
"//pkg/client/informers/informers_generated/internalversion/core/internalversion:go_default_library",
"//pkg/features:go_default_library",
"//plugin/pkg/auth/authorizer/rbac:go_default_library",
"//third_party/forked/gonum/graph:go_default_library",
"//third_party/forked/gonum/graph/simple:go_default_library",
"//third_party/forked/gonum/graph/traverse:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/client-go/tools/cache: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,9 @@
approvers:
- tallclair
- liggitt
- deads2k
reviewers:
- tallclair
- liggitt
- deads2k
- ericchiang

View File

@ -0,0 +1,265 @@
/*
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 node
import (
"sync"
pvutil "k8s.io/kubernetes/pkg/api/persistentvolume"
podutil "k8s.io/kubernetes/pkg/api/pod"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/third_party/forked/gonum/graph"
"k8s.io/kubernetes/third_party/forked/gonum/graph/simple"
)
// namedVertex implements graph.Node and remembers the type, namespace, and name of its related API object
type namedVertex struct {
name string
namespace string
id int
vertexType vertexType
}
func newNamedVertex(vertexType vertexType, namespace, name string, id int) *namedVertex {
return &namedVertex{
vertexType: vertexType,
name: name,
namespace: namespace,
id: id,
}
}
func (n *namedVertex) ID() int {
return n.id
}
func (n *namedVertex) String() string {
if len(n.namespace) == 0 {
return vertexTypes[n.vertexType] + ":" + n.name
}
return vertexTypes[n.vertexType] + ":" + n.namespace + "/" + n.name
}
// destinationEdge is a graph edge that includes a denormalized reference to the final destination vertex.
// This should only be used when there is a single leaf vertex reachable from T.
type destinationEdge struct {
F graph.Node
T graph.Node
Destination graph.Node
}
func newDestinationEdge(from, to, destination graph.Node) graph.Edge {
return &destinationEdge{F: from, T: to, Destination: destination}
}
func (e *destinationEdge) From() graph.Node { return e.F }
func (e *destinationEdge) To() graph.Node { return e.T }
func (e *destinationEdge) Weight() float64 { return 0 }
func (e *destinationEdge) DestinationID() int { return e.Destination.ID() }
// Graph holds graph vertices and a way to look up a vertex for a particular API type/namespace/name.
// All edges point toward the vertices representing Kubernetes nodes:
//
// node <- pod
// pod <- secret,configmap,pvc
// pvc <- pv
// pv <- secret
type Graph struct {
lock sync.RWMutex
graph *simple.DirectedAcyclicGraph
// vertices is a map of type -> namespace -> name -> vertex
vertices map[vertexType]namespaceVertexMapping
}
// namespaceVertexMapping is a map of namespace -> name -> vertex
type namespaceVertexMapping map[string]nameVertexMapping
// nameVertexMapping is a map of name -> vertex
type nameVertexMapping map[string]*namedVertex
func NewGraph() *Graph {
return &Graph{
vertices: map[vertexType]namespaceVertexMapping{},
graph: simple.NewDirectedAcyclicGraph(0, 0),
}
}
// vertexType indicates the type of the API object the vertex represents.
// represented as a byte to minimize space used in the vertices.
type vertexType byte
const (
configMapVertexType vertexType = iota
nodeVertexType
podVertexType
pvcVertexType
pvVertexType
secretVertexType
)
var vertexTypes = map[vertexType]string{
configMapVertexType: "configmap",
nodeVertexType: "node",
podVertexType: "pod",
pvcVertexType: "pvc",
pvVertexType: "pv",
secretVertexType: "secret",
}
// must be called under a write lock
func (g *Graph) getOrCreateVertex_locked(vertexType vertexType, namespace, name string) *namedVertex {
if vertex, exists := g.getVertex_rlocked(vertexType, namespace, name); exists {
return vertex
}
return g.createVertex_locked(vertexType, namespace, name)
}
// must be called under a read lock
func (g *Graph) getVertex_rlocked(vertexType vertexType, namespace, name string) (*namedVertex, bool) {
vertex, exists := g.vertices[vertexType][namespace][name]
return vertex, exists
}
// must be called under a write lock
func (g *Graph) createVertex_locked(vertexType vertexType, namespace, name string) *namedVertex {
typedVertices, exists := g.vertices[vertexType]
if !exists {
typedVertices = namespaceVertexMapping{}
g.vertices[vertexType] = typedVertices
}
namespacedVertices, exists := typedVertices[namespace]
if !exists {
namespacedVertices = map[string]*namedVertex{}
typedVertices[namespace] = namespacedVertices
}
vertex := newNamedVertex(vertexType, namespace, name, g.graph.NewNodeID())
namespacedVertices[name] = vertex
g.graph.AddNode(vertex)
return vertex
}
// must be called under write lock
func (g *Graph) deleteVertex_locked(vertexType vertexType, namespace, name string) {
vertex, exists := g.getVertex_rlocked(vertexType, namespace, name)
if !exists {
return
}
// find existing neighbors with a single edge (meaning we are their only neighbor)
neighborsToRemove := []graph.Node{}
g.graph.VisitFrom(vertex, func(neighbor graph.Node) bool {
// this downstream neighbor has only one edge (which must be from us), so remove them as well
if g.graph.Degree(neighbor) == 1 {
neighborsToRemove = append(neighborsToRemove, neighbor)
}
return true
})
g.graph.VisitTo(vertex, func(neighbor graph.Node) bool {
// this upstream neighbor has only one edge (which must be to us), so remove them as well
if g.graph.Degree(neighbor) == 1 {
neighborsToRemove = append(neighborsToRemove, neighbor)
}
return true
})
// remove the vertex
g.graph.RemoveNode(vertex)
delete(g.vertices[vertexType][namespace], name)
if len(g.vertices[vertexType][namespace]) == 0 {
delete(g.vertices[vertexType], namespace)
}
// remove neighbors that are now edgeless
for _, neighbor := range neighborsToRemove {
g.graph.RemoveNode(neighbor)
n := neighbor.(*namedVertex)
delete(g.vertices[n.vertexType][n.namespace], n.name)
if len(g.vertices[n.vertexType][n.namespace]) == 0 {
delete(g.vertices[n.vertexType], n.namespace)
}
}
}
// AddPod should only be called once spec.NodeName is populated.
// It sets up edges for the following relationships (which are immutable for a pod once bound to a node):
//
// pod -> node
//
// secret -> pod
// configmap -> pod
// pvc -> pod
func (g *Graph) AddPod(pod *api.Pod) {
g.lock.Lock()
defer g.lock.Unlock()
g.deleteVertex_locked(podVertexType, pod.Namespace, pod.Name)
podVertex := g.getOrCreateVertex_locked(podVertexType, pod.Namespace, pod.Name)
nodeVertex := g.getOrCreateVertex_locked(nodeVertexType, "", pod.Spec.NodeName)
g.graph.SetEdge(newDestinationEdge(podVertex, nodeVertex, nodeVertex))
podutil.VisitPodSecretNames(pod, func(secret string) bool {
g.graph.SetEdge(newDestinationEdge(g.getOrCreateVertex_locked(secretVertexType, pod.Namespace, secret), podVertex, nodeVertex))
return true
})
podutil.VisitPodConfigmapNames(pod, func(configmap string) bool {
g.graph.SetEdge(newDestinationEdge(g.getOrCreateVertex_locked(configMapVertexType, pod.Namespace, configmap), podVertex, nodeVertex))
return true
})
for _, v := range pod.Spec.Volumes {
if v.PersistentVolumeClaim != nil {
g.graph.SetEdge(newDestinationEdge(g.getOrCreateVertex_locked(pvcVertexType, pod.Namespace, v.PersistentVolumeClaim.ClaimName), podVertex, nodeVertex))
}
}
}
func (g *Graph) DeletePod(name, namespace string) {
g.lock.Lock()
defer g.lock.Unlock()
g.deleteVertex_locked(podVertexType, namespace, name)
}
// AddPV sets up edges for the following relationships:
//
// secret -> pv
//
// pv -> pvc
func (g *Graph) AddPV(pv *api.PersistentVolume) {
g.lock.Lock()
defer g.lock.Unlock()
// clear existing edges
g.deleteVertex_locked(pvVertexType, "", pv.Name)
// if we have a pvc, establish new edges
if pv.Spec.ClaimRef != nil {
pvVertex := g.getOrCreateVertex_locked(pvVertexType, "", pv.Name)
// since we don't know the other end of the pvc -> pod -> node chain (or it may not even exist yet), we can't decorate these edges with kubernetes node info
g.graph.SetEdge(simple.Edge{F: pvVertex, T: g.getOrCreateVertex_locked(pvcVertexType, pv.Spec.ClaimRef.Namespace, pv.Spec.ClaimRef.Name)})
pvutil.VisitPVSecretNames(pv, func(namespace, secret string) bool {
// This grants access to the named secret in the same namespace as the bound PVC
g.graph.SetEdge(simple.Edge{F: g.getOrCreateVertex_locked(secretVertexType, namespace, secret), T: pvVertex})
return true
})
}
}
func (g *Graph) DeletePV(name string) {
g.lock.Lock()
defer g.lock.Unlock()
g.deleteVertex_locked(pvVertexType, "", name)
}

View File

@ -0,0 +1,108 @@
/*
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 node
import (
"github.com/golang/glog"
"k8s.io/client-go/tools/cache"
api "k8s.io/kubernetes/pkg/apis/core"
coreinformers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/core/internalversion"
)
type graphPopulator struct {
graph *Graph
}
func AddGraphEventHandlers(graph *Graph, pods coreinformers.PodInformer, pvs coreinformers.PersistentVolumeInformer) {
g := &graphPopulator{
graph: graph,
}
pods.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: g.addPod,
UpdateFunc: g.updatePod,
DeleteFunc: g.deletePod,
})
pvs.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: g.addPV,
UpdateFunc: g.updatePV,
DeleteFunc: g.deletePV,
})
}
func (g *graphPopulator) addPod(obj interface{}) {
g.updatePod(nil, obj)
}
func (g *graphPopulator) updatePod(oldObj, obj interface{}) {
pod := obj.(*api.Pod)
if len(pod.Spec.NodeName) == 0 {
// No node assigned
glog.V(5).Infof("updatePod %s/%s, no node", pod.Namespace, pod.Name)
return
}
if oldPod, ok := oldObj.(*api.Pod); ok && oldPod != nil {
if (pod.Spec.NodeName == oldPod.Spec.NodeName) && (pod.UID == oldPod.UID) {
// Node and uid are unchanged, all object references in the pod spec are immutable
glog.V(5).Infof("updatePod %s/%s, node unchanged", pod.Namespace, pod.Name)
return
}
}
glog.V(4).Infof("updatePod %s/%s for node %s", pod.Namespace, pod.Name, pod.Spec.NodeName)
g.graph.AddPod(pod)
}
func (g *graphPopulator) deletePod(obj interface{}) {
if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok {
obj = tombstone.Obj
}
pod, ok := obj.(*api.Pod)
if !ok {
glog.Infof("unexpected type %T", obj)
return
}
if len(pod.Spec.NodeName) == 0 {
glog.V(5).Infof("deletePod %s/%s, no node", pod.Namespace, pod.Name)
return
}
glog.V(4).Infof("deletePod %s/%s for node %s", pod.Namespace, pod.Name, pod.Spec.NodeName)
g.graph.DeletePod(pod.Name, pod.Namespace)
}
func (g *graphPopulator) addPV(obj interface{}) {
g.updatePV(nil, obj)
}
func (g *graphPopulator) updatePV(oldObj, obj interface{}) {
pv := obj.(*api.PersistentVolume)
// TODO: skip add if uid, pvc, and secrets are all identical between old and new
g.graph.AddPV(pv)
}
func (g *graphPopulator) deletePV(obj interface{}) {
if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok {
obj = tombstone.Obj
}
pv, ok := obj.(*api.PersistentVolume)
if !ok {
glog.Infof("unexpected type %T", obj)
return
}
g.graph.DeletePV(pv.Name)
}

View File

@ -0,0 +1,199 @@
/*
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 node
import (
"fmt"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authorization/authorizer"
utilfeature "k8s.io/apiserver/pkg/util/feature"
api "k8s.io/kubernetes/pkg/apis/core"
rbacapi "k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
"k8s.io/kubernetes/third_party/forked/gonum/graph"
"k8s.io/kubernetes/third_party/forked/gonum/graph/traverse"
)
// NodeAuthorizer authorizes requests from kubelets, with the following logic:
// 1. If a request is not from a node (NodeIdentity() returns isNode=false), reject
// 2. If a specific node cannot be identified (NodeIdentity() returns nodeName=""), reject
// 3. If a request is for a secret, configmap, persistent volume or persistent volume claim, reject unless the verb is get, and the requested object is related to the requesting node:
// node <- pod
// node <- pod <- secret
// node <- pod <- configmap
// node <- pod <- pvc
// node <- pod <- pvc <- pv
// node <- pod <- pvc <- pv <- secret
// 4. For other resources, authorize all nodes uniformly using statically defined rules
type NodeAuthorizer struct {
graph *Graph
identifier nodeidentifier.NodeIdentifier
nodeRules []rbacapi.PolicyRule
}
// NewAuthorizer returns a new node authorizer
func NewAuthorizer(graph *Graph, identifier nodeidentifier.NodeIdentifier, rules []rbacapi.PolicyRule) authorizer.Authorizer {
return &NodeAuthorizer{
graph: graph,
identifier: identifier,
nodeRules: rules,
}
}
var (
configMapResource = api.Resource("configmaps")
secretResource = api.Resource("secrets")
pvcResource = api.Resource("persistentvolumeclaims")
pvResource = api.Resource("persistentvolumes")
)
func (r *NodeAuthorizer) Authorize(attrs authorizer.Attributes) (authorizer.Decision, string, error) {
nodeName, isNode := r.identifier.NodeIdentity(attrs.GetUser())
if !isNode {
// reject requests from non-nodes
return authorizer.DecisionNoOpinion, "", nil
}
if len(nodeName) == 0 {
// reject requests from unidentifiable nodes
glog.V(2).Infof("NODE DENY: unknown node for user %q", attrs.GetUser().GetName())
return authorizer.DecisionNoOpinion, fmt.Sprintf("unknown node for user %q", attrs.GetUser().GetName()), nil
}
// subdivide access to specific resources
if attrs.IsResourceRequest() {
requestResource := schema.GroupResource{Group: attrs.GetAPIGroup(), Resource: attrs.GetResource()}
switch requestResource {
case secretResource:
return r.authorizeGet(nodeName, secretVertexType, attrs)
case configMapResource:
return r.authorizeGet(nodeName, configMapVertexType, attrs)
case pvcResource:
if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
if attrs.GetSubresource() == "status" {
return r.authorizeStatusUpdate(nodeName, pvcVertexType, attrs)
}
}
return r.authorizeGet(nodeName, pvcVertexType, attrs)
case pvResource:
return r.authorizeGet(nodeName, pvVertexType, attrs)
}
}
// Access to other resources is not subdivided, so just evaluate against the statically defined node rules
if rbac.RulesAllow(attrs, r.nodeRules...) {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionNoOpinion, "", nil
}
// authorizeStatusUpdate authorizes get/update/patch requests to status subresources of the specified type if they are related to the specified node
func (r *NodeAuthorizer) authorizeStatusUpdate(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
switch attrs.GetVerb() {
case "update", "patch":
// ok
default:
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs)
return authorizer.DecisionNoOpinion, "can only get/update/patch this type", nil
}
if attrs.GetSubresource() != "status" {
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs)
return authorizer.DecisionNoOpinion, "can only update status subresource", nil
}
return r.authorize(nodeName, startingType, attrs)
}
// authorizeGet authorizes "get" requests to objects of the specified type if they are related to the specified node
func (r *NodeAuthorizer) authorizeGet(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
if attrs.GetVerb() != "get" {
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs)
return authorizer.DecisionNoOpinion, "can only get individual resources of this type", nil
}
if len(attrs.GetSubresource()) > 0 {
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs)
return authorizer.DecisionNoOpinion, "cannot get subresource", nil
}
return r.authorize(nodeName, startingType, attrs)
}
func (r *NodeAuthorizer) authorize(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
if len(attrs.GetName()) == 0 {
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs)
return authorizer.DecisionNoOpinion, "No Object name found", nil
}
ok, err := r.hasPathFrom(nodeName, startingType, attrs.GetNamespace(), attrs.GetName())
if err != nil {
glog.V(2).Infof("NODE DENY: %v", err)
return authorizer.DecisionNoOpinion, "no path found to object", nil
}
if !ok {
glog.V(2).Infof("NODE DENY: %q %#v", nodeName, attrs)
return authorizer.DecisionNoOpinion, "no path found to object", nil
}
return authorizer.DecisionAllow, "", nil
}
// hasPathFrom returns true if there is a directed path from the specified type/namespace/name to the specified Node
func (r *NodeAuthorizer) hasPathFrom(nodeName string, startingType vertexType, startingNamespace, startingName string) (bool, error) {
r.graph.lock.RLock()
defer r.graph.lock.RUnlock()
nodeVertex, exists := r.graph.getVertex_rlocked(nodeVertexType, "", nodeName)
if !exists {
return false, fmt.Errorf("unknown node %q cannot get %s %s/%s", nodeName, vertexTypes[startingType], startingNamespace, startingName)
}
startingVertex, exists := r.graph.getVertex_rlocked(startingType, startingNamespace, startingName)
if !exists {
return false, fmt.Errorf("node %q cannot get unknown %s %s/%s", nodeName, vertexTypes[startingType], startingNamespace, startingName)
}
found := false
traversal := &traverse.VisitingDepthFirst{
EdgeFilter: func(edge graph.Edge) bool {
if destinationEdge, ok := edge.(*destinationEdge); ok {
if destinationEdge.DestinationID() != nodeVertex.ID() {
// Don't follow edges leading to other nodes
return false
}
// We found an edge leading to the node we want
found = true
}
// Visit this edge
return true
},
}
traversal.Walk(r.graph.graph, startingVertex, func(n graph.Node) bool {
if n.ID() == nodeVertex.ID() {
// We found the node we want
found = true
}
// Stop visiting if we've found the node we want
return found
})
if !found {
return false, fmt.Errorf("node %q cannot get %s %s/%s, no path was found", nodeName, vertexTypes[startingType], startingNamespace, startingName)
}
return true, nil
}

View File

@ -0,0 +1,437 @@
/*
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 node
import (
"fmt"
"runtime"
"runtime/pprof"
"testing"
"os"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy"
)
func TestAuthorizer(t *testing.T) {
g := NewGraph()
opts := sampleDataOpts{
nodes: 2,
namespaces: 2,
podsPerNode: 2,
sharedConfigMapsPerPod: 0,
uniqueConfigMapsPerPod: 1,
sharedSecretsPerPod: 1,
uniqueSecretsPerPod: 1,
sharedPVCsPerPod: 0,
uniquePVCsPerPod: 1,
}
pods, pvs := generate(opts)
populate(g, pods, pvs)
identifier := nodeidentifier.NewDefaultNodeIdentifier()
authz := NewAuthorizer(g, identifier, bootstrappolicy.NodeRules())
node0 := &user.DefaultInfo{Name: "system:node:node0", Groups: []string{"system:nodes"}}
tests := []struct {
name string
attrs authorizer.AttributesRecord
expect authorizer.Decision
}{
{
name: "allowed configmap",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node0", Namespace: "ns0"},
expect: authorizer.DecisionAllow,
},
{
name: "allowed secret via pod",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node0", Namespace: "ns0"},
expect: authorizer.DecisionAllow,
},
{
name: "allowed shared secret via pod",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-shared", Namespace: "ns0"},
expect: authorizer.DecisionAllow,
},
{
name: "allowed shared secret via pvc",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret-pv0-pod0-node0-ns0", Namespace: "ns0"},
expect: authorizer.DecisionAllow,
},
{
name: "allowed pvc",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node0", Namespace: "ns0"},
expect: authorizer.DecisionAllow,
},
{
name: "allowed pv",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node0-ns0", Namespace: ""},
expect: authorizer.DecisionAllow,
},
{
name: "disallowed configmap",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node1", Namespace: "ns0"},
expect: authorizer.DecisionNoOpinion,
},
{
name: "disallowed secret via pod",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node1", Namespace: "ns0"},
expect: authorizer.DecisionNoOpinion,
},
{
name: "disallowed shared secret via pvc",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret-pv0-pod0-node1-ns0", Namespace: "ns0"},
expect: authorizer.DecisionNoOpinion,
},
{
name: "disallowed pvc",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node1", Namespace: "ns0"},
expect: authorizer.DecisionNoOpinion,
},
{
name: "disallowed pv",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node1-ns0", Namespace: ""},
expect: authorizer.DecisionNoOpinion,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
decision, _, _ := authz.Authorize(tc.attrs)
if decision != tc.expect {
t.Errorf("expected %v, got %v", tc.expect, decision)
}
})
}
}
func TestAuthorizerSharedResources(t *testing.T) {
g := NewGraph()
identifier := nodeidentifier.NewDefaultNodeIdentifier()
authz := NewAuthorizer(g, identifier, bootstrappolicy.NodeRules())
node1 := &user.DefaultInfo{Name: "system:node:node1", Groups: []string{"system:nodes"}}
node2 := &user.DefaultInfo{Name: "system:node:node2", Groups: []string{"system:nodes"}}
node3 := &user.DefaultInfo{Name: "system:node:node3", Groups: []string{"system:nodes"}}
g.AddPod(&api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod1-node1", Namespace: "ns1"},
Spec: api.PodSpec{
NodeName: "node1",
Volumes: []api.Volume{
{VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "node1-only"}}},
{VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "node1-node2-only"}}},
{VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "shared-all"}}},
},
},
})
g.AddPod(&api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod2-node2", Namespace: "ns1"},
Spec: api.PodSpec{
NodeName: "node2",
Volumes: []api.Volume{
{VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "node1-node2-only"}}},
{VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "shared-all"}}},
},
},
})
g.AddPod(&api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod3-node3", Namespace: "ns1"},
Spec: api.PodSpec{
NodeName: "node3",
Volumes: []api.Volume{
{VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "shared-all"}}},
},
},
})
testcases := []struct {
User user.Info
Secret string
ExpectAllowed bool
}{
{User: node1, ExpectAllowed: true, Secret: "node1-only"},
{User: node1, ExpectAllowed: true, Secret: "node1-node2-only"},
{User: node1, ExpectAllowed: true, Secret: "shared-all"},
{User: node2, ExpectAllowed: false, Secret: "node1-only"},
{User: node2, ExpectAllowed: true, Secret: "node1-node2-only"},
{User: node2, ExpectAllowed: true, Secret: "shared-all"},
{User: node3, ExpectAllowed: false, Secret: "node1-only"},
{User: node3, ExpectAllowed: false, Secret: "node1-node2-only"},
{User: node3, ExpectAllowed: true, Secret: "shared-all"},
}
for i, tc := range testcases {
decision, _, err := authz.Authorize(authorizer.AttributesRecord{User: tc.User, ResourceRequest: true, Verb: "get", Resource: "secrets", Namespace: "ns1", Name: tc.Secret})
if err != nil {
t.Errorf("%d: unexpected error: %v", i, err)
continue
}
if (decision == authorizer.DecisionAllow) != tc.ExpectAllowed {
t.Errorf("%d: expected %v, got %v", i, tc.ExpectAllowed, decision)
}
}
}
type sampleDataOpts struct {
nodes int
namespaces int
podsPerNode int
sharedConfigMapsPerPod int
sharedSecretsPerPod int
sharedPVCsPerPod int
uniqueSecretsPerPod int
uniqueConfigMapsPerPod int
uniquePVCsPerPod int
}
func BenchmarkPopulationAllocation(b *testing.B) {
opts := sampleDataOpts{
nodes: 500,
namespaces: 200,
podsPerNode: 200,
sharedConfigMapsPerPod: 0,
uniqueConfigMapsPerPod: 1,
sharedSecretsPerPod: 1,
uniqueSecretsPerPod: 1,
sharedPVCsPerPod: 0,
uniquePVCsPerPod: 1,
}
pods, pvs := generate(opts)
b.ResetTimer()
for i := 0; i < b.N; i++ {
g := NewGraph()
populate(g, pods, pvs)
}
}
func BenchmarkPopulationRetention(b *testing.B) {
// Run with:
// go test ./plugin/pkg/auth/authorizer/node -benchmem -bench . -run None -v -o node.test -timeout 300m
// Evaluate retained memory with:
// go tool pprof --inuse_space node.test plugin/pkg/auth/authorizer/node/BenchmarkPopulationRetention.profile
// list populate
opts := sampleDataOpts{
nodes: 500,
namespaces: 200,
podsPerNode: 200,
sharedConfigMapsPerPod: 0,
uniqueConfigMapsPerPod: 1,
sharedSecretsPerPod: 1,
uniqueSecretsPerPod: 1,
sharedPVCsPerPod: 0,
uniquePVCsPerPod: 1,
}
pods, pvs := generate(opts)
// Garbage collect before the first iteration
runtime.GC()
b.ResetTimer()
for i := 0; i < b.N; i++ {
g := NewGraph()
populate(g, pods, pvs)
if i == 0 {
f, _ := os.Create("BenchmarkPopulationRetention.profile")
runtime.GC()
pprof.WriteHeapProfile(f)
f.Close()
// reference the graph to keep it from getting garbage collected
_ = fmt.Sprintf("%T\n", g)
}
}
}
func BenchmarkAuthorization(b *testing.B) {
g := NewGraph()
opts := sampleDataOpts{
nodes: 500,
namespaces: 200,
podsPerNode: 200,
sharedConfigMapsPerPod: 0,
uniqueConfigMapsPerPod: 1,
sharedSecretsPerPod: 1,
uniqueSecretsPerPod: 1,
sharedPVCsPerPod: 0,
uniquePVCsPerPod: 1,
}
pods, pvs := generate(opts)
populate(g, pods, pvs)
identifier := nodeidentifier.NewDefaultNodeIdentifier()
authz := NewAuthorizer(g, identifier, bootstrappolicy.NodeRules())
node0 := &user.DefaultInfo{Name: "system:node:node0", Groups: []string{"system:nodes"}}
tests := []struct {
name string
attrs authorizer.AttributesRecord
expect authorizer.Decision
}{
{
name: "allowed configmap",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node0", Namespace: "ns0"},
expect: authorizer.DecisionAllow,
},
{
name: "allowed secret via pod",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node0", Namespace: "ns0"},
expect: authorizer.DecisionAllow,
},
{
name: "allowed shared secret via pod",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-shared", Namespace: "ns0"},
expect: authorizer.DecisionAllow,
},
{
name: "disallowed configmap",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node1", Namespace: "ns0"},
expect: authorizer.DecisionNoOpinion,
},
{
name: "disallowed secret via pod",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node1", Namespace: "ns0"},
expect: authorizer.DecisionNoOpinion,
},
{
name: "disallowed shared secret via pvc",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret-pv0-pod0-node1-ns0", Namespace: "ns0"},
expect: authorizer.DecisionNoOpinion,
},
{
name: "disallowed pvc",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node1", Namespace: "ns0"},
expect: authorizer.DecisionNoOpinion,
},
{
name: "disallowed pv",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node1-ns0", Namespace: ""},
expect: authorizer.DecisionNoOpinion,
},
}
b.ResetTimer()
for _, tc := range tests {
b.Run(tc.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
decision, _, _ := authz.Authorize(tc.attrs)
if decision != tc.expect {
b.Errorf("expected %v, got %v", tc.expect, decision)
}
}
})
}
}
func populate(graph *Graph, pods []*api.Pod, pvs []*api.PersistentVolume) {
p := &graphPopulator{}
p.graph = graph
for _, pod := range pods {
p.addPod(pod)
}
for _, pv := range pvs {
p.addPV(pv)
}
}
// generate creates sample pods and persistent volumes based on the provided options.
// the secret/configmap/pvc/node references in the pod and pv objects are named to indicate the connections between the objects.
// for example, secret0-pod0-node0 is a secret referenced by pod0 which is bound to node0.
// when populated into the graph, the node authorizer should allow node0 to access that secret, but not node1.
func generate(opts sampleDataOpts) ([]*api.Pod, []*api.PersistentVolume) {
pods := make([]*api.Pod, 0, opts.nodes*opts.podsPerNode)
pvs := make([]*api.PersistentVolume, 0, (opts.nodes*opts.podsPerNode*opts.uniquePVCsPerPod)+(opts.sharedPVCsPerPod*opts.namespaces))
for n := 0; n < opts.nodes; n++ {
nodeName := fmt.Sprintf("node%d", n)
for p := 0; p < opts.podsPerNode; p++ {
pod := &api.Pod{}
pod.Namespace = fmt.Sprintf("ns%d", p%opts.namespaces)
pod.Name = fmt.Sprintf("pod%d-%s", p, nodeName)
pod.Spec.NodeName = nodeName
for i := 0; i < opts.uniqueSecretsPerPod; i++ {
pod.Spec.Volumes = append(pod.Spec.Volumes, api.Volume{VolumeSource: api.VolumeSource{
Secret: &api.SecretVolumeSource{SecretName: fmt.Sprintf("secret%d-%s", i, pod.Name)},
}})
}
for i := 0; i < opts.sharedSecretsPerPod; i++ {
pod.Spec.Volumes = append(pod.Spec.Volumes, api.Volume{VolumeSource: api.VolumeSource{
Secret: &api.SecretVolumeSource{SecretName: fmt.Sprintf("secret%d-shared", i)},
}})
}
for i := 0; i < opts.uniqueConfigMapsPerPod; i++ {
pod.Spec.Volumes = append(pod.Spec.Volumes, api.Volume{VolumeSource: api.VolumeSource{
ConfigMap: &api.ConfigMapVolumeSource{LocalObjectReference: api.LocalObjectReference{Name: fmt.Sprintf("configmap%d-%s", i, pod.Name)}},
}})
}
for i := 0; i < opts.sharedConfigMapsPerPod; i++ {
pod.Spec.Volumes = append(pod.Spec.Volumes, api.Volume{VolumeSource: api.VolumeSource{
ConfigMap: &api.ConfigMapVolumeSource{LocalObjectReference: api.LocalObjectReference{Name: fmt.Sprintf("configmap%d-shared", i)}},
}})
}
for i := 0; i < opts.uniquePVCsPerPod; i++ {
pv := &api.PersistentVolume{}
pv.Name = fmt.Sprintf("pv%d-%s-%s", i, pod.Name, pod.Namespace)
pv.Spec.FlexVolume = &api.FlexVolumeSource{SecretRef: &api.LocalObjectReference{Name: fmt.Sprintf("secret-%s", pv.Name)}}
pv.Spec.ClaimRef = &api.ObjectReference{Name: fmt.Sprintf("pvc%d-%s", i, pod.Name), Namespace: pod.Namespace}
pvs = append(pvs, pv)
pod.Spec.Volumes = append(pod.Spec.Volumes, api.Volume{VolumeSource: api.VolumeSource{
PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ClaimName: pv.Spec.ClaimRef.Name},
}})
}
for i := 0; i < opts.sharedPVCsPerPod; i++ {
pv := &api.PersistentVolume{}
pv.Name = fmt.Sprintf("pv%d-shared-%s", i, pod.Namespace)
pv.Spec.FlexVolume = &api.FlexVolumeSource{SecretRef: &api.LocalObjectReference{Name: fmt.Sprintf("secret-%s", pv.Name)}}
pv.Spec.ClaimRef = &api.ObjectReference{Name: fmt.Sprintf("pvc%d-shared", i), Namespace: pod.Namespace}
pvs = append(pvs, pv)
pod.Spec.Volumes = append(pod.Spec.Volumes, api.Volume{VolumeSource: api.VolumeSource{
PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ClaimName: pv.Spec.ClaimRef.Name},
}})
}
pods = append(pods, pod)
}
}
return pods, pvs
}

View File

@ -0,0 +1,60 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"rbac.go",
"subject_locator.go",
],
importpath = "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac",
deps = [
"//pkg/apis/rbac:go_default_library",
"//pkg/client/listers/rbac/internalversion:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"rbac_test.go",
"subject_locator_test.go",
],
importpath = "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac",
library = ":go_default_library",
deps = [
"//pkg/apis/rbac:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//plugin/pkg/auth/authorizer/rbac/bootstrappolicy:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//plugin/pkg/auth/authorizer/rbac/bootstrappolicy:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -0,0 +1,77 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"controller_policy.go",
"namespace_policy.go",
"policy.go",
],
importpath = "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy",
deps = [
"//pkg/apis/rbac:go_default_library",
"//pkg/features:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
],
)
go_test(
name = "go_default_xtest",
srcs = ["policy_test.go"],
data = glob([
"testdata/*",
]),
importpath = "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy_test",
deps = [
":go_default_library",
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/install:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/install:go_default_library",
"//pkg/registry/rbac/validation:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["controller_policy_test.go"],
data = glob(["testdata/**"]),
importpath = "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy",
library = ":go_default_library",
deps = [
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets: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,351 @@
/*
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 bootstrappolicy
import (
"strings"
"github.com/golang/glog"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
rbac "k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/features"
)
const saRolePrefix = "system:controller:"
func addControllerRole(controllerRoles *[]rbac.ClusterRole, controllerRoleBindings *[]rbac.ClusterRoleBinding, role rbac.ClusterRole) {
if !strings.HasPrefix(role.Name, saRolePrefix) {
glog.Fatalf(`role %q must start with %q`, role.Name, saRolePrefix)
}
for _, existingRole := range *controllerRoles {
if role.Name == existingRole.Name {
glog.Fatalf("role %q was already registered", role.Name)
}
}
*controllerRoles = append(*controllerRoles, role)
addClusterRoleLabel(*controllerRoles)
*controllerRoleBindings = append(*controllerRoleBindings,
rbac.NewClusterBinding(role.Name).SAs("kube-system", role.Name[len(saRolePrefix):]).BindingOrDie())
addClusterRoleBindingLabel(*controllerRoleBindings)
}
func eventsRule() rbac.PolicyRule {
return rbac.NewRule("create", "update", "patch").Groups(legacyGroup).Resources("events").RuleOrDie()
}
func buildControllerRoles() ([]rbac.ClusterRole, []rbac.ClusterRoleBinding) {
// controllerRoles is a slice of roles used for controllers
controllerRoles := []rbac.ClusterRole{}
// controllerRoleBindings is a slice of roles used for controllers
controllerRoleBindings := []rbac.ClusterRoleBinding{}
addControllerRole(&controllerRoles, &controllerRoleBindings, func() rbac.ClusterRole {
role := rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "attachdetach-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("persistentvolumes", "persistentvolumeclaims").RuleOrDie(),
rbac.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
rbac.NewRule("patch", "update").Groups(legacyGroup).Resources("nodes/status").RuleOrDie(),
rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("pods").RuleOrDie(),
eventsRule(),
},
}
if utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) {
role.Rules = append(role.Rules, rbac.NewRule("get", "create", "delete", "list", "watch").Groups(storageGroup).Resources("volumeattachments").RuleOrDie())
}
return role
}())
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "clusterrole-aggregation-controller"},
Rules: []rbac.PolicyRule{
// this controller must have full permissions to allow it to mutate any role in any way
rbac.NewRule("*").Groups("*").Resources("*").RuleOrDie(),
rbac.NewRule("*").URLs("*").RuleOrDie(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "cronjob-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "update").Groups(batchGroup).Resources("cronjobs").RuleOrDie(),
rbac.NewRule("get", "list", "watch", "create", "update", "delete", "patch").Groups(batchGroup).Resources("jobs").RuleOrDie(),
rbac.NewRule("update").Groups(batchGroup).Resources("cronjobs/status").RuleOrDie(),
rbac.NewRule("update").Groups(batchGroup).Resources("cronjobs/finalizers").RuleOrDie(),
rbac.NewRule("list", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "daemon-set-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch").Groups(extensionsGroup, appsGroup).Resources("daemonsets").RuleOrDie(),
rbac.NewRule("update").Groups(extensionsGroup, appsGroup).Resources("daemonsets/status").RuleOrDie(),
rbac.NewRule("update").Groups(extensionsGroup, appsGroup).Resources("daemonsets/finalizers").RuleOrDie(),
rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
rbac.NewRule("list", "watch", "create", "delete", "patch").Groups(legacyGroup).Resources("pods").RuleOrDie(),
rbac.NewRule("create").Groups(legacyGroup).Resources("pods/binding").RuleOrDie(),
rbac.NewRule("list", "watch", "create", "delete", "update", "patch").Groups(appsGroup).Resources("controllerrevisions").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "deployment-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "update").Groups(extensionsGroup, appsGroup).Resources("deployments").RuleOrDie(),
rbac.NewRule("update").Groups(extensionsGroup, appsGroup).Resources("deployments/status").RuleOrDie(),
rbac.NewRule("update").Groups(extensionsGroup, appsGroup).Resources("deployments/finalizers").RuleOrDie(),
rbac.NewRule("get", "list", "watch", "create", "update", "patch", "delete").Groups(appsGroup, extensionsGroup).Resources("replicasets").RuleOrDie(),
// TODO: remove "update" once
// https://github.com/kubernetes/kubernetes/issues/36897 is resolved.
rbac.NewRule("get", "list", "watch", "update").Groups(legacyGroup).Resources("pods").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "disruption-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch").Groups(extensionsGroup, appsGroup).Resources("deployments").RuleOrDie(),
rbac.NewRule("get", "list", "watch").Groups(appsGroup, extensionsGroup).Resources("replicasets").RuleOrDie(),
rbac.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("replicationcontrollers").RuleOrDie(),
rbac.NewRule("get", "list", "watch").Groups(policyGroup).Resources("poddisruptionbudgets").RuleOrDie(),
rbac.NewRule("get", "list", "watch").Groups(appsGroup).Resources("statefulsets").RuleOrDie(),
rbac.NewRule("update").Groups(policyGroup).Resources("poddisruptionbudgets/status").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "endpoint-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("services", "pods").RuleOrDie(),
rbac.NewRule("get", "list", "create", "update", "delete").Groups(legacyGroup).Resources("endpoints").RuleOrDie(),
rbac.NewRule("create").Groups(legacyGroup).Resources("endpoints/restricted").RuleOrDie(),
eventsRule(),
},
})
if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "expand-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "update", "patch").Groups(legacyGroup).Resources("persistentvolumes").RuleOrDie(),
rbac.NewRule("update", "patch").Groups(legacyGroup).Resources("persistentvolumeclaims/status").RuleOrDie(),
rbac.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("persistentvolumeclaims").RuleOrDie(),
// glusterfs
rbac.NewRule("get", "list", "watch").Groups(storageGroup).Resources("storageclasses").RuleOrDie(),
rbac.NewRule("get").Groups(legacyGroup).Resources("services", "endpoints").RuleOrDie(),
rbac.NewRule("get").Groups(legacyGroup).Resources("secrets").RuleOrDie(),
eventsRule(),
},
})
}
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "generic-garbage-collector"},
Rules: []rbac.PolicyRule{
// the GC controller needs to run list/watches, selective gets, and updates against any resource
rbac.NewRule("get", "list", "watch", "patch", "update", "delete").Groups("*").Resources("*").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "horizontal-pod-autoscaler"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch").Groups(autoscalingGroup).Resources("horizontalpodautoscalers").RuleOrDie(),
rbac.NewRule("update").Groups(autoscalingGroup).Resources("horizontalpodautoscalers/status").RuleOrDie(),
rbac.NewRule("get", "update").Groups("*").Resources("*/scale").RuleOrDie(),
rbac.NewRule("list").Groups(legacyGroup).Resources("pods").RuleOrDie(),
// TODO: restrict this to the appropriate namespace
rbac.NewRule("get").Groups(legacyGroup).Resources("services/proxy").Names("https:heapster:", "http:heapster:").RuleOrDie(),
// allow listing resource metrics and custom metrics
rbac.NewRule("list").Groups(resMetricsGroup).Resources("pods").RuleOrDie(),
rbac.NewRule("get", "list").Groups(customMetricsGroup).Resources("*").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "job-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "update").Groups(batchGroup).Resources("jobs").RuleOrDie(),
rbac.NewRule("update").Groups(batchGroup).Resources("jobs/status").RuleOrDie(),
rbac.NewRule("update").Groups(batchGroup).Resources("jobs/finalizers").RuleOrDie(),
rbac.NewRule("list", "watch", "create", "delete", "patch").Groups(legacyGroup).Resources("pods").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "namespace-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "delete").Groups(legacyGroup).Resources("namespaces").RuleOrDie(),
rbac.NewRule("update").Groups(legacyGroup).Resources("namespaces/finalize", "namespaces/status").RuleOrDie(),
rbac.NewRule("get", "list", "delete", "deletecollection").Groups("*").Resources("*").RuleOrDie(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "node-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "update", "delete", "patch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
rbac.NewRule("patch", "update").Groups(legacyGroup).Resources("nodes/status").RuleOrDie(),
// used for pod eviction
rbac.NewRule("update").Groups(legacyGroup).Resources("pods/status").RuleOrDie(),
rbac.NewRule("list", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "persistent-volume-binder"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "update", "create", "delete").Groups(legacyGroup).Resources("persistentvolumes").RuleOrDie(),
rbac.NewRule("update").Groups(legacyGroup).Resources("persistentvolumes/status").RuleOrDie(),
rbac.NewRule("get", "list", "watch", "update").Groups(legacyGroup).Resources("persistentvolumeclaims").RuleOrDie(),
rbac.NewRule("update").Groups(legacyGroup).Resources("persistentvolumeclaims/status").RuleOrDie(),
rbac.NewRule("list", "watch", "get", "create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
// glusterfs
rbac.NewRule("get", "list", "watch").Groups(storageGroup).Resources("storageclasses").RuleOrDie(),
rbac.NewRule("get", "create", "delete").Groups(legacyGroup).Resources("services", "endpoints").RuleOrDie(),
rbac.NewRule("get").Groups(legacyGroup).Resources("secrets").RuleOrDie(),
// openstack
rbac.NewRule("get", "list").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
// recyclerClient.WatchPod
rbac.NewRule("watch").Groups(legacyGroup).Resources("events").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "pod-garbage-collector"},
Rules: []rbac.PolicyRule{
rbac.NewRule("list", "watch", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
rbac.NewRule("list").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "replicaset-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "update").Groups(appsGroup, extensionsGroup).Resources("replicasets").RuleOrDie(),
rbac.NewRule("update").Groups(appsGroup, extensionsGroup).Resources("replicasets/status").RuleOrDie(),
rbac.NewRule("update").Groups(appsGroup, extensionsGroup).Resources("replicasets/finalizers").RuleOrDie(),
rbac.NewRule("list", "watch", "patch", "create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "replication-controller"},
Rules: []rbac.PolicyRule{
// 1.0 controllers needed get, update, so without these old controllers break on new servers
rbac.NewRule("get", "list", "watch", "update").Groups(legacyGroup).Resources("replicationcontrollers").RuleOrDie(),
rbac.NewRule("update").Groups(legacyGroup).Resources("replicationcontrollers/status").RuleOrDie(),
rbac.NewRule("update").Groups(legacyGroup).Resources("replicationcontrollers/finalizers").RuleOrDie(),
rbac.NewRule("list", "watch", "patch", "create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "resourcequota-controller"},
Rules: []rbac.PolicyRule{
// quota can count quota on anything for reconcilation, so it needs full viewing powers
rbac.NewRule("list", "watch").Groups("*").Resources("*").RuleOrDie(),
rbac.NewRule("update").Groups(legacyGroup).Resources("resourcequotas/status").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "route-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
rbac.NewRule("patch").Groups(legacyGroup).Resources("nodes/status").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "service-account-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("create").Groups(legacyGroup).Resources("serviceaccounts").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "service-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("services").RuleOrDie(),
rbac.NewRule("update").Groups(legacyGroup).Resources("services/status").RuleOrDie(),
rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "statefulset-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("pods").RuleOrDie(),
rbac.NewRule("get", "list", "watch").Groups(appsGroup).Resources("statefulsets").RuleOrDie(),
rbac.NewRule("update").Groups(appsGroup).Resources("statefulsets/status").RuleOrDie(),
rbac.NewRule("update").Groups(appsGroup).Resources("statefulsets/finalizers").RuleOrDie(),
rbac.NewRule("get", "create", "delete", "update", "patch").Groups(legacyGroup).Resources("pods").RuleOrDie(),
rbac.NewRule("get", "create", "delete", "update", "patch", "list", "watch").Groups(appsGroup).Resources("controllerrevisions").RuleOrDie(),
rbac.NewRule("get", "create").Groups(legacyGroup).Resources("persistentvolumeclaims").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "ttl-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("update", "patch", "list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "certificate-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch").Groups(certificatesGroup).Resources("certificatesigningrequests").RuleOrDie(),
rbac.NewRule("update").Groups(certificatesGroup).Resources("certificatesigningrequests/status", "certificatesigningrequests/approval").RuleOrDie(),
rbac.NewRule("create").Groups(authorizationGroup).Resources("subjectaccessreviews").RuleOrDie(),
eventsRule(),
},
})
if utilfeature.DefaultFeatureGate.Enabled(features.PVCProtection) {
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "pvc-protection-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "update").Groups(legacyGroup).Resources("persistentvolumeclaims").RuleOrDie(),
rbac.NewRule("list", "watch", "get").Groups(legacyGroup).Resources("pods").RuleOrDie(),
eventsRule(),
},
})
}
return controllerRoles, controllerRoleBindings
}
// ControllerRoles returns the cluster roles used by controllers
func ControllerRoles() []rbac.ClusterRole {
controllerRoles, _ := buildControllerRoles()
return controllerRoles
}
// ControllerRoleBindings returns the role bindings used by controllers
func ControllerRoleBindings() []rbac.ClusterRoleBinding {
_, controllerRoleBindings := buildControllerRoles()
return controllerRoleBindings
}

View File

@ -0,0 +1,92 @@
/*
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 bootstrappolicy
import (
"reflect"
"testing"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/util/sets"
)
// rolesWithAllowStar are the controller roles which are allowed to contain a *. These are
// namespace lifecycle and GC which have to delete anything. If you're adding to this list
// tag sig-auth
var rolesWithAllowStar = sets.NewString(
saRolePrefix+"namespace-controller",
saRolePrefix+"generic-garbage-collector",
saRolePrefix+"resourcequota-controller",
saRolePrefix+"horizontal-pod-autoscaler",
saRolePrefix+"clusterrole-aggregation-controller",
)
// TestNoStarsForControllers confirms that no controller role has star verbs, groups,
// or resources. There are three known exceptions: namespace lifecycle and GC which have to
// delete anything, and HPA, which has the power to read metrics associated
// with any object.
func TestNoStarsForControllers(t *testing.T) {
for _, role := range ControllerRoles() {
if rolesWithAllowStar.Has(role.Name) {
continue
}
for i, rule := range role.Rules {
for j, verb := range rule.Verbs {
if verb == "*" {
t.Errorf("%s.Rule[%d].Verbs[%d] is star", role.Name, i, j)
}
}
for j, group := range rule.APIGroups {
if group == "*" {
t.Errorf("%s.Rule[%d].APIGroups[%d] is star", role.Name, i, j)
}
}
for j, resource := range rule.Resources {
if resource == "*" {
t.Errorf("%s.Rule[%d].Resources[%d] is star", role.Name, i, j)
}
}
}
}
}
func TestControllerRoleLabel(t *testing.T) {
roles := ControllerRoles()
for i := range roles {
role := roles[i]
accessor, err := meta.Accessor(&role)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got, want := accessor.GetLabels(), map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"}; !reflect.DeepEqual(got, want) {
t.Errorf("ClusterRole: %s GetLabels() = %s, want %s", accessor.GetName(), got, want)
}
}
rolebindings := ControllerRoleBindings()
for i := range rolebindings {
rolebinding := rolebindings[i]
accessor, err := meta.Accessor(&rolebinding)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got, want := accessor.GetLabels(), map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"}; !reflect.DeepEqual(got, want) {
t.Errorf("ClusterRoleBinding: %s GetLabels() = %s, want %s", accessor.GetName(), got, want)
}
}
}

View File

@ -0,0 +1,153 @@
/*
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 bootstrappolicy
import (
"strings"
"github.com/golang/glog"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
rbac "k8s.io/kubernetes/pkg/apis/rbac"
)
var (
// namespaceRoles is a map of namespace to slice of roles to create
namespaceRoles = map[string][]rbac.Role{}
// namespaceRoleBindings is a map of namespace to slice of roleBindings to create
namespaceRoleBindings = map[string][]rbac.RoleBinding{}
)
func addNamespaceRole(namespace string, role rbac.Role) {
if !strings.HasPrefix(namespace, "kube-") {
glog.Fatalf(`roles can only be bootstrapped into reserved namespaces starting with "kube-", not %q`, namespace)
}
existingRoles := namespaceRoles[namespace]
for _, existingRole := range existingRoles {
if role.Name == existingRole.Name {
glog.Fatalf("role %q was already registered in %q", role.Name, namespace)
}
}
role.Namespace = namespace
addDefaultMetadata(&role)
existingRoles = append(existingRoles, role)
namespaceRoles[namespace] = existingRoles
}
func addNamespaceRoleBinding(namespace string, roleBinding rbac.RoleBinding) {
if !strings.HasPrefix(namespace, "kube-") {
glog.Fatalf(`rolebindings can only be bootstrapped into reserved namespaces starting with "kube-", not %q`, namespace)
}
existingRoleBindings := namespaceRoleBindings[namespace]
for _, existingRoleBinding := range existingRoleBindings {
if roleBinding.Name == existingRoleBinding.Name {
glog.Fatalf("rolebinding %q was already registered in %q", roleBinding.Name, namespace)
}
}
roleBinding.Namespace = namespace
addDefaultMetadata(&roleBinding)
existingRoleBindings = append(existingRoleBindings, roleBinding)
namespaceRoleBindings[namespace] = existingRoleBindings
}
func init() {
addNamespaceRole(metav1.NamespaceSystem, rbac.Role{
// role for finding authentication config info for starting a server
ObjectMeta: metav1.ObjectMeta{Name: "extension-apiserver-authentication-reader"},
Rules: []rbac.PolicyRule{
// this particular config map is exposed and contains authentication configuration information
rbac.NewRule("get").Groups(legacyGroup).Resources("configmaps").Names("extension-apiserver-authentication").RuleOrDie(),
},
})
addNamespaceRole(metav1.NamespaceSystem, rbac.Role{
// role for the bootstrap signer to be able to inspect kube-system secrets
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "bootstrap-signer"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("secrets").RuleOrDie(),
},
})
addNamespaceRole(metav1.NamespaceSystem, rbac.Role{
// role for the cloud providers to access/create kube-system configmaps
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "cloud-provider"},
Rules: []rbac.PolicyRule{
rbac.NewRule("create", "get", "list", "watch").Groups(legacyGroup).Resources("configmaps").RuleOrDie(),
},
})
addNamespaceRole(metav1.NamespaceSystem, rbac.Role{
// role for the token-cleaner to be able to remove secrets, but only in kube-system
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "token-cleaner"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "delete").Groups(legacyGroup).Resources("secrets").RuleOrDie(),
eventsRule(),
},
})
// TODO: Create util on Role+Binding for leader locking if more cases evolve.
addNamespaceRole(metav1.NamespaceSystem, rbac.Role{
// role for the leader locking on supplied configmap
ObjectMeta: metav1.ObjectMeta{Name: "system::leader-locking-kube-controller-manager"},
Rules: []rbac.PolicyRule{
rbac.NewRule("watch").Groups(legacyGroup).Resources("configmaps").RuleOrDie(),
rbac.NewRule("get", "update").Groups(legacyGroup).Resources("configmaps").Names("kube-controller-manager").RuleOrDie(),
},
})
addNamespaceRole(metav1.NamespaceSystem, rbac.Role{
// role for the leader locking on supplied configmap
ObjectMeta: metav1.ObjectMeta{Name: "system::leader-locking-kube-scheduler"},
Rules: []rbac.PolicyRule{
rbac.NewRule("watch").Groups(legacyGroup).Resources("configmaps").RuleOrDie(),
rbac.NewRule("get", "update").Groups(legacyGroup).Resources("configmaps").Names("kube-scheduler").RuleOrDie(),
},
})
addNamespaceRoleBinding(metav1.NamespaceSystem,
rbac.NewRoleBinding("system::leader-locking-kube-controller-manager", metav1.NamespaceSystem).SAs(metav1.NamespaceSystem, "kube-controller-manager").BindingOrDie())
addNamespaceRoleBinding(metav1.NamespaceSystem,
rbac.NewRoleBinding("system::leader-locking-kube-scheduler", metav1.NamespaceSystem).SAs(metav1.NamespaceSystem, "kube-scheduler").BindingOrDie())
addNamespaceRoleBinding(metav1.NamespaceSystem,
rbac.NewRoleBinding(saRolePrefix+"bootstrap-signer", metav1.NamespaceSystem).SAs(metav1.NamespaceSystem, "bootstrap-signer").BindingOrDie())
addNamespaceRoleBinding(metav1.NamespaceSystem,
rbac.NewRoleBinding(saRolePrefix+"cloud-provider", metav1.NamespaceSystem).SAs(metav1.NamespaceSystem, "cloud-provider").BindingOrDie())
addNamespaceRoleBinding(metav1.NamespaceSystem,
rbac.NewRoleBinding(saRolePrefix+"token-cleaner", metav1.NamespaceSystem).SAs(metav1.NamespaceSystem, "token-cleaner").BindingOrDie())
addNamespaceRole(metav1.NamespacePublic, rbac.Role{
// role for the bootstrap signer to be able to write its configmap
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "bootstrap-signer"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("configmaps").RuleOrDie(),
rbac.NewRule("update").Groups(legacyGroup).Resources("configmaps").Names("cluster-info").RuleOrDie(),
eventsRule(),
},
})
addNamespaceRoleBinding(metav1.NamespacePublic,
rbac.NewRoleBinding(saRolePrefix+"bootstrap-signer", metav1.NamespacePublic).SAs(metav1.NamespaceSystem, "bootstrap-signer").BindingOrDie())
}
// NamespaceRoles returns a map of namespace to slice of roles to create
func NamespaceRoles() map[string][]rbac.Role {
return namespaceRoles
}
// NamespaceRoleBindings returns a map of namespace to slice of roles to create
func NamespaceRoleBindings() map[string][]rbac.RoleBinding {
return namespaceRoleBindings
}

View File

@ -0,0 +1,513 @@
/*
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 bootstrappolicy
import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authentication/user"
utilfeature "k8s.io/apiserver/pkg/util/feature"
rbac "k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/features"
)
var (
ReadWrite = []string{"get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"}
Read = []string{"get", "list", "watch"}
Label = map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"}
Annotation = map[string]string{rbac.AutoUpdateAnnotationKey: "true"}
)
const (
legacyGroup = ""
appsGroup = "apps"
authenticationGroup = "authentication.k8s.io"
authorizationGroup = "authorization.k8s.io"
autoscalingGroup = "autoscaling"
batchGroup = "batch"
certificatesGroup = "certificates.k8s.io"
extensionsGroup = "extensions"
policyGroup = "policy"
rbacGroup = "rbac.authorization.k8s.io"
storageGroup = "storage.k8s.io"
resMetricsGroup = "metrics.k8s.io"
customMetricsGroup = "custom.metrics.k8s.io"
)
func addDefaultMetadata(obj runtime.Object) {
metadata, err := meta.Accessor(obj)
if err != nil {
// if this happens, then some static code is broken
panic(err)
}
labels := metadata.GetLabels()
if labels == nil {
labels = map[string]string{}
}
for k, v := range Label {
labels[k] = v
}
metadata.SetLabels(labels)
annotations := metadata.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
for k, v := range Annotation {
annotations[k] = v
}
metadata.SetAnnotations(annotations)
}
func addClusterRoleLabel(roles []rbac.ClusterRole) {
for i := range roles {
addDefaultMetadata(&roles[i])
}
return
}
func addClusterRoleBindingLabel(rolebindings []rbac.ClusterRoleBinding) {
for i := range rolebindings {
addDefaultMetadata(&rolebindings[i])
}
return
}
func NodeRules() []rbac.PolicyRule {
nodePolicyRules := []rbac.PolicyRule{
// Needed to check API access. These creates are non-mutating
rbac.NewRule("create").Groups(authenticationGroup).Resources("tokenreviews").RuleOrDie(),
rbac.NewRule("create").Groups(authorizationGroup).Resources("subjectaccessreviews", "localsubjectaccessreviews").RuleOrDie(),
// Needed to build serviceLister, to populate env vars for services
rbac.NewRule(Read...).Groups(legacyGroup).Resources("services").RuleOrDie(),
// Nodes can register Node API objects and report status.
// Use the NodeRestriction admission plugin to limit a node to creating/updating its own API object.
rbac.NewRule("create", "get", "list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
rbac.NewRule("update", "patch").Groups(legacyGroup).Resources("nodes/status").RuleOrDie(),
rbac.NewRule("update", "patch", "delete").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
// TODO: restrict to the bound node as creator in the NodeRestrictions admission plugin
rbac.NewRule("create", "update", "patch").Groups(legacyGroup).Resources("events").RuleOrDie(),
// TODO: restrict to pods scheduled on the bound node once field selectors are supported by list/watch authorization
rbac.NewRule(Read...).Groups(legacyGroup).Resources("pods").RuleOrDie(),
// Needed for the node to create/delete mirror pods.
// Use the NodeRestriction admission plugin to limit a node to creating/deleting mirror pods bound to itself.
rbac.NewRule("create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
// Needed for the node to report status of pods it is running.
// Use the NodeRestriction admission plugin to limit a node to updating status of pods bound to itself.
rbac.NewRule("update").Groups(legacyGroup).Resources("pods/status").RuleOrDie(),
// Needed for the node to create pod evictions.
// Use the NodeRestriction admission plugin to limit a node to creating evictions for pods bound to itself.
rbac.NewRule("create").Groups(legacyGroup).Resources("pods/eviction").RuleOrDie(),
// Needed for imagepullsecrets, rbd/ceph and secret volumes, and secrets in envs
// Needed for configmap volume and envs
// Use the Node authorization mode to limit a node to get secrets/configmaps referenced by pods bound to itself.
rbac.NewRule("get").Groups(legacyGroup).Resources("secrets", "configmaps").RuleOrDie(),
// Needed for persistent volumes
// Use the Node authorization mode to limit a node to get pv/pvc objects referenced by pods bound to itself.
rbac.NewRule("get").Groups(legacyGroup).Resources("persistentvolumeclaims", "persistentvolumes").RuleOrDie(),
// TODO: add to the Node authorizer and restrict to endpoints referenced by pods or PVs bound to the node
// Needed for glusterfs volumes
rbac.NewRule("get").Groups(legacyGroup).Resources("endpoints").RuleOrDie(),
// Used to create a certificatesigningrequest for a node-specific client certificate, and watch
// for it to be signed. This allows the kubelet to rotate it's own certificate.
rbac.NewRule("create", "get", "list", "watch").Groups(certificatesGroup).Resources("certificatesigningrequests").RuleOrDie(),
}
if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
// Use the Node authorization mode to limit a node to update status of pvc objects referenced by pods bound to itself.
// Use the NodeRestriction admission plugin to limit a node to just update the status stanza.
pvcStatusPolicyRule := rbac.NewRule("get", "update", "patch").Groups(legacyGroup).Resources("persistentvolumeclaims/status").RuleOrDie()
nodePolicyRules = append(nodePolicyRules, pvcStatusPolicyRule)
}
// CSI
if utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) {
volAttachRule := rbac.NewRule("get").Groups(storageGroup).Resources("volumeattachments").RuleOrDie()
nodePolicyRules = append(nodePolicyRules, volAttachRule)
}
return nodePolicyRules
}
// ClusterRoles returns the cluster roles to bootstrap an API server with
func ClusterRoles() []rbac.ClusterRole {
roles := []rbac.ClusterRole{
{
// a "root" role which can do absolutely anything
ObjectMeta: metav1.ObjectMeta{Name: "cluster-admin"},
Rules: []rbac.PolicyRule{
rbac.NewRule("*").Groups("*").Resources("*").RuleOrDie(),
rbac.NewRule("*").URLs("*").RuleOrDie(),
},
},
{
// a role which provides just enough power to determine if the server is ready and discover API versions for negotiation
ObjectMeta: metav1.ObjectMeta{Name: "system:discovery"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get").URLs(
"/healthz", "/version",
// remove once swagger 1.2 support is removed
"/swaggerapi", "/swaggerapi/*",
// do not expand this pattern for openapi discovery docs
// move to a single openapi endpoint that takes accept/accept-encoding headers
"/swagger.json", "/swagger-2.0.0.pb-v1",
"/api", "/api/*",
"/apis", "/apis/*",
).RuleOrDie(),
},
},
{
// a role which provides minimal resource access to allow a "normal" user to learn information about themselves
ObjectMeta: metav1.ObjectMeta{Name: "system:basic-user"},
Rules: []rbac.PolicyRule{
// TODO add future selfsubjectrulesreview, project request APIs, project listing APIs
rbac.NewRule("create").Groups(authorizationGroup).Resources("selfsubjectaccessreviews", "selfsubjectrulesreviews").RuleOrDie(),
},
},
{
// a role for a namespace level admin. It is `edit` plus the power to grant permissions to other users.
ObjectMeta: metav1.ObjectMeta{Name: "admin"},
AggregationRule: &rbac.AggregationRule{
ClusterRoleSelectors: []metav1.LabelSelector{{MatchLabels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-admin": "true"}}},
},
},
{
// a role for a namespace level editor. It grants access to all user level actions in a namespace.
// It does not grant powers for "privileged" resources which are domain of the system: `/status`
// subresources or `quota`/`limits` which are used to control namespaces
ObjectMeta: metav1.ObjectMeta{Name: "edit"},
AggregationRule: &rbac.AggregationRule{
ClusterRoleSelectors: []metav1.LabelSelector{{MatchLabels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-edit": "true"}}},
},
},
{
// a role for namespace level viewing. It grants Read-only access to non-escalating resources in
// a namespace.
ObjectMeta: metav1.ObjectMeta{Name: "view"},
AggregationRule: &rbac.AggregationRule{
ClusterRoleSelectors: []metav1.LabelSelector{{MatchLabels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-view": "true"}}},
},
},
{
// a role for a namespace level admin. It is `edit` plus the power to grant permissions to other users.
ObjectMeta: metav1.ObjectMeta{Name: "system:aggregate-to-admin", Labels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-admin": "true"}},
Rules: []rbac.PolicyRule{
rbac.NewRule(ReadWrite...).Groups(legacyGroup).Resources("pods", "pods/attach", "pods/proxy", "pods/exec", "pods/portforward").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(legacyGroup).Resources("replicationcontrollers", "replicationcontrollers/scale", "serviceaccounts",
"services", "services/proxy", "endpoints", "persistentvolumeclaims", "configmaps", "secrets").RuleOrDie(),
rbac.NewRule(Read...).Groups(legacyGroup).Resources("limitranges", "resourcequotas", "bindings", "events",
"pods/status", "resourcequotas/status", "namespaces/status", "replicationcontrollers/status", "pods/log").RuleOrDie(),
// read access to namespaces at the namespace scope means you can read *this* namespace. This can be used as an
// indicator of which namespaces you have access to.
rbac.NewRule(Read...).Groups(legacyGroup).Resources("namespaces").RuleOrDie(),
rbac.NewRule("impersonate").Groups(legacyGroup).Resources("serviceaccounts").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(appsGroup).Resources("statefulsets",
"daemonsets",
"deployments", "deployments/scale", "deployments/rollback",
"replicasets", "replicasets/scale").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(autoscalingGroup).Resources("horizontalpodautoscalers").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(batchGroup).Resources("jobs", "cronjobs").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(extensionsGroup).Resources("daemonsets",
"deployments", "deployments/scale", "deployments/rollback", "ingresses",
"replicasets", "replicasets/scale", "replicationcontrollers/scale").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(policyGroup).Resources("poddisruptionbudgets").RuleOrDie(),
// additional admin powers
rbac.NewRule("create").Groups(authorizationGroup).Resources("localsubjectaccessreviews").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(rbacGroup).Resources("roles", "rolebindings").RuleOrDie(),
},
},
{
// a role for a namespace level editor. It grants access to all user level actions in a namespace.
// It does not grant powers for "privileged" resources which are domain of the system: `/status`
// subresources or `quota`/`limits` which are used to control namespaces
ObjectMeta: metav1.ObjectMeta{Name: "system:aggregate-to-edit", Labels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-edit": "true"}},
Rules: []rbac.PolicyRule{
rbac.NewRule(ReadWrite...).Groups(legacyGroup).Resources("pods", "pods/attach", "pods/proxy", "pods/exec", "pods/portforward").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(legacyGroup).Resources("replicationcontrollers", "replicationcontrollers/scale", "serviceaccounts",
"services", "services/proxy", "endpoints", "persistentvolumeclaims", "configmaps", "secrets").RuleOrDie(),
rbac.NewRule(Read...).Groups(legacyGroup).Resources("limitranges", "resourcequotas", "bindings", "events",
"pods/status", "resourcequotas/status", "namespaces/status", "replicationcontrollers/status", "pods/log").RuleOrDie(),
// read access to namespaces at the namespace scope means you can read *this* namespace. This can be used as an
// indicator of which namespaces you have access to.
rbac.NewRule(Read...).Groups(legacyGroup).Resources("namespaces").RuleOrDie(),
rbac.NewRule("impersonate").Groups(legacyGroup).Resources("serviceaccounts").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(appsGroup).Resources("statefulsets",
"daemonsets",
"deployments", "deployments/scale", "deployments/rollback",
"replicasets", "replicasets/scale").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(autoscalingGroup).Resources("horizontalpodautoscalers").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(batchGroup).Resources("jobs", "cronjobs").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(extensionsGroup).Resources("daemonsets",
"deployments", "deployments/scale", "deployments/rollback", "ingresses",
"replicasets", "replicasets/scale", "replicationcontrollers/scale").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(policyGroup).Resources("poddisruptionbudgets").RuleOrDie(),
},
},
{
// a role for namespace level viewing. It grants Read-only access to non-escalating resources in
// a namespace.
ObjectMeta: metav1.ObjectMeta{Name: "system:aggregate-to-view", Labels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-view": "true"}},
Rules: []rbac.PolicyRule{
rbac.NewRule(Read...).Groups(legacyGroup).Resources("pods", "replicationcontrollers", "replicationcontrollers/scale", "serviceaccounts",
"services", "endpoints", "persistentvolumeclaims", "configmaps").RuleOrDie(),
rbac.NewRule(Read...).Groups(legacyGroup).Resources("limitranges", "resourcequotas", "bindings", "events",
"pods/status", "resourcequotas/status", "namespaces/status", "replicationcontrollers/status", "pods/log").RuleOrDie(),
// read access to namespaces at the namespace scope means you can read *this* namespace. This can be used as an
// indicator of which namespaces you have access to.
rbac.NewRule(Read...).Groups(legacyGroup).Resources("namespaces").RuleOrDie(),
rbac.NewRule(Read...).Groups(appsGroup).Resources("statefulsets",
"daemonsets",
"deployments", "deployments/scale",
"replicasets", "replicasets/scale").RuleOrDie(),
rbac.NewRule(Read...).Groups(autoscalingGroup).Resources("horizontalpodautoscalers").RuleOrDie(),
rbac.NewRule(Read...).Groups(batchGroup).Resources("jobs", "cronjobs").RuleOrDie(),
rbac.NewRule(Read...).Groups(extensionsGroup).Resources("daemonsets", "deployments", "deployments/scale",
"ingresses", "replicasets", "replicasets/scale", "replicationcontrollers/scale").RuleOrDie(),
rbac.NewRule(Read...).Groups(policyGroup).Resources("poddisruptionbudgets").RuleOrDie(),
},
},
{
// a role to use for heapster's connections back to the API server
ObjectMeta: metav1.ObjectMeta{Name: "system:heapster"},
Rules: []rbac.PolicyRule{
rbac.NewRule(Read...).Groups(legacyGroup).Resources("events", "pods", "nodes", "namespaces").RuleOrDie(),
rbac.NewRule(Read...).Groups(extensionsGroup).Resources("deployments").RuleOrDie(),
},
},
{
// a role for nodes to use to have the access they need for running pods
ObjectMeta: metav1.ObjectMeta{Name: "system:node"},
Rules: NodeRules(),
},
{
// a role to use for node-problem-detector access. It does not get bound to default location since
// deployment locations can reasonably vary.
ObjectMeta: metav1.ObjectMeta{Name: "system:node-problem-detector"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
rbac.NewRule("patch").Groups(legacyGroup).Resources("nodes/status").RuleOrDie(),
eventsRule(),
},
},
{
// a role to use for setting up a proxy
ObjectMeta: metav1.ObjectMeta{Name: "system:node-proxier"},
Rules: []rbac.PolicyRule{
// Used to build serviceLister
rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("services", "endpoints").RuleOrDie(),
rbac.NewRule("get").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
eventsRule(),
},
},
{
// a role to use for bootstrapping a node's client certificates
ObjectMeta: metav1.ObjectMeta{Name: "system:node-bootstrapper"},
Rules: []rbac.PolicyRule{
// used to create a certificatesigningrequest for a node-specific client certificate, and watch for it to be signed
rbac.NewRule("create", "get", "list", "watch").Groups(certificatesGroup).Resources("certificatesigningrequests").RuleOrDie(),
},
},
{
// a role to use for allowing authentication and authorization delegation
ObjectMeta: metav1.ObjectMeta{Name: "system:auth-delegator"},
Rules: []rbac.PolicyRule{
// These creates are non-mutating
rbac.NewRule("create").Groups(authenticationGroup).Resources("tokenreviews").RuleOrDie(),
rbac.NewRule("create").Groups(authorizationGroup).Resources("subjectaccessreviews").RuleOrDie(),
},
},
{
// a role to use for the API registry, summarization, and proxy handling
ObjectMeta: metav1.ObjectMeta{Name: "system:kube-aggregator"},
Rules: []rbac.PolicyRule{
// it needs to see all services so that it knows whether the ones it points to exist or not
rbac.NewRule(Read...).Groups(legacyGroup).Resources("services", "endpoints").RuleOrDie(),
},
},
{
// a role to use for bootstrapping the kube-controller-manager so it can create the shared informers
// service accounts, and secrets that we need to create separate identities for other controllers
ObjectMeta: metav1.ObjectMeta{Name: "system:kube-controller-manager"},
Rules: []rbac.PolicyRule{
eventsRule(),
rbac.NewRule("create").Groups(legacyGroup).Resources("endpoints", "secrets", "serviceaccounts").RuleOrDie(),
rbac.NewRule("delete").Groups(legacyGroup).Resources("secrets").RuleOrDie(),
rbac.NewRule("get").Groups(legacyGroup).Resources("endpoints", "namespaces", "secrets", "serviceaccounts").RuleOrDie(),
rbac.NewRule("update").Groups(legacyGroup).Resources("endpoints", "secrets", "serviceaccounts").RuleOrDie(),
// Needed to check API access. These creates are non-mutating
rbac.NewRule("create").Groups(authenticationGroup).Resources("tokenreviews").RuleOrDie(),
// Needed for all shared informers
rbac.NewRule("list", "watch").Groups("*").Resources("*").RuleOrDie(),
},
},
{
// a role to use for the kube-scheduler
ObjectMeta: metav1.ObjectMeta{Name: "system:kube-scheduler"},
Rules: []rbac.PolicyRule{
eventsRule(),
// this is for leaderlease access
// TODO: scope this to the kube-system namespace
rbac.NewRule("create").Groups(legacyGroup).Resources("endpoints").RuleOrDie(),
rbac.NewRule("get", "update", "patch", "delete").Groups(legacyGroup).Resources("endpoints").Names("kube-scheduler").RuleOrDie(),
// fundamental resources
rbac.NewRule(Read...).Groups(legacyGroup).Resources("nodes").RuleOrDie(),
rbac.NewRule("get", "list", "watch", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
rbac.NewRule("create").Groups(legacyGroup).Resources("pods/binding", "bindings").RuleOrDie(),
rbac.NewRule("patch", "update").Groups(legacyGroup).Resources("pods/status").RuleOrDie(),
// things that select pods
rbac.NewRule(Read...).Groups(legacyGroup).Resources("services", "replicationcontrollers").RuleOrDie(),
rbac.NewRule(Read...).Groups(appsGroup, extensionsGroup).Resources("replicasets").RuleOrDie(),
rbac.NewRule(Read...).Groups(appsGroup).Resources("statefulsets").RuleOrDie(),
// things that pods use or applies to them
rbac.NewRule(Read...).Groups(policyGroup).Resources("poddisruptionbudgets").RuleOrDie(),
rbac.NewRule(Read...).Groups(legacyGroup).Resources("persistentvolumeclaims", "persistentvolumes").RuleOrDie(),
},
},
{
// a role to use for the kube-dns pod
ObjectMeta: metav1.ObjectMeta{Name: "system:kube-dns"},
Rules: []rbac.PolicyRule{
rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("endpoints", "services").RuleOrDie(),
},
},
{
// a role for an external/out-of-tree persistent volume provisioner
ObjectMeta: metav1.ObjectMeta{Name: "system:persistent-volume-provisioner"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "create", "delete").Groups(legacyGroup).Resources("persistentvolumes").RuleOrDie(),
// update is needed in addition to read access for setting lock annotations on PVCs
rbac.NewRule("get", "list", "watch", "update").Groups(legacyGroup).Resources("persistentvolumeclaims").RuleOrDie(),
rbac.NewRule(Read...).Groups(storageGroup).Resources("storageclasses").RuleOrDie(),
// Needed for watching provisioning success and failure events
rbac.NewRule("watch").Groups(legacyGroup).Resources("events").RuleOrDie(),
eventsRule(),
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "system:aws-cloud-provider"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "patch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
eventsRule(),
},
},
{
// a role making the csrapprover controller approve a node client CSR
ObjectMeta: metav1.ObjectMeta{Name: "system:certificates.k8s.io:certificatesigningrequests:nodeclient"},
Rules: []rbac.PolicyRule{
rbac.NewRule("create").Groups(certificatesGroup).Resources("certificatesigningrequests/nodeclient").RuleOrDie(),
},
},
{
// a role making the csrapprover controller approve a node client CSR requested by the node itself
ObjectMeta: metav1.ObjectMeta{Name: "system:certificates.k8s.io:certificatesigningrequests:selfnodeclient"},
Rules: []rbac.PolicyRule{
rbac.NewRule("create").Groups(certificatesGroup).Resources("certificatesigningrequests/selfnodeclient").RuleOrDie(),
},
},
}
if utilfeature.DefaultFeatureGate.Enabled(features.RotateKubeletServerCertificate) {
roles = append(roles, rbac.ClusterRole{
// a role making the csrapprover controller approve a node server CSR requested by the node itself
ObjectMeta: metav1.ObjectMeta{Name: "system:certificates.k8s.io:certificatesigningrequests:selfnodeserver"},
Rules: []rbac.PolicyRule{
rbac.NewRule("create").Groups(certificatesGroup).Resources("certificatesigningrequests/selfnodeserver").RuleOrDie(),
},
})
}
if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
// Find the scheduler role
for i, role := range roles {
if role.Name == "system:kube-scheduler" {
pvRule := rbac.NewRule("update").Groups(legacyGroup).Resources("persistentvolumes").RuleOrDie()
scRule := rbac.NewRule(Read...).Groups(storageGroup).Resources("storageclasses").RuleOrDie()
roles[i].Rules = append(role.Rules, pvRule, scRule)
break
}
}
}
addClusterRoleLabel(roles)
return roles
}
const systemNodeRoleName = "system:node"
// ClusterRoleBindings return default rolebindings to the default roles
func ClusterRoleBindings() []rbac.ClusterRoleBinding {
rolebindings := []rbac.ClusterRoleBinding{
rbac.NewClusterBinding("cluster-admin").Groups(user.SystemPrivilegedGroup).BindingOrDie(),
rbac.NewClusterBinding("system:discovery").Groups(user.AllAuthenticated, user.AllUnauthenticated).BindingOrDie(),
rbac.NewClusterBinding("system:basic-user").Groups(user.AllAuthenticated, user.AllUnauthenticated).BindingOrDie(),
rbac.NewClusterBinding("system:node-proxier").Users(user.KubeProxy).BindingOrDie(),
rbac.NewClusterBinding("system:kube-controller-manager").Users(user.KubeControllerManager).BindingOrDie(),
rbac.NewClusterBinding("system:kube-dns").SAs("kube-system", "kube-dns").BindingOrDie(),
rbac.NewClusterBinding("system:kube-scheduler").Users(user.KubeScheduler).BindingOrDie(),
rbac.NewClusterBinding("system:aws-cloud-provider").SAs("kube-system", "aws-cloud-provider").BindingOrDie(),
// This default binding of the system:node role to the system:nodes group is deprecated in 1.7 with the availability of the Node authorizer.
// This leaves the binding, but with an empty set of subjects, so that tightening reconciliation can remove the subject.
{
ObjectMeta: metav1.ObjectMeta{Name: systemNodeRoleName},
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "ClusterRole", Name: systemNodeRoleName},
},
}
addClusterRoleBindingLabel(rolebindings)
return rolebindings
}
func ClusterRolesToAggregate() map[string]string {
return map[string]string{
"admin": "system:aggregate-to-admin",
"edit": "system:aggregate-to-edit",
"view": "system:aggregate-to-view",
}
}

View File

@ -0,0 +1,339 @@
/*
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 bootstrappolicy_test
import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"testing"
"github.com/ghodss/yaml"
"k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
_ "k8s.io/kubernetes/pkg/apis/core/install"
"k8s.io/kubernetes/pkg/apis/rbac"
_ "k8s.io/kubernetes/pkg/apis/rbac/install"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy"
)
// semanticRoles is a few enumerated roles for which the relationships are well established
// and we want to maintain symmetric roles
type semanticRoles struct {
admin *rbac.ClusterRole
edit *rbac.ClusterRole
view *rbac.ClusterRole
}
func getSemanticRoles(roles []rbac.ClusterRole) semanticRoles {
ret := semanticRoles{}
for i := range roles {
role := roles[i]
switch role.Name {
case "system:aggregate-to-admin":
ret.admin = &role
case "system:aggregate-to-edit":
ret.edit = &role
case "system:aggregate-to-view":
ret.view = &role
}
}
return ret
}
// Some roles should always cover others
func TestCovers(t *testing.T) {
semanticRoles := getSemanticRoles(bootstrappolicy.ClusterRoles())
if covers, miss := rbacregistryvalidation.Covers(semanticRoles.admin.Rules, semanticRoles.edit.Rules); !covers {
t.Errorf("failed to cover: %#v", miss)
}
if covers, miss := rbacregistryvalidation.Covers(semanticRoles.admin.Rules, semanticRoles.view.Rules); !covers {
t.Errorf("failed to cover: %#v", miss)
}
if covers, miss := rbacregistryvalidation.Covers(semanticRoles.edit.Rules, semanticRoles.view.Rules); !covers {
t.Errorf("failed to cover: %#v", miss)
}
}
// additionalAdminPowers is the list of powers that we expect to be different than the editor role.
// one resource per rule to make the "does not already contain" check easy
var additionalAdminPowers = []rbac.PolicyRule{
rbac.NewRule("create").Groups("authorization.k8s.io").Resources("localsubjectaccessreviews").RuleOrDie(),
rbac.NewRule(bootstrappolicy.ReadWrite...).Groups("rbac.authorization.k8s.io").Resources("rolebindings").RuleOrDie(),
rbac.NewRule(bootstrappolicy.ReadWrite...).Groups("rbac.authorization.k8s.io").Resources("roles").RuleOrDie(),
}
func TestAdminEditRelationship(t *testing.T) {
semanticRoles := getSemanticRoles(bootstrappolicy.ClusterRoles())
// confirm that the edit role doesn't already have extra powers
for _, rule := range additionalAdminPowers {
if covers, _ := rbacregistryvalidation.Covers(semanticRoles.edit.Rules, []rbac.PolicyRule{rule}); covers {
t.Errorf("edit has extra powers: %#v", rule)
}
}
semanticRoles.edit.Rules = append(semanticRoles.edit.Rules, additionalAdminPowers...)
// at this point, we should have a two way covers relationship
if covers, miss := rbacregistryvalidation.Covers(semanticRoles.admin.Rules, semanticRoles.edit.Rules); !covers {
t.Errorf("admin has lost rules for: %#v", miss)
}
if covers, miss := rbacregistryvalidation.Covers(semanticRoles.edit.Rules, semanticRoles.admin.Rules); !covers {
t.Errorf("edit is missing rules for: %#v\nIf these should only be admin powers, add them to the list. Otherwise, add them to the edit role.", miss)
}
}
// viewEscalatingNamespaceResources is the list of rules that would allow privilege escalation attacks based on
// ability to view (GET) them
var viewEscalatingNamespaceResources = []rbac.PolicyRule{
rbac.NewRule(bootstrappolicy.Read...).Groups("").Resources("pods/attach").RuleOrDie(),
rbac.NewRule(bootstrappolicy.Read...).Groups("").Resources("pods/proxy").RuleOrDie(),
rbac.NewRule(bootstrappolicy.Read...).Groups("").Resources("pods/exec").RuleOrDie(),
rbac.NewRule(bootstrappolicy.Read...).Groups("").Resources("pods/portforward").RuleOrDie(),
rbac.NewRule(bootstrappolicy.Read...).Groups("").Resources("secrets").RuleOrDie(),
rbac.NewRule(bootstrappolicy.Read...).Groups("").Resources("services/proxy").RuleOrDie(),
}
// ungettableResources is the list of rules that don't allow to view (GET) them
// this is purposefully separate list to distinguish from escalating privs
var ungettableResources = []rbac.PolicyRule{
rbac.NewRule(bootstrappolicy.Read...).Groups("apps", "extensions").Resources("deployments/rollback").RuleOrDie(),
}
func TestEditViewRelationship(t *testing.T) {
readVerbs := sets.NewString(bootstrappolicy.Read...)
semanticRoles := getSemanticRoles(bootstrappolicy.ClusterRoles())
// modify the edit role rules to make then read-only for comparison against view role rules
for i := range semanticRoles.edit.Rules {
rule := semanticRoles.edit.Rules[i]
remainingVerbs := []string{}
for _, verb := range rule.Verbs {
if readVerbs.Has(verb) {
remainingVerbs = append(remainingVerbs, verb)
}
}
rule.Verbs = remainingVerbs
semanticRoles.edit.Rules[i] = rule
}
// confirm that the view role doesn't already have extra powers
for _, rule := range viewEscalatingNamespaceResources {
if covers, _ := rbacregistryvalidation.Covers(semanticRoles.view.Rules, []rbac.PolicyRule{rule}); covers {
t.Errorf("view has extra powers: %#v", rule)
}
}
semanticRoles.view.Rules = append(semanticRoles.view.Rules, viewEscalatingNamespaceResources...)
// confirm that the view role doesn't have ungettable resources
for _, rule := range ungettableResources {
if covers, _ := rbacregistryvalidation.Covers(semanticRoles.view.Rules, []rbac.PolicyRule{rule}); covers {
t.Errorf("view has ungettable resource: %#v", rule)
}
}
semanticRoles.view.Rules = append(semanticRoles.view.Rules, ungettableResources...)
// at this point, we should have a two way covers relationship
if covers, miss := rbacregistryvalidation.Covers(semanticRoles.edit.Rules, semanticRoles.view.Rules); !covers {
t.Errorf("edit has lost rules for: %#v", miss)
}
if covers, miss := rbacregistryvalidation.Covers(semanticRoles.view.Rules, semanticRoles.edit.Rules); !covers {
t.Errorf("view is missing rules for: %#v\nIf these are escalating powers, add them to the list. Otherwise, add them to the view role.", miss)
}
}
func TestBootstrapNamespaceRoles(t *testing.T) {
list := &api.List{}
names := sets.NewString()
roles := map[string]runtime.Object{}
namespaceRoles := bootstrappolicy.NamespaceRoles()
for _, namespace := range sets.StringKeySet(namespaceRoles).List() {
bootstrapRoles := namespaceRoles[namespace]
for i := range bootstrapRoles {
role := bootstrapRoles[i]
names.Insert(role.Name)
roles[role.Name] = &role
}
for _, name := range names.List() {
list.Items = append(list.Items, roles[name])
}
}
testObjects(t, list, "namespace-roles.yaml")
}
func TestBootstrapNamespaceRoleBindings(t *testing.T) {
list := &api.List{}
names := sets.NewString()
roleBindings := map[string]runtime.Object{}
namespaceRoleBindings := bootstrappolicy.NamespaceRoleBindings()
for _, namespace := range sets.StringKeySet(namespaceRoleBindings).List() {
bootstrapRoleBindings := namespaceRoleBindings[namespace]
for i := range bootstrapRoleBindings {
roleBinding := bootstrapRoleBindings[i]
names.Insert(roleBinding.Name)
roleBindings[roleBinding.Name] = &roleBinding
}
for _, name := range names.List() {
list.Items = append(list.Items, roleBindings[name])
}
}
testObjects(t, list, "namespace-role-bindings.yaml")
}
func TestBootstrapClusterRoles(t *testing.T) {
list := &api.List{}
names := sets.NewString()
roles := map[string]runtime.Object{}
bootstrapRoles := bootstrappolicy.ClusterRoles()
for i := range bootstrapRoles {
role := bootstrapRoles[i]
names.Insert(role.Name)
roles[role.Name] = &role
}
for _, name := range names.List() {
list.Items = append(list.Items, roles[name])
}
testObjects(t, list, "cluster-roles.yaml")
}
func TestBootstrapClusterRoleBindings(t *testing.T) {
list := &api.List{}
names := sets.NewString()
roleBindings := map[string]runtime.Object{}
bootstrapRoleBindings := bootstrappolicy.ClusterRoleBindings()
for i := range bootstrapRoleBindings {
role := bootstrapRoleBindings[i]
names.Insert(role.Name)
roleBindings[role.Name] = &role
}
for _, name := range names.List() {
list.Items = append(list.Items, roleBindings[name])
}
testObjects(t, list, "cluster-role-bindings.yaml")
}
func TestBootstrapControllerRoles(t *testing.T) {
list := &api.List{}
names := sets.NewString()
roles := map[string]runtime.Object{}
bootstrapRoles := bootstrappolicy.ControllerRoles()
for i := range bootstrapRoles {
role := bootstrapRoles[i]
names.Insert(role.Name)
roles[role.Name] = &role
}
for _, name := range names.List() {
list.Items = append(list.Items, roles[name])
}
testObjects(t, list, "controller-roles.yaml")
}
func TestBootstrapControllerRoleBindings(t *testing.T) {
list := &api.List{}
names := sets.NewString()
roleBindings := map[string]runtime.Object{}
bootstrapRoleBindings := bootstrappolicy.ControllerRoleBindings()
for i := range bootstrapRoleBindings {
roleBinding := bootstrapRoleBindings[i]
names.Insert(roleBinding.Name)
roleBindings[roleBinding.Name] = &roleBinding
}
for _, name := range names.List() {
list.Items = append(list.Items, roleBindings[name])
}
testObjects(t, list, "controller-role-bindings.yaml")
}
func testObjects(t *testing.T, list *api.List, fixtureFilename string) {
filename := filepath.Join("testdata", fixtureFilename)
expectedYAML, err := ioutil.ReadFile(filename)
if err != nil {
t.Fatal(err)
}
if err := runtime.EncodeList(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion, rbacv1.SchemeGroupVersion), list.Items); err != nil {
t.Fatal(err)
}
jsonData, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion, rbacv1.SchemeGroupVersion), list)
if err != nil {
t.Fatal(err)
}
yamlData, err := yaml.JSONToYAML(jsonData)
if err != nil {
t.Fatal(err)
}
if string(yamlData) != string(expectedYAML) {
t.Errorf("Bootstrap policy data does not match the test fixture in %s", filename)
const updateEnvVar = "UPDATE_BOOTSTRAP_POLICY_FIXTURE_DATA"
if os.Getenv(updateEnvVar) == "true" {
if err := ioutil.WriteFile(filename, []byte(yamlData), os.FileMode(0755)); err == nil {
t.Logf("Updated data in %s", filename)
t.Logf("Verify the diff, commit changes, and rerun the tests")
} else {
t.Logf("Could not update data in %s: %v", filename, err)
}
} else {
t.Logf("Diff between bootstrap data and fixture data in %s:\n-------------\n%s", filename, diff.StringDiff(string(yamlData), string(expectedYAML)))
t.Logf("If the change is expected, re-run with %s=true to update the fixtures", updateEnvVar)
}
}
}
func TestClusterRoleLabel(t *testing.T) {
roles := bootstrappolicy.ClusterRoles()
for i := range roles {
role := roles[i]
accessor, err := meta.Accessor(&role)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if accessor.GetLabels()["kubernetes.io/bootstrapping"] != "rbac-defaults" {
t.Errorf("ClusterRole: %s GetLabels() = %s, want %s", accessor.GetName(), accessor.GetLabels(), map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"})
}
}
rolebindings := bootstrappolicy.ClusterRoleBindings()
for i := range rolebindings {
rolebinding := rolebindings[i]
accessor, err := meta.Accessor(&rolebinding)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got, want := accessor.GetLabels(), map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"}; !reflect.DeepEqual(got, want) {
t.Errorf("ClusterRoleBinding: %s GetLabels() = %s, want %s", accessor.GetName(), got, want)
}
}
}

View File

@ -0,0 +1,160 @@
apiVersion: v1
items:
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: cluster-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:masters
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:aws-cloud-provider
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:aws-cloud-provider
subjects:
- kind: ServiceAccount
name: aws-cloud-provider
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:basic-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:basic-user
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:authenticated
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:unauthenticated
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:discovery
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:discovery
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:authenticated
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:unauthenticated
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:kube-controller-manager
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:kube-controller-manager
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: system:kube-controller-manager
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:kube-dns
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:kube-dns
subjects:
- kind: ServiceAccount
name: kube-dns
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:kube-scheduler
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:kube-scheduler
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: system:kube-scheduler
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:node
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:node
subjects: null
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:node-proxier
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:node-proxier
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: system:kube-proxy
kind: List
metadata: {}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,395 @@
apiVersion: v1
items:
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:attachdetach-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:attachdetach-controller
subjects:
- kind: ServiceAccount
name: attachdetach-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:certificate-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:certificate-controller
subjects:
- kind: ServiceAccount
name: certificate-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:clusterrole-aggregation-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:clusterrole-aggregation-controller
subjects:
- kind: ServiceAccount
name: clusterrole-aggregation-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:cronjob-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:cronjob-controller
subjects:
- kind: ServiceAccount
name: cronjob-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:daemon-set-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:daemon-set-controller
subjects:
- kind: ServiceAccount
name: daemon-set-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:deployment-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:deployment-controller
subjects:
- kind: ServiceAccount
name: deployment-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:disruption-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:disruption-controller
subjects:
- kind: ServiceAccount
name: disruption-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:endpoint-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:endpoint-controller
subjects:
- kind: ServiceAccount
name: endpoint-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:generic-garbage-collector
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:generic-garbage-collector
subjects:
- kind: ServiceAccount
name: generic-garbage-collector
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:horizontal-pod-autoscaler
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:horizontal-pod-autoscaler
subjects:
- kind: ServiceAccount
name: horizontal-pod-autoscaler
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:job-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:job-controller
subjects:
- kind: ServiceAccount
name: job-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:namespace-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:namespace-controller
subjects:
- kind: ServiceAccount
name: namespace-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:node-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:node-controller
subjects:
- kind: ServiceAccount
name: node-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:persistent-volume-binder
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:persistent-volume-binder
subjects:
- kind: ServiceAccount
name: persistent-volume-binder
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:pod-garbage-collector
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:pod-garbage-collector
subjects:
- kind: ServiceAccount
name: pod-garbage-collector
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:replicaset-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:replicaset-controller
subjects:
- kind: ServiceAccount
name: replicaset-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:replication-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:replication-controller
subjects:
- kind: ServiceAccount
name: replication-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:resourcequota-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:resourcequota-controller
subjects:
- kind: ServiceAccount
name: resourcequota-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:route-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:route-controller
subjects:
- kind: ServiceAccount
name: route-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:service-account-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:service-account-controller
subjects:
- kind: ServiceAccount
name: service-account-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:service-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:service-controller
subjects:
- kind: ServiceAccount
name: service-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:statefulset-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:statefulset-controller
subjects:
- kind: ServiceAccount
name: statefulset-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:ttl-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:ttl-controller
subjects:
- kind: ServiceAccount
name: ttl-controller
namespace: kube-system
kind: List
metadata: {}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,112 @@
apiVersion: v1
items:
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:bootstrap-signer
namespace: kube-public
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: system:controller:bootstrap-signer
subjects:
- kind: ServiceAccount
name: bootstrap-signer
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system::leader-locking-kube-controller-manager
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: system::leader-locking-kube-controller-manager
subjects:
- kind: ServiceAccount
name: kube-controller-manager
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system::leader-locking-kube-scheduler
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: system::leader-locking-kube-scheduler
subjects:
- kind: ServiceAccount
name: kube-scheduler
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:bootstrap-signer
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: system:controller:bootstrap-signer
subjects:
- kind: ServiceAccount
name: bootstrap-signer
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:cloud-provider
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: system:controller:cloud-provider
subjects:
- kind: ServiceAccount
name: cloud-provider
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:token-cleaner
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: system:controller:token-cleaner
subjects:
- kind: ServiceAccount
name: token-cleaner
namespace: kube-system
kind: List
metadata: {}

View File

@ -0,0 +1,177 @@
apiVersion: v1
items:
- apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:bootstrap-signer
namespace: kube-public
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- apiGroups:
- ""
resourceNames:
- cluster-info
resources:
- configmaps
verbs:
- update
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- update
- apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: extension-apiserver-authentication-reader
namespace: kube-system
rules:
- apiGroups:
- ""
resourceNames:
- extension-apiserver-authentication
resources:
- configmaps
verbs:
- get
- apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system::leader-locking-kube-controller-manager
namespace: kube-system
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- watch
- apiGroups:
- ""
resourceNames:
- kube-controller-manager
resources:
- configmaps
verbs:
- get
- update
- apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system::leader-locking-kube-scheduler
namespace: kube-system
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- watch
- apiGroups:
- ""
resourceNames:
- kube-scheduler
resources:
- configmaps
verbs:
- get
- update
- apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:bootstrap-signer
namespace: kube-system
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
- apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:cloud-provider
namespace: kube-system
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- get
- list
- watch
- apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:token-cleaner
namespace: kube-system
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- delete
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- update
kind: List
metadata: {}

View File

@ -0,0 +1,222 @@
/*
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 rbac implements the authorizer.Authorizer interface using roles base access control.
package rbac
import (
"fmt"
"github.com/golang/glog"
"bytes"
"k8s.io/apimachinery/pkg/labels"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/pkg/apis/rbac"
rbaclisters "k8s.io/kubernetes/pkg/client/listers/rbac/internalversion"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
)
type RequestToRuleMapper interface {
// RulesFor returns all known PolicyRules and any errors that happened while locating those rules.
// Any rule returned is still valid, since rules are deny by default. If you can pass with the rules
// supplied, you do not have to fail the request. If you cannot, you should indicate the error along
// with your denial.
RulesFor(subject user.Info, namespace string) ([]rbac.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. Rule may be nil if err is non-nil.
// If visitor() returns false, visiting is short-circuited.
VisitRulesFor(user user.Info, namespace string, visitor func(rule *rbac.PolicyRule, err error) bool)
}
type RBACAuthorizer struct {
authorizationRuleResolver RequestToRuleMapper
}
// authorizingVisitor short-circuits once allowed, and collects any resolution errors encountered
type authorizingVisitor struct {
requestAttributes authorizer.Attributes
allowed bool
errors []error
}
func (v *authorizingVisitor) visit(rule *rbac.PolicyRule, err error) bool {
if rule != nil && RuleAllows(v.requestAttributes, rule) {
v.allowed = true
return false
}
if err != nil {
v.errors = append(v.errors, err)
}
return true
}
func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (authorizer.Decision, string, error) {
ruleCheckingVisitor := &authorizingVisitor{requestAttributes: requestAttributes}
r.authorizationRuleResolver.VisitRulesFor(requestAttributes.GetUser(), requestAttributes.GetNamespace(), ruleCheckingVisitor.visit)
if ruleCheckingVisitor.allowed {
return authorizer.DecisionAllow, "", nil
}
// Build a detailed log of the denial.
// Make the whole block conditional so we don't do a lot of string-building we won't use.
if glog.V(2) {
var operation string
if requestAttributes.IsResourceRequest() {
b := &bytes.Buffer{}
b.WriteString(`"`)
b.WriteString(requestAttributes.GetVerb())
b.WriteString(`" resource "`)
b.WriteString(requestAttributes.GetResource())
if len(requestAttributes.GetAPIGroup()) > 0 {
b.WriteString(`.`)
b.WriteString(requestAttributes.GetAPIGroup())
}
if len(requestAttributes.GetSubresource()) > 0 {
b.WriteString(`/`)
b.WriteString(requestAttributes.GetSubresource())
}
b.WriteString(`"`)
if len(requestAttributes.GetName()) > 0 {
b.WriteString(` named "`)
b.WriteString(requestAttributes.GetName())
b.WriteString(`"`)
}
operation = b.String()
} else {
operation = fmt.Sprintf("%q nonResourceURL %q", requestAttributes.GetVerb(), requestAttributes.GetPath())
}
var scope string
if ns := requestAttributes.GetNamespace(); len(ns) > 0 {
scope = fmt.Sprintf("in namespace %q", ns)
} else {
scope = "cluster-wide"
}
glog.Infof("RBAC DENY: user %q groups %q cannot %s %s", requestAttributes.GetUser().GetName(), requestAttributes.GetUser().GetGroups(), operation, scope)
}
reason := ""
if len(ruleCheckingVisitor.errors) > 0 {
reason = fmt.Sprintf("%v", utilerrors.NewAggregate(ruleCheckingVisitor.errors))
}
return authorizer.DecisionNoOpinion, reason, nil
}
func (r *RBACAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
var (
resourceRules []authorizer.ResourceRuleInfo
nonResourceRules []authorizer.NonResourceRuleInfo
)
policyRules, err := r.authorizationRuleResolver.RulesFor(user, namespace)
for _, policyRule := range policyRules {
if len(policyRule.Resources) > 0 {
r := authorizer.DefaultResourceRuleInfo{
Verbs: policyRule.Verbs,
APIGroups: policyRule.APIGroups,
Resources: policyRule.Resources,
ResourceNames: policyRule.ResourceNames,
}
var resourceRule authorizer.ResourceRuleInfo = &r
resourceRules = append(resourceRules, resourceRule)
}
if len(policyRule.NonResourceURLs) > 0 {
r := authorizer.DefaultNonResourceRuleInfo{
Verbs: policyRule.Verbs,
NonResourceURLs: policyRule.NonResourceURLs,
}
var nonResourceRule authorizer.NonResourceRuleInfo = &r
nonResourceRules = append(nonResourceRules, nonResourceRule)
}
}
return resourceRules, nonResourceRules, false, err
}
func New(roles rbacregistryvalidation.RoleGetter, roleBindings rbacregistryvalidation.RoleBindingLister, clusterRoles rbacregistryvalidation.ClusterRoleGetter, clusterRoleBindings rbacregistryvalidation.ClusterRoleBindingLister) *RBACAuthorizer {
authorizer := &RBACAuthorizer{
authorizationRuleResolver: rbacregistryvalidation.NewDefaultRuleResolver(
roles, roleBindings, clusterRoles, clusterRoleBindings,
),
}
return authorizer
}
func RulesAllow(requestAttributes authorizer.Attributes, rules ...rbac.PolicyRule) bool {
for i := range rules {
if RuleAllows(requestAttributes, &rules[i]) {
return true
}
}
return false
}
func RuleAllows(requestAttributes authorizer.Attributes, rule *rbac.PolicyRule) bool {
if requestAttributes.IsResourceRequest() {
combinedResource := requestAttributes.GetResource()
if len(requestAttributes.GetSubresource()) > 0 {
combinedResource = requestAttributes.GetResource() + "/" + requestAttributes.GetSubresource()
}
return rbac.VerbMatches(rule, requestAttributes.GetVerb()) &&
rbac.APIGroupMatches(rule, requestAttributes.GetAPIGroup()) &&
rbac.ResourceMatches(rule, combinedResource, requestAttributes.GetSubresource()) &&
rbac.ResourceNameMatches(rule, requestAttributes.GetName())
}
return rbac.VerbMatches(rule, requestAttributes.GetVerb()) &&
rbac.NonResourceURLMatches(rule, requestAttributes.GetPath())
}
type RoleGetter struct {
Lister rbaclisters.RoleLister
}
func (g *RoleGetter) GetRole(namespace, name string) (*rbac.Role, error) {
return g.Lister.Roles(namespace).Get(name)
}
type RoleBindingLister struct {
Lister rbaclisters.RoleBindingLister
}
func (l *RoleBindingLister) ListRoleBindings(namespace string) ([]*rbac.RoleBinding, error) {
return l.Lister.RoleBindings(namespace).List(labels.Everything())
}
type ClusterRoleGetter struct {
Lister rbaclisters.ClusterRoleLister
}
func (g *ClusterRoleGetter) GetClusterRole(name string) (*rbac.ClusterRole, error) {
return g.Lister.Get(name)
}
type ClusterRoleBindingLister struct {
Lister rbaclisters.ClusterRoleBindingLister
}
func (l *ClusterRoleBindingLister) ListClusterRoleBindings() ([]*rbac.ClusterRoleBinding, error) {
return l.Lister.List(labels.Everything())
}

View File

@ -0,0 +1,522 @@
/*
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 rbac
import (
"fmt"
"strings"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/pkg/apis/rbac"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy"
)
func newRule(verbs, apiGroups, resources, nonResourceURLs string) rbac.PolicyRule {
return rbac.PolicyRule{
Verbs: strings.Split(verbs, ","),
APIGroups: strings.Split(apiGroups, ","),
Resources: strings.Split(resources, ","),
NonResourceURLs: strings.Split(nonResourceURLs, ","),
}
}
func newRole(name, namespace string, rules ...rbac.PolicyRule) *rbac.Role {
return &rbac.Role{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, Rules: rules}
}
func newClusterRole(name string, rules ...rbac.PolicyRule) *rbac.ClusterRole {
return &rbac.ClusterRole{ObjectMeta: metav1.ObjectMeta{Name: name}, Rules: rules}
}
const (
bindToRole uint16 = 0x0
bindToClusterRole uint16 = 0x1
)
func newClusterRoleBinding(roleName string, subjects ...string) *rbac.ClusterRoleBinding {
r := &rbac.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{},
RoleRef: rbac.RoleRef{
APIGroup: rbac.GroupName,
Kind: "ClusterRole", // ClusterRoleBindings can only refer to ClusterRole
Name: roleName,
},
}
r.Subjects = make([]rbac.Subject, len(subjects))
for i, subject := range subjects {
split := strings.SplitN(subject, ":", 2)
r.Subjects[i].Kind, r.Subjects[i].Name = split[0], split[1]
switch r.Subjects[i].Kind {
case rbac.ServiceAccountKind:
r.Subjects[i].APIGroup = ""
case rbac.UserKind, rbac.GroupKind:
r.Subjects[i].APIGroup = rbac.GroupName
default:
panic(fmt.Errorf("invalid kind %s", r.Subjects[i].Kind))
}
}
return r
}
func newRoleBinding(namespace, roleName string, bindType uint16, subjects ...string) *rbac.RoleBinding {
r := &rbac.RoleBinding{ObjectMeta: metav1.ObjectMeta{Namespace: namespace}}
switch bindType {
case bindToRole:
r.RoleRef = rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "Role", Name: roleName}
case bindToClusterRole:
r.RoleRef = rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "ClusterRole", Name: roleName}
}
r.Subjects = make([]rbac.Subject, len(subjects))
for i, subject := range subjects {
split := strings.SplitN(subject, ":", 2)
r.Subjects[i].Kind, r.Subjects[i].Name = split[0], split[1]
switch r.Subjects[i].Kind {
case rbac.ServiceAccountKind:
r.Subjects[i].APIGroup = ""
case rbac.UserKind, rbac.GroupKind:
r.Subjects[i].APIGroup = rbac.GroupName
default:
panic(fmt.Errorf("invalid kind %s", r.Subjects[i].Kind))
}
}
return r
}
type defaultAttributes struct {
user string
groups string
verb string
resource string
subresource string
namespace string
apiGroup string
}
func (d *defaultAttributes) String() string {
return fmt.Sprintf("user=(%s), groups=(%s), verb=(%s), resource=(%s), namespace=(%s), apiGroup=(%s)",
d.user, strings.Split(d.groups, ","), d.verb, d.resource, d.namespace, d.apiGroup)
}
func (d *defaultAttributes) GetUser() user.Info {
return &user.DefaultInfo{Name: d.user, Groups: strings.Split(d.groups, ",")}
}
func (d *defaultAttributes) GetVerb() string { return d.verb }
func (d *defaultAttributes) IsReadOnly() bool { return d.verb == "get" || d.verb == "watch" }
func (d *defaultAttributes) GetNamespace() string { return d.namespace }
func (d *defaultAttributes) GetResource() string { return d.resource }
func (d *defaultAttributes) GetSubresource() string { return d.subresource }
func (d *defaultAttributes) GetName() string { return "" }
func (d *defaultAttributes) GetAPIGroup() string { return d.apiGroup }
func (d *defaultAttributes) GetAPIVersion() string { return "" }
func (d *defaultAttributes) IsResourceRequest() bool { return true }
func (d *defaultAttributes) GetPath() string { return "" }
func TestAuthorizer(t *testing.T) {
tests := []struct {
roles []*rbac.Role
roleBindings []*rbac.RoleBinding
clusterRoles []*rbac.ClusterRole
clusterRoleBindings []*rbac.ClusterRoleBinding
shouldPass []authorizer.Attributes
shouldFail []authorizer.Attributes
}{
{
clusterRoles: []*rbac.ClusterRole{
newClusterRole("admin", newRule("*", "*", "*", "*")),
},
roleBindings: []*rbac.RoleBinding{
newRoleBinding("ns1", "admin", bindToClusterRole, "User:admin", "Group:admins"),
},
shouldPass: []authorizer.Attributes{
&defaultAttributes{"admin", "", "get", "Pods", "", "ns1", ""},
&defaultAttributes{"admin", "", "watch", "Pods", "", "ns1", ""},
&defaultAttributes{"admin", "group1", "watch", "Foobar", "", "ns1", ""},
&defaultAttributes{"joe", "admins", "watch", "Foobar", "", "ns1", ""},
&defaultAttributes{"joe", "group1,admins", "watch", "Foobar", "", "ns1", ""},
},
shouldFail: []authorizer.Attributes{
&defaultAttributes{"admin", "", "GET", "Pods", "", "ns2", ""},
&defaultAttributes{"admin", "", "GET", "Nodes", "", "", ""},
&defaultAttributes{"admin", "admins", "GET", "Pods", "", "ns2", ""},
&defaultAttributes{"admin", "admins", "GET", "Nodes", "", "", ""},
},
},
{
// Non-resource-url tests
clusterRoles: []*rbac.ClusterRole{
newClusterRole("non-resource-url-getter", newRule("get", "", "", "/apis")),
newClusterRole("non-resource-url", newRule("*", "", "", "/apis")),
newClusterRole("non-resource-url-prefix", newRule("get", "", "", "/apis/*")),
},
clusterRoleBindings: []*rbac.ClusterRoleBinding{
newClusterRoleBinding("non-resource-url-getter", "User:foo", "Group:bar"),
newClusterRoleBinding("non-resource-url", "User:admin", "Group:admin"),
newClusterRoleBinding("non-resource-url-prefix", "User:prefixed", "Group:prefixed"),
},
shouldPass: []authorizer.Attributes{
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "foo"}, Verb: "get", Path: "/apis"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"bar"}}, Verb: "get", Path: "/apis"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "admin"}, Verb: "get", Path: "/apis"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"admin"}}, Verb: "get", Path: "/apis"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "admin"}, Verb: "watch", Path: "/apis"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"admin"}}, Verb: "watch", Path: "/apis"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "prefixed"}, Verb: "get", Path: "/apis/v1"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"prefixed"}}, Verb: "get", Path: "/apis/v1"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "prefixed"}, Verb: "get", Path: "/apis/v1/foobar"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"prefixed"}}, Verb: "get", Path: "/apis/v1/foorbar"},
},
shouldFail: []authorizer.Attributes{
// wrong verb
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "foo"}, Verb: "watch", Path: "/apis"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"bar"}}, Verb: "watch", Path: "/apis"},
// wrong path
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "foo"}, Verb: "get", Path: "/api/v1"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"bar"}}, Verb: "get", Path: "/api/v1"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "admin"}, Verb: "get", Path: "/api/v1"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"admin"}}, Verb: "get", Path: "/api/v1"},
// not covered by prefix
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "prefixed"}, Verb: "get", Path: "/api/v1"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"prefixed"}}, Verb: "get", Path: "/api/v1"},
},
},
{
// test subresource resolution
clusterRoles: []*rbac.ClusterRole{
newClusterRole("admin", newRule("*", "*", "pods", "*")),
},
roleBindings: []*rbac.RoleBinding{
newRoleBinding("ns1", "admin", bindToClusterRole, "User:admin", "Group:admins"),
},
shouldPass: []authorizer.Attributes{
&defaultAttributes{"admin", "", "get", "pods", "", "ns1", ""},
},
shouldFail: []authorizer.Attributes{
&defaultAttributes{"admin", "", "get", "pods", "status", "ns1", ""},
},
},
{
// test subresource resolution
clusterRoles: []*rbac.ClusterRole{
newClusterRole("admin",
newRule("*", "*", "pods/status", "*"),
newRule("*", "*", "*/scale", "*"),
),
},
roleBindings: []*rbac.RoleBinding{
newRoleBinding("ns1", "admin", bindToClusterRole, "User:admin", "Group:admins"),
},
shouldPass: []authorizer.Attributes{
&defaultAttributes{"admin", "", "get", "pods", "status", "ns1", ""},
&defaultAttributes{"admin", "", "get", "pods", "scale", "ns1", ""},
&defaultAttributes{"admin", "", "get", "deployments", "scale", "ns1", ""},
&defaultAttributes{"admin", "", "get", "anything", "scale", "ns1", ""},
},
shouldFail: []authorizer.Attributes{
&defaultAttributes{"admin", "", "get", "pods", "", "ns1", ""},
},
},
}
for i, tt := range tests {
ruleResolver, _ := rbacregistryvalidation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings)
a := RBACAuthorizer{ruleResolver}
for _, attr := range tt.shouldPass {
if decision, _, _ := a.Authorize(attr); decision != authorizer.DecisionAllow {
t.Errorf("case %d: incorrectly restricted %s", i, attr)
}
}
for _, attr := range tt.shouldFail {
if decision, _, _ := a.Authorize(attr); decision == authorizer.DecisionAllow {
t.Errorf("case %d: incorrectly passed %s", i, attr)
}
}
}
}
func TestRuleMatches(t *testing.T) {
tests := []struct {
name string
rule rbac.PolicyRule
requestsToExpected map[authorizer.AttributesRecord]bool
}{
{
name: "star verb, exact match other",
rule: rbac.NewRule("*").Groups("group1").Resources("resource1").RuleOrDie(),
requestsToExpected: map[authorizer.AttributesRecord]bool{
resourceRequest("verb1").Group("group1").Resource("resource1").New(): true,
resourceRequest("verb1").Group("group2").Resource("resource1").New(): false,
resourceRequest("verb1").Group("group1").Resource("resource2").New(): false,
resourceRequest("verb1").Group("group2").Resource("resource2").New(): false,
resourceRequest("verb2").Group("group1").Resource("resource1").New(): true,
resourceRequest("verb2").Group("group2").Resource("resource1").New(): false,
resourceRequest("verb2").Group("group1").Resource("resource2").New(): false,
resourceRequest("verb2").Group("group2").Resource("resource2").New(): false,
},
},
{
name: "star group, exact match other",
rule: rbac.NewRule("verb1").Groups("*").Resources("resource1").RuleOrDie(),
requestsToExpected: map[authorizer.AttributesRecord]bool{
resourceRequest("verb1").Group("group1").Resource("resource1").New(): true,
resourceRequest("verb1").Group("group2").Resource("resource1").New(): true,
resourceRequest("verb1").Group("group1").Resource("resource2").New(): false,
resourceRequest("verb1").Group("group2").Resource("resource2").New(): false,
resourceRequest("verb2").Group("group1").Resource("resource1").New(): false,
resourceRequest("verb2").Group("group2").Resource("resource1").New(): false,
resourceRequest("verb2").Group("group1").Resource("resource2").New(): false,
resourceRequest("verb2").Group("group2").Resource("resource2").New(): false,
},
},
{
name: "star resource, exact match other",
rule: rbac.NewRule("verb1").Groups("group1").Resources("*").RuleOrDie(),
requestsToExpected: map[authorizer.AttributesRecord]bool{
resourceRequest("verb1").Group("group1").Resource("resource1").New(): true,
resourceRequest("verb1").Group("group2").Resource("resource1").New(): false,
resourceRequest("verb1").Group("group1").Resource("resource2").New(): true,
resourceRequest("verb1").Group("group2").Resource("resource2").New(): false,
resourceRequest("verb2").Group("group1").Resource("resource1").New(): false,
resourceRequest("verb2").Group("group2").Resource("resource1").New(): false,
resourceRequest("verb2").Group("group1").Resource("resource2").New(): false,
resourceRequest("verb2").Group("group2").Resource("resource2").New(): false,
},
},
{
name: "tuple expansion",
rule: rbac.NewRule("verb1", "verb2").Groups("group1", "group2").Resources("resource1", "resource2").RuleOrDie(),
requestsToExpected: map[authorizer.AttributesRecord]bool{
resourceRequest("verb1").Group("group1").Resource("resource1").New(): true,
resourceRequest("verb1").Group("group2").Resource("resource1").New(): true,
resourceRequest("verb1").Group("group1").Resource("resource2").New(): true,
resourceRequest("verb1").Group("group2").Resource("resource2").New(): true,
resourceRequest("verb2").Group("group1").Resource("resource1").New(): true,
resourceRequest("verb2").Group("group2").Resource("resource1").New(): true,
resourceRequest("verb2").Group("group1").Resource("resource2").New(): true,
resourceRequest("verb2").Group("group2").Resource("resource2").New(): true,
},
},
{
name: "subresource expansion",
rule: rbac.NewRule("*").Groups("*").Resources("resource1/subresource1").RuleOrDie(),
requestsToExpected: map[authorizer.AttributesRecord]bool{
resourceRequest("verb1").Group("group1").Resource("resource1").Subresource("subresource1").New(): true,
resourceRequest("verb1").Group("group2").Resource("resource1").Subresource("subresource2").New(): false,
resourceRequest("verb1").Group("group1").Resource("resource2").Subresource("subresource1").New(): false,
resourceRequest("verb1").Group("group2").Resource("resource2").Subresource("subresource1").New(): false,
resourceRequest("verb2").Group("group1").Resource("resource1").Subresource("subresource1").New(): true,
resourceRequest("verb2").Group("group2").Resource("resource1").Subresource("subresource2").New(): false,
resourceRequest("verb2").Group("group1").Resource("resource2").Subresource("subresource1").New(): false,
resourceRequest("verb2").Group("group2").Resource("resource2").Subresource("subresource1").New(): false,
},
},
{
name: "star nonresource, exact match other",
rule: rbac.NewRule("verb1").URLs("*").RuleOrDie(),
requestsToExpected: map[authorizer.AttributesRecord]bool{
nonresourceRequest("verb1").URL("/foo").New(): true,
nonresourceRequest("verb1").URL("/foo/bar").New(): true,
nonresourceRequest("verb1").URL("/foo/baz").New(): true,
nonresourceRequest("verb1").URL("/foo/bar/one").New(): true,
nonresourceRequest("verb1").URL("/foo/baz/one").New(): true,
nonresourceRequest("verb2").URL("/foo").New(): false,
nonresourceRequest("verb2").URL("/foo/bar").New(): false,
nonresourceRequest("verb2").URL("/foo/baz").New(): false,
nonresourceRequest("verb2").URL("/foo/bar/one").New(): false,
nonresourceRequest("verb2").URL("/foo/baz/one").New(): false,
},
},
{
name: "star nonresource subpath",
rule: rbac.NewRule("verb1").URLs("/foo/*").RuleOrDie(),
requestsToExpected: map[authorizer.AttributesRecord]bool{
nonresourceRequest("verb1").URL("/foo").New(): false,
nonresourceRequest("verb1").URL("/foo/bar").New(): true,
nonresourceRequest("verb1").URL("/foo/baz").New(): true,
nonresourceRequest("verb1").URL("/foo/bar/one").New(): true,
nonresourceRequest("verb1").URL("/foo/baz/one").New(): true,
nonresourceRequest("verb1").URL("/notfoo").New(): false,
nonresourceRequest("verb1").URL("/notfoo/bar").New(): false,
nonresourceRequest("verb1").URL("/notfoo/baz").New(): false,
nonresourceRequest("verb1").URL("/notfoo/bar/one").New(): false,
nonresourceRequest("verb1").URL("/notfoo/baz/one").New(): false,
},
},
{
name: "star verb, exact nonresource",
rule: rbac.NewRule("*").URLs("/foo", "/foo/bar/one").RuleOrDie(),
requestsToExpected: map[authorizer.AttributesRecord]bool{
nonresourceRequest("verb1").URL("/foo").New(): true,
nonresourceRequest("verb1").URL("/foo/bar").New(): false,
nonresourceRequest("verb1").URL("/foo/baz").New(): false,
nonresourceRequest("verb1").URL("/foo/bar/one").New(): true,
nonresourceRequest("verb1").URL("/foo/baz/one").New(): false,
nonresourceRequest("verb2").URL("/foo").New(): true,
nonresourceRequest("verb2").URL("/foo/bar").New(): false,
nonresourceRequest("verb2").URL("/foo/baz").New(): false,
nonresourceRequest("verb2").URL("/foo/bar/one").New(): true,
nonresourceRequest("verb2").URL("/foo/baz/one").New(): false,
},
},
}
for _, tc := range tests {
for request, expected := range tc.requestsToExpected {
if e, a := expected, RuleAllows(request, &tc.rule); e != a {
t.Errorf("%q: expected %v, got %v for %v", tc.name, e, a, request)
}
}
}
}
type requestAttributeBuilder struct {
request authorizer.AttributesRecord
}
func resourceRequest(verb string) *requestAttributeBuilder {
return &requestAttributeBuilder{
request: authorizer.AttributesRecord{ResourceRequest: true, Verb: verb},
}
}
func nonresourceRequest(verb string) *requestAttributeBuilder {
return &requestAttributeBuilder{
request: authorizer.AttributesRecord{ResourceRequest: false, Verb: verb},
}
}
func (r *requestAttributeBuilder) Group(group string) *requestAttributeBuilder {
r.request.APIGroup = group
return r
}
func (r *requestAttributeBuilder) Resource(resource string) *requestAttributeBuilder {
r.request.Resource = resource
return r
}
func (r *requestAttributeBuilder) Subresource(subresource string) *requestAttributeBuilder {
r.request.Subresource = subresource
return r
}
func (r *requestAttributeBuilder) Name(name string) *requestAttributeBuilder {
r.request.Name = name
return r
}
func (r *requestAttributeBuilder) URL(url string) *requestAttributeBuilder {
r.request.Path = url
return r
}
func (r *requestAttributeBuilder) New() authorizer.AttributesRecord {
return r.request
}
func BenchmarkAuthorize(b *testing.B) {
bootstrapRoles := []rbac.ClusterRole{}
bootstrapRoles = append(bootstrapRoles, bootstrappolicy.ControllerRoles()...)
bootstrapRoles = append(bootstrapRoles, bootstrappolicy.ClusterRoles()...)
bootstrapBindings := []rbac.ClusterRoleBinding{}
bootstrapBindings = append(bootstrapBindings, bootstrappolicy.ClusterRoleBindings()...)
bootstrapBindings = append(bootstrapBindings, bootstrappolicy.ControllerRoleBindings()...)
clusterRoles := []*rbac.ClusterRole{}
for i := range bootstrapRoles {
clusterRoles = append(clusterRoles, &bootstrapRoles[i])
}
clusterRoleBindings := []*rbac.ClusterRoleBinding{}
for i := range bootstrapBindings {
clusterRoleBindings = append(clusterRoleBindings, &bootstrapBindings[i])
}
_, resolver := rbacregistryvalidation.NewTestRuleResolver(nil, nil, clusterRoles, clusterRoleBindings)
authz := New(resolver, resolver, resolver, resolver)
nodeUser := &user.DefaultInfo{Name: "system:node:node1", Groups: []string{"system:nodes", "system:authenticated"}}
requests := []struct {
name string
attrs authorizer.Attributes
}{
{
"allow list pods",
authorizer.AttributesRecord{
ResourceRequest: true,
User: nodeUser,
Verb: "list",
Resource: "pods",
Subresource: "",
Name: "",
Namespace: "",
APIGroup: "",
APIVersion: "v1",
},
},
{
"allow update pods/status",
authorizer.AttributesRecord{
ResourceRequest: true,
User: nodeUser,
Verb: "update",
Resource: "pods",
Subresource: "status",
Name: "mypods",
Namespace: "myns",
APIGroup: "",
APIVersion: "v1",
},
},
{
"forbid educate dolphins",
authorizer.AttributesRecord{
ResourceRequest: true,
User: nodeUser,
Verb: "educate",
Resource: "dolphins",
Subresource: "",
Name: "",
Namespace: "",
APIGroup: "",
APIVersion: "v1",
},
},
}
b.ResetTimer()
for _, request := range requests {
b.Run(request.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
authz.Authorize(request.attrs)
}
})
}
}

View File

@ -0,0 +1,123 @@
/*
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 rbac implements the authorizer.Authorizer interface using roles base access control.
package rbac
import (
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/pkg/apis/rbac"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
)
type RoleToRuleMapper interface {
// GetRoleReferenceRules attempts to resolve the role reference of a RoleBinding or ClusterRoleBinding. The passed namespace should be the namespace
// of the role binding, the empty string if a cluster role binding.
GetRoleReferenceRules(roleRef rbac.RoleRef, namespace string) ([]rbac.PolicyRule, error)
}
type SubjectLocator interface {
AllowedSubjects(attributes authorizer.Attributes) ([]rbac.Subject, error)
}
var _ = SubjectLocator(&SubjectAccessEvaluator{})
type SubjectAccessEvaluator struct {
superUser string
roleBindingLister rbacregistryvalidation.RoleBindingLister
clusterRoleBindingLister rbacregistryvalidation.ClusterRoleBindingLister
roleToRuleMapper RoleToRuleMapper
}
func NewSubjectAccessEvaluator(roles rbacregistryvalidation.RoleGetter, roleBindings rbacregistryvalidation.RoleBindingLister, clusterRoles rbacregistryvalidation.ClusterRoleGetter, clusterRoleBindings rbacregistryvalidation.ClusterRoleBindingLister, superUser string) *SubjectAccessEvaluator {
subjectLocator := &SubjectAccessEvaluator{
superUser: superUser,
roleBindingLister: roleBindings,
clusterRoleBindingLister: clusterRoleBindings,
roleToRuleMapper: rbacregistryvalidation.NewDefaultRuleResolver(
roles, roleBindings, clusterRoles, clusterRoleBindings,
),
}
return subjectLocator
}
// AllowedSubjects returns the subjects that can perform an action and any errors encountered while computing the list.
// It is possible to have both subjects and errors returned if some rolebindings couldn't be resolved, but others could be.
func (r *SubjectAccessEvaluator) AllowedSubjects(requestAttributes authorizer.Attributes) ([]rbac.Subject, error) {
subjects := []rbac.Subject{{Kind: rbac.GroupKind, APIGroup: rbac.GroupName, Name: user.SystemPrivilegedGroup}}
if len(r.superUser) > 0 {
subjects = append(subjects, rbac.Subject{Kind: rbac.UserKind, APIGroup: rbac.GroupName, Name: r.superUser})
}
errorlist := []error{}
if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(); err != nil {
errorlist = append(errorlist, err)
} else {
for _, clusterRoleBinding := range clusterRoleBindings {
rules, err := r.roleToRuleMapper.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "")
if err != nil {
// if we have an error, just keep track of it and keep processing. Since rules are additive,
// missing a reference is bad, but we can continue with other rolebindings and still have a list
// that does not contain any invalid values
errorlist = append(errorlist, err)
}
if RulesAllow(requestAttributes, rules...) {
subjects = append(subjects, clusterRoleBinding.Subjects...)
}
}
}
if namespace := requestAttributes.GetNamespace(); len(namespace) > 0 {
if roleBindings, err := r.roleBindingLister.ListRoleBindings(namespace); err != nil {
errorlist = append(errorlist, err)
} else {
for _, roleBinding := range roleBindings {
rules, err := r.roleToRuleMapper.GetRoleReferenceRules(roleBinding.RoleRef, namespace)
if err != nil {
// if we have an error, just keep track of it and keep processing. Since rules are additive,
// missing a reference is bad, but we can continue with other rolebindings and still have a list
// that does not contain any invalid values
errorlist = append(errorlist, err)
}
if RulesAllow(requestAttributes, rules...) {
subjects = append(subjects, roleBinding.Subjects...)
}
}
}
}
dedupedSubjects := []rbac.Subject{}
for _, subject := range subjects {
found := false
for _, curr := range dedupedSubjects {
if curr == subject {
found = true
break
}
}
if !found {
dedupedSubjects = append(dedupedSubjects, subject)
}
}
return subjects, utilerrors.NewAggregate(errorlist)
}

View File

@ -0,0 +1,151 @@
/*
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 rbac
import (
"reflect"
"testing"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/pkg/apis/rbac"
rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
)
func TestSubjectLocator(t *testing.T) {
type actionToSubjects struct {
action authorizer.Attributes
subjects []rbac.Subject
}
tests := []struct {
name string
roles []*rbac.Role
roleBindings []*rbac.RoleBinding
clusterRoles []*rbac.ClusterRole
clusterRoleBindings []*rbac.ClusterRoleBinding
superUser string
actionsToSubjects []actionToSubjects
}{
{
name: "no super user, star matches star",
clusterRoles: []*rbac.ClusterRole{
newClusterRole("admin", newRule("*", "*", "*", "*")),
},
clusterRoleBindings: []*rbac.ClusterRoleBinding{
newClusterRoleBinding("admin", "User:super-admin", "Group:super-admins"),
},
roleBindings: []*rbac.RoleBinding{
newRoleBinding("ns1", "admin", bindToClusterRole, "User:admin", "Group:admins"),
},
actionsToSubjects: []actionToSubjects{
{
&defaultAttributes{"", "", "get", "Pods", "", "ns1", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, APIGroup: rbac.GroupName, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, APIGroup: rbac.GroupName, Name: "super-admin"},
{Kind: rbac.GroupKind, APIGroup: rbac.GroupName, Name: "super-admins"},
{Kind: rbac.UserKind, APIGroup: rbac.GroupName, Name: "admin"},
{Kind: rbac.GroupKind, APIGroup: rbac.GroupName, Name: "admins"},
},
},
{
// cluster role matches star in namespace
&defaultAttributes{"", "", "*", "Pods", "", "*", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, APIGroup: rbac.GroupName, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, APIGroup: rbac.GroupName, Name: "super-admin"},
{Kind: rbac.GroupKind, APIGroup: rbac.GroupName, Name: "super-admins"},
},
},
{
// empty ns
&defaultAttributes{"", "", "*", "Pods", "", "", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, APIGroup: rbac.GroupName, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, APIGroup: rbac.GroupName, Name: "super-admin"},
{Kind: rbac.GroupKind, APIGroup: rbac.GroupName, Name: "super-admins"},
},
},
},
},
{
name: "super user, local roles work",
superUser: "foo",
clusterRoles: []*rbac.ClusterRole{
newClusterRole("admin", newRule("*", "*", "*", "*")),
},
clusterRoleBindings: []*rbac.ClusterRoleBinding{
newClusterRoleBinding("admin", "User:super-admin", "Group:super-admins"),
},
roles: []*rbac.Role{
newRole("admin", "ns1", newRule("get", "*", "Pods", "*")),
},
roleBindings: []*rbac.RoleBinding{
newRoleBinding("ns1", "admin", bindToRole, "User:admin", "Group:admins"),
},
actionsToSubjects: []actionToSubjects{
{
&defaultAttributes{"", "", "get", "Pods", "", "ns1", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, APIGroup: rbac.GroupName, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, APIGroup: rbac.GroupName, Name: "foo"},
{Kind: rbac.UserKind, APIGroup: rbac.GroupName, Name: "super-admin"},
{Kind: rbac.GroupKind, APIGroup: rbac.GroupName, Name: "super-admins"},
{Kind: rbac.UserKind, APIGroup: rbac.GroupName, Name: "admin"},
{Kind: rbac.GroupKind, APIGroup: rbac.GroupName, Name: "admins"},
},
},
{
// verb matchies correctly
&defaultAttributes{"", "", "create", "Pods", "", "ns1", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, APIGroup: rbac.GroupName, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, APIGroup: rbac.GroupName, Name: "foo"},
{Kind: rbac.UserKind, APIGroup: rbac.GroupName, Name: "super-admin"},
{Kind: rbac.GroupKind, APIGroup: rbac.GroupName, Name: "super-admins"},
},
},
{
// binding only works in correct ns
&defaultAttributes{"", "", "get", "Pods", "", "ns2", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, APIGroup: rbac.GroupName, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, APIGroup: rbac.GroupName, Name: "foo"},
{Kind: rbac.UserKind, APIGroup: rbac.GroupName, Name: "super-admin"},
{Kind: rbac.GroupKind, APIGroup: rbac.GroupName, Name: "super-admins"},
},
},
},
},
}
for _, tt := range tests {
ruleResolver, lister := rbacregistryvalidation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings)
a := SubjectAccessEvaluator{tt.superUser, lister, lister, ruleResolver}
for i, action := range tt.actionsToSubjects {
actualSubjects, err := a.AllowedSubjects(action.action)
if err != nil {
t.Errorf("case %q %d: error %v", tt.name, i, err)
}
if !reflect.DeepEqual(actualSubjects, action.subjects) {
t.Errorf("case %q %d: expected\n%v\nactual\n%v", tt.name, i, action.subjects, actualSubjects)
}
}
}
}

18
vendor/k8s.io/kubernetes/plugin/pkg/auth/doc.go generated vendored Normal file
View File

@ -0,0 +1,18 @@
/*
Copyright 2014 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 auth contains implementations for interfaces in the pkg/auth package
package auth // import "k8s.io/kubernetes/plugin/pkg/auth"