1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 16:06:56 +00:00
kyverno/pkg/webhooks/server.go

194 lines
6.4 KiB
Go
Raw Normal View History

2019-05-13 21:33:01 +03:00
package webhooks
import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"time"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/annotations"
"github.com/nirmata/kyverno/pkg/client/listers/policy/v1alpha1"
"github.com/nirmata/kyverno/pkg/config"
client "github.com/nirmata/kyverno/pkg/dclient"
2019-06-26 18:04:50 -07:00
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/sharedinformer"
tlsutils "github.com/nirmata/kyverno/pkg/tls"
2019-07-31 17:43:46 -07:00
"github.com/nirmata/kyverno/pkg/utils"
"github.com/nirmata/kyverno/pkg/violation"
v1beta1 "k8s.io/api/admission/v1beta1"
)
// WebhookServer contains configured TLS server with MutationWebhook.
// MutationWebhook gets policies from policyController and takes control of the cluster with kubeclient.
type WebhookServer struct {
server http.Server
client *client.Client
policyLister v1alpha1.PolicyLister
eventController event.Generator
violationBuilder violation.Generator
annotationsController annotations.Controller
webhookRegistrationClient *WebhookRegistrationClient
filterK8Resources []utils.K8Resource
}
// NewWebhookServer creates new instance of WebhookServer accordingly to given configuration
// Policy Controller and Kubernetes Client should be initialized in configuration
func NewWebhookServer(
client *client.Client,
tlsPair *tlsutils.TlsPemPair,
2019-06-17 23:41:18 -07:00
shareInformer sharedinformer.PolicyInformer,
2019-06-26 18:04:50 -07:00
eventController event.Generator,
2019-07-15 14:49:22 -07:00
violationBuilder violation.Generator,
annotationsController annotations.Controller,
webhookRegistrationClient *WebhookRegistrationClient,
2019-07-31 17:43:46 -07:00
filterK8Resources string) (*WebhookServer, error) {
if tlsPair == nil {
return nil, errors.New("NewWebhookServer is not initialized properly")
}
var tlsConfig tls.Config
pair, err := tls.X509KeyPair(tlsPair.Certificate, tlsPair.PrivateKey)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{pair}
ws := &WebhookServer{
client: client,
policyLister: shareInformer.GetLister(),
eventController: eventController,
violationBuilder: violationBuilder,
annotationsController: annotationsController,
webhookRegistrationClient: webhookRegistrationClient,
filterK8Resources: utils.ParseKinds(filterK8Resources),
}
mux := http.NewServeMux()
mux.HandleFunc(config.MutatingWebhookServicePath, ws.serve)
mux.HandleFunc(config.ValidatingWebhookServicePath, ws.serve)
mux.HandleFunc(config.PolicyValidatingWebhookServicePath, ws.serve)
ws.server = http.Server{
Addr: ":443", // Listen on port for HTTPS requests
TLSConfig: &tlsConfig,
Handler: mux,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
}
return ws, nil
}
// Main server endpoint for all requests
func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
admissionReview := ws.bodyToAdmissionReview(r, w)
if admissionReview == nil {
return
}
admissionReview.Response = &v1beta1.AdmissionResponse{
Allowed: true,
}
2019-06-19 14:05:23 -07:00
// Do not process the admission requests for kinds that are in filterKinds for filtering
2019-07-31 17:43:46 -07:00
if !utils.SkipFilteredResourcesReq(admissionReview.Request, ws.filterK8Resources) {
// if the resource is being deleted we need to clear any existing Policy Violations
// TODO: can report to the user that we clear the violation corresponding to this resource
2019-08-08 13:09:40 -07:00
if admissionReview.Request.Operation == v1beta1.Delete && admissionReview.Request.Kind.Kind != policyKind {
// Resource DELETE
err := ws.removePolicyViolation(admissionReview.Request)
if err != nil {
glog.Info(err)
}
admissionReview.Response = &v1beta1.AdmissionResponse{
Allowed: true,
}
admissionReview.Response.UID = admissionReview.Request.UID
} else {
// Resource CREATE
// Resource UPDATE
switch r.URL.Path {
case config.MutatingWebhookServicePath:
admissionReview.Response = ws.HandleMutation(admissionReview.Request)
case config.ValidatingWebhookServicePath:
admissionReview.Response = ws.HandleValidation(admissionReview.Request)
case config.PolicyValidatingWebhookServicePath:
admissionReview.Response = ws.HandlePolicyValidation(admissionReview.Request)
}
2019-06-17 23:41:18 -07:00
}
}
admissionReview.Response.UID = admissionReview.Request.UID
responseJSON, err := json.Marshal(admissionReview)
if err != nil {
http.Error(w, fmt.Sprintf("Could not encode response: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if _, err := w.Write(responseJSON); err != nil {
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
}
}
// RunAsync TLS server in separate thread and returns control immediately
func (ws *WebhookServer) RunAsync() {
go func(ws *WebhookServer) {
glog.V(3).Infof("serving on %s\n", ws.server.Addr)
err := ws.server.ListenAndServeTLS("", "")
if err != nil {
glog.Fatalf("error serving TLS: %v\n", err)
}
}(ws)
glog.Info("Started Webhook Server")
}
// Stop TLS server and returns control after the server is shut down
func (ws *WebhookServer) Stop() {
err := ws.server.Shutdown(context.Background())
if err != nil {
// Error from closing listeners, or context timeout:
glog.Info("Server Shutdown error: ", err)
ws.server.Close()
}
}
// bodyToAdmissionReview creates AdmissionReview object from request body
// Answers to the http.ResponseWriter if request is not valid
func (ws *WebhookServer) bodyToAdmissionReview(request *http.Request, writer http.ResponseWriter) *v1beta1.AdmissionReview {
var body []byte
if request.Body != nil {
if data, err := ioutil.ReadAll(request.Body); err == nil {
body = data
}
}
if len(body) == 0 {
glog.Error("Error: empty body")
http.Error(writer, "empty body", http.StatusBadRequest)
return nil
}
contentType := request.Header.Get("Content-Type")
if contentType != "application/json" {
glog.Error("Error: invalid Content-Type: ", contentType)
http.Error(writer, "invalid Content-Type, expect `application/json`", http.StatusUnsupportedMediaType)
return nil
}
admissionReview := &v1beta1.AdmissionReview{}
if err := json.Unmarshal(body, &admissionReview); err != nil {
glog.Errorf("Error: Can't decode body as AdmissionReview: %v", err)
http.Error(writer, "Can't decode body as AdmissionReview", http.StatusExpectationFailed)
return nil
}
2019-06-05 17:43:59 -07:00
return admissionReview
}