start creating controller SA roles. start with just one

pull/6/head
deads2k 2016-09-21 13:51:44 -04:00
parent 4219d9584b
commit b330b0a220
5 changed files with 204 additions and 106 deletions

View File

@ -17,6 +17,7 @@ limitations under the License.
package rbac
import (
"fmt"
"strings"
"k8s.io/kubernetes/pkg/api/unversioned"
@ -94,3 +95,70 @@ func NonResourceURLMatches(rule PolicyRule, requestedURL string) bool {
return false
}
// +k8s:deepcopy-gen=false
// PolicyRuleBuilder let's us attach methods. A no-no for API types.
// We use it to construct rules in code. It's more compact than trying to write them
// out in a literal and allows us to perform some basic checking during construction
type PolicyRuleBuilder struct {
PolicyRule PolicyRule
}
func NewRule(verbs ...string) *PolicyRuleBuilder {
return &PolicyRuleBuilder{
PolicyRule: PolicyRule{Verbs: verbs},
}
}
func (r *PolicyRuleBuilder) Groups(groups ...string) *PolicyRuleBuilder {
r.PolicyRule.APIGroups = append(r.PolicyRule.APIGroups, groups...)
return r
}
func (r *PolicyRuleBuilder) Resources(resources ...string) *PolicyRuleBuilder {
r.PolicyRule.Resources = append(r.PolicyRule.Resources, resources...)
return r
}
func (r *PolicyRuleBuilder) Names(names ...string) *PolicyRuleBuilder {
r.PolicyRule.ResourceNames = append(r.PolicyRule.ResourceNames, names...)
return r
}
func (r *PolicyRuleBuilder) URLs(urls ...string) *PolicyRuleBuilder {
r.PolicyRule.NonResourceURLs = append(r.PolicyRule.NonResourceURLs, urls...)
return r
}
func (r *PolicyRuleBuilder) RuleOrDie() PolicyRule {
ret, err := r.Rule()
if err != nil {
panic(err)
}
return ret
}
func (r *PolicyRuleBuilder) Rule() (PolicyRule, error) {
if len(r.PolicyRule.Verbs) == 0 {
return PolicyRule{}, fmt.Errorf("verbs are required: %#v", r.PolicyRule)
}
switch {
case len(r.PolicyRule.NonResourceURLs) > 0:
if len(r.PolicyRule.APIGroups) != 0 || len(r.PolicyRule.Resources) != 0 || len(r.PolicyRule.ResourceNames) != 0 {
return PolicyRule{}, fmt.Errorf("non-resource rule may not have apiGroups, resources, or resourceNames: %#v", r.PolicyRule)
}
case len(r.PolicyRule.Resources) > 0:
if len(r.PolicyRule.NonResourceURLs) != 0 {
return PolicyRule{}, fmt.Errorf("resource rule may not have nonResourceURLs: %#v", r.PolicyRule)
}
if len(r.PolicyRule.APIGroups) == 0 {
// this a common bug
return PolicyRule{}, fmt.Errorf("resource rule must have apiGroups: %#v", r.PolicyRule)
}
default:
return PolicyRule{}, fmt.Errorf("a rule must have either nonResourceURLs or resources: %#v", r.PolicyRule)
}
return r.PolicyRule, nil
}

View File

@ -122,7 +122,7 @@ func newPostStartHook(directClusterRoleAccess *clusterroleetcd.REST) genericapis
return nil
}
for _, clusterRole := range bootstrappolicy.ClusterRoles() {
for _, clusterRole := range append(bootstrappolicy.ClusterRoles(), bootstrappolicy.ControllerRoles()...) {
if _, err := directClusterRoleAccess.Create(ctx, &clusterRole); err != nil {
// don't fail on failures, try to create as many as you can
utilruntime.HandleError(fmt.Errorf("unable to initialize clusterroles: %v", err))

View File

@ -0,0 +1,66 @@
/*
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"
"k8s.io/kubernetes/pkg/api"
rbac "k8s.io/kubernetes/pkg/apis/rbac"
)
var (
// controllerRoles is a slice of roles used for controllers
controllerRoles = []rbac.ClusterRole{}
)
func addControllerRole(role rbac.ClusterRole) {
if !strings.HasPrefix(role.Name, "system:controller:") {
glog.Fatalf(`role %q must start with "system:controller:"`, role.Name)
}
for _, existingRole := range controllerRoles {
if role.Name == existingRole.Name {
glog.Fatalf("role %q was already registered", role.Name)
}
}
controllerRoles = append(controllerRoles, role)
}
func eventsRule() rbac.PolicyRule {
return rbac.NewRule("create", "update", "patch").Groups(legacyGroup).Resources("events").RuleOrDie()
}
func init() {
addControllerRole(rbac.ClusterRole{
ObjectMeta: api.ObjectMeta{Name: "system:controller:replication-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "update").Groups(legacyGroup).Resources("replicationcontrollers").RuleOrDie(),
rbac.NewRule("update").Groups(legacyGroup).Resources("replicationcontrollers/status").RuleOrDie(),
rbac.NewRule("list", "watch", "create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
eventsRule(),
},
})
}
// ControllerRoles returns the cluster roles used by controllers
func ControllerRoles() []rbac.ClusterRole {
return controllerRoles
}

View File

@ -18,18 +18,24 @@ package bootstrappolicy
import (
"k8s.io/kubernetes/pkg/api"
rbacapi "k8s.io/kubernetes/pkg/apis/rbac"
rbac "k8s.io/kubernetes/pkg/apis/rbac"
)
var (
readWrite = []string{"get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"}
read = []string{"get", "list", "watch"}
legacyGroup = ""
)
// ClusterRoles returns the cluster roles to bootstrap an API server with
func ClusterRoles() []rbacapi.ClusterRole {
return []rbacapi.ClusterRole{
// TODO update the expression of these rules to match openshift for ease of inspection
func ClusterRoles() []rbac.ClusterRole {
return []rbac.ClusterRole{
{
ObjectMeta: api.ObjectMeta{Name: "cluster-admin"},
Rules: []rbacapi.PolicyRule{
{Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}},
{Verbs: []string{"*"}, NonResourceURLs: []string{"*"}},
Rules: []rbac.PolicyRule{
rbac.NewRule("*").Groups("*").Resources("*").RuleOrDie(),
rbac.NewRule("*").URLs("*").RuleOrDie(),
},
},
}

View File

@ -19,8 +19,6 @@ limitations under the License.
package auth
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
@ -35,10 +33,7 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/v1"
rbacapi "k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/v1alpha1"
"k8s.io/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/authenticator/bearertoken"
"k8s.io/kubernetes/pkg/auth/authorizer"
@ -80,6 +75,12 @@ func clientForUser(user string) *http.Client {
}
}
func clientsetForUser(user string, config *restclient.Config) clientset.Interface {
configCopy := *config
configCopy.BearerToken = user
return clientset.NewForConfigOrDie(&configCopy)
}
func newRBACAuthorizer(t *testing.T, superUser string, config *master.Config) authorizer.Authorizer {
newRESTOptions := func(resource string) generic.RESTOptions {
storageConfig, err := config.StorageFactory.NewConfig(rbacapi.Resource(resource))
@ -98,72 +99,41 @@ func newRBACAuthorizer(t *testing.T, superUser string, config *master.Config) au
// bootstrapRoles are a set of RBAC roles which will be populated before the test.
type bootstrapRoles struct {
roles []v1alpha1.Role
roleBindings []v1alpha1.RoleBinding
clusterRoles []v1alpha1.ClusterRole
clusterRoleBindings []v1alpha1.ClusterRoleBinding
roles []rbacapi.Role
roleBindings []rbacapi.RoleBinding
clusterRoles []rbacapi.ClusterRole
clusterRoleBindings []rbacapi.ClusterRoleBinding
}
// bootstrap uses the provided client to create the bootstrap roles and role bindings.
//
// client should be authenticated as the RBAC super user.
func (b bootstrapRoles) bootstrap(client *http.Client, serverURL string) error {
newReq := func(resource, name, namespace string, v interface{}) *http.Request {
body, err := json.Marshal(v)
if err != nil {
panic(err)
}
path := testapi.Rbac.ResourcePath(resource, namespace, name)
req, err := http.NewRequest("PUT", serverURL+path, bytes.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
req.ContentLength = int64(len(body))
return req
}
apiVersion := v1alpha1.SchemeGroupVersion.String()
var requests []*http.Request
func (b bootstrapRoles) bootstrap(client clientset.Interface) error {
for _, r := range b.clusterRoles {
r.TypeMeta = unversioned.TypeMeta{Kind: "ClusterRole", APIVersion: apiVersion}
requests = append(requests, newReq("clusterroles", r.Name, r.Namespace, r))
_, err := client.Rbac().ClusterRoles().Create(&r)
if err != nil {
return fmt.Errorf("failed to make request: %v", err)
}
}
for _, r := range b.roles {
r.TypeMeta = unversioned.TypeMeta{Kind: "Role", APIVersion: apiVersion}
requests = append(requests, newReq("roles", r.Name, r.Namespace, r))
}
for _, r := range b.clusterRoleBindings {
r.TypeMeta = unversioned.TypeMeta{Kind: "ClusterRoleBinding", APIVersion: apiVersion}
requests = append(requests, newReq("clusterrolebindings", r.Name, r.Namespace, r))
}
for _, r := range b.roleBindings {
r.TypeMeta = unversioned.TypeMeta{Kind: "RoleBinding", APIVersion: apiVersion}
requests = append(requests, newReq("rolebindings", r.Name, r.Namespace, r))
}
for _, req := range requests {
err := func() error {
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("failed to make request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read body: %v", err)
}
return fmt.Errorf("POST %s: expected %d got %s\n%s", req.URL, resp.Status, body)
}
return nil
}()
_, err := client.Rbac().Roles(r.Namespace).Create(&r)
if err != nil {
return err
return fmt.Errorf("failed to make request: %v", err)
}
}
for _, r := range b.clusterRoleBindings {
_, err := client.Rbac().ClusterRoleBindings().Create(&r)
if err != nil {
return fmt.Errorf("failed to make request: %v", err)
}
}
for _, r := range b.roleBindings {
_, err := client.Rbac().RoleBindings(r.Namespace).Create(&r)
if err != nil {
return fmt.Errorf("failed to make request: %v", err)
}
}
return nil
}
@ -263,23 +233,9 @@ var (
// Declare some PolicyRules beforehand.
var (
ruleAllowAll = v1alpha1.PolicyRule{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"*"},
}
ruleReadPods = v1alpha1.PolicyRule{
Verbs: []string{"list", "get", "watch"},
APIGroups: []string{""},
Resources: []string{"pods"},
}
ruleWriteJobs = v1alpha1.PolicyRule{
Verbs: []string{"*"},
APIGroups: []string{"batch"},
Resources: []string{"*"},
}
ruleAllowAll = rbacapi.NewRule("*").Groups("*").Resources("*").RuleOrDie()
ruleReadPods = rbacapi.NewRule("list", "get", "watch").Groups("").Resources("pods").RuleOrDie()
ruleWriteJobs = rbacapi.NewRule("*").Groups("batch").Resources("*").RuleOrDie()
)
func TestRBAC(t *testing.T) {
@ -292,23 +248,23 @@ func TestRBAC(t *testing.T) {
}{
{
bootstrapRoles: bootstrapRoles{
clusterRoles: []v1alpha1.ClusterRole{
clusterRoles: []rbacapi.ClusterRole{
{
ObjectMeta: v1.ObjectMeta{Name: "allow-all"},
Rules: []v1alpha1.PolicyRule{ruleAllowAll},
ObjectMeta: api.ObjectMeta{Name: "allow-all"},
Rules: []rbacapi.PolicyRule{ruleAllowAll},
},
{
ObjectMeta: v1.ObjectMeta{Name: "read-pods"},
Rules: []v1alpha1.PolicyRule{ruleReadPods},
ObjectMeta: api.ObjectMeta{Name: "read-pods"},
Rules: []rbacapi.PolicyRule{ruleReadPods},
},
},
clusterRoleBindings: []v1alpha1.ClusterRoleBinding{
clusterRoleBindings: []rbacapi.ClusterRoleBinding{
{
ObjectMeta: v1.ObjectMeta{Name: "read-pods"},
Subjects: []v1alpha1.Subject{
ObjectMeta: api.ObjectMeta{Name: "read-pods"},
Subjects: []rbacapi.Subject{
{Kind: "User", Name: "pod-reader"},
},
RoleRef: v1alpha1.RoleRef{Kind: "ClusterRole", Name: "read-pods"},
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "read-pods"},
},
},
},
@ -330,24 +286,24 @@ func TestRBAC(t *testing.T) {
},
{
bootstrapRoles: bootstrapRoles{
clusterRoles: []v1alpha1.ClusterRole{
clusterRoles: []rbacapi.ClusterRole{
{
ObjectMeta: v1.ObjectMeta{Name: "write-jobs"},
Rules: []v1alpha1.PolicyRule{ruleWriteJobs},
ObjectMeta: api.ObjectMeta{Name: "write-jobs"},
Rules: []rbacapi.PolicyRule{ruleWriteJobs},
},
},
clusterRoleBindings: []v1alpha1.ClusterRoleBinding{
clusterRoleBindings: []rbacapi.ClusterRoleBinding{
{
ObjectMeta: v1.ObjectMeta{Name: "write-jobs"},
Subjects: []v1alpha1.Subject{{Kind: "User", Name: "job-writer"}},
RoleRef: v1alpha1.RoleRef{Kind: "ClusterRole", Name: "write-jobs"},
ObjectMeta: api.ObjectMeta{Name: "write-jobs"},
Subjects: []rbacapi.Subject{{Kind: "User", Name: "job-writer"}},
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "write-jobs"},
},
},
roleBindings: []v1alpha1.RoleBinding{
roleBindings: []rbacapi.RoleBinding{
{
ObjectMeta: v1.ObjectMeta{Name: "write-jobs", Namespace: "job-namespace"},
Subjects: []v1alpha1.Subject{{Kind: "User", Name: "job-writer-namespace"}},
RoleRef: v1alpha1.RoleRef{Kind: "ClusterRole", Name: "write-jobs"},
ObjectMeta: api.ObjectMeta{Name: "write-jobs", Namespace: "job-namespace"},
Subjects: []rbacapi.Subject{{Kind: "User", Name: "job-writer-namespace"}},
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "write-jobs"},
},
},
},
@ -388,8 +344,10 @@ func TestRBAC(t *testing.T) {
_, s := framework.RunAMaster(masterConfig)
defer s.Close()
clientConfig := &restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{NegotiatedSerializer: api.Codecs}}
// Bootstrap the API Server with the test case's initial roles.
if err := tc.bootstrapRoles.bootstrap(clientForUser(superUser), s.URL); err != nil {
if err := tc.bootstrapRoles.bootstrap(clientsetForUser(superUser, clientConfig)); err != nil {
t.Errorf("case %d: failed to apply initial roles: %v", i, err)
continue
}