mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 09:22:44 +00:00
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:
commit
452420484c
@ -11,8 +11,8 @@ load(
|
|||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = [
|
||||||
"reconcile_clusterrole_test.go",
|
|
||||||
"reconcile_clusterrolebindings_test.go",
|
"reconcile_clusterrolebindings_test.go",
|
||||||
|
"reconcile_role_test.go",
|
||||||
],
|
],
|
||||||
library = ":go_default_library",
|
library = ":go_default_library",
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
@ -26,8 +26,9 @@ go_test(
|
|||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"reconcile_clusterrole.go",
|
|
||||||
"reconcile_clusterrolebindings.go",
|
"reconcile_clusterrolebindings.go",
|
||||||
|
"reconcile_role.go",
|
||||||
|
"role_interfaces.go",
|
||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
|
88
pkg/registry/rbac/reconciliation/role_interfaces.go
Normal file
88
pkg/registry/rbac/reconciliation/role_interfaces.go
Normal 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
|
||||||
|
|
||||||
|
}
|
@ -165,7 +165,6 @@ func PostStartHook(hookContext genericapiserver.PostStartHookContext) error {
|
|||||||
|
|
||||||
// ensure bootstrap rolebindings are created or reconciled
|
// ensure bootstrap rolebindings are created or reconciled
|
||||||
for _, clusterRoleBinding := range append(bootstrappolicy.ClusterRoleBindings(), bootstrappolicy.ControllerRoleBindings()...) {
|
for _, clusterRoleBinding := range append(bootstrappolicy.ClusterRoleBindings(), bootstrappolicy.ControllerRoleBindings()...) {
|
||||||
|
|
||||||
opts := reconciliation.ReconcileClusterRoleBindingOptions{
|
opts := reconciliation.ReconcileClusterRoleBindingOptions{
|
||||||
RoleBinding: &clusterRoleBinding,
|
RoleBinding: &clusterRoleBinding,
|
||||||
Client: clientset.ClusterRoleBindings(),
|
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
|
return true, nil
|
||||||
})
|
})
|
||||||
// if we're never able to make it through intialization, kill the API server
|
// if we're never able to make it through intialization, kill the API server
|
||||||
|
@ -12,13 +12,16 @@ go_library(
|
|||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"controller_policy.go",
|
"controller_policy.go",
|
||||||
|
"namespace_policy.go",
|
||||||
"policy.go",
|
"policy.go",
|
||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/apis/rbac:go_default_library",
|
"//pkg/apis/rbac:go_default_library",
|
||||||
"//vendor:github.com/golang/glog",
|
"//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/apis/meta/v1",
|
||||||
|
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||||
"//vendor:k8s.io/apiserver/pkg/authentication/user",
|
"//vendor:k8s.io/apiserver/pkg/authentication/user",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
|
}
|
@ -17,7 +17,9 @@ limitations under the License.
|
|||||||
package bootstrappolicy
|
package bootstrappolicy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
rbac "k8s.io/kubernetes/pkg/apis/rbac"
|
rbac "k8s.io/kubernetes/pkg/apis/rbac"
|
||||||
)
|
)
|
||||||
@ -44,40 +46,42 @@ const (
|
|||||||
storageGroup = "storage.k8s.io"
|
storageGroup = "storage.k8s.io"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addClusterRoleLabel(roles []rbac.ClusterRole) {
|
func addDefaultMetadata(obj runtime.Object) {
|
||||||
for i := range roles {
|
metadata, err := meta.Accessor(obj)
|
||||||
if roles[i].ObjectMeta.Labels == nil {
|
if err != nil {
|
||||||
roles[i].ObjectMeta.Labels = make(map[string]string)
|
// if this happens, then some static code is broken
|
||||||
}
|
panic(err)
|
||||||
for k, v := range Label {
|
|
||||||
roles[i].ObjectMeta.Labels[k] = v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if roles[i].ObjectMeta.Annotations == nil {
|
labels := metadata.GetLabels()
|
||||||
roles[i].ObjectMeta.Annotations = make(map[string]string)
|
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 {
|
for k, v := range Annotation {
|
||||||
roles[i].ObjectMeta.Annotations[k] = v
|
annotations[k] = v
|
||||||
}
|
}
|
||||||
|
metadata.SetAnnotations(annotations)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addClusterRoleLabel(roles []rbac.ClusterRole) {
|
||||||
|
for i := range roles {
|
||||||
|
addDefaultMetadata(&roles[i])
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func addClusterRoleBindingLabel(rolebindings []rbac.ClusterRoleBinding) {
|
func addClusterRoleBindingLabel(rolebindings []rbac.ClusterRoleBinding) {
|
||||||
for i := range rolebindings {
|
for i := range rolebindings {
|
||||||
if rolebindings[i].ObjectMeta.Labels == nil {
|
addDefaultMetadata(&rolebindings[i])
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
func TestBootstrapClusterRoles(t *testing.T) {
|
||||||
list := &api.List{}
|
list := &api.List{}
|
||||||
names := sets.NewString()
|
names := sets.NewString()
|
||||||
|
23
plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/namespace-roles.yaml
vendored
Normal file
23
plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/namespace-roles.yaml
vendored
Normal 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: {}
|
Loading…
Reference in New Issue
Block a user