Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vm webhook limits #477

Merged
merged 15 commits into from
Oct 17, 2023
3 changes: 2 additions & 1 deletion cmd/webhook/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ func main() {
}
mux := http.NewServeMux()

mux.HandleFunc("/mutate-users-pods", mutatingwebhook.HandleMutate)
mux.HandleFunc("/mutate-users-pods", mutatingwebhook.HandleMutateUserPods)
mux.HandleFunc("/mutate-virtual-machines", mutatingwebhook.HandleMutateVirtualMachines)
mux.HandleFunc("/validate-users-rolebindings", rolebindingValidator.HandleValidate)
mux.HandleFunc("/validate-users-checlusters", checlusterValidator.HandleValidate)
mux.HandleFunc("/validate-spacebindingrequests", spacebindingrequestValidator.HandleValidate)
Expand Down
32 changes: 32 additions & 0 deletions deploy/webhook/member-operator-webhook.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ objects:
- get
- list
- watch
- apiGroups:
- "kubevirt.io"
resources:
- "virtualmachines"
verbs:
- get
- list
- watch
- apiVersion: v1
kind: ServiceAccount
metadata:
Expand Down Expand Up @@ -137,6 +145,30 @@ objects:
namespaceSelector:
matchLabels:
toolchain.dev.openshift.com/provider: codeready-toolchain
- name: users.virtualmachines.webhook.sandbox
admissionReviewVersions:
- v1
clientConfig:
caBundle: ${CA_BUNDLE}
service:
name: member-operator-webhook
namespace: ${NAMESPACE}
path: "/mutate-virtual-machines"
port: 443
matchPolicy: Equivalent
rules:
- operations: ["CREATE"]
rajivnathan marked this conversation as resolved.
Show resolved Hide resolved
apiGroups: ["kubevirt.io"]
apiVersions: ["v1"]
resources: ["virtualmachines"]
scope: "Namespaced"
sideEffects: None
timeoutSeconds: 5
reinvocationPolicy: Never
failurePolicy: Ignore
alexeykazakov marked this conversation as resolved.
Show resolved Hide resolved
namespaceSelector:
matchLabels:
toolchain.dev.openshift.com/provider: codeready-toolchain
- apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
Expand Down
4 changes: 2 additions & 2 deletions pkg/webhook/deploy/deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func deployment(namespace, sa string, image string) string {
}

func mutatingWebhookConfig(namespace, caBundle string) string {
return fmt.Sprintf(`{"apiVersion":"admissionregistration.k8s.io/v1","kind":"MutatingWebhookConfiguration","metadata":{"name":"member-operator-webhook","labels":{"app":"member-operator-webhook","toolchain.dev.openshift.com/provider":"codeready-toolchain"}},"webhooks":[{"name":"users.pods.webhook.sandbox","admissionReviewVersions":["v1"],"clientConfig":{"caBundle":"%s","service":{"name":"member-operator-webhook","namespace":"%s","path":"/mutate-users-pods","port":443}},"matchPolicy":"Equivalent","rules":[{"operations":["CREATE"],"apiGroups":[""],"apiVersions":["v1"],"resources":["pods"],"scope":"Namespaced"}],"sideEffects":"None","timeoutSeconds":5,"reinvocationPolicy":"Never","failurePolicy":"Ignore","namespaceSelector":{"matchLabels":{"toolchain.dev.openshift.com/provider":"codeready-toolchain"}}}]}`, caBundle, namespace)
return fmt.Sprintf(`{"apiVersion":"admissionregistration.k8s.io/v1","kind":"MutatingWebhookConfiguration","metadata":{"name":"member-operator-webhook","labels":{"app":"member-operator-webhook","toolchain.dev.openshift.com/provider":"codeready-toolchain"}},"webhooks":[{"name":"users.pods.webhook.sandbox","admissionReviewVersions":["v1"],"clientConfig":{"caBundle":"%[1]s","service":{"name":"member-operator-webhook","namespace":"%[2]s","path":"/mutate-users-pods","port":443}},"matchPolicy":"Equivalent","rules":[{"operations":["CREATE"],"apiGroups":[""],"apiVersions":["v1"],"resources":["pods"],"scope":"Namespaced"}],"sideEffects":"None","timeoutSeconds":5,"reinvocationPolicy":"Never","failurePolicy":"Ignore","namespaceSelector":{"matchLabels":{"toolchain.dev.openshift.com/provider":"codeready-toolchain"}}},{"name":"users.virtualmachines.webhook.sandbox","admissionReviewVersions":["v1"],"clientConfig":{"caBundle":"%[1]s","service":{"name":"member-operator-webhook","namespace":"%[2]s","path":"/mutate-virtual-machines","port":443}},"matchPolicy":"Equivalent","rules":[{"operations":["CREATE"],"apiGroups":["kubevirt.io"],"apiVersions":["v1"],"resources":["virtualmachines"],"scope":"Namespaced"}],"sideEffects":"None","timeoutSeconds":5,"reinvocationPolicy":"Never","failurePolicy":"Ignore","namespaceSelector":{"matchLabels":{"toolchain.dev.openshift.com/provider":"codeready-toolchain"}}}]}`, caBundle, namespace)
}

func validatingWebhookConfig(namespace, caBundle string) string {
Expand All @@ -235,7 +235,7 @@ func serviceAccount(namespace string) string {
}

func clusterRole() string {
return `{"apiVersion": "rbac.authorization.k8s.io/v1","kind": "ClusterRole","metadata": {"creationTimestamp": null,"name": "webhook-role"}, "rules": [{"apiGroups": ["user.openshift.io"],"resources": ["identities","useridentitymappings","users"],"verbs": ["get","list","watch"]},{"apiGroups": ["toolchain.dev.openshift.com"],"resources": ["spacebindingrequests"],"verbs": ["get","list","watch"]}]}`
return `{"apiVersion": "rbac.authorization.k8s.io/v1","kind": "ClusterRole","metadata": {"creationTimestamp": null,"name": "webhook-role"}, "rules": [{"apiGroups": ["user.openshift.io"],"resources": ["identities","useridentitymappings","users"],"verbs": ["get","list","watch"]},{"apiGroups": ["toolchain.dev.openshift.com"],"resources": ["spacebindingrequests"],"verbs": ["get","list","watch"]},{"apiGroups": ["kubevirt.io"],"resources": ["virtualmachines"],"verbs": ["get","list","watch"]}]}`
}

func clusterRoleBinding(namespace string) string {
Expand Down
89 changes: 15 additions & 74 deletions pkg/webhook/mutatingwebhook/mutate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,87 +5,59 @@ import (
"fmt"
"io"
"net/http"
"os"

"github.com/pkg/errors"
"github.com/go-logr/logr"
v1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
logf "sigs.k8s.io/controller-runtime/pkg/log"
)

var (
runtimeScheme = runtime.NewScheme()
codecs = serializer.NewCodecFactory(runtimeScheme)
deserializer = codecs.UniversalDeserializer()

log = logf.Log.WithName("users_pods_mutating_webhook")

patchContent = patchedContent()
)

const (
priority = int32(-3)
priorityClassName = "sandbox-users-pods"
)

func patchedContent() []byte {
patchItems := []map[string]interface{}{
{
"op": "replace",
"path": "/spec/priorityClassName",
"value": priorityClassName,
},
{
"op": "replace",
"path": "/spec/priority",
"value": priority,
},
}

patchContent, err := json.Marshal(patchItems)
if err != nil {
log.Error(err, "unable marshal patch items")
os.Exit(1)
}
return patchContent
}
type mutateHandler func(admReview v1.AdmissionReview) *v1.AdmissionResponse

func HandleMutate(w http.ResponseWriter, r *http.Request) {
func handleMutate(logger logr.Logger, w http.ResponseWriter, r *http.Request, mutator mutateHandler) {
var respBody []byte
body, err := io.ReadAll(r.Body)
defer func() {
if err := r.Body.Close(); err != nil {
log.Error(err, "unable to close the body")
logger.Error(err, "unable to close the body")
}
}()
if err != nil {
log.Error(err, "unable to read the body of the request")
logger.Error(err, "unable to read the body of the request")
w.WriteHeader(http.StatusInternalServerError)
respBody = []byte("unable to read the body of the request")
} else {
// mutate the request
respBody = mutate(body)
respBody = mutate(logger, body, mutator)
w.WriteHeader(http.StatusOK)
}
if _, err := io.WriteString(w, string(respBody)); err != nil {
log.Error(err, "unable to write response")
logger.Error(err, "unable to write response")
}
}

func mutate(body []byte) []byte {
func mutate(logger logr.Logger, body []byte, mutator mutateHandler) []byte {
admReview := v1.AdmissionReview{}
if _, _, err := deserializer.Decode(body, nil, &admReview); err != nil {
log.Error(err, "unable to deserialize the admission review object", "body", string(body))
logger.Error(err, "unable to deserialize the admission review object", "body", string(body))
admReview.Response = responseWithError(err)
} else if admReview.Request == nil {
err := fmt.Errorf("admission review request is nil")
logger.Error(err, "cannot read the admission review request", "AdmissionReview", admReview)
admReview.Response = responseWithError(err)
} else {
admReview.Response = createAdmissionReviewResponse(admReview)
admReview.Response = mutator(admReview)
}
responseBody, err := json.Marshal(admReview)
if err != nil {
log.Error(err, "unable to marshal the admission review with response", "admissionReview", admReview)
logger.Error(err, "unable to marshal the admission review with response", "admissionReview", admReview)
}
return responseBody
}
Expand All @@ -97,34 +69,3 @@ func responseWithError(err error) *v1.AdmissionResponse {
},
}
}

func createAdmissionReviewResponse(admReview v1.AdmissionReview) *v1.AdmissionResponse {
if admReview.Request == nil {
err := fmt.Errorf("admission review request is nil")
log.Error(err, "cannot read the admission review request", "AdmissionReview", admReview)
return responseWithError(err)
}

// let's unmarshal the object to be sure that it's a pod
var pod *corev1.Pod
if err := json.Unmarshal(admReview.Request.Object.Raw, &pod); err != nil {
log.Error(err, "unable unmarshal pod json object", "AdmissionReview", admReview)
return responseWithError(errors.Wrapf(err, "unable unmarshal pod json object - raw request object: %v", admReview.Request.Object.Raw))
}

patchType := v1.PatchTypeJSONPatch
resp := &v1.AdmissionResponse{
Allowed: true,
UID: admReview.Request.UID,
PatchType: &patchType,
}
resp.AuditAnnotations = map[string]string{
"users_pods_mutating_webhook": "the sandbox-users-pods PriorityClass was set",
}

// instead of changing the pod object we need to tell K8s how to change the object
resp.Patch = patchContent

log.Info("the sandbox-users-pods PriorityClass was set to the pod", "pod-name", pod.Name, "namespace", pod.Namespace)
return resp
}
Loading