Merge pull request #55000 from deads2k/admission-10-split

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

make easy validation admission plugins into validators

This switches "easy" admission plugins that need to be validators to be validators.  I also did one simple plugin to do both mutation and validation for practice.

@kubernetes/sig-api-machinery-pr-reviews @caesarxuchao
This commit is contained in:
Kubernetes Submit Queue 2017-11-03 06:33:17 -07:00 committed by GitHub
commit 6fe3a4d82a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 164 additions and 61 deletions

View File

@ -33,6 +33,9 @@ func Register(plugins *admission.Plugins) {
// It is useful in tests and when using kubernetes in an open manner.
type AlwaysAdmit struct{}
var _ admission.MutationInterface = AlwaysAdmit{}
var _ admission.ValidationInterface = AlwaysAdmit{}
// Admit makes an admission decision based on the request attributes
func (AlwaysAdmit) Admit(a admission.Attributes) (err error) {
return nil

View File

@ -13,6 +13,7 @@ go_library(
deps = [
"//pkg/api:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
],
)

View File

@ -28,6 +28,7 @@ import (
"io"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/admission"
"k8s.io/kubernetes/pkg/api"
)
@ -45,10 +46,13 @@ type AlwaysPullImages struct {
*admission.Handler
}
var _ admission.MutationInterface = &AlwaysPullImages{}
var _ admission.ValidationInterface = &AlwaysPullImages{}
// Admit makes an admission decision based on the request attributes
func (a *AlwaysPullImages) Admit(attributes admission.Attributes) (err error) {
// Ignore all calls to subresources or resources other than pods.
if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") {
if shouldIgnore(attributes) {
return nil
}
pod, ok := attributes.GetObject().(*api.Pod)
@ -67,6 +71,48 @@ func (a *AlwaysPullImages) Admit(attributes admission.Attributes) (err error) {
return nil
}
// Validate makes sure that all containers are set to always pull images
func (*AlwaysPullImages) Validate(attributes admission.Attributes) (err error) {
if shouldIgnore(attributes) {
return nil
}
pod, ok := attributes.GetObject().(*api.Pod)
if !ok {
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
}
for i := range pod.Spec.InitContainers {
if pod.Spec.InitContainers[i].ImagePullPolicy != api.PullAlways {
return admission.NewForbidden(attributes,
field.NotSupported(field.NewPath("spec", "initContainers").Index(i).Child("imagePullPolicy"),
pod.Spec.InitContainers[i].ImagePullPolicy, []string{string(api.PullAlways)},
),
)
}
}
for i := range pod.Spec.Containers {
if pod.Spec.Containers[i].ImagePullPolicy != api.PullAlways {
return admission.NewForbidden(attributes,
field.NotSupported(field.NewPath("spec", "containers").Index(i).Child("imagePullPolicy"),
pod.Spec.Containers[i].ImagePullPolicy, []string{string(api.PullAlways)},
),
)
}
}
return nil
}
func shouldIgnore(attributes admission.Attributes) bool {
// Ignore all calls to subresources or resources other than pods.
if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") {
return true
}
return false
}
// NewAlwaysPullImages creates a new always pull images admission control handler
func NewAlwaysPullImages() *AlwaysPullImages {
return &AlwaysPullImages{

View File

@ -63,6 +63,36 @@ func TestAdmission(t *testing.T) {
}
}
func TestValidate(t *testing.T) {
namespace := "test"
handler := &AlwaysPullImages{}
pod := api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: namespace},
Spec: api.PodSpec{
InitContainers: []api.Container{
{Name: "init1", Image: "image"},
{Name: "init2", Image: "image", ImagePullPolicy: api.PullNever},
{Name: "init3", Image: "image", ImagePullPolicy: api.PullIfNotPresent},
{Name: "init4", Image: "image", ImagePullPolicy: api.PullAlways},
},
Containers: []api.Container{
{Name: "ctr1", Image: "image"},
{Name: "ctr2", Image: "image", ImagePullPolicy: api.PullNever},
{Name: "ctr3", Image: "image", ImagePullPolicy: api.PullIfNotPresent},
{Name: "ctr4", Image: "image", ImagePullPolicy: api.PullAlways},
},
},
}
expectedError := `pods "123" is forbidden: spec.initContainers[0].imagePullPolicy: Unsupported value: "": supported values: "Always"`
err := handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err == nil {
t.Fatal("missing expected error")
}
if err.Error() != expectedError {
t.Fatal(err)
}
}
// TestOtherResources ensures that this admission controller is a no-op for other resources,
// subresources, and non-pods.
func TestOtherResources(t *testing.T) {

View File

@ -38,6 +38,8 @@ type Plugin struct {
*admission.Handler
}
var _ admission.ValidationInterface = &Plugin{}
// NewInterPodAntiAffinity creates a new instance of the LimitPodHardAntiAffinityTopology admission controller
func NewInterPodAntiAffinity() *Plugin {
return &Plugin{
@ -45,9 +47,9 @@ func NewInterPodAntiAffinity() *Plugin {
}
}
// Admit will deny any pod that defines AntiAffinity topology key other than kubeletapis.LabelHostname i.e. "kubernetes.io/hostname"
// Validate will deny any pod that defines AntiAffinity topology key other than kubeletapis.LabelHostname i.e. "kubernetes.io/hostname"
// in requiredDuringSchedulingRequiredDuringExecution and requiredDuringSchedulingIgnoredDuringExecution.
func (p *Plugin) Admit(attributes admission.Attributes) (err error) {
func (p *Plugin) Validate(attributes admission.Attributes) (err error) {
// Ignore all calls to subresources or resources other than pods.
if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") {
return nil

View File

@ -199,7 +199,7 @@ func TestInterPodAffinityAdmission(t *testing.T) {
}
for _, test := range tests {
pod.Spec.Affinity = test.affinity
err := handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil))
err := handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil))
if test.errorExpected && err == nil {
t.Errorf("Expected error for Anti Affinity %+v but did not get an error", test.affinity)
@ -267,7 +267,7 @@ func TestOtherResources(t *testing.T) {
for _, tc := range tests {
handler := &Plugin{}
err := handler.Admit(admission.NewAttributesRecord(tc.object, nil, api.Kind(tc.kind).WithVersion("version"), namespace, name, api.Resource(tc.resource).WithVersion("version"), tc.subresource, admission.Create, nil))
err := handler.Validate(admission.NewAttributesRecord(tc.object, nil, api.Kind(tc.kind).WithVersion("version"), namespace, name, api.Resource(tc.resource).WithVersion("version"), tc.subresource, admission.Create, nil))
if tc.expectError {
if err == nil {

View File

@ -34,11 +34,19 @@ func Register(plugins *admission.Plugins) {
// It is useful in unit tests to force an operation to be forbidden.
type AlwaysDeny struct{}
var _ admission.MutationInterface = AlwaysDeny{}
var _ admission.ValidationInterface = AlwaysDeny{}
// Admit makes an admission decision based on the request attributes.
func (AlwaysDeny) Admit(a admission.Attributes) (err error) {
return admission.NewForbidden(a, errors.New("Admission control is denying all modifications"))
}
// Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate.
func (AlwaysDeny) Validate(a admission.Attributes) (err error) {
return admission.NewForbidden(a, errors.New("Admission control is denying all modifications"))
}
// Handles returns true if this admission controller can handle the given operation
// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
func (AlwaysDeny) Handles(operation admission.Operation) bool {

View File

@ -54,6 +54,8 @@ type Plugin struct {
limitEnforcers []*limitEnforcer
}
var _ admission.ValidationInterface = &Plugin{}
// newEventRateLimit configures an admission controller that can enforce event rate limits
func newEventRateLimit(config *eventratelimitapi.Configuration, clock flowcontrol.Clock) (*Plugin, error) {
limitEnforcers := make([]*limitEnforcer, 0, len(config.Limits))
@ -73,8 +75,8 @@ func newEventRateLimit(config *eventratelimitapi.Configuration, clock flowcontro
return eventRateLimitAdmission, nil
}
// Admit makes admission decisions while enforcing event rate limits
func (a *Plugin) Admit(attr admission.Attributes) (err error) {
// Validate makes admission decisions while enforcing event rate limits
func (a *Plugin) Validate(attr admission.Attributes) (err error) {
// ignore all operations that do not correspond to an Event kind
if attr.GetKind().GroupKind() != api.Kind("Event") {
return nil

View File

@ -482,7 +482,7 @@ func TestEventRateLimiting(t *testing.T) {
clock.Step(rq.delay)
}
attributes := attributesForRequest(rq)
err = eventratelimit.Admit(attributes)
err = eventratelimit.Validate(attributes)
if rq.accepted != (err == nil) {
expectedAction := "admitted"
if !rq.accepted {

View File

@ -54,6 +54,8 @@ type DenyExec struct {
privileged bool
}
var _ admission.ValidationInterface = &DenyExec{}
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&DenyExec{})
// NewDenyEscalatingExec creates a new admission controller that denies an exec operation on a pod
@ -79,8 +81,8 @@ func NewDenyExecOnPrivileged() *DenyExec {
}
}
// Admit makes an admission decision based on the request attributes
func (d *DenyExec) Admit(a admission.Attributes) (err error) {
// Validate makes an admission decision based on the request attributes
func (d *DenyExec) Validate(a admission.Attributes) (err error) {
connectRequest, ok := a.GetObject().(*rest.ConnectRequest)
if !ok {
return errors.NewBadRequest("a connect request was received, but could not convert the request object.")

View File

@ -123,7 +123,7 @@ func testAdmission(t *testing.T, pod *api.Pod, handler *DenyExec, shouldAccept b
// pods/exec
{
req := &rest.ConnectRequest{Name: pod.Name, ResourcePath: "pods/exec"}
err := handler.Admit(admission.NewAttributesRecord(req, nil, api.Kind("Pod").WithVersion("version"), "test", "name", api.Resource("pods").WithVersion("version"), "exec", admission.Connect, nil))
err := handler.Validate(admission.NewAttributesRecord(req, nil, api.Kind("Pod").WithVersion("version"), "test", "name", api.Resource("pods").WithVersion("version"), "exec", admission.Connect, nil))
if shouldAccept && err != nil {
t.Errorf("Unexpected error returned from admission handler: %v", err)
}
@ -135,7 +135,7 @@ func testAdmission(t *testing.T, pod *api.Pod, handler *DenyExec, shouldAccept b
// pods/attach
{
req := &rest.ConnectRequest{Name: pod.Name, ResourcePath: "pods/attach"}
err := handler.Admit(admission.NewAttributesRecord(req, nil, api.Kind("Pod").WithVersion("version"), "test", "name", api.Resource("pods").WithVersion("version"), "attach", admission.Connect, nil))
err := handler.Validate(admission.NewAttributesRecord(req, nil, api.Kind("Pod").WithVersion("version"), "test", "name", api.Resource("pods").WithVersion("version"), "attach", admission.Connect, nil))
if shouldAccept && err != nil {
t.Errorf("Unexpected error returned from admission handler: %v", err)
}

View File

@ -63,6 +63,8 @@ type gcPermissionsEnforcement struct {
whiteList []whiteListItem
}
var _ admission.ValidationInterface = &gcPermissionsEnforcement{}
// whiteListItem describes an entry in a whitelist ignored by gc permission enforcement.
type whiteListItem struct {
groupResource schema.GroupResource
@ -79,7 +81,7 @@ func (a *gcPermissionsEnforcement) isWhiteListed(groupResource schema.GroupResou
return false
}
func (a *gcPermissionsEnforcement) Admit(attributes admission.Attributes) (err error) {
func (a *gcPermissionsEnforcement) Validate(attributes admission.Attributes) (err error) {
// // if the request is in the whitelist, we skip mutation checks for this resource.
if a.isWhiteListed(attributes.GetResource().GroupResource(), attributes.GetSubresource()) {
return nil

View File

@ -269,7 +269,7 @@ func TestGCAdmission(t *testing.T) {
user := &user.DefaultInfo{Name: tc.username}
attributes := admission.NewAttributesRecord(tc.newObj, tc.oldObj, schema.GroupVersionKind{}, metav1.NamespaceDefault, "foo", tc.resource, tc.subresource, operation, user)
err := gcAdmit.Admit(attributes)
err := gcAdmit.Validate(attributes)
if !tc.checkError(err) {
t.Errorf("%v: unexpected err: %v", tc.name, err)
}
@ -517,7 +517,7 @@ func TestBlockOwnerDeletionAdmission(t *testing.T) {
user := &user.DefaultInfo{Name: tc.username}
attributes := admission.NewAttributesRecord(tc.newObj, tc.oldObj, schema.GroupVersionKind{}, metav1.NamespaceDefault, "foo", tc.resource, tc.subresource, operation, user)
err := gcAdmit.Admit(attributes)
err := gcAdmit.Validate(attributes)
if !tc.checkError(err) {
t.Errorf("%v: unexpected err: %v", tc.name, err)
}

View File

@ -69,6 +69,8 @@ type Plugin struct {
defaultAllow bool
}
var _ admission.ValidationInterface = &Plugin{}
func (a *Plugin) statusTTL(status v1alpha1.ImageReviewStatus) time.Duration {
if status.Allowed {
return a.allowTTL
@ -107,8 +109,8 @@ func (a *Plugin) webhookError(pod *api.Pod, attributes admission.Attributes, err
return nil
}
// Admit makes an admission decision based on the request attributes
func (a *Plugin) Admit(attributes admission.Attributes) (err error) {
// Validate makes an admission decision based on the request attributes
func (a *Plugin) Validate(attributes admission.Attributes) (err error) {
// Ignore all calls to subresources or resources other than pods.
if attributes.GetSubresource() != "" || attributes.GetResource().GroupResource() != api.Resource("pods") {
return nil

View File

@ -477,7 +477,7 @@ func TestTLSConfig(t *testing.T) {
// Allow all and see if we get an error.
service.Allow()
err = wh.Admit(attr)
err = wh.Validate(attr)
if tt.wantAllowed {
if err != nil {
t.Errorf("expected successful admission")
@ -499,7 +499,7 @@ func TestTLSConfig(t *testing.T) {
}
service.Deny()
if err := wh.Admit(attr); err == nil {
if err := wh.Validate(attr); err == nil {
t.Errorf("%s: incorrectly admitted with DenyAll policy", tt.test)
}
}()
@ -516,7 +516,7 @@ type webhookCacheTestCase struct {
func testWebhookCacheCases(t *testing.T, serv *mockService, wh *Plugin, attr admission.Attributes, tests []webhookCacheTestCase) {
for _, test := range tests {
serv.statusCode = test.statusCode
err := wh.Admit(attr)
err := wh.Validate(attr)
authorized := err == nil
if test.expectedErr && err == nil {
@ -749,7 +749,7 @@ func TestContainerCombinations(t *testing.T) {
attr := admission.NewAttributesRecord(tt.pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
err = wh.Admit(attr)
err = wh.Validate(attr)
if tt.wantAllowed {
if err != nil {
t.Errorf("expected successful admission: %s", tt.test)
@ -827,7 +827,7 @@ func TestDefaultAllow(t *testing.T) {
attr := admission.NewAttributesRecord(tt.pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
err = wh.Admit(attr)
err = wh.Validate(attr)
if tt.wantAllowed {
if err != nil {
t.Errorf("expected successful admission")
@ -919,7 +919,7 @@ func TestAnnotationFiltering(t *testing.T) {
attr := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
err = wh.Admit(attr)
err = wh.Validate(attr)
if err != nil {
t.Errorf("expected successful admission")
}

View File

@ -46,11 +46,12 @@ type Exists struct {
namespaceLister corelisters.NamespaceLister
}
var _ admission.ValidationInterface = &Exists{}
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&Exists{})
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&Exists{})
// Admit makes an admission decision based on the request attributes
func (e *Exists) Admit(a admission.Attributes) error {
// Validate makes an admission decision based on the request attributes
func (e *Exists) Validate(a admission.Attributes) error {
// if we're here, then we've already passed authentication, so we're allowed to do what we're trying to do
// if we're here, then the API server has found a route, which means that if we have a non-empty namespace
// its a namespaced resource.

View File

@ -34,7 +34,7 @@ import (
)
// newHandlerForTest returns the admission controller configured for testing.
func newHandlerForTest(c clientset.Interface) (admission.MutationInterface, informers.SharedInformerFactory, error) {
func newHandlerForTest(c clientset.Interface) (admission.ValidationInterface, informers.SharedInformerFactory, error) {
f := informers.NewSharedInformerFactory(c, 5*time.Minute)
handler := NewExists()
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil, nil, nil)
@ -87,7 +87,7 @@ func TestAdmissionNamespaceExists(t *testing.T) {
informerFactory.Start(wait.NeverStop)
pod := newPod(namespace)
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err = handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err != nil {
t.Errorf("unexpected error returned from admission handler")
}
@ -107,7 +107,7 @@ func TestAdmissionNamespaceDoesNotExist(t *testing.T) {
informerFactory.Start(wait.NeverStop)
pod := newPod(namespace)
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err = handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err == nil {
actions := ""
for _, action := range mockClient.Actions() {

View File

@ -43,6 +43,7 @@ func Register(plugins *admission.Plugins) {
}
var _ admission.Interface = &persistentVolumeClaimResize{}
var _ admission.ValidationInterface = &persistentVolumeClaimResize{}
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&persistentVolumeClaimResize{})
type persistentVolumeClaimResize struct {
@ -79,7 +80,7 @@ func (pvcr *persistentVolumeClaimResize) ValidateInitialization() error {
return nil
}
func (pvcr *persistentVolumeClaimResize) Admit(a admission.Attributes) error {
func (pvcr *persistentVolumeClaimResize) Validate(a admission.Attributes) error {
if a.GetResource().GroupResource() != api.Resource("persistentvolumeclaims") {
return nil
}

View File

@ -323,7 +323,7 @@ func TestPVCResizeAdmission(t *testing.T) {
operation := admission.Update
attributes := admission.NewAttributesRecord(tc.newObj, tc.oldObj, schema.GroupVersionKind{}, metav1.NamespaceDefault, "foo", tc.resource, tc.subresource, operation, nil)
err := ctrl.Admit(attributes)
err := ctrl.Validate(attributes)
fmt.Println(tc.name)
fmt.Println(err)
if !tc.checkError(err) {

View File

@ -61,6 +61,7 @@ type QuotaAdmission struct {
evaluator Evaluator
}
var _ admission.ValidationInterface = &QuotaAdmission{}
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&QuotaAdmission{})
var _ = kubeapiserveradmission.WantsQuotaConfiguration(&QuotaAdmission{})
@ -120,8 +121,8 @@ func (a *QuotaAdmission) ValidateInitialization() error {
return nil
}
// Admit makes admission decisions while enforcing quota
func (a *QuotaAdmission) Admit(attr admission.Attributes) (err error) {
// Validate makes admission decisions while enforcing quota
func (a *QuotaAdmission) Validate(attr admission.Attributes) (err error) {
// ignore all operations that correspond to sub-resource actions
if attr.GetSubresource() != "" {
return nil

View File

@ -140,7 +140,7 @@ func TestAdmissionIgnoresDelete(t *testing.T) {
evaluator: evaluator,
}
namespace := "default"
err := handler.Admit(admission.NewAttributesRecord(nil, nil, api.Kind("Pod").WithVersion("version"), namespace, "name", api.Resource("pods").WithVersion("version"), "", admission.Delete, nil))
err := handler.Validate(admission.NewAttributesRecord(nil, nil, api.Kind("Pod").WithVersion("version"), namespace, "name", api.Resource("pods").WithVersion("version"), "", admission.Delete, nil))
if err != nil {
t.Errorf("ResourceQuota should admit all deletes: %v", err)
}
@ -177,11 +177,11 @@ func TestAdmissionIgnoresSubresources(t *testing.T) {
}
informerFactory.Core().InternalVersion().ResourceQuotas().Informer().GetIndexer().Add(resourceQuota)
newPod := validPod("123", 1, getResourceRequirements(getResourceList("100m", "2Gi"), getResourceList("", "")))
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err == nil {
t.Errorf("Expected an error because the pod exceeded allowed quota")
}
err = handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "subresource", admission.Create, nil))
err = handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "subresource", admission.Create, nil))
if err != nil {
t.Errorf("Did not expect an error because the action went to a subresource: %v", err)
}
@ -222,7 +222,7 @@ func TestAdmitBelowQuotaLimit(t *testing.T) {
}
informerFactory.Core().InternalVersion().ResourceQuotas().Informer().GetIndexer().Add(resourceQuota)
newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("100m", "2Gi"), getResourceList("", "")))
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -318,7 +318,7 @@ func TestAdmitHandlesOldObjects(t *testing.T) {
Ports: []api.ServicePort{{Port: 1234}},
},
}
err := handler.Admit(admission.NewAttributesRecord(newService, existingService, api.Kind("Service").WithVersion("version"), newService.Namespace, newService.Name, api.Resource("services").WithVersion("version"), "", admission.Update, nil))
err := handler.Validate(admission.NewAttributesRecord(newService, existingService, api.Kind("Service").WithVersion("version"), newService.Namespace, newService.Name, api.Resource("services").WithVersion("version"), "", admission.Update, nil))
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -427,7 +427,7 @@ func TestAdmitHandlesNegativePVCUpdates(t *testing.T) {
},
}
err = handler.Admit(admission.NewAttributesRecord(newPVC, oldPVC, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPVC.Namespace, newPVC.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Update, nil))
err = handler.Validate(admission.NewAttributesRecord(newPVC, oldPVC, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPVC.Namespace, newPVC.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Update, nil))
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -494,7 +494,7 @@ func TestAdmitHandlesPVCUpdates(t *testing.T) {
},
}
err = handler.Admit(admission.NewAttributesRecord(newPVC, oldPVC, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPVC.Namespace, newPVC.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Update, nil))
err = handler.Validate(admission.NewAttributesRecord(newPVC, oldPVC, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPVC.Namespace, newPVC.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Update, nil))
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -591,7 +591,7 @@ func TestAdmitHandlesCreatingUpdates(t *testing.T) {
Ports: []api.ServicePort{{Port: 1234}},
},
}
err := handler.Admit(admission.NewAttributesRecord(newService, oldService, api.Kind("Service").WithVersion("version"), newService.Namespace, newService.Name, api.Resource("services").WithVersion("version"), "", admission.Update, nil))
err := handler.Validate(admission.NewAttributesRecord(newService, oldService, api.Kind("Service").WithVersion("version"), newService.Namespace, newService.Name, api.Resource("services").WithVersion("version"), "", admission.Update, nil))
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -674,7 +674,7 @@ func TestAdmitExceedQuotaLimit(t *testing.T) {
}
informerFactory.Core().InternalVersion().ResourceQuotas().Informer().GetIndexer().Add(resourceQuota)
newPod := validPod("not-allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", "")))
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err == nil {
t.Errorf("Expected an error exceeding quota")
}
@ -720,13 +720,13 @@ func TestAdmitEnforceQuotaConstraints(t *testing.T) {
informerFactory.Core().InternalVersion().ResourceQuotas().Informer().GetIndexer().Add(resourceQuota)
// verify all values are specified as required on the quota
newPod := validPod("not-allowed-pod", 1, getResourceRequirements(getResourceList("100m", "2Gi"), getResourceList("200m", "")))
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err == nil {
t.Errorf("Expected an error because the pod does not specify a memory limit")
}
// verify the requests and limits are actually valid (in this case, we fail because the limits < requests)
newPod = validPod("not-allowed-pod", 1, getResourceRequirements(getResourceList("200m", "2Gi"), getResourceList("100m", "1Gi")))
err = handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err = handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err == nil {
t.Errorf("Expected an error because the pod does not specify a memory limit")
}
@ -777,7 +777,7 @@ func TestAdmitPodInNamespaceWithoutQuota(t *testing.T) {
newPod := validPod("not-allowed-pod", 1, getResourceRequirements(getResourceList("100m", "2Gi"), getResourceList("200m", "")))
// Add to the lru cache so we do not do a live client lookup
liveLookupCache.Add(newPod.Namespace, liveLookupEntry{expiry: time.Now().Add(time.Duration(30 * time.Second)), items: []*api.ResourceQuota{}})
err = handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err = handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err != nil {
t.Errorf("Did not expect an error because the pod is in a different namespace than the quota")
}
@ -846,7 +846,7 @@ func TestAdmitBelowTerminatingQuotaLimit(t *testing.T) {
newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("100m", "2Gi"), getResourceList("", "")))
activeDeadlineSeconds := int64(30)
newPod.Spec.ActiveDeadlineSeconds = &activeDeadlineSeconds
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -950,7 +950,7 @@ func TestAdmitBelowBestEffortQuotaLimit(t *testing.T) {
// create a pod that is best effort because it does not make a request for anything
newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("", ""), getResourceList("", "")))
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -1040,7 +1040,7 @@ func TestAdmitBestEffortQuotaLimitIgnoresBurstable(t *testing.T) {
}
informerFactory.Core().InternalVersion().ResourceQuotas().Informer().GetIndexer().Add(resourceQuota)
newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", "")))
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -1130,7 +1130,7 @@ func TestAdmissionSetsMissingNamespace(t *testing.T) {
// unset the namespace
newPod.ObjectMeta.Namespace = ""
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err != nil {
t.Errorf("Got unexpected error: %v", err)
}
@ -1173,14 +1173,14 @@ func TestAdmitRejectsNegativeUsage(t *testing.T) {
informerFactory.Core().InternalVersion().ResourceQuotas().Informer().GetIndexer().Add(resourceQuota)
// verify quota rejects negative pvc storage requests
newPvc := validPersistentVolumeClaim("not-allowed-pvc", getResourceRequirements(api.ResourceList{api.ResourceStorage: resource.MustParse("-1Gi")}, api.ResourceList{}))
err := handler.Admit(admission.NewAttributesRecord(newPvc, nil, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPvc.Namespace, newPvc.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Create, nil))
err := handler.Validate(admission.NewAttributesRecord(newPvc, nil, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPvc.Namespace, newPvc.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Create, nil))
if err == nil {
t.Errorf("Expected an error because the pvc has negative storage usage")
}
// verify quota accepts non-negative pvc storage requests
newPvc = validPersistentVolumeClaim("not-allowed-pvc", getResourceRequirements(api.ResourceList{api.ResourceStorage: resource.MustParse("1Gi")}, api.ResourceList{}))
err = handler.Admit(admission.NewAttributesRecord(newPvc, nil, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPvc.Namespace, newPvc.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Create, nil))
err = handler.Validate(admission.NewAttributesRecord(newPvc, nil, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPvc.Namespace, newPvc.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Create, nil))
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -1221,7 +1221,7 @@ func TestAdmitWhenUnrelatedResourceExceedsQuota(t *testing.T) {
// create a pod that should pass existing quota
newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("", ""), getResourceList("", "")))
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -1255,7 +1255,7 @@ func TestAdmitLimitedResourceNoQuota(t *testing.T) {
evaluator: evaluator,
}
newPod := validPod("not-allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", "")))
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err == nil {
t.Errorf("Expected an error for consuming a limited resource without quota.")
}
@ -1289,7 +1289,7 @@ func TestAdmitLimitedResourceNoQuotaIgnoresNonMatchingResources(t *testing.T) {
evaluator: evaluator,
}
newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", "")))
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
@ -1337,7 +1337,7 @@ func TestAdmitLimitedResourceWithQuota(t *testing.T) {
}
indexer.Add(resourceQuota)
newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", "")))
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -1397,7 +1397,7 @@ func TestAdmitLimitedResourceWithMultipleQuota(t *testing.T) {
indexer.Add(resourceQuota1)
indexer.Add(resourceQuota2)
newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", "")))
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -1445,7 +1445,7 @@ func TestAdmitLimitedResourceWithQuotaThatDoesNotCover(t *testing.T) {
}
indexer.Add(resourceQuota)
newPod := validPod("not-allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", "")))
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if err == nil {
t.Fatalf("Expected an error since the quota did not cover cpu")
}

View File

@ -37,6 +37,8 @@ type Plugin struct {
*admission.Handler
}
var _ admission.ValidationInterface = &Plugin{}
// NewSecurityContextDeny creates a new instance of the SecurityContextDeny admission controller
func NewSecurityContextDeny() *Plugin {
return &Plugin{
@ -44,8 +46,8 @@ func NewSecurityContextDeny() *Plugin {
}
}
// Admit will deny any pod that defines SELinuxOptions or RunAsUser.
func (p *Plugin) Admit(a admission.Attributes) (err error) {
// Validate will deny any pod that defines SELinuxOptions or RunAsUser.
func (p *Plugin) Validate(a admission.Attributes) (err error) {
if a.GetSubresource() != "" || a.GetResource().GroupResource() != api.Resource("pods") {
return nil
}

View File

@ -82,7 +82,7 @@ func TestAdmission(t *testing.T) {
p.Spec.SecurityContext = tc.podSc
p.Spec.Containers[0].SecurityContext = tc.sc
err := handler.Admit(admission.NewAttributesRecord(p, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil))
err := handler.Validate(admission.NewAttributesRecord(p, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil))
if err != nil && !tc.expectError {
t.Errorf("%v: unexpected error: %v", tc.name, err)
} else if err == nil && tc.expectError {
@ -96,7 +96,7 @@ func TestAdmission(t *testing.T) {
p.Spec.InitContainers = p.Spec.Containers
p.Spec.Containers = nil
err = handler.Admit(admission.NewAttributesRecord(p, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil))
err = handler.Validate(admission.NewAttributesRecord(p, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil))
if err != nil && !tc.expectError {
t.Errorf("%v: unexpected error: %v", tc.name, err)
} else if err == nil && tc.expectError {
@ -140,7 +140,7 @@ func TestPodSecurityContextAdmission(t *testing.T) {
}
for _, test := range tests {
pod.Spec.SecurityContext = &test.securityContext
err := handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil))
err := handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil))
if test.errorExpected && err == nil {
t.Errorf("Expected error for security context %+v but did not get an error", test.securityContext)