Commit 2750c778 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #33294 from deads2k/rbac-08-compact-init

Automatic merge from submit-queue start creating controller SA roles. start with just one This creates a clusterrole for the replicationcontroller controller. It also streamlines the rule creation code and I'll use this role as practice for wiring up RBAC rules. @kubernetes/sig-auth @ericchiang Jordan is ooto, mind taking a look?
parents 69083bcf b330b0a2
...@@ -17,6 +17,7 @@ limitations under the License. ...@@ -17,6 +17,7 @@ limitations under the License.
package rbac package rbac
import ( import (
"fmt"
"strings" "strings"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
...@@ -94,3 +95,70 @@ func NonResourceURLMatches(rule PolicyRule, requestedURL string) bool { ...@@ -94,3 +95,70 @@ func NonResourceURLMatches(rule PolicyRule, requestedURL string) bool {
return false 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
}
...@@ -122,7 +122,7 @@ func newPostStartHook(directClusterRoleAccess *clusterroleetcd.REST) genericapis ...@@ -122,7 +122,7 @@ func newPostStartHook(directClusterRoleAccess *clusterroleetcd.REST) genericapis
return nil return nil
} }
for _, clusterRole := range bootstrappolicy.ClusterRoles() { for _, clusterRole := range append(bootstrappolicy.ClusterRoles(), bootstrappolicy.ControllerRoles()...) {
if _, err := directClusterRoleAccess.Create(ctx, &clusterRole); err != nil { if _, err := directClusterRoleAccess.Create(ctx, &clusterRole); err != nil {
// don't fail on failures, try to create as many as you can // don't fail on failures, try to create as many as you can
utilruntime.HandleError(fmt.Errorf("unable to initialize clusterroles: %v", err)) utilruntime.HandleError(fmt.Errorf("unable to initialize clusterroles: %v", err))
......
/*
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
}
...@@ -18,18 +18,24 @@ package bootstrappolicy ...@@ -18,18 +18,24 @@ package bootstrappolicy
import ( import (
"k8s.io/kubernetes/pkg/api" "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 // ClusterRoles returns the cluster roles to bootstrap an API server with
func ClusterRoles() []rbacapi.ClusterRole { func ClusterRoles() []rbac.ClusterRole {
return []rbacapi.ClusterRole{ return []rbac.ClusterRole{
// TODO update the expression of these rules to match openshift for ease of inspection
{ {
ObjectMeta: api.ObjectMeta{Name: "cluster-admin"}, ObjectMeta: api.ObjectMeta{Name: "cluster-admin"},
Rules: []rbacapi.PolicyRule{ Rules: []rbac.PolicyRule{
{Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}}, rbac.NewRule("*").Groups("*").Resources("*").RuleOrDie(),
{Verbs: []string{"*"}, NonResourceURLs: []string{"*"}}, rbac.NewRule("*").URLs("*").RuleOrDie(),
}, },
}, },
} }
......
...@@ -19,8 +19,6 @@ limitations under the License. ...@@ -19,8 +19,6 @@ limitations under the License.
package auth package auth
import ( import (
"bytes"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
...@@ -35,10 +33,7 @@ import ( ...@@ -35,10 +33,7 @@ import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi" "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" 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"
"k8s.io/kubernetes/pkg/auth/authenticator/bearertoken" "k8s.io/kubernetes/pkg/auth/authenticator/bearertoken"
"k8s.io/kubernetes/pkg/auth/authorizer" "k8s.io/kubernetes/pkg/auth/authorizer"
...@@ -80,6 +75,12 @@ func clientForUser(user string) *http.Client { ...@@ -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 { func newRBACAuthorizer(t *testing.T, superUser string, config *master.Config) authorizer.Authorizer {
newRESTOptions := func(resource string) generic.RESTOptions { newRESTOptions := func(resource string) generic.RESTOptions {
storageConfig, err := config.StorageFactory.NewConfig(rbacapi.Resource(resource)) storageConfig, err := config.StorageFactory.NewConfig(rbacapi.Resource(resource))
...@@ -98,72 +99,41 @@ func newRBACAuthorizer(t *testing.T, superUser string, config *master.Config) au ...@@ -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. // bootstrapRoles are a set of RBAC roles which will be populated before the test.
type bootstrapRoles struct { type bootstrapRoles struct {
roles []v1alpha1.Role roles []rbacapi.Role
roleBindings []v1alpha1.RoleBinding roleBindings []rbacapi.RoleBinding
clusterRoles []v1alpha1.ClusterRole clusterRoles []rbacapi.ClusterRole
clusterRoleBindings []v1alpha1.ClusterRoleBinding clusterRoleBindings []rbacapi.ClusterRoleBinding
} }
// bootstrap uses the provided client to create the bootstrap roles and role bindings. // bootstrap uses the provided client to create the bootstrap roles and role bindings.
// //
// client should be authenticated as the RBAC super user. // client should be authenticated as the RBAC super user.
func (b bootstrapRoles) bootstrap(client *http.Client, serverURL string) error { func (b bootstrapRoles) bootstrap(client clientset.Interface) error {
newReq := func(resource, name, namespace string, v interface{}) *http.Request { for _, r := range b.clusterRoles {
body, err := json.Marshal(v) _, err := client.Rbac().ClusterRoles().Create(&r)
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 { if err != nil {
panic(err) return fmt.Errorf("failed to make request: %v", err)
} }
req.Header.Set("Content-Type", "application/json")
req.ContentLength = int64(len(body))
return req
}
apiVersion := v1alpha1.SchemeGroupVersion.String()
var requests []*http.Request
for _, r := range b.clusterRoles {
r.TypeMeta = unversioned.TypeMeta{Kind: "ClusterRole", APIVersion: apiVersion}
requests = append(requests, newReq("clusterroles", r.Name, r.Namespace, r))
} }
for _, r := range b.roles { for _, r := range b.roles {
r.TypeMeta = unversioned.TypeMeta{Kind: "Role", APIVersion: apiVersion} _, err := client.Rbac().Roles(r.Namespace).Create(&r)
requests = append(requests, newReq("roles", r.Name, r.Namespace, r)) if err != nil {
return fmt.Errorf("failed to make request: %v", err)
}
} }
for _, r := range b.clusterRoleBindings { for _, r := range b.clusterRoleBindings {
r.TypeMeta = unversioned.TypeMeta{Kind: "ClusterRoleBinding", APIVersion: apiVersion} _, err := client.Rbac().ClusterRoleBindings().Create(&r)
requests = append(requests, newReq("clusterrolebindings", r.Name, r.Namespace, r)) if err != nil {
return fmt.Errorf("failed to make request: %v", err)
}
} }
for _, r := range b.roleBindings { for _, r := range b.roleBindings {
r.TypeMeta = unversioned.TypeMeta{Kind: "RoleBinding", APIVersion: apiVersion} _, err := client.Rbac().RoleBindings(r.Namespace).Create(&r)
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
}()
if err != nil { if err != nil {
return err return fmt.Errorf("failed to make request: %v", err)
} }
} }
return nil return nil
} }
...@@ -263,23 +233,9 @@ var ( ...@@ -263,23 +233,9 @@ var (
// Declare some PolicyRules beforehand. // Declare some PolicyRules beforehand.
var ( var (
ruleAllowAll = v1alpha1.PolicyRule{ ruleAllowAll = rbacapi.NewRule("*").Groups("*").Resources("*").RuleOrDie()
Verbs: []string{"*"}, ruleReadPods = rbacapi.NewRule("list", "get", "watch").Groups("").Resources("pods").RuleOrDie()
APIGroups: []string{"*"}, ruleWriteJobs = rbacapi.NewRule("*").Groups("batch").Resources("*").RuleOrDie()
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{"*"},
}
) )
func TestRBAC(t *testing.T) { func TestRBAC(t *testing.T) {
...@@ -292,23 +248,23 @@ func TestRBAC(t *testing.T) { ...@@ -292,23 +248,23 @@ func TestRBAC(t *testing.T) {
}{ }{
{ {
bootstrapRoles: bootstrapRoles{ bootstrapRoles: bootstrapRoles{
clusterRoles: []v1alpha1.ClusterRole{ clusterRoles: []rbacapi.ClusterRole{
{ {
ObjectMeta: v1.ObjectMeta{Name: "allow-all"}, ObjectMeta: api.ObjectMeta{Name: "allow-all"},
Rules: []v1alpha1.PolicyRule{ruleAllowAll}, Rules: []rbacapi.PolicyRule{ruleAllowAll},
}, },
{ {
ObjectMeta: v1.ObjectMeta{Name: "read-pods"}, ObjectMeta: api.ObjectMeta{Name: "read-pods"},
Rules: []v1alpha1.PolicyRule{ruleReadPods}, Rules: []rbacapi.PolicyRule{ruleReadPods},
}, },
}, },
clusterRoleBindings: []v1alpha1.ClusterRoleBinding{ clusterRoleBindings: []rbacapi.ClusterRoleBinding{
{ {
ObjectMeta: v1.ObjectMeta{Name: "read-pods"}, ObjectMeta: api.ObjectMeta{Name: "read-pods"},
Subjects: []v1alpha1.Subject{ Subjects: []rbacapi.Subject{
{Kind: "User", Name: "pod-reader"}, {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) { ...@@ -330,24 +286,24 @@ func TestRBAC(t *testing.T) {
}, },
{ {
bootstrapRoles: bootstrapRoles{ bootstrapRoles: bootstrapRoles{
clusterRoles: []v1alpha1.ClusterRole{ clusterRoles: []rbacapi.ClusterRole{
{ {
ObjectMeta: v1.ObjectMeta{Name: "write-jobs"}, ObjectMeta: api.ObjectMeta{Name: "write-jobs"},
Rules: []v1alpha1.PolicyRule{ruleWriteJobs}, Rules: []rbacapi.PolicyRule{ruleWriteJobs},
}, },
}, },
clusterRoleBindings: []v1alpha1.ClusterRoleBinding{ clusterRoleBindings: []rbacapi.ClusterRoleBinding{
{ {
ObjectMeta: v1.ObjectMeta{Name: "write-jobs"}, ObjectMeta: api.ObjectMeta{Name: "write-jobs"},
Subjects: []v1alpha1.Subject{{Kind: "User", Name: "job-writer"}}, Subjects: []rbacapi.Subject{{Kind: "User", Name: "job-writer"}},
RoleRef: v1alpha1.RoleRef{Kind: "ClusterRole", Name: "write-jobs"}, RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "write-jobs"},
}, },
}, },
roleBindings: []v1alpha1.RoleBinding{ roleBindings: []rbacapi.RoleBinding{
{ {
ObjectMeta: v1.ObjectMeta{Name: "write-jobs", Namespace: "job-namespace"}, ObjectMeta: api.ObjectMeta{Name: "write-jobs", Namespace: "job-namespace"},
Subjects: []v1alpha1.Subject{{Kind: "User", Name: "job-writer-namespace"}}, Subjects: []rbacapi.Subject{{Kind: "User", Name: "job-writer-namespace"}},
RoleRef: v1alpha1.RoleRef{Kind: "ClusterRole", Name: "write-jobs"}, RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "write-jobs"},
}, },
}, },
}, },
...@@ -388,8 +344,10 @@ func TestRBAC(t *testing.T) { ...@@ -388,8 +344,10 @@ func TestRBAC(t *testing.T) {
_, s := framework.RunAMaster(masterConfig) _, s := framework.RunAMaster(masterConfig)
defer s.Close() 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. // 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) t.Errorf("case %d: failed to apply initial roles: %v", i, err)
continue continue
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment