1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

refactor: webhooks package (#3516)

* refactor: use more policy interface

Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com>

* refactor: migrate to policy interface

Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com>

* refactor: webhooks package

Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com>
This commit is contained in:
Charles-Edouard Brétéché 2022-03-31 17:34:10 +02:00 committed by GitHub
parent 9fc65fa5a7
commit 6e813a6b9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 547 additions and 571 deletions

View file

@ -62,3 +62,28 @@ func ResponseWithMessageAndPatch(allowed bool, msg string, patch []byte) *v1beta
r.Patch = patch
return r
}
func ResponseStatus(allowed bool, status, msg string) *v1beta1.AdmissionResponse {
r := Response(allowed)
r.Result = &metav1.Status{
Status: status,
Message: msg,
}
return r
}
func ResponseFailure(allowed bool, msg string) *v1beta1.AdmissionResponse {
return ResponseStatus(allowed, metav1.StatusFailure, msg)
}
func ResponseSuccess(allowed bool, msg string) *v1beta1.AdmissionResponse {
return ResponseStatus(allowed, metav1.StatusSuccess, msg)
}
func ResponseSuccessWithPatch(allowed bool, msg string, patch []byte) *v1beta1.AdmissionResponse {
r := ResponseSuccess(allowed, msg)
if len(patch) > 0 {
r.Patch = patch
}
return r
}

View file

@ -1,14 +0,0 @@
package webhooks
import (
"k8s.io/api/admission/v1beta1"
)
func (ws *WebhookServer) verifyHandler(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger := ws.log.WithName("verifyHandler").WithValues("action", "verify", "kind", request.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation, "gvk", request.Kind.String())
logger.V(3).Info("incoming request", "last admission request timestamp", ws.webhookMonitor.Time())
return &v1beta1.AdmissionResponse{
Allowed: true,
}
}

View file

@ -7,9 +7,12 @@ import (
"github.com/go-logr/logr"
wildcard "github.com/kyverno/go-wildcard"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
v1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/autogen"
enginectx "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/response"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/pkg/errors"
yamlv2 "gopkg.in/yaml.v2"
"k8s.io/api/admission/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -125,6 +128,14 @@ func (i *ArrayFlags) Set(value string) error {
return nil
}
// patchRequest applies patches to the request.Object and returns a new copy of the request
func patchRequest(patches []byte, request *v1beta1.AdmissionRequest, logger logr.Logger) *v1beta1.AdmissionRequest {
patchedResource := processResourceWithPatches(patches, request.Object.Raw, logger)
newRequest := request.DeepCopy()
newRequest.Object.Raw = patchedResource
return newRequest
}
func processResourceWithPatches(patch []byte, resource []byte, log logr.Logger) []byte {
if patch == nil {
return resource
@ -246,3 +257,17 @@ func excludeKyvernoResources(kind string) bool {
return false
}
}
func newVariablesContext(request *v1beta1.AdmissionRequest, userRequestInfo *v1.RequestInfo) (*enginectx.Context, error) {
ctx := enginectx.NewContext()
if err := ctx.AddRequest(request); err != nil {
return nil, errors.Wrap(err, "failed to load incoming request in context")
}
if err := ctx.AddUserInfo(*userRequestInfo); err != nil {
return nil, errors.Wrap(err, "failed to load userInfo in context")
}
if err := ctx.AddServiceAccount(userRequestInfo.AdmissionUserInfo.Username); err != nil {
return nil, errors.Wrap(err, "failed to load service account in context")
}
return ctx, nil
}

225
pkg/webhooks/handlers.go Normal file
View file

@ -0,0 +1,225 @@
package webhooks
import (
"fmt"
"net/http"
"reflect"
"strings"
"time"
"github.com/go-logr/logr"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/common"
"github.com/kyverno/kyverno/pkg/engine"
enginectx "github.com/kyverno/kyverno/pkg/engine/context"
policyvalidate "github.com/kyverno/kyverno/pkg/policy"
"github.com/kyverno/kyverno/pkg/policycache"
"github.com/kyverno/kyverno/pkg/policymutation"
"github.com/kyverno/kyverno/pkg/userinfo"
"github.com/kyverno/kyverno/pkg/utils"
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
"github.com/kyverno/kyverno/pkg/webhooks/handlers"
"k8s.io/api/admission/v1beta1"
)
// TODO: use admission review sub resource ?
func isStatusUpdate(old, new kyverno.PolicyInterface) bool {
if !reflect.DeepEqual(old.GetAnnotations(), new.GetAnnotations()) {
return false
}
if !reflect.DeepEqual(old.GetLabels(), new.GetLabels()) {
return false
}
if !reflect.DeepEqual(old.GetSpec(), new.GetSpec()) {
return false
}
return true
}
func errorResponse(logger logr.Logger, err error, message string) *v1beta1.AdmissionResponse {
logger.Error(err, message)
return admissionutils.ResponseFailure(false, message+": "+err.Error())
}
func setupLogger(logger logr.Logger, name string, request *v1beta1.AdmissionRequest) logr.Logger {
return logger.WithName("MutateWebhook").WithValues(
"uid", request.UID,
"kind", request.Kind,
"namespace", request.Namespace,
"name", request.Name,
"operation", request.Operation,
"gvk", request.Kind.String(),
)
}
func (ws *WebhookServer) admissionHandler(filter bool, inner handlers.AdmissionHandler) http.HandlerFunc {
if filter {
inner = handlers.Filter(ws.configHandler, inner)
}
return handlers.Monitor(ws.webhookMonitor, handlers.Admission(ws.log, inner))
}
func (ws *WebhookServer) policyMutation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger := setupLogger(ws.log, "policy mutation", request)
policy, oldPolicy, err := admissionutils.GetPolicies(request)
if err != nil {
logger.Error(err, "failed to unmarshal policies from admission request")
return admissionutils.ResponseWithMessage(true, fmt.Sprintf("failed to default value, check kyverno controller logs for details: %v", err))
}
if oldPolicy != nil && isStatusUpdate(oldPolicy, policy) {
logger.V(4).Info("skip policy mutation on status update")
return admissionutils.Response(true)
}
startTime := time.Now()
logger.V(3).Info("start policy change mutation")
defer logger.V(3).Info("finished policy change mutation", "time", time.Since(startTime).String())
// Generate JSON Patches for defaults
if patches, updateMsgs := policymutation.GenerateJSONPatchesForDefaults(policy, logger); len(patches) != 0 {
return admissionutils.ResponseWithMessageAndPatch(true, strings.Join(updateMsgs, "'"), patches)
}
return admissionutils.Response(true)
}
//policyValidation performs the validation check on policy resource
func (ws *WebhookServer) policyValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger := setupLogger(ws.log, "policy validation", request)
policy, oldPolicy, err := admissionutils.GetPolicies(request)
if err != nil {
logger.Error(err, "failed to unmarshal policies from admission request")
return admissionutils.ResponseWithMessage(true, fmt.Sprintf("failed to validate policy, check kyverno controller logs for details: %v", err))
}
if oldPolicy != nil && isStatusUpdate(oldPolicy, policy) {
logger.V(4).Info("skip policy validation on status update")
return admissionutils.Response(true)
}
startTime := time.Now()
logger.V(3).Info("start policy change validation")
defer logger.V(3).Info("finished policy change validation", "time", time.Since(startTime).String())
response, err := policyvalidate.Validate(policy, ws.client, false, ws.openAPIController)
if err != nil {
logger.Error(err, "policy validation errors")
return admissionutils.ResponseWithMessage(true, err.Error())
}
if response != nil && len(response.Warnings) != 0 {
return response
}
return admissionutils.Response(true)
}
// resourceMutation mutates resource
func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger := setupLogger(ws.log, "resource mutation", request)
if excludeKyvernoResources(request.Kind.Kind) {
return admissionutils.ResponseSuccess(true, "")
}
if request.Operation == v1beta1.Delete {
resource, err := utils.ConvertResource(request.OldObject.Raw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace)
if err == nil {
ws.prGenerator.Add(buildDeletionPrInfo(resource))
} else {
logger.Info(fmt.Sprintf("Converting oldObject failed: %v", err))
}
return admissionutils.ResponseSuccess(true, "")
}
logger.V(4).Info("received an admission request in mutating webhook")
requestTime := time.Now().Unix()
mutatePolicies := ws.pCache.GetPolicies(policycache.Mutate, request.Kind.Kind, request.Namespace)
verifyImagesPolicies := ws.pCache.GetPolicies(policycache.VerifyImages, request.Kind.Kind, request.Namespace)
if len(mutatePolicies) == 0 && len(verifyImagesPolicies) == 0 {
logger.V(4).Info("no policies matched admission request")
return admissionutils.ResponseSuccess(true, "")
}
addRoles := containsRBACInfo(mutatePolicies)
policyContext, err := ws.buildPolicyContext(request, addRoles)
if err != nil {
logger.Error(err, "failed to build policy context")
return admissionutils.ResponseFailure(false, err.Error())
}
// update container images to a canonical form
if err := enginectx.MutateResourceWithImageInfo(request.Object.Raw, policyContext.JSONContext); err != nil {
ws.log.Error(err, "failed to patch images info to resource, policies that mutate images may be impacted")
}
mutatePatches := ws.applyMutatePolicies(request, policyContext, mutatePolicies, requestTime, logger)
newRequest := patchRequest(mutatePatches, request, logger)
imagePatches, err := ws.applyImageVerifyPolicies(newRequest, policyContext, verifyImagesPolicies, logger)
if err != nil {
logger.Error(err, "image verification failed")
return admissionutils.ResponseFailure(false, err.Error())
}
var patches = append(mutatePatches, imagePatches...)
return admissionutils.ResponseSuccessWithPatch(true, "", patches)
}
func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger := setupLogger(ws.log, "resource validation", request)
if request.Operation == v1beta1.Delete {
ws.handleDelete(request)
}
if excludeKyvernoResources(request.Kind.Kind) {
return admissionutils.ResponseSuccess(true, "")
}
logger.V(6).Info("received an admission request in validating webhook")
// timestamp at which this admission request got triggered
requestTime := time.Now().Unix()
policies := ws.pCache.GetPolicies(policycache.ValidateEnforce, request.Kind.Kind, "")
// Get namespace policies from the cache for the requested resource namespace
nsPolicies := ws.pCache.GetPolicies(policycache.ValidateEnforce, request.Kind.Kind, request.Namespace)
policies = append(policies, nsPolicies...)
generatePolicies := ws.pCache.GetPolicies(policycache.Generate, request.Kind.Kind, request.Namespace)
if len(generatePolicies) == 0 && request.Operation == v1beta1.Update {
// handle generate source resource updates
go ws.handleUpdatesForGenerateRules(request, []kyverno.PolicyInterface{})
}
var roles, clusterRoles []string
if containsRBACInfo(policies, generatePolicies) {
var err error
roles, clusterRoles, err = userinfo.GetRoleRef(ws.rbLister, ws.crbLister, request, ws.configHandler)
if err != nil {
return errorResponse(logger, err, "failed to fetch RBAC data")
}
}
userRequestInfo := kyverno.RequestInfo{
Roles: roles,
ClusterRoles: clusterRoles,
AdmissionUserInfo: *request.UserInfo.DeepCopy(),
}
ctx, err := newVariablesContext(request, &userRequestInfo)
if err != nil {
return errorResponse(logger, err, "failed create policy rule context")
}
namespaceLabels := make(map[string]string)
if request.Kind.Kind != "Namespace" && request.Namespace != "" {
namespaceLabels = common.GetNamespaceSelectorsFromNamespaceLister(request.Kind.Kind, request.Namespace, ws.nsLister, logger)
}
newResource, oldResource, err := utils.ExtractResources(nil, request)
if err != nil {
return errorResponse(logger, err, "failed create parse resource")
}
if err := ctx.AddImageInfo(&newResource); err != nil {
return errorResponse(logger, err, "failed add image information to policy rule context")
}
policyContext := &engine.PolicyContext{
NewResource: newResource,
OldResource: oldResource,
AdmissionInfo: userRequestInfo,
ExcludeGroupRole: ws.configHandler.GetExcludeGroupRole(),
ExcludeResourceFunc: ws.configHandler.ToFilter,
JSONContext: ctx,
Client: ws.client,
}
vh := &validationHandler{
log: ws.log,
eventGen: ws.eventGen,
prGenerator: ws.prGenerator,
}
ok, msg := vh.handleValidation(ws.promConfig, request, policies, policyContext, namespaceLabels, requestTime)
if !ok {
logger.Info("admission request denied")
return admissionutils.ResponseFailure(false, msg)
}
// push admission request to audit handler, this won't block the admission request
ws.auditHandler.Add(request.DeepCopy())
// process generate policies
ws.applyGeneratePolicies(request, policyContext, generatePolicies, requestTime, logger)
return admissionutils.ResponseSuccess(true, "")
}

View file

@ -0,0 +1,96 @@
package handlers
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/pkg/config"
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
"github.com/kyverno/kyverno/pkg/webhookconfig"
"k8s.io/api/admission/v1beta1"
)
type AdmissionHandler func(*v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse
func Admission(logger logr.Logger, inner AdmissionHandler) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
startTime := time.Now()
if request.Body == nil {
logger.Info("empty body", "req", request.URL.String())
http.Error(writer, "empty body", http.StatusBadRequest)
return
}
defer request.Body.Close()
body, err := ioutil.ReadAll(request.Body)
if err != nil {
logger.Info("failed to read HTTP body", "req", request.URL.String())
http.Error(writer, "failed to read HTTP body", http.StatusBadRequest)
return
}
contentType := request.Header.Get("Content-Type")
if contentType != "application/json" {
logger.Info("invalid Content-Type", "contextType", contentType)
http.Error(writer, "invalid Content-Type, expect `application/json`", http.StatusUnsupportedMediaType)
return
}
admissionReview := &v1beta1.AdmissionReview{}
if err := json.Unmarshal(body, &admissionReview); err != nil {
logger.Error(err, "failed to decode request body to type 'AdmissionReview")
http.Error(writer, "Can't decode body as AdmissionReview", http.StatusExpectationFailed)
return
}
logger = logger.WithName("handlerFunc").WithValues(
"kind", admissionReview.Request.Kind,
"namespace", admissionReview.Request.Namespace,
"name", admissionReview.Request.Name,
"operation", admissionReview.Request.Operation,
"uid", admissionReview.Request.UID,
)
admissionReview.Response = &v1beta1.AdmissionResponse{
Allowed: true,
UID: admissionReview.Request.UID,
}
adminssionResponse := inner(admissionReview.Request)
if adminssionResponse != nil {
admissionReview.Response = adminssionResponse
}
responseJSON, err := json.Marshal(admissionReview)
if err != nil {
http.Error(writer, fmt.Sprintf("Could not encode response: %v", err), http.StatusInternalServerError)
return
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
if _, err := writer.Write(responseJSON); err != nil {
http.Error(writer, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
}
logger.V(4).Info("admission review request processed", "time", time.Since(startTime).String())
}
}
func Filter(c config.Interface, inner AdmissionHandler) AdmissionHandler {
return func(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
if c.ToFilter(request.Kind.Kind, request.Namespace, request.Name) {
return nil
}
return inner(request)
}
}
func Verify(m *webhookconfig.Monitor, logger logr.Logger) AdmissionHandler {
return func(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger = logger.WithName("verifyHandler").WithValues(
"action", "verify",
"kind", request.Kind,
"namespace", request.Namespace,
"name", request.Name,
"operation", request.Operation,
"gvk", request.Kind.String(),
)
logger.V(3).Info("incoming request", "last admission request timestamp", m.Time())
return admissionutils.Response(true)
}
}

View file

@ -0,0 +1,15 @@
package handlers
import (
"net/http"
"time"
"github.com/kyverno/kyverno/pkg/webhookconfig"
)
func Monitor(m *webhookconfig.Monitor, inner http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
m.SetTime(time.Now())
inner(w, r)
}
}

View file

@ -0,0 +1,14 @@
package handlers
import "net/http"
func Probe(check func() error) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if check != nil {
if err := check(); err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
}
w.WriteHeader(http.StatusOK)
}
}

120
pkg/webhooks/metrics.go Normal file
View file

@ -0,0 +1,120 @@
package webhooks
import (
"github.com/go-logr/logr"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
v1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/metrics"
admissionRequests "github.com/kyverno/kyverno/pkg/metrics/admissionrequests"
admissionReviewDuration "github.com/kyverno/kyverno/pkg/metrics/admissionreviewduration"
policyExecutionDuration "github.com/kyverno/kyverno/pkg/metrics/policyexecutionduration"
policyResults "github.com/kyverno/kyverno/pkg/metrics/policyresults"
)
func registerAdmissionReviewDurationMetricMutate(logger logr.Logger, promConfig metrics.PromConfig, requestOperation string, engineResponses []*response.EngineResponse, admissionReviewLatencyDuration int64) {
resourceRequestOperationPromAlias, err := admissionReviewDuration.ParseResourceRequestOperation(requestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_review_duration_seconds metrics")
}
if err := admissionReviewDuration.ParsePromConfig(promConfig).ProcessEngineResponses(engineResponses, admissionReviewLatencyDuration, resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_review_duration_seconds metrics")
}
}
func registerAdmissionRequestsMetricMutate(logger logr.Logger, promConfig metrics.PromConfig, requestOperation string, engineResponses []*response.EngineResponse) {
resourceRequestOperationPromAlias, err := admissionReviewDuration.ParseResourceRequestOperation(requestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_requests_total metrics")
}
if err := admissionRequests.ParsePromConfig(promConfig).ProcessEngineResponses(engineResponses, resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_requests_total metrics")
}
}
func registerAdmissionReviewDurationMetricGenerate(logger logr.Logger, promConfig metrics.PromConfig, requestOperation string, latencyReceiver *chan int64, engineResponsesReceiver *chan []*response.EngineResponse) {
defer close(*latencyReceiver)
defer close(*engineResponsesReceiver)
engineResponses := <-(*engineResponsesReceiver)
resourceRequestOperationPromAlias, err := admissionReviewDuration.ParseResourceRequestOperation(requestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_review_duration_seconds metrics")
}
// this goroutine will keep on waiting here till it doesn't receive the admission review latency int64 from the other goroutine i.e. ws.HandleGenerate
admissionReviewLatencyDuration := <-(*latencyReceiver)
if err := admissionReviewDuration.ParsePromConfig(promConfig).ProcessEngineResponses(engineResponses, admissionReviewLatencyDuration, resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_review_duration_seconds metrics")
}
}
func registerAdmissionRequestsMetricGenerate(logger logr.Logger, promConfig metrics.PromConfig, requestOperation string, engineResponsesReceiver *chan []*response.EngineResponse) {
defer close(*engineResponsesReceiver)
engineResponses := <-(*engineResponsesReceiver)
resourceRequestOperationPromAlias, err := admissionReviewDuration.ParseResourceRequestOperation(requestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_requests_total metrics")
}
if err := admissionRequests.ParsePromConfig(promConfig).ProcessEngineResponses(engineResponses, resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_requests_total metrics")
}
}
func registerPolicyResultsMetricValidation(promConfig *metrics.PromConfig, logger logr.Logger, requestOperation string, policy v1.PolicyInterface, engineResponse response.EngineResponse) {
resourceRequestOperationPromAlias, err := policyResults.ParseResourceRequestOperation(requestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_results_total metrics for the above policy", "name", policy.GetName())
}
if err := policyResults.ParsePromConfig(*promConfig).ProcessEngineResponse(policy, engineResponse, metrics.AdmissionRequest, resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_results_total metrics for the above policy", "name", policy.GetName())
}
}
func registerPolicyExecutionDurationMetricValidate(promConfig *metrics.PromConfig, logger logr.Logger, requestOperation string, policy v1.PolicyInterface, engineResponse response.EngineResponse) {
resourceRequestOperationPromAlias, err := policyExecutionDuration.ParseResourceRequestOperation(requestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_execution_duration_seconds metrics for the above policy", "name", policy.GetName())
}
if err := policyExecutionDuration.ParsePromConfig(*promConfig).ProcessEngineResponse(policy, engineResponse, metrics.AdmissionRequest, "", resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_execution_duration_seconds metrics for the above policy", "name", policy.GetName())
}
}
func registerAdmissionReviewDurationMetricValidate(promConfig *metrics.PromConfig, logger logr.Logger, requestOperation string, engineResponses []*response.EngineResponse, admissionReviewLatencyDuration int64) {
resourceRequestOperationPromAlias, err := admissionReviewDuration.ParseResourceRequestOperation(requestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_review_duration_seconds metrics")
}
if err := admissionReviewDuration.ParsePromConfig(*promConfig).ProcessEngineResponses(engineResponses, admissionReviewLatencyDuration, resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_review_duration_seconds metrics")
}
}
func registerAdmissionRequestsMetricValidate(promConfig *metrics.PromConfig, logger logr.Logger, requestOperation string, engineResponses []*response.EngineResponse) {
resourceRequestOperationPromAlias, err := admissionRequests.ParseResourceRequestOperation(requestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_requests_total metrics")
}
if err := admissionRequests.ParsePromConfig(*promConfig).ProcessEngineResponses(engineResponses, resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_requests_total metrics")
}
}
func (ws *WebhookServer) registerPolicyResultsMetricMutation(logger logr.Logger, resourceRequestOperation string, policy kyverno.PolicyInterface, engineResponse response.EngineResponse) {
resourceRequestOperationPromAlias, err := policyResults.ParseResourceRequestOperation(resourceRequestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_results_total metrics for the above policy", "name", policy.GetName())
}
if err := policyResults.ParsePromConfig(*ws.promConfig).ProcessEngineResponse(policy, engineResponse, metrics.AdmissionRequest, resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_results_total metrics for the above policy", "name", policy.GetName())
}
}
func (ws *WebhookServer) registerPolicyExecutionDurationMetricMutate(logger logr.Logger, resourceRequestOperation string, policy kyverno.PolicyInterface, engineResponse response.EngineResponse) {
resourceRequestOperationPromAlias, err := policyExecutionDuration.ParseResourceRequestOperation(resourceRequestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_execution_duration_seconds metrics for the above policy", "name", policy.GetName())
}
if err := policyExecutionDuration.ParsePromConfig(*ws.promConfig).ProcessEngineResponse(policy, engineResponse, metrics.AdmissionRequest, "", resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_execution_duration_seconds metrics for the above policy", "name", policy.GetName())
}
}

View file

@ -12,9 +12,6 @@ import (
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/response"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/metrics"
policyExecutionDuration "github.com/kyverno/kyverno/pkg/metrics/policyexecutionduration"
policyResults "github.com/kyverno/kyverno/pkg/metrics/policyresults"
"github.com/kyverno/kyverno/pkg/utils"
"github.com/pkg/errors"
"k8s.io/api/admission/v1beta1"
@ -159,23 +156,3 @@ func (ws *WebhookServer) applyMutation(request *v1beta1.AdmissionRequest, policy
return engineResponse, policyPatches, nil
}
func (ws *WebhookServer) registerPolicyResultsMetricMutation(logger logr.Logger, resourceRequestOperation string, policy kyverno.PolicyInterface, engineResponse response.EngineResponse) {
resourceRequestOperationPromAlias, err := policyResults.ParseResourceRequestOperation(resourceRequestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_results_total metrics for the above policy", "name", policy.GetName())
}
if err := policyResults.ParsePromConfig(*ws.promConfig).ProcessEngineResponse(policy, engineResponse, metrics.AdmissionRequest, resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_results_total metrics for the above policy", "name", policy.GetName())
}
}
func (ws *WebhookServer) registerPolicyExecutionDurationMetricMutate(logger logr.Logger, resourceRequestOperation string, policy kyverno.PolicyInterface, engineResponse response.EngineResponse) {
resourceRequestOperationPromAlias, err := policyExecutionDuration.ParseResourceRequestOperation(resourceRequestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_execution_duration_seconds metrics for the above policy", "name", policy.GetName())
}
if err := policyExecutionDuration.ParsePromConfig(*ws.promConfig).ProcessEngineResponse(policy, engineResponse, metrics.AdmissionRequest, "", resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_execution_duration_seconds metrics for the above policy", "name", policy.GetName())
}
}

View file

@ -1,47 +0,0 @@
package webhooks
import (
"fmt"
"reflect"
"strings"
"time"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/policymutation"
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
v1beta1 "k8s.io/api/admission/v1beta1"
)
func (ws *WebhookServer) policyMutation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger := ws.log.WithValues("action", "policy mutation", "uid", request.UID, "kind", request.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation, "gvk", request.Kind.String())
policy, oldPolicy, err := admissionutils.GetPolicies(request)
if err != nil {
logger.Error(err, "failed to unmarshal policies from admission request")
return admissionutils.ResponseWithMessage(true, fmt.Sprintf("failed to default value, check kyverno controller logs for details: %v", err))
}
if oldPolicy != nil && isStatusUpdate(oldPolicy, policy) {
logger.V(4).Info("skip policy mutation on status update")
return admissionutils.Response(true)
}
startTime := time.Now()
logger.V(3).Info("start policy change mutation")
defer logger.V(3).Info("finished policy change mutation", "time", time.Since(startTime).String())
// Generate JSON Patches for defaults
if patches, updateMsgs := policymutation.GenerateJSONPatchesForDefaults(policy, logger); len(patches) != 0 {
return admissionutils.ResponseWithMessageAndPatch(true, strings.Join(updateMsgs, "'"), patches)
}
return admissionutils.Response(true)
}
func isStatusUpdate(old, new kyverno.PolicyInterface) bool {
if !reflect.DeepEqual(old.GetAnnotations(), new.GetAnnotations()) {
return false
}
if !reflect.DeepEqual(old.GetLabels(), new.GetLabels()) {
return false
}
if !reflect.DeepEqual(old.GetSpec(), new.GetSpec()) {
return false
}
return true
}

View file

@ -1,36 +0,0 @@
package webhooks
import (
"fmt"
"time"
policyvalidate "github.com/kyverno/kyverno/pkg/policy"
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
v1beta1 "k8s.io/api/admission/v1beta1"
)
//policyValidation performs the validation check on policy resource
func (ws *WebhookServer) policyValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger := ws.log.WithValues("action", "policy validation", "uid", request.UID, "kind", request.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation, "gvk", request.Kind.String())
policy, oldPolicy, err := admissionutils.GetPolicies(request)
if err != nil {
logger.Error(err, "failed to unmarshal policies from admission request")
return admissionutils.ResponseWithMessage(true, fmt.Sprintf("failed to validate policy, check kyverno controller logs for details: %v", err))
}
if oldPolicy != nil && isStatusUpdate(oldPolicy, policy) {
logger.V(4).Info("skip policy validation on status update")
return admissionutils.Response(true)
}
startTime := time.Now()
logger.V(3).Info("start policy change validation")
defer logger.V(3).Info("finished policy change validation", "time", time.Since(startTime).String())
response, err := policyvalidate.Validate(policy, ws.client, false, ws.openAPIController)
if err != nil {
logger.Error(err, "policy validation errors")
return admissionutils.ResponseWithMessage(true, err.Error())
}
if response != nil && len(response.Warnings) != 0 {
return response
}
return admissionutils.Response(true)
}

View file

@ -3,9 +3,6 @@ package webhooks
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"sync"
"time"
@ -16,17 +13,12 @@ import (
kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned"
kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1"
kyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
"github.com/kyverno/kyverno/pkg/common"
"github.com/kyverno/kyverno/pkg/config"
client "github.com/kyverno/kyverno/pkg/dclient"
"github.com/kyverno/kyverno/pkg/engine"
enginectx "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/event"
"github.com/kyverno/kyverno/pkg/generate"
"github.com/kyverno/kyverno/pkg/metrics"
admissionRequests "github.com/kyverno/kyverno/pkg/metrics/admissionrequests"
admissionReviewDuration "github.com/kyverno/kyverno/pkg/metrics/admissionreviewduration"
"github.com/kyverno/kyverno/pkg/openapi"
"github.com/kyverno/kyverno/pkg/policycache"
"github.com/kyverno/kyverno/pkg/policyreport"
@ -35,9 +27,9 @@ import (
"github.com/kyverno/kyverno/pkg/utils"
"github.com/kyverno/kyverno/pkg/webhookconfig"
webhookgenerate "github.com/kyverno/kyverno/pkg/webhooks/generate"
"github.com/kyverno/kyverno/pkg/webhooks/handlers"
"github.com/pkg/errors"
"k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
informers "k8s.io/client-go/informers/core/v1"
rbacinformer "k8s.io/client-go/informers/rbac/v1"
listerv1 "k8s.io/client-go/listers/core/v1"
@ -156,32 +148,26 @@ func NewWebhookServer(
grc *generate.Controller,
promConfig *metrics.PromConfig,
) (*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,
kyvernoClient: kyvernoClient,
grLister: grInformer.Lister().GenerateRequests(config.KyvernoNamespace),
grSynced: grInformer.Informer().HasSynced,
pLister: pInformer.Lister(),
pSynced: pInformer.Informer().HasSynced,
rbLister: rbInformer.Lister(),
rbSynced: rbInformer.Informer().HasSynced,
rLister: rInformer.Lister(),
rSynced: rInformer.Informer().HasSynced,
nsLister: namespace.Lister(),
nsListerSynced: namespace.Informer().HasSynced,
client: client,
kyvernoClient: kyvernoClient,
grLister: grInformer.Lister().GenerateRequests(config.KyvernoNamespace),
grSynced: grInformer.Informer().HasSynced,
pLister: pInformer.Lister(),
pSynced: pInformer.Informer().HasSynced,
rbLister: rbInformer.Lister(),
rbSynced: rbInformer.Informer().HasSynced,
rLister: rInformer.Lister(),
rSynced: rInformer.Informer().HasSynced,
nsLister: namespace.Lister(),
nsListerSynced: namespace.Informer().HasSynced,
crbLister: crbInformer.Lister(),
crLister: crInformer.Lister(),
crbSynced: crbInformer.Informer().HasSynced,
@ -200,153 +186,24 @@ func NewWebhookServer(
openAPIController: openAPIController,
promConfig: promConfig,
}
mux := httprouter.New()
mux.HandlerFunc("POST", config.MutatingWebhookServicePath, ws.handlerFunc(ws.resourceMutation, true))
mux.HandlerFunc("POST", config.ValidatingWebhookServicePath, ws.handlerFunc(ws.resourceValidation, true))
mux.HandlerFunc("POST", config.PolicyMutatingWebhookServicePath, ws.handlerFunc(ws.policyMutation, true))
mux.HandlerFunc("POST", config.PolicyValidatingWebhookServicePath, ws.handlerFunc(ws.policyValidation, true))
mux.HandlerFunc("POST", config.VerifyMutatingWebhookServicePath, ws.handlerFunc(ws.verifyHandler, false))
// Patch Liveness responds to a Kubernetes Liveness probe
// Fail this request if Kubernetes should restart this instance
mux.HandlerFunc("GET", config.LivenessServicePath, func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
if err := ws.webhookRegister.Check(); err != nil {
w.WriteHeader(http.StatusInternalServerError)
} else {
w.WriteHeader(http.StatusOK)
}
})
// Patch Readiness responds to a Kubernetes Readiness probe
// Fail this request if this instance can't accept traffic, but Kubernetes shouldn't restart it
mux.HandlerFunc("GET", config.ReadinessServicePath, func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
w.WriteHeader(http.StatusOK)
})
mux.HandlerFunc("POST", config.MutatingWebhookServicePath, ws.admissionHandler(true, ws.resourceMutation))
mux.HandlerFunc("POST", config.ValidatingWebhookServicePath, ws.admissionHandler(true, ws.resourceValidation))
mux.HandlerFunc("POST", config.PolicyMutatingWebhookServicePath, ws.admissionHandler(true, ws.policyMutation))
mux.HandlerFunc("POST", config.PolicyValidatingWebhookServicePath, ws.admissionHandler(true, ws.policyValidation))
mux.HandlerFunc("POST", config.VerifyMutatingWebhookServicePath, ws.admissionHandler(false, handlers.Verify(ws.webhookMonitor, ws.log)))
mux.HandlerFunc("GET", config.LivenessServicePath, handlers.Probe(ws.webhookRegister.Check))
mux.HandlerFunc("GET", config.ReadinessServicePath, handlers.Probe(nil))
ws.server = &http.Server{
Addr: ":9443", // Listen on port for HTTPS requests
TLSConfig: &tlsConfig,
TLSConfig: &tls.Config{Certificates: []tls.Certificate{pair}},
Handler: mux,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
}
return ws, nil
}
func (ws *WebhookServer) handlerFunc(handler func(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse, filter bool) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
startTime := time.Now()
ws.webhookMonitor.SetTime(startTime)
admissionReview := ws.bodyToAdmissionReview(r, rw)
if admissionReview == nil {
ws.log.Info("failed to parse admission review request", "request", r)
return
}
logger := ws.log.WithName("handlerFunc").WithValues("kind", admissionReview.Request.Kind, "namespace", admissionReview.Request.Namespace,
"name", admissionReview.Request.Name, "operation", admissionReview.Request.Operation, "uid", admissionReview.Request.UID)
admissionReview.Response = &v1beta1.AdmissionResponse{
Allowed: true,
UID: admissionReview.Request.UID,
}
// Do not process the admission requests for kinds that are in filterKinds for filtering
request := admissionReview.Request
if filter && ws.configHandler.ToFilter(request.Kind.Kind, request.Namespace, request.Name) {
writeResponse(rw, admissionReview)
return
}
admissionReview.Response = handler(request)
writeResponse(rw, admissionReview)
logger.V(4).Info("admission review request processed", "time", time.Since(startTime).String())
}
}
func writeResponse(rw http.ResponseWriter, admissionReview *v1beta1.AdmissionReview) {
responseJSON, err := json.Marshal(admissionReview)
if err != nil {
http.Error(rw, fmt.Sprintf("Could not encode response: %v", err), http.StatusInternalServerError)
return
}
rw.Header().Set("Content-Type", "application/json; charset=utf-8")
if _, err := rw.Write(responseJSON); err != nil {
http.Error(rw, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
}
}
// resourceMutation mutates resource
func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger := ws.log.WithName("MutateWebhook").WithValues("uid", request.UID, "kind", request.Kind.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation, "gvk", request.Kind.String())
if excludeKyvernoResources(request.Kind.Kind) {
return successResponse(nil)
}
if request.Operation == v1beta1.Delete {
resource, err := utils.ConvertResource(request.OldObject.Raw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace)
if err == nil {
ws.prGenerator.Add(buildDeletionPrInfo(resource))
} else {
logger.Info(fmt.Sprintf("Converting oldObject failed: %v", err))
}
return successResponse(nil)
}
logger.V(4).Info("received an admission request in mutating webhook")
requestTime := time.Now().Unix()
kind := request.Kind.Kind
mutatePolicies := ws.pCache.GetPolicies(policycache.Mutate, kind, request.Namespace)
verifyImagesPolicies := ws.pCache.GetPolicies(policycache.VerifyImages, kind, request.Namespace)
if len(mutatePolicies) == 0 && len(verifyImagesPolicies) == 0 {
logger.V(4).Info("no policies matched admission request")
return successResponse(nil)
}
addRoles := containsRBACInfo(mutatePolicies)
policyContext, err := ws.buildPolicyContext(request, addRoles)
if err != nil {
logger.Error(err, "failed to build policy context")
return failureResponse(err.Error())
}
// update container images to a canonical form
if err := enginectx.MutateResourceWithImageInfo(request.Object.Raw, policyContext.JSONContext); err != nil {
ws.log.Error(err, "failed to patch images info to resource, policies that mutate images may be impacted")
}
mutatePatches := ws.applyMutatePolicies(request, policyContext, mutatePolicies, requestTime, logger)
newRequest := patchRequest(mutatePatches, request, logger)
imagePatches, err := ws.applyImageVerifyPolicies(newRequest, policyContext, verifyImagesPolicies, logger)
if err != nil {
logger.Error(err, "image verification failed")
return failureResponse(err.Error())
}
var patches = append(mutatePatches, imagePatches...)
return successResponse(patches)
}
// patchRequest applies patches to the request.Object and returns a new copy of the request
func patchRequest(patches []byte, request *v1beta1.AdmissionRequest, logger logr.Logger) *v1beta1.AdmissionRequest {
patchedResource := processResourceWithPatches(patches, request.Object.Raw, logger)
newRequest := request.DeepCopy()
newRequest.Object.Raw = patchedResource
return newRequest
}
func (ws *WebhookServer) buildPolicyContext(request *v1beta1.AdmissionRequest, addRoles bool) (*engine.PolicyContext, error) {
userRequestInfo := v1.RequestInfo{
AdmissionUserInfo: *request.UserInfo.DeepCopy(),
@ -398,269 +255,32 @@ func (ws *WebhookServer) buildPolicyContext(request *v1beta1.AdmissionRequest, a
return policyContext, nil
}
func successResponse(patch []byte) *v1beta1.AdmissionResponse {
r := &v1beta1.AdmissionResponse{
Allowed: true,
Result: &metav1.Status{
Status: "Success",
},
}
if len(patch) > 0 {
patchType := v1beta1.PatchTypeJSONPatch
r.PatchType = &patchType
r.Patch = patch
}
return r
}
func errorResponse(logger logr.Logger, err error, message string) *v1beta1.AdmissionResponse {
logger.Error(err, message)
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Status: "Failure",
Message: message + ": " + err.Error(),
},
}
}
func failureResponse(message string) *v1beta1.AdmissionResponse {
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Status: "Failure",
Message: message,
},
}
}
func registerAdmissionReviewDurationMetricMutate(logger logr.Logger, promConfig metrics.PromConfig, requestOperation string, engineResponses []*response.EngineResponse, admissionReviewLatencyDuration int64) {
resourceRequestOperationPromAlias, err := admissionReviewDuration.ParseResourceRequestOperation(requestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_review_duration_seconds metrics")
}
if err := admissionReviewDuration.ParsePromConfig(promConfig).ProcessEngineResponses(engineResponses, admissionReviewLatencyDuration, resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_review_duration_seconds metrics")
}
}
func registerAdmissionRequestsMetricMutate(logger logr.Logger, promConfig metrics.PromConfig, requestOperation string, engineResponses []*response.EngineResponse) {
resourceRequestOperationPromAlias, err := admissionReviewDuration.ParseResourceRequestOperation(requestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_requests_total metrics")
}
if err := admissionRequests.ParsePromConfig(promConfig).ProcessEngineResponses(engineResponses, resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_requests_total metrics")
}
}
func registerAdmissionReviewDurationMetricGenerate(logger logr.Logger, promConfig metrics.PromConfig, requestOperation string, latencyReceiver *chan int64, engineResponsesReceiver *chan []*response.EngineResponse) {
defer close(*latencyReceiver)
defer close(*engineResponsesReceiver)
engineResponses := <-(*engineResponsesReceiver)
resourceRequestOperationPromAlias, err := admissionReviewDuration.ParseResourceRequestOperation(requestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_review_duration_seconds metrics")
}
// this goroutine will keep on waiting here till it doesn't receive the admission review latency int64 from the other goroutine i.e. ws.HandleGenerate
admissionReviewLatencyDuration := <-(*latencyReceiver)
if err := admissionReviewDuration.ParsePromConfig(promConfig).ProcessEngineResponses(engineResponses, admissionReviewLatencyDuration, resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_review_duration_seconds metrics")
}
}
func registerAdmissionRequestsMetricGenerate(logger logr.Logger, promConfig metrics.PromConfig, requestOperation string, engineResponsesReceiver *chan []*response.EngineResponse) {
defer close(*engineResponsesReceiver)
engineResponses := <-(*engineResponsesReceiver)
resourceRequestOperationPromAlias, err := admissionReviewDuration.ParseResourceRequestOperation(requestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_requests_total metrics")
}
if err := admissionRequests.ParsePromConfig(promConfig).ProcessEngineResponses(engineResponses, resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_requests_total metrics")
}
}
func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger := ws.log.WithName("ValidateWebhook").WithValues("uid", request.UID, "kind", request.Kind.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation)
if request.Operation == v1beta1.Delete {
ws.handleDelete(request)
}
if excludeKyvernoResources(request.Kind.Kind) {
return successResponse(nil)
}
logger.V(6).Info("received an admission request in validating webhook")
// timestamp at which this admission request got triggered
admissionRequestTimestamp := time.Now().Unix()
kind := request.Kind.Kind
policies := ws.pCache.GetPolicies(policycache.ValidateEnforce, kind, "")
// Get namespace policies from the cache for the requested resource namespace
nsPolicies := ws.pCache.GetPolicies(policycache.ValidateEnforce, kind, request.Namespace)
policies = append(policies, nsPolicies...)
generatePolicies := ws.pCache.GetPolicies(policycache.Generate, kind, request.Namespace)
if len(generatePolicies) == 0 && request.Operation == v1beta1.Update {
// handle generate source resource updates
go ws.handleUpdatesForGenerateRules(request, []v1.PolicyInterface{})
}
var roles, clusterRoles []string
if containsRBACInfo(policies, generatePolicies) {
var err error
roles, clusterRoles, err = userinfo.GetRoleRef(ws.rbLister, ws.crbLister, request, ws.configHandler)
if err != nil {
return errorResponse(logger, err, "failed to fetch RBAC data")
}
}
userRequestInfo := v1.RequestInfo{
Roles: roles,
ClusterRoles: clusterRoles,
AdmissionUserInfo: *request.UserInfo.DeepCopy(),
}
ctx, err := newVariablesContext(request, &userRequestInfo)
if err != nil {
return errorResponse(logger, err, "failed create policy rule context")
}
namespaceLabels := make(map[string]string)
if request.Kind.Kind != "Namespace" && request.Namespace != "" {
namespaceLabels = common.GetNamespaceSelectorsFromNamespaceLister(request.Kind.Kind, request.Namespace, ws.nsLister, logger)
}
newResource, oldResource, err := utils.ExtractResources(nil, request)
if err != nil {
return errorResponse(logger, err, "failed create parse resource")
}
if err := ctx.AddImageInfo(&newResource); err != nil {
return errorResponse(logger, err, "failed add image information to policy rule context")
}
policyContext := &engine.PolicyContext{
NewResource: newResource,
OldResource: oldResource,
AdmissionInfo: userRequestInfo,
ExcludeGroupRole: ws.configHandler.GetExcludeGroupRole(),
ExcludeResourceFunc: ws.configHandler.ToFilter,
JSONContext: ctx,
Client: ws.client,
}
vh := &validationHandler{
log: ws.log,
eventGen: ws.eventGen,
prGenerator: ws.prGenerator,
}
ok, msg := vh.handleValidation(ws.promConfig, request, policies, policyContext, namespaceLabels, admissionRequestTimestamp)
if !ok {
logger.Info("admission request denied")
return failureResponse(msg)
}
// push admission request to audit handler, this won't block the admission request
ws.auditHandler.Add(request.DeepCopy())
// process generate policies
ws.applyGeneratePolicies(request, policyContext, generatePolicies, admissionRequestTimestamp, logger)
return successResponse(nil)
}
// RunAsync TLS server in separate thread and returns control immediately
func (ws *WebhookServer) RunAsync(stopCh <-chan struct{}) {
logger := ws.log
if !cache.WaitForCacheSync(stopCh, ws.grSynced, ws.pSynced, ws.rbSynced, ws.crbSynced, ws.rSynced, ws.crSynced) {
logger.Info("failed to sync informer cache")
ws.log.Info("failed to sync informer cache")
}
go func() {
logger.V(3).Info("started serving requests", "addr", ws.server.Addr)
ws.log.V(3).Info("started serving requests", "addr", ws.server.Addr)
if err := ws.server.ListenAndServeTLS("", ""); err != http.ErrServerClosed {
logger.Error(err, "failed to listen to requests")
ws.log.Error(err, "failed to listen to requests")
}
}()
logger.Info("starting service")
ws.log.Info("starting service")
}
// Stop TLS server and returns control after the server is shut down
func (ws *WebhookServer) Stop(ctx context.Context) {
logger := ws.log
// remove the static webhook configurations
go ws.webhookRegister.Remove(ws.cleanUp)
// shutdown http.Server with context timeout
err := ws.server.Shutdown(ctx)
if err != nil {
// Error from closing listeners, or context timeout:
logger.Error(err, "shutting down server")
ws.log.Error(err, "shutting down server")
err = ws.server.Close()
if err != nil {
logger.Error(err, "server shut down failed")
ws.log.Error(err, "server shut down failed")
}
}
}
// 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 {
logger := ws.log
if request.Body == nil {
logger.Info("empty body", "req", request.URL.String())
http.Error(writer, "empty body", http.StatusBadRequest)
return nil
}
defer request.Body.Close()
body, err := ioutil.ReadAll(request.Body)
if err != nil {
logger.Info("failed to read HTTP body", "req", request.URL.String())
http.Error(writer, "failed to read HTTP body", http.StatusBadRequest)
}
contentType := request.Header.Get("Content-Type")
if contentType != "application/json" {
logger.Info("invalid Content-Type", "contextType", 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 {
logger.Error(err, "failed to decode request body to type 'AdmissionReview")
http.Error(writer, "Can't decode body as AdmissionReview", http.StatusExpectationFailed)
return nil
}
return admissionReview
}
func newVariablesContext(request *v1beta1.AdmissionRequest, userRequestInfo *v1.RequestInfo) (*enginectx.Context, error) {
ctx := enginectx.NewContext()
if err := ctx.AddRequest(request); err != nil {
return nil, errors.Wrap(err, "failed to load incoming request in context")
}
if err := ctx.AddUserInfo(*userRequestInfo); err != nil {
return nil, errors.Wrap(err, "failed to load userInfo in context")
}
if err := ctx.AddServiceAccount(userRequestInfo.AdmissionUserInfo.Username); err != nil {
return nil, errors.Wrap(err, "failed to load service account in context")
}
return ctx, nil
}

View file

@ -11,10 +11,6 @@ import (
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/metrics"
admissionRequests "github.com/kyverno/kyverno/pkg/metrics/admissionrequests"
admissionReviewDuration "github.com/kyverno/kyverno/pkg/metrics/admissionreviewduration"
policyExecutionDuration "github.com/kyverno/kyverno/pkg/metrics/policyexecutionduration"
policyResults "github.com/kyverno/kyverno/pkg/metrics/policyresults"
"github.com/kyverno/kyverno/pkg/policyreport"
v1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -150,46 +146,6 @@ func getResourceName(request *v1beta1.AdmissionRequest) string {
return resourceName
}
func registerPolicyResultsMetricValidation(promConfig *metrics.PromConfig, logger logr.Logger, requestOperation string, policy v1.PolicyInterface, engineResponse response.EngineResponse) {
resourceRequestOperationPromAlias, err := policyResults.ParseResourceRequestOperation(requestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_results_total metrics for the above policy", "name", policy.GetName())
}
if err := policyResults.ParsePromConfig(*promConfig).ProcessEngineResponse(policy, engineResponse, metrics.AdmissionRequest, resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_results_total metrics for the above policy", "name", policy.GetName())
}
}
func registerPolicyExecutionDurationMetricValidate(promConfig *metrics.PromConfig, logger logr.Logger, requestOperation string, policy v1.PolicyInterface, engineResponse response.EngineResponse) {
resourceRequestOperationPromAlias, err := policyExecutionDuration.ParseResourceRequestOperation(requestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_execution_duration_seconds metrics for the above policy", "name", policy.GetName())
}
if err := policyExecutionDuration.ParsePromConfig(*promConfig).ProcessEngineResponse(policy, engineResponse, metrics.AdmissionRequest, "", resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_execution_duration_seconds metrics for the above policy", "name", policy.GetName())
}
}
func registerAdmissionReviewDurationMetricValidate(promConfig *metrics.PromConfig, logger logr.Logger, requestOperation string, engineResponses []*response.EngineResponse, admissionReviewLatencyDuration int64) {
resourceRequestOperationPromAlias, err := admissionReviewDuration.ParseResourceRequestOperation(requestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_review_duration_seconds metrics")
}
if err := admissionReviewDuration.ParsePromConfig(*promConfig).ProcessEngineResponses(engineResponses, admissionReviewLatencyDuration, resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_review_duration_seconds metrics")
}
}
func registerAdmissionRequestsMetricValidate(promConfig *metrics.PromConfig, logger logr.Logger, requestOperation string, engineResponses []*response.EngineResponse) {
resourceRequestOperationPromAlias, err := admissionRequests.ParseResourceRequestOperation(requestOperation)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_requests_total metrics")
}
if err := admissionRequests.ParsePromConfig(*promConfig).ProcessEngineResponses(engineResponses, resourceRequestOperationPromAlias); err != nil {
logger.Error(err, "error occurred while registering kyverno_admission_requests_total metrics")
}
}
func buildDeletionPrInfo(oldR unstructured.Unstructured) policyreport.Info {
return policyreport.Info{
Namespace: oldR.GetNamespace(),