mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-01-15 14:26:57 +00:00
Merge pull request #39383 from liggitt/bind-check
Automatic merge from submit-queue (batch tested with PRs 39694, 39383, 39651, 39691, 39497) Allow rolebinding/clusterrolebinding with explicit bind permission check Fixes https://github.com/kubernetes/kubernetes/issues/39176 Fixes https://github.com/kubernetes/kubernetes/issues/39258 Allows creating/updating a rolebinding/clusterrolebinding if the user has explicitly been granted permission to perform the "bind" verb against the referenced role/clusterrole (previously, they could only bind if they already had all the permissions in the referenced role via an RBAC role themselves) ```release-note To create or update an RBAC RoleBinding or ClusterRoleBinding object, a user must: 1. Be authorized to make the create or update API request 2. Be allowed to bind the referenced role, either by already having all of the permissions contained in the referenced role, or by having the "bind" permission on the referenced role. ```
This commit is contained in:
@@ -252,7 +252,7 @@ func (c completedConfig) New() (*Master, error) {
|
||||
certificatesrest.RESTStorageProvider{},
|
||||
extensionsrest.RESTStorageProvider{ResourceInterface: thirdparty.NewThirdPartyResourceServer(s, c.StorageFactory)},
|
||||
policyrest.RESTStorageProvider{},
|
||||
rbacrest.RESTStorageProvider{},
|
||||
rbacrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorizer},
|
||||
storagerest.RESTStorageProvider{},
|
||||
}
|
||||
m.InstallAPIs(c.Config.APIResourceConfigSource, restOptionsFactory, restStorageProviders...)
|
||||
|
||||
@@ -12,8 +12,11 @@ go_library(
|
||||
srcs = ["escalation_check.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/apis/rbac:go_default_library",
|
||||
"//pkg/genericapiserver/api/request:go_default_library",
|
||||
"//pkg/util/runtime:go_default_library",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/user",
|
||||
"//vendor:k8s.io/apiserver/pkg/authorization/authorizer",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ go_library(
|
||||
"//pkg/genericapiserver/api/request:go_default_library",
|
||||
"//pkg/registry/rbac:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//vendor:k8s.io/apiserver/pkg/authorization/authorizer",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ limitations under the License.
|
||||
package policybased
|
||||
|
||||
import (
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/api/rest"
|
||||
"k8s.io/kubernetes/pkg/apis/rbac"
|
||||
@@ -32,11 +33,13 @@ var groupResource = rbac.Resource("clusterrolebindings")
|
||||
type Storage struct {
|
||||
rest.StandardStorage
|
||||
|
||||
authorizer authorizer.Authorizer
|
||||
|
||||
ruleResolver validation.AuthorizationRuleResolver
|
||||
}
|
||||
|
||||
func NewStorage(s rest.StandardStorage, ruleResolver validation.AuthorizationRuleResolver) *Storage {
|
||||
return &Storage{s, ruleResolver}
|
||||
func NewStorage(s rest.StandardStorage, authorizer authorizer.Authorizer, ruleResolver validation.AuthorizationRuleResolver) *Storage {
|
||||
return &Storage{s, authorizer, ruleResolver}
|
||||
}
|
||||
|
||||
func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) {
|
||||
@@ -45,6 +48,10 @@ func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object) (run
|
||||
}
|
||||
|
||||
clusterRoleBinding := obj.(*rbac.ClusterRoleBinding)
|
||||
if rbacregistry.BindingAuthorized(ctx, clusterRoleBinding.RoleRef, clusterRoleBinding.Namespace, s.authorizer) {
|
||||
return s.StandardStorage.Create(ctx, obj)
|
||||
}
|
||||
|
||||
rules, err := s.ruleResolver.GetRoleReferenceRules(clusterRoleBinding.RoleRef, clusterRoleBinding.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -63,6 +70,12 @@ func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.Up
|
||||
nonEscalatingInfo := rest.WrapUpdatedObjectInfo(obj, func(ctx genericapirequest.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) {
|
||||
clusterRoleBinding := obj.(*rbac.ClusterRoleBinding)
|
||||
|
||||
// if we're explicitly authorized to bind this clusterrole, return
|
||||
if rbacregistry.BindingAuthorized(ctx, clusterRoleBinding.RoleRef, clusterRoleBinding.Namespace, s.authorizer) {
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// Otherwise, see if we already have all the permissions contained in the referenced clusterrole
|
||||
rules, err := s.ruleResolver.GetRoleReferenceRules(clusterRoleBinding.RoleRef, clusterRoleBinding.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -17,8 +17,13 @@ limitations under the License.
|
||||
package rbac
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/kubernetes/pkg/apis/rbac"
|
||||
genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request"
|
||||
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
|
||||
)
|
||||
|
||||
func EscalationAllowed(ctx genericapirequest.Context) bool {
|
||||
@@ -39,3 +44,49 @@ func EscalationAllowed(ctx genericapirequest.Context) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// BindingAuthorized returns true if the user associated with the context is explicitly authorized to bind the specified roleRef
|
||||
func BindingAuthorized(ctx genericapirequest.Context, roleRef rbac.RoleRef, bindingNamespace string, a authorizer.Authorizer) bool {
|
||||
if a == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
user, ok := genericapirequest.UserFrom(ctx)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
attrs := authorizer.AttributesRecord{
|
||||
User: user,
|
||||
Verb: "bind",
|
||||
// check against the namespace where the binding is being created (or the empty namespace for clusterrolebindings).
|
||||
// this allows delegation to bind particular clusterroles in rolebindings within particular namespaces,
|
||||
// and to authorize binding a clusterrole across all namespaces in a clusterrolebinding.
|
||||
Namespace: bindingNamespace,
|
||||
ResourceRequest: true,
|
||||
}
|
||||
|
||||
// This occurs after defaulting and conversion, so values pulled from the roleRef won't change
|
||||
// Invalid APIGroup or Name values will fail validation
|
||||
switch roleRef.Kind {
|
||||
case "ClusterRole":
|
||||
attrs.APIGroup = roleRef.APIGroup
|
||||
attrs.Resource = "clusterroles"
|
||||
attrs.Name = roleRef.Name
|
||||
case "Role":
|
||||
attrs.APIGroup = roleRef.APIGroup
|
||||
attrs.Resource = "roles"
|
||||
attrs.Name = roleRef.Name
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
ok, _, err := a.Authorize(attrs)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf(
|
||||
"error authorizing user %#v to bind %#v in namespace %s: %v",
|
||||
roleRef, bindingNamespace, user, err,
|
||||
))
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ go_library(
|
||||
"//pkg/util/wait:go_default_library",
|
||||
"//plugin/pkg/auth/authorizer/rbac/bootstrappolicy:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:k8s.io/apiserver/pkg/authorization/authorizer",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/rest"
|
||||
"k8s.io/kubernetes/pkg/apis/rbac"
|
||||
@@ -48,7 +49,9 @@ import (
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy"
|
||||
)
|
||||
|
||||
type RESTStorageProvider struct{}
|
||||
type RESTStorageProvider struct {
|
||||
Authorizer authorizer.Authorizer
|
||||
}
|
||||
|
||||
var _ genericapiserver.PostStartHookProvider = RESTStorageProvider{}
|
||||
|
||||
@@ -98,7 +101,7 @@ func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource genericapis
|
||||
}
|
||||
if apiResourceConfigSource.ResourceEnabled(version.WithResource("rolebindings")) {
|
||||
initializeStorage()
|
||||
storage["rolebindings"] = rolebindingpolicybased.NewStorage(roleBindingsStorage, authorizationRuleResolver)
|
||||
storage["rolebindings"] = rolebindingpolicybased.NewStorage(roleBindingsStorage, p.Authorizer, authorizationRuleResolver)
|
||||
}
|
||||
if apiResourceConfigSource.ResourceEnabled(version.WithResource("clusterroles")) {
|
||||
initializeStorage()
|
||||
@@ -106,7 +109,7 @@ func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource genericapis
|
||||
}
|
||||
if apiResourceConfigSource.ResourceEnabled(version.WithResource("clusterrolebindings")) {
|
||||
initializeStorage()
|
||||
storage["clusterrolebindings"] = clusterrolebindingpolicybased.NewStorage(clusterRoleBindingsStorage, authorizationRuleResolver)
|
||||
storage["clusterrolebindings"] = clusterrolebindingpolicybased.NewStorage(clusterRoleBindingsStorage, p.Authorizer, authorizationRuleResolver)
|
||||
}
|
||||
return storage
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ go_library(
|
||||
"//pkg/genericapiserver/api/request:go_default_library",
|
||||
"//pkg/registry/rbac:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//vendor:k8s.io/apiserver/pkg/authorization/authorizer",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ limitations under the License.
|
||||
package policybased
|
||||
|
||||
import (
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/api/rest"
|
||||
"k8s.io/kubernetes/pkg/apis/rbac"
|
||||
@@ -32,11 +33,13 @@ var groupResource = rbac.Resource("rolebindings")
|
||||
type Storage struct {
|
||||
rest.StandardStorage
|
||||
|
||||
authorizer authorizer.Authorizer
|
||||
|
||||
ruleResolver validation.AuthorizationRuleResolver
|
||||
}
|
||||
|
||||
func NewStorage(s rest.StandardStorage, ruleResolver validation.AuthorizationRuleResolver) *Storage {
|
||||
return &Storage{s, ruleResolver}
|
||||
func NewStorage(s rest.StandardStorage, authorizer authorizer.Authorizer, ruleResolver validation.AuthorizationRuleResolver) *Storage {
|
||||
return &Storage{s, authorizer, ruleResolver}
|
||||
}
|
||||
|
||||
func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) {
|
||||
@@ -45,6 +48,10 @@ func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object) (run
|
||||
}
|
||||
|
||||
roleBinding := obj.(*rbac.RoleBinding)
|
||||
if rbacregistry.BindingAuthorized(ctx, roleBinding.RoleRef, roleBinding.Namespace, s.authorizer) {
|
||||
return s.StandardStorage.Create(ctx, obj)
|
||||
}
|
||||
|
||||
rules, err := s.ruleResolver.GetRoleReferenceRules(roleBinding.RoleRef, roleBinding.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -63,6 +70,12 @@ func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.Up
|
||||
nonEscalatingInfo := rest.WrapUpdatedObjectInfo(obj, func(ctx genericapirequest.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) {
|
||||
roleBinding := obj.(*rbac.RoleBinding)
|
||||
|
||||
// if we're explicitly authorized to bind this role, return
|
||||
if rbacregistry.BindingAuthorized(ctx, roleBinding.RoleRef, roleBinding.Namespace, s.authorizer) {
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// Otherwise, see if we already have all the permissions contained in the referenced role
|
||||
rules, err := s.ruleResolver.GetRoleReferenceRules(roleBinding.RoleRef, roleBinding.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user