containerized-data-importer/pkg/apiserver/webhooks/datavolume-mutate_test.go
Michael Henriksen ec52c85a25 Validating webhook and token authorization for PVC cloning (#869)
* baseline refactoring of webhook package

* datavolume clone validation webhook

* rename datavolumes/clone-init to datavolumes/source

* add RBAC doc

* updates from review

* make clone permission check exportable function

* force dumb cloning in in functional test
2019-07-09 14:02:31 -04:00

187 lines
5.6 KiB
Go

/*
* This file is part of the CDI project
*
* 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.
*
* Copyright 2019 Red Hat, Inc.
*
*/
package webhooks
import (
"crypto/rand"
"crypto/rsa"
"encoding/json"
"fmt"
"github.com/appscode/jsonpatch"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
"k8s.io/api/admission/v1beta1"
authorization "k8s.io/api/authorization/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
fakeclient "k8s.io/client-go/kubernetes/fake"
k8stesting "k8s.io/client-go/testing"
cdicorev1alpha1 "kubevirt.io/containerized-data-importer/pkg/apis/core/v1alpha1"
"kubevirt.io/containerized-data-importer/pkg/controller"
)
var _ = Describe("Mutating DataVolume Webhook", func() {
Context("with DataVolume admission review", func() {
key, _ := rsa.GenerateKey(rand.Reader, 2048)
It("should reject review without request", func() {
ar := &v1beta1.AdmissionReview{}
resp := mutateDVs(key, ar, true)
Expect(resp.Allowed).To(BeFalse())
Expect(resp.Result.Message).Should(Equal("AdmissionReview.Request is nil"))
})
It("should allow a DataVolume with HTTP source", func() {
dataVolume := newHTTPDataVolume("testDV", "http://www.example.com")
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := mutateDVs(key, ar, true)
Expect(resp.Allowed).To(BeTrue())
Expect(resp.Patch).To(BeNil())
})
It("should allow a DataVolume update with token unchanged", func() {
dataVolume := newPVCDataVolume("testDV", "testNamespace", "test")
Expect(dataVolume.Annotations).To(BeNil())
dataVolume.Annotations = make(map[string]string)
dataVolume.Annotations[controller.AnnCloneToken] = "baz"
dvBytes, _ := json.Marshal(&dataVolume)
dataVolume.Annotations["foo"] = "bar"
dvBytesUpdated, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Operation: v1beta1.Update,
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytesUpdated,
},
OldObject: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := mutateDVs(key, ar, true)
Expect(resp.Allowed).To(Equal(true))
Expect(resp.Patch).To(BeNil())
})
It("should reject a clone DataVolume", func() {
dataVolume := newPVCDataVolume("testDV", "testNamespace", "test")
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := mutateDVs(key, ar, false)
Expect(resp.Allowed).To(BeFalse())
Expect(resp.Patch).To(BeNil())
})
DescribeTable("should", func(srcNamespace string) {
dataVolume := newPVCDataVolume("testDV", srcNamespace, "test")
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := mutateDVs(key, ar, true)
Expect(resp.Allowed).To(BeTrue())
Expect(resp.Patch).ToNot(BeNil())
var patchObjs []jsonpatch.Operation
err := json.Unmarshal(resp.Patch, &patchObjs)
Expect(err).ToNot(HaveOccurred())
Expect(patchObjs).Should(HaveLen(1))
Expect(patchObjs[0].Operation).Should(Equal("add"))
Expect(patchObjs[0].Path).Should(Equal("/metadata/annotations"))
},
Entry("succeed with explicit namespace", "testNamespace"),
Entry("succeed with same (default) namespace", "default"),
Entry("succeed with empty namespace", ""),
)
})
})
func mutateDVs(key *rsa.PrivateKey, ar *v1beta1.AdmissionReview, isAuthorized bool) *v1beta1.AdmissionResponse {
client := fakeclient.NewSimpleClientset()
client.PrependReactor("create", "subjectaccessreviews", func(action k8stesting.Action) (bool, runtime.Object, error) {
if action.GetResource().Resource != "subjectaccessreviews" {
return false, nil, nil
}
sar := &authorization.SubjectAccessReview{
Status: authorization.SubjectAccessReviewStatus{
Allowed: isAuthorized,
Reason: fmt.Sprintf("isAuthorized=%t", isAuthorized),
},
}
return true, sar, nil
})
wh := NewDataVolumeMutatingWebhook(client, key)
return serve(ar, wh)
}