Merge pull request #41982 from deads2k/agg-18-ca-permissions

Automatic merge from submit-queue

Add namespaced role to inspect particular configmap for delegated authentication

Builds on https://github.com/kubernetes/kubernetes/pull/41814 and https://github.com/kubernetes/kubernetes/pull/41922 (those are already lgtm'ed) with the ultimate goal of making an extension API server zero-config for "normal" authentication cases.

This part creates a namespace role in `kube-system` that can *only* look the configmap which gives the delegated authentication check.  When a cluster-admin grants the SA running the extension API server the power to run delegated authentication checks, he should also bind this role in this namespace.

@sttts Should we add a flag to aggregated API servers to indicate they want to look this up so they can crashloop on startup?  The alternative is sometimes having it and sometimes not.  I guess we could try to key on explicit "disable front-proxy" which may make more sense.

@kubernetes/sig-api-machinery-misc 

@ncdc I spoke to @liggitt about this before he left and he was ok in concept.  Can you take a look at the details?
This commit is contained in:
Kubernetes Submit Queue 2017-02-26 12:12:49 -08:00 committed by GitHub
commit 452420484c
10 changed files with 264 additions and 29 deletions

View File

@ -11,8 +11,8 @@ load(
go_test(
name = "go_default_test",
srcs = [
"reconcile_clusterrole_test.go",
"reconcile_clusterrolebindings_test.go",
"reconcile_role_test.go",
],
library = ":go_default_library",
tags = ["automanaged"],
@ -26,8 +26,9 @@ go_test(
go_library(
name = "go_default_library",
srcs = [
"reconcile_clusterrole.go",
"reconcile_clusterrolebindings.go",
"reconcile_role.go",
"role_interfaces.go",
],
tags = ["automanaged"],
deps = [

View File

@ -0,0 +1,88 @@
/*
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 reconciliation
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion"
)
type RoleRuleOwner struct {
Role *rbac.Role
}
func (o RoleRuleOwner) GetNamespace() string {
return o.Role.Namespace
}
func (o RoleRuleOwner) GetName() string {
return o.Role.Name
}
func (o RoleRuleOwner) GetLabels() map[string]string {
return o.Role.Labels
}
func (o RoleRuleOwner) SetLabels(in map[string]string) {
o.Role.Labels = in
}
func (o RoleRuleOwner) GetAnnotations() map[string]string {
return o.Role.Annotations
}
func (o RoleRuleOwner) SetAnnotations(in map[string]string) {
o.Role.Annotations = in
}
func (o RoleRuleOwner) GetRules() []rbac.PolicyRule {
return o.Role.Rules
}
func (o RoleRuleOwner) SetRules(in []rbac.PolicyRule) {
o.Role.Rules = in
}
type RoleModifier struct {
Client internalversion.RolesGetter
}
func (c RoleModifier) Get(namespace, name string) (RuleOwner, error) {
ret, err := c.Client.Roles(namespace).Get(name, metav1.GetOptions{})
if err != nil {
return nil, err
}
return RoleRuleOwner{Role: ret}, err
}
func (c RoleModifier) Create(in RuleOwner) (RuleOwner, error) {
ret, err := c.Client.Roles(in.GetNamespace()).Create(in.(RoleRuleOwner).Role)
if err != nil {
return nil, err
}
return RoleRuleOwner{Role: ret}, err
}
func (c RoleModifier) Update(in RuleOwner) (RuleOwner, error) {
ret, err := c.Client.Roles(in.GetNamespace()).Create(in.(RoleRuleOwner).Role)
if err != nil {
return nil, err
}
return RoleRuleOwner{Role: ret}, err
}

View File

@ -165,7 +165,6 @@ func PostStartHook(hookContext genericapiserver.PostStartHookContext) error {
// ensure bootstrap rolebindings are created or reconciled
for _, clusterRoleBinding := range append(bootstrappolicy.ClusterRoleBindings(), bootstrappolicy.ControllerRoleBindings()...) {
opts := reconciliation.ReconcileClusterRoleBindingOptions{
RoleBinding: &clusterRoleBinding,
Client: clientset.ClusterRoleBindings(),
@ -194,6 +193,36 @@ func PostStartHook(hookContext genericapiserver.PostStartHookContext) error {
}
}
// ensure bootstrap namespaced roles are created or reconciled
for namespace, roles := range bootstrappolicy.NamespaceRoles() {
for _, role := range roles {
opts := reconciliation.ReconcileClusterRoleOptions{
Role: reconciliation.RoleRuleOwner{Role: &role},
Client: reconciliation.RoleModifier{Client: clientset},
Confirm: true,
}
err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
result, err := opts.Run()
if err != nil {
return err
}
switch {
case result.Protected && result.Operation != reconciliation.ReconcileNone:
glog.Warningf("skipped reconcile-protected role.%s/%s in %v with missing permissions: %v", rbac.GroupName, role.Name, namespace, result.MissingRules)
case result.Operation == reconciliation.ReconcileUpdate:
glog.Infof("updated role.%s/%s in %v with additional permissions: %v", rbac.GroupName, role.Name, namespace, result.MissingRules)
case result.Operation == reconciliation.ReconcileCreate:
glog.Infof("created role.%s/%s in %v ", rbac.GroupName, role.Name, namespace)
}
return nil
})
if err != nil {
// don't fail on failures, try to create as many as you can
utilruntime.HandleError(fmt.Errorf("unable to reconcile role.%s/%s in %v: %v", rbac.GroupName, role.Name, namespace, err))
}
}
}
return true, nil
})
// if we're never able to make it through intialization, kill the API server

View File

@ -12,13 +12,16 @@ go_library(
name = "go_default_library",
srcs = [
"controller_policy.go",
"namespace_policy.go",
"policy.go",
],
tags = ["automanaged"],
deps = [
"//pkg/apis/rbac:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:k8s.io/apimachinery/pkg/api/meta",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apiserver/pkg/authentication/user",
],
)

View File

@ -0,0 +1,65 @@
/*
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{}
)
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 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(),
},
})
}
// NamespaceRoles returns a map of namespace to slice of roles to create
func NamespaceRoles() map[string][]rbac.Role {
return namespaceRoles
}

View File

@ -17,7 +17,9 @@ 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"
rbac "k8s.io/kubernetes/pkg/apis/rbac"
)
@ -44,40 +46,42 @@ const (
storageGroup = "storage.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 {
if roles[i].ObjectMeta.Labels == nil {
roles[i].ObjectMeta.Labels = make(map[string]string)
}
for k, v := range Label {
roles[i].ObjectMeta.Labels[k] = v
}
if roles[i].ObjectMeta.Annotations == nil {
roles[i].ObjectMeta.Annotations = make(map[string]string)
}
for k, v := range Annotation {
roles[i].ObjectMeta.Annotations[k] = v
}
addDefaultMetadata(&roles[i])
}
return
}
func addClusterRoleBindingLabel(rolebindings []rbac.ClusterRoleBinding) {
for i := range rolebindings {
if rolebindings[i].ObjectMeta.Labels == nil {
rolebindings[i].ObjectMeta.Labels = make(map[string]string)
}
for k, v := range Label {
rolebindings[i].ObjectMeta.Labels[k] = v
}
if rolebindings[i].ObjectMeta.Annotations == nil {
rolebindings[i].ObjectMeta.Annotations = make(map[string]string)
}
for k, v := range Annotation {
rolebindings[i].ObjectMeta.Annotations[k] = v
}
addDefaultMetadata(&rolebindings[i])
}
return
}

View File

@ -151,6 +151,28 @@ func TestEditViewRelationship(t *testing.T) {
}
}
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 TestBootstrapClusterRoles(t *testing.T) {
list := &api.List{}
names := sets.NewString()

View File

@ -0,0 +1,23 @@
apiVersion: v1
items:
- apiVersion: rbac.authorization.k8s.io/v1beta1
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
kind: List
metadata: {}