Commit 9d7a8df5 authored by Chao Xu's avatar Chao Xu

add gc admission plugin that prevents user who doesn't have delete permission of…

add gc admission plugin that prevents user who doesn't have delete permission of the owner from setting blockOwnerDeletion
parent 32600927
...@@ -384,7 +384,9 @@ func BuildAdmission(s *options.ServerRunOptions, plugins *admission.Plugins, cli ...@@ -384,7 +384,9 @@ func BuildAdmission(s *options.ServerRunOptions, plugins *admission.Plugins, cli
glog.Fatalf("Error reading from cloud configuration file %s: %#v", s.CloudProvider.CloudConfigFile, err) glog.Fatalf("Error reading from cloud configuration file %s: %#v", s.CloudProvider.CloudConfigFile, err)
} }
} }
pluginInitializer := kubeapiserveradmission.NewPluginInitializer(client, sharedInformers, apiAuthorizer, cloudConfig) // TODO: use a dynamic restmapper. See https://github.com/kubernetes/kubernetes/pull/42615.
restMapper := api.Registry.RESTMapper()
pluginInitializer := kubeapiserveradmission.NewPluginInitializer(client, sharedInformers, apiAuthorizer, cloudConfig, restMapper)
admissionConfigProvider, err := admission.ReadAdmissionConfiguration(admissionControlPluginNames, s.GenericServerRunOptions.AdmissionControlConfigFile) admissionConfigProvider, err := admission.ReadAdmissionConfiguration(admissionControlPluginNames, s.GenericServerRunOptions.AdmissionControlConfigFile)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read plugin config: %v", err) return nil, fmt.Errorf("failed to read plugin config: %v", err)
......
...@@ -194,7 +194,7 @@ func NonBlockingRun(s *options.ServerRunOptions, stopCh <-chan struct{}) error { ...@@ -194,7 +194,7 @@ func NonBlockingRun(s *options.ServerRunOptions, stopCh <-chan struct{}) error {
glog.Fatalf("Error reading from cloud configuration file %s: %#v", s.CloudProvider.CloudConfigFile, err) glog.Fatalf("Error reading from cloud configuration file %s: %#v", s.CloudProvider.CloudConfigFile, err)
} }
} }
pluginInitializer := kubeapiserveradmission.NewPluginInitializer(client, sharedInformers, apiAuthorizer, cloudConfig) pluginInitializer := kubeapiserveradmission.NewPluginInitializer(client, sharedInformers, apiAuthorizer, cloudConfig, nil)
admissionConfigProvider, err := admission.ReadAdmissionConfiguration(admissionControlPluginNames, s.GenericServerRunOptions.AdmissionControlConfigFile) admissionConfigProvider, err := admission.ReadAdmissionConfiguration(admissionControlPluginNames, s.GenericServerRunOptions.AdmissionControlConfigFile)
if err != nil { if err != nil {
return fmt.Errorf("failed to read plugin config: %v", err) return fmt.Errorf("failed to read plugin config: %v", err)
......
...@@ -29,6 +29,7 @@ go_library( ...@@ -29,6 +29,7 @@ go_library(
deps = [ deps = [
"//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/api/meta",
"//vendor:k8s.io/apiserver/pkg/admission", "//vendor:k8s.io/apiserver/pkg/admission",
"//vendor:k8s.io/apiserver/pkg/authorization/authorizer", "//vendor:k8s.io/apiserver/pkg/authorization/authorizer",
], ],
......
...@@ -51,7 +51,7 @@ var _ WantsAuthorizer = &WantAuthorizerAdmission{} ...@@ -51,7 +51,7 @@ var _ WantsAuthorizer = &WantAuthorizerAdmission{}
// TestWantsAuthorizer ensures that the authorizer is injected when the WantsAuthorizer // TestWantsAuthorizer ensures that the authorizer is injected when the WantsAuthorizer
// interface is implemented. // interface is implemented.
func TestWantsAuthorizer(t *testing.T) { func TestWantsAuthorizer(t *testing.T) {
initializer := NewPluginInitializer(nil, nil, &TestAuthorizer{}, nil) initializer := NewPluginInitializer(nil, nil, &TestAuthorizer{}, nil, nil)
wantAuthorizerAdmission := &WantAuthorizerAdmission{} wantAuthorizerAdmission := &WantAuthorizerAdmission{}
initializer.Initialize(wantAuthorizerAdmission) initializer.Initialize(wantAuthorizerAdmission)
if wantAuthorizerAdmission.auth == nil { if wantAuthorizerAdmission.auth == nil {
...@@ -73,7 +73,7 @@ func (self *WantsCloudConfigAdmissionPlugin) Validate() error ...@@ -73,7 +73,7 @@ func (self *WantsCloudConfigAdmissionPlugin) Validate() error
func TestCloudConfigAdmissionPlugin(t *testing.T) { func TestCloudConfigAdmissionPlugin(t *testing.T) {
cloudConfig := []byte("cloud-configuration") cloudConfig := []byte("cloud-configuration")
initializer := NewPluginInitializer(nil, nil, &TestAuthorizer{}, cloudConfig) initializer := NewPluginInitializer(nil, nil, &TestAuthorizer{}, cloudConfig, nil)
wantsCloudConfigAdmission := &WantsCloudConfigAdmissionPlugin{} wantsCloudConfigAdmission := &WantsCloudConfigAdmissionPlugin{}
initializer.Initialize(wantsCloudConfigAdmission) initializer.Initialize(wantsCloudConfigAdmission)
......
...@@ -17,6 +17,7 @@ limitations under the License. ...@@ -17,6 +17,7 @@ limitations under the License.
package admission package admission
import ( import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
...@@ -48,22 +49,29 @@ type WantsCloudConfig interface { ...@@ -48,22 +49,29 @@ type WantsCloudConfig interface {
SetCloudConfig([]byte) SetCloudConfig([]byte)
} }
// WantsRESTMapper defines a function which sets RESTMapper for admission plugins that need it.
type WantsRESTMapper interface {
SetRESTMapper(meta.RESTMapper)
}
type pluginInitializer struct { type pluginInitializer struct {
internalClient internalclientset.Interface internalClient internalclientset.Interface
informers informers.SharedInformerFactory informers informers.SharedInformerFactory
authorizer authorizer.Authorizer authorizer authorizer.Authorizer
cloudConfig []byte cloudConfig []byte
restMapper meta.RESTMapper
} }
var _ admission.PluginInitializer = pluginInitializer{} var _ admission.PluginInitializer = pluginInitializer{}
// NewPluginInitializer constructs new instance of PluginInitializer // NewPluginInitializer constructs new instance of PluginInitializer
func NewPluginInitializer(internalClient internalclientset.Interface, sharedInformers informers.SharedInformerFactory, authz authorizer.Authorizer, cloudConfig []byte) admission.PluginInitializer { func NewPluginInitializer(internalClient internalclientset.Interface, sharedInformers informers.SharedInformerFactory, authz authorizer.Authorizer, cloudConfig []byte, restMapper meta.RESTMapper) admission.PluginInitializer {
return pluginInitializer{ return pluginInitializer{
internalClient: internalClient, internalClient: internalClient,
informers: sharedInformers, informers: sharedInformers,
authorizer: authz, authorizer: authz,
cloudConfig: cloudConfig, cloudConfig: cloudConfig,
restMapper: restMapper,
} }
} }
...@@ -85,4 +93,8 @@ func (i pluginInitializer) Initialize(plugin admission.Interface) { ...@@ -85,4 +93,8 @@ func (i pluginInitializer) Initialize(plugin admission.Interface) {
if wants, ok := plugin.(WantsCloudConfig); ok { if wants, ok := plugin.(WantsCloudConfig); ok {
wants.SetCloudConfig(i.cloudConfig) wants.SetCloudConfig(i.cloudConfig)
} }
if wants, ok := plugin.(WantsRESTMapper); ok {
wants.SetRESTMapper(i.restMapper)
}
} }
...@@ -16,7 +16,10 @@ go_library( ...@@ -16,7 +16,10 @@ go_library(
"//pkg/kubeapiserver/admission:go_default_library", "//pkg/kubeapiserver/admission:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/api/equality", "//vendor:k8s.io/apimachinery/pkg/api/equality",
"//vendor:k8s.io/apimachinery/pkg/api/meta", "//vendor:k8s.io/apimachinery/pkg/api/meta",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/runtime", "//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apiserver/pkg/admission", "//vendor:k8s.io/apiserver/pkg/admission",
"//vendor:k8s.io/apiserver/pkg/authorization/authorizer", "//vendor:k8s.io/apiserver/pkg/authorization/authorizer",
], ],
...@@ -29,6 +32,7 @@ go_test( ...@@ -29,6 +32,7 @@ go_test(
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//pkg/api:go_default_library", "//pkg/api:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library",
"//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/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema", "//vendor:k8s.io/apimachinery/pkg/runtime/schema",
......
...@@ -22,7 +22,10 @@ import ( ...@@ -22,7 +22,10 @@ import (
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizer"
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission" kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
...@@ -41,6 +44,8 @@ type gcPermissionsEnforcement struct { ...@@ -41,6 +44,8 @@ type gcPermissionsEnforcement struct {
*admission.Handler *admission.Handler
authorizer authorizer.Authorizer authorizer authorizer.Authorizer
restMapper meta.RESTMapper
} }
func (a *gcPermissionsEnforcement) Admit(attributes admission.Attributes) (err error) { func (a *gcPermissionsEnforcement) Admit(attributes admission.Attributes) (err error) {
...@@ -62,11 +67,32 @@ func (a *gcPermissionsEnforcement) Admit(attributes admission.Attributes) (err e ...@@ -62,11 +67,32 @@ func (a *gcPermissionsEnforcement) Admit(attributes admission.Attributes) (err e
Path: "", Path: "",
} }
allowed, reason, err := a.authorizer.Authorize(deleteAttributes) allowed, reason, err := a.authorizer.Authorize(deleteAttributes)
if allowed { if !allowed {
return nil return admission.NewForbidden(attributes, fmt.Errorf("cannot set an ownerRef on a resource you can't delete: %v, %v", reason, err))
} }
return admission.NewForbidden(attributes, fmt.Errorf("cannot set an ownerRef on a resource you can't delete: %v, %v", reason, err)) // Further check if the user is setting ownerReference.blockOwnerDeletion to
// true. If so, only allows the change if the user has delete permission of
// the _OWNER_
newBlockingRefs := newBlockingOwnerDeletionRefs(attributes.GetObject(), attributes.GetOldObject())
for _, ref := range newBlockingRefs {
records, err := a.ownerRefToDeleteAttributeRecords(ref, attributes)
if err != nil {
return admission.NewForbidden(attributes, fmt.Errorf("cannot set blockOwnerDeletion in this case because cannot find RESTMapping for APIVersion %s Kind %s: %v, %v", ref.APIVersion, ref.Kind, reason, err))
}
// Multiple records are returned if ref.Kind could map to multiple
// resources. User needs to have delete permission on all the
// matched Resources.
for _, record := range records {
allowed, reason, err := a.authorizer.Authorize(record)
if !allowed {
return admission.NewForbidden(attributes, fmt.Errorf("cannot set blockOwnerDeletion if an ownerReference refers to a resource you can't delete: %v, %v", reason, err))
}
}
}
return nil
} }
func isChangingOwnerReference(newObj, oldObj runtime.Object) bool { func isChangingOwnerReference(newObj, oldObj runtime.Object) bool {
...@@ -100,13 +126,109 @@ func isChangingOwnerReference(newObj, oldObj runtime.Object) bool { ...@@ -100,13 +126,109 @@ func isChangingOwnerReference(newObj, oldObj runtime.Object) bool {
return false return false
} }
// Translates ref to a DeleteAttribute deleting the object referred by the ref.
// OwnerReference only records the object kind, which might map to multiple
// resources, so multiple DeleteAttribute might be returned.
func (a *gcPermissionsEnforcement) ownerRefToDeleteAttributeRecords(ref metav1.OwnerReference, attributes admission.Attributes) ([]authorizer.AttributesRecord, error) {
var ret []authorizer.AttributesRecord
groupVersion, err := schema.ParseGroupVersion(ref.APIVersion)
if err != nil {
return ret, err
}
mappings, err := a.restMapper.RESTMappings(schema.GroupKind{Group: groupVersion.Group, Kind: ref.Kind}, groupVersion.Version)
if err != nil {
return ret, err
}
for _, mapping := range mappings {
ret = append(ret, authorizer.AttributesRecord{
User: attributes.GetUserInfo(),
Verb: "delete",
// ownerReference can only refer to an object in the same namespace, so attributes.GetNamespace() equals to the owner's namespace
Namespace: attributes.GetNamespace(),
APIGroup: groupVersion.Group,
APIVersion: groupVersion.Version,
Resource: mapping.Resource,
Name: ref.Name,
ResourceRequest: true,
Path: "",
})
}
return ret, nil
}
// only keeps the blocking refs
func blockingOwnerRefs(refs []metav1.OwnerReference) []metav1.OwnerReference {
var ret []metav1.OwnerReference
for _, ref := range refs {
if ref.BlockOwnerDeletion != nil && *ref.BlockOwnerDeletion == true {
ret = append(ret, ref)
}
}
return ret
}
func indexByUID(refs []metav1.OwnerReference) map[types.UID]metav1.OwnerReference {
ret := make(map[types.UID]metav1.OwnerReference)
for _, ref := range refs {
ret[ref.UID] = ref
}
return ret
}
// Returns new blocking ownerReferences, and references whose blockOwnerDeletion
// field is changed from nil or false to true.
func newBlockingOwnerDeletionRefs(newObj, oldObj runtime.Object) []metav1.OwnerReference {
newMeta, err := meta.Accessor(newObj)
if err != nil {
// if we don't have objectmeta, we don't have the object reference
return nil
}
newRefs := newMeta.GetOwnerReferences()
blockingNewRefs := blockingOwnerRefs(newRefs)
if len(blockingNewRefs) == 0 {
return nil
}
if oldObj == nil {
return blockingNewRefs
}
oldMeta, err := meta.Accessor(oldObj)
if err != nil {
// if we don't have objectmeta, treat it as if all the ownerReference are newly created
return blockingNewRefs
}
var ret []metav1.OwnerReference
indexedOldRefs := indexByUID(oldMeta.GetOwnerReferences())
for _, ref := range blockingNewRefs {
oldRef, ok := indexedOldRefs[ref.UID]
if !ok {
// if ref is newly added, and it's blocking, then returns it.
ret = append(ret, ref)
continue
}
wasNotBlocking := oldRef.BlockOwnerDeletion == nil || *oldRef.BlockOwnerDeletion == false
if wasNotBlocking {
ret = append(ret, ref)
}
}
return ret
}
func (a *gcPermissionsEnforcement) SetAuthorizer(authorizer authorizer.Authorizer) { func (a *gcPermissionsEnforcement) SetAuthorizer(authorizer authorizer.Authorizer) {
a.authorizer = authorizer a.authorizer = authorizer
} }
func (a *gcPermissionsEnforcement) SetRESTMapper(restMapper meta.RESTMapper) {
a.restMapper = restMapper
}
func (a *gcPermissionsEnforcement) Validate() error { func (a *gcPermissionsEnforcement) Validate() error {
if a.authorizer == nil { if a.authorizer == nil {
return fmt.Errorf("missing authorizer") return fmt.Errorf("missing authorizer")
} }
if a.restMapper == nil {
return fmt.Errorf("missing restMapper")
}
return nil return nil
} }
...@@ -595,7 +595,7 @@ func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.Sh ...@@ -595,7 +595,7 @@ func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.Sh
if err != nil { if err != nil {
return nil, f, err return nil, f, err
} }
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil) pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
pluginInitializer.Initialize(handler) pluginInitializer.Initialize(handler)
err = admission.Validate(handler) err = admission.Validate(handler)
return handler, f, err return handler, f, err
......
...@@ -38,7 +38,7 @@ import ( ...@@ -38,7 +38,7 @@ import (
func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.SharedInformerFactory, error) { func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.SharedInformerFactory, error) {
f := informers.NewSharedInformerFactory(c, 5*time.Minute) f := informers.NewSharedInformerFactory(c, 5*time.Minute)
handler := NewProvision() handler := NewProvision()
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil) pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
pluginInitializer.Initialize(handler) pluginInitializer.Initialize(handler)
err := admission.Validate(handler) err := admission.Validate(handler)
return handler, f, err return handler, f, err
......
...@@ -37,7 +37,7 @@ import ( ...@@ -37,7 +37,7 @@ import (
func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.SharedInformerFactory, error) { func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.SharedInformerFactory, error) {
f := informers.NewSharedInformerFactory(c, 5*time.Minute) f := informers.NewSharedInformerFactory(c, 5*time.Minute)
handler := NewExists() handler := NewExists()
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil) pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
pluginInitializer.Initialize(handler) pluginInitializer.Initialize(handler)
err := admission.Validate(handler) err := admission.Validate(handler)
return handler, f, err return handler, f, err
......
...@@ -48,7 +48,7 @@ func newHandlerForTestWithClock(c clientset.Interface, cacheClock clock.Clock) ( ...@@ -48,7 +48,7 @@ func newHandlerForTestWithClock(c clientset.Interface, cacheClock clock.Clock) (
if err != nil { if err != nil {
return nil, f, err return nil, f, err
} }
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil) pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
pluginInitializer.Initialize(handler) pluginInitializer.Initialize(handler)
err = admission.Validate(handler) err = admission.Validate(handler)
return handler, f, err return handler, f, err
......
...@@ -183,7 +183,7 @@ func TestHandles(t *testing.T) { ...@@ -183,7 +183,7 @@ func TestHandles(t *testing.T) {
func newHandlerForTest(c clientset.Interface) (*podNodeSelector, informers.SharedInformerFactory, error) { func newHandlerForTest(c clientset.Interface) (*podNodeSelector, informers.SharedInformerFactory, error) {
f := informers.NewSharedInformerFactory(c, 5*time.Minute) f := informers.NewSharedInformerFactory(c, 5*time.Minute)
handler := NewPodNodeSelector(nil) handler := NewPodNodeSelector(nil)
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil) pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
pluginInitializer.Initialize(handler) pluginInitializer.Initialize(handler)
err := admission.Validate(handler) err := admission.Validate(handler)
return handler, f, err return handler, f, err
......
...@@ -193,7 +193,7 @@ func newHandlerForTest(c clientset.Interface) (*podTolerationsPlugin, informers. ...@@ -193,7 +193,7 @@ func newHandlerForTest(c clientset.Interface) (*podTolerationsPlugin, informers.
return nil, nil, err return nil, nil, err
} }
handler := NewPodTolerationsPlugin(pluginConfig) handler := NewPodTolerationsPlugin(pluginConfig)
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil) pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
pluginInitializer.Initialize(handler) pluginInitializer.Initialize(handler)
err = admission.Validate(handler) err = admission.Validate(handler)
return handler, f, err return handler, f, err
......
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