forked from rook/rook
adds deploy.sh script to deploy validatingwebhookconfiguration and create secrets. adds new command ceph admission-controller to start webhook servers. adds validation for various rook custom resources Signed-off-by: Vineet Badrinath <vbadrina@redhat.com>
120 lines
4.5 KiB
Go
120 lines
4.5 KiB
Go
/*
|
|
Copyright 2020 The Rook Authors. All rights reserved.
|
|
|
|
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.
|
|
*/
|
|
|
|
package admission
|
|
|
|
import (
|
|
"encoding/json"
|
|
"github.com/pkg/errors"
|
|
"github.com/rook/rook/pkg/clusterd"
|
|
"io/ioutil"
|
|
"k8s.io/api/admission/v1beta1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
|
"net/http"
|
|
)
|
|
|
|
const (
|
|
jsonContentType = `application/json`
|
|
)
|
|
|
|
var (
|
|
universalDeserializer = serializer.NewCodecFactory(runtime.NewScheme()).UniversalDeserializer()
|
|
)
|
|
|
|
// admitFunc is a callback for admission controller logic. Given an AdmissionRequest, it returns an error that will be shown when the operation is rejected.
|
|
type admitFunc func(*v1beta1.AdmissionRequest, *clusterd.Context) error
|
|
|
|
// doServeAdmitFunc parses the HTTP request for an admission controller webhook, and -- in case of a well-formed
|
|
// request -- delegates the admission control logic to the given admitFunc. The response body is then returned as raw
|
|
// bytes.
|
|
func doServeAdmitFunc(w http.ResponseWriter, r *http.Request, a *AdmissionController) ([]byte, error) {
|
|
// Step 1: Request validation. Only handle POST requests with a body and json content type.
|
|
if r.Method != http.MethodPost {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return nil, errors.Errorf("invalid method %q, only POST requess are allowed", r.Method)
|
|
}
|
|
body, err := ioutil.ReadAll(r.Body)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return nil, errors.Wrap(err, "could not read request body")
|
|
}
|
|
if contentType := r.Header.Get("Content-Type"); contentType != jsonContentType {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return nil, errors.Errorf("unsupported content type %q, only %q is supported", contentType, jsonContentType)
|
|
}
|
|
// Step 2: Parse the AdmissionReview request.
|
|
var admissionReviewReq v1beta1.AdmissionReview
|
|
if _, _, err := universalDeserializer.Decode(body, nil, &admissionReviewReq); err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return nil, errors.Wrap(err, "failed to deserialize request")
|
|
} else if admissionReviewReq.Request == nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return nil, errors.New("malformed admission review. request is nil")
|
|
}
|
|
logger.Infof("processing webhook request for resource type %s", admissionReviewReq.Request.Kind.Kind)
|
|
// Step 3: Construct the AdmissionReview response.
|
|
admissionReviewResponse := v1beta1.AdmissionReview{
|
|
Response: &v1beta1.AdmissionResponse{
|
|
UID: admissionReviewReq.Request.UID,
|
|
},
|
|
}
|
|
err = a.validator(admissionReviewReq.Request, a.context)
|
|
if err != nil {
|
|
// If the handler returned an error, incorporate the error message into the response and deny the object
|
|
// creation.
|
|
admissionReviewResponse.Response.Allowed = false
|
|
admissionReviewResponse.Response.Result = &metav1.Status{
|
|
Message: err.Error(),
|
|
}
|
|
} else {
|
|
admissionReviewResponse.Response.Allowed = true
|
|
}
|
|
// Return the AdmissionReview with a response as JSON.
|
|
bytes, err := json.Marshal(&admissionReviewResponse)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to marshal response to AdmissionReview")
|
|
}
|
|
return bytes, nil
|
|
}
|
|
|
|
// serveAdmitFunc is a wrapper around doServeAdmitFunc that adds error handling and logging.
|
|
func serveAdmitFunc(w http.ResponseWriter, r *http.Request, a *AdmissionController) {
|
|
logger.Info("handling webhook request")
|
|
|
|
var writeErr error
|
|
if bytes, err := doServeAdmitFunc(w, r, a); err != nil {
|
|
logger.Errorf("failed to handle webhook request. %v", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_, writeErr = w.Write([]byte(err.Error()))
|
|
} else {
|
|
logger.Info("webhook request handled successfully")
|
|
_, writeErr = w.Write(bytes)
|
|
}
|
|
|
|
if writeErr != nil {
|
|
logger.Errorf("failed to write response to webhook request. %v", writeErr)
|
|
}
|
|
}
|
|
|
|
// admitFuncHandler takes an admitFunc and wraps it into a http.Handler by means of calling serveAdmitFunc.
|
|
func admitFuncHandler(a *AdmissionController) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
serveAdmitFunc(w, r, a)
|
|
})
|
|
}
|