2019-05-13 21:33:01 +03:00
|
|
|
package webhooks
|
2019-02-07 19:22:04 +02:00
|
|
|
|
|
|
|
import (
|
2019-03-04 20:40:02 +02:00
|
|
|
"context"
|
|
|
|
"crypto/tls"
|
2022-09-06 17:43:04 +02:00
|
|
|
"fmt"
|
2019-03-04 20:40:02 +02:00
|
|
|
"net/http"
|
2022-10-21 17:17:49 +01:00
|
|
|
"strings"
|
2019-03-04 20:40:02 +02:00
|
|
|
"time"
|
|
|
|
|
2020-03-17 11:05:20 -07:00
|
|
|
"github.com/go-logr/logr"
|
2020-04-27 18:38:03 +05:30
|
|
|
"github.com/julienschmidt/httprouter"
|
2022-09-19 11:11:12 +02:00
|
|
|
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
2020-10-07 11:12:31 -07:00
|
|
|
"github.com/kyverno/kyverno/pkg/config"
|
2022-10-21 17:17:49 +01:00
|
|
|
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
|
2022-10-19 15:12:55 +02:00
|
|
|
"github.com/kyverno/kyverno/pkg/logging"
|
2022-09-06 17:43:04 +02:00
|
|
|
"github.com/kyverno/kyverno/pkg/toggle"
|
|
|
|
"github.com/kyverno/kyverno/pkg/utils"
|
|
|
|
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
|
2022-10-12 08:52:42 +02:00
|
|
|
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
|
|
|
|
runtimeutils "github.com/kyverno/kyverno/pkg/utils/runtime"
|
2022-03-31 17:34:10 +02:00
|
|
|
"github.com/kyverno/kyverno/pkg/webhooks/handlers"
|
2022-04-06 22:43:07 +02:00
|
|
|
admissionv1 "k8s.io/api/admission/v1"
|
2022-10-12 08:52:42 +02:00
|
|
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
2022-10-21 17:17:49 +01:00
|
|
|
authenticationv1 "k8s.io/api/authentication/v1"
|
2022-10-12 08:52:42 +02:00
|
|
|
coordinationv1 "k8s.io/api/coordination/v1"
|
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2022-09-06 17:43:04 +02:00
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
2022-10-21 17:17:49 +01:00
|
|
|
"k8s.io/apimachinery/pkg/types"
|
2019-02-07 19:22:04 +02:00
|
|
|
)
|
|
|
|
|
2022-10-21 17:17:49 +01:00
|
|
|
// DebugModeOptions holds the options to configure debug mode
|
|
|
|
type DebugModeOptions struct {
|
|
|
|
// DumpPayload is used to activate/deactivate debug mode.
|
|
|
|
DumpPayload bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// AdmissionRequestPayload holds a copy of the AdmissionRequest payload
|
|
|
|
type AdmissionRequestPayload struct {
|
|
|
|
UID types.UID `json:"uid"`
|
|
|
|
Kind metav1.GroupVersionKind `json:"kind"`
|
|
|
|
Resource metav1.GroupVersionResource `json:"resource"`
|
|
|
|
SubResource string `json:"subResource,omitempty"`
|
|
|
|
RequestKind *metav1.GroupVersionKind `json:"requestKind,omitempty"`
|
|
|
|
RequestResource *metav1.GroupVersionResource `json:"requestResource,omitempty"`
|
|
|
|
RequestSubResource string `json:"requestSubResource,omitempty"`
|
|
|
|
Name string `json:"name,omitempty"`
|
|
|
|
Namespace string `json:"namespace,omitempty"`
|
|
|
|
Operation string `json:"operation"`
|
|
|
|
UserInfo authenticationv1.UserInfo `json:"userInfo"`
|
|
|
|
Object unstructured.Unstructured `json:"object,omitempty"`
|
|
|
|
OldObject unstructured.Unstructured `json:"oldObject,omitempty"`
|
|
|
|
DryRun *bool `json:"dryRun,omitempty"`
|
|
|
|
Options unstructured.Unstructured `json:"options,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func newAdmissionRequestPayload(rq *admissionv1.AdmissionRequest) (*AdmissionRequestPayload, error) {
|
|
|
|
newResource, oldResource, err := utils.ExtractResources(nil, rq)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
options := new(unstructured.Unstructured)
|
|
|
|
if rq.Options.Raw != nil {
|
|
|
|
options, err = engineutils.ConvertToUnstructured(rq.Options.Raw)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return redactPayload(&AdmissionRequestPayload{
|
|
|
|
UID: rq.UID,
|
|
|
|
Kind: rq.Kind,
|
|
|
|
Resource: rq.Resource,
|
|
|
|
SubResource: rq.SubResource,
|
|
|
|
RequestKind: rq.RequestKind,
|
|
|
|
RequestResource: rq.RequestResource,
|
|
|
|
RequestSubResource: rq.RequestSubResource,
|
|
|
|
Name: rq.Name,
|
|
|
|
Namespace: rq.Namespace,
|
|
|
|
Operation: string(rq.Operation),
|
|
|
|
UserInfo: rq.UserInfo,
|
|
|
|
Object: newResource,
|
|
|
|
OldObject: oldResource,
|
|
|
|
DryRun: rq.DryRun,
|
|
|
|
Options: *options,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func redactPayload(payload *AdmissionRequestPayload) (*AdmissionRequestPayload, error) {
|
|
|
|
if strings.EqualFold(payload.Kind.Kind, "Secret") {
|
|
|
|
if payload.Object.Object != nil {
|
|
|
|
obj, err := utils.RedactSecret(&payload.Object)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
payload.Object = obj
|
|
|
|
}
|
|
|
|
if payload.OldObject.Object != nil {
|
|
|
|
oldObj, err := utils.RedactSecret(&payload.OldObject)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
payload.OldObject = oldObj
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return payload, nil
|
|
|
|
}
|
|
|
|
|
2022-05-16 16:36:21 +02:00
|
|
|
type Server interface {
|
|
|
|
// Run TLS server in separate thread and returns control immediately
|
|
|
|
Run(<-chan struct{})
|
|
|
|
// Stop TLS server and returns control after the server is shut down
|
|
|
|
Stop(context.Context)
|
2022-09-30 16:13:29 +02:00
|
|
|
// Cleanup returns the chanel used to wait for the server to clean up resources
|
|
|
|
Cleanup() <-chan struct{}
|
2022-05-16 16:36:21 +02:00
|
|
|
}
|
|
|
|
|
2022-09-26 17:55:46 +02:00
|
|
|
type PolicyHandlers interface {
|
2022-05-13 07:33:20 +02:00
|
|
|
// Mutate performs the mutation of policy resources
|
2022-09-08 09:34:55 +02:00
|
|
|
Mutate(logr.Logger, *admissionv1.AdmissionRequest, time.Time) *admissionv1.AdmissionResponse
|
2022-05-13 07:33:20 +02:00
|
|
|
// Validate performs the validation check on policy resources
|
2022-09-08 09:34:55 +02:00
|
|
|
Validate(logr.Logger, *admissionv1.AdmissionRequest, time.Time) *admissionv1.AdmissionResponse
|
2022-05-13 07:33:20 +02:00
|
|
|
}
|
|
|
|
|
2022-09-26 17:55:46 +02:00
|
|
|
type ResourceHandlers interface {
|
|
|
|
// Mutate performs the mutation of kube resources
|
|
|
|
Mutate(logr.Logger, *admissionv1.AdmissionRequest, string, time.Time) *admissionv1.AdmissionResponse
|
|
|
|
// Validate performs the validation check on kube resources
|
|
|
|
Validate(logr.Logger, *admissionv1.AdmissionRequest, string, time.Time) *admissionv1.AdmissionResponse
|
|
|
|
}
|
|
|
|
|
2022-05-16 16:36:21 +02:00
|
|
|
type server struct {
|
2022-10-12 08:52:42 +02:00
|
|
|
server *http.Server
|
|
|
|
runtime runtimeutils.Runtime
|
|
|
|
mwcClient controllerutils.DeleteClient[*admissionregistrationv1.MutatingWebhookConfiguration]
|
|
|
|
vwcClient controllerutils.DeleteClient[*admissionregistrationv1.ValidatingWebhookConfiguration]
|
|
|
|
leaseClient controllerutils.DeleteClient[*coordinationv1.Lease]
|
|
|
|
cleanUp chan struct{}
|
2019-03-04 20:40:02 +02:00
|
|
|
}
|
|
|
|
|
2022-05-16 16:36:21 +02:00
|
|
|
type TlsProvider func() ([]byte, []byte, error)
|
|
|
|
|
|
|
|
// NewServer creates new instance of server accordingly to given configuration
|
|
|
|
func NewServer(
|
2022-09-26 17:55:46 +02:00
|
|
|
policyHandlers PolicyHandlers,
|
|
|
|
resourceHandlers ResourceHandlers,
|
2022-05-16 16:36:21 +02:00
|
|
|
configuration config.Configuration,
|
2022-10-21 17:17:49 +01:00
|
|
|
debugModeOpts DebugModeOptions,
|
2022-10-12 08:52:42 +02:00
|
|
|
tlsProvider TlsProvider,
|
|
|
|
mwcClient controllerutils.DeleteClient[*admissionregistrationv1.MutatingWebhookConfiguration],
|
|
|
|
vwcClient controllerutils.DeleteClient[*admissionregistrationv1.ValidatingWebhookConfiguration],
|
|
|
|
leaseClient controllerutils.DeleteClient[*coordinationv1.Lease],
|
|
|
|
runtime runtimeutils.Runtime,
|
2022-05-16 16:36:21 +02:00
|
|
|
) Server {
|
2020-04-27 18:38:03 +05:30
|
|
|
mux := httprouter.New()
|
2022-05-16 16:36:21 +02:00
|
|
|
resourceLogger := logger.WithName("resource")
|
|
|
|
policyLogger := logger.WithName("policy")
|
|
|
|
verifyLogger := logger.WithName("verify")
|
2022-10-21 17:17:49 +01:00
|
|
|
registerWebhookHandlers(resourceLogger.WithName("mutate"), mux, config.MutatingWebhookServicePath, configuration, resourceHandlers.Mutate, debugModeOpts)
|
|
|
|
registerWebhookHandlers(resourceLogger.WithName("validate"), mux, config.ValidatingWebhookServicePath, configuration, resourceHandlers.Validate, debugModeOpts)
|
|
|
|
mux.HandlerFunc("POST", config.PolicyMutatingWebhookServicePath, admission(policyLogger.WithName("mutate"), filter(configuration, policyHandlers.Mutate), debugModeOpts))
|
|
|
|
mux.HandlerFunc("POST", config.PolicyValidatingWebhookServicePath, admission(policyLogger.WithName("validate"), filter(configuration, policyHandlers.Validate), debugModeOpts))
|
|
|
|
mux.HandlerFunc("POST", config.VerifyMutatingWebhookServicePath, admission(verifyLogger.WithName("mutate"), handlers.Verify(), DebugModeOptions{}))
|
2022-10-12 08:52:42 +02:00
|
|
|
mux.HandlerFunc("GET", config.LivenessServicePath, handlers.Probe(runtime.IsLive))
|
|
|
|
mux.HandlerFunc("GET", config.ReadinessServicePath, handlers.Probe(runtime.IsReady))
|
2022-05-16 16:36:21 +02:00
|
|
|
return &server{
|
|
|
|
server: &http.Server{
|
|
|
|
Addr: ":9443",
|
|
|
|
TLSConfig: &tls.Config{
|
|
|
|
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
|
|
certPem, keyPem, err := tlsProvider()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
pair, err := tls.X509KeyPair(certPem, keyPem)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &pair, nil
|
|
|
|
},
|
|
|
|
MinVersion: tls.VersionTLS12,
|
2022-05-10 11:55:39 +02:00
|
|
|
},
|
2022-08-24 15:08:24 +02:00
|
|
|
Handler: mux,
|
|
|
|
ReadTimeout: 30 * time.Second,
|
|
|
|
WriteTimeout: 30 * time.Second,
|
|
|
|
ReadHeaderTimeout: 30 * time.Second,
|
2022-10-19 14:09:04 +02:00
|
|
|
IdleTimeout: 5 * time.Minute,
|
2022-10-19 15:12:55 +02:00
|
|
|
ErrorLog: logging.StdLogger(logger.WithName("server"), ""),
|
2022-05-10 11:55:39 +02:00
|
|
|
},
|
2022-10-12 08:52:42 +02:00
|
|
|
mwcClient: mwcClient,
|
|
|
|
vwcClient: vwcClient,
|
|
|
|
leaseClient: leaseClient,
|
|
|
|
runtime: runtime,
|
|
|
|
cleanUp: make(chan struct{}),
|
2019-03-04 20:40:02 +02:00
|
|
|
}
|
2019-02-21 20:31:18 +02:00
|
|
|
}
|
|
|
|
|
2022-05-16 16:36:21 +02:00
|
|
|
func (s *server) Run(stopCh <-chan struct{}) {
|
|
|
|
go func() {
|
|
|
|
logger.V(3).Info("started serving requests", "addr", s.server.Addr)
|
|
|
|
if err := s.server.ListenAndServeTLS("", ""); err != http.ErrServerClosed {
|
|
|
|
logger.Error(err, "failed to listen to requests")
|
2022-05-05 14:06:18 -07:00
|
|
|
}
|
2022-05-16 16:36:21 +02:00
|
|
|
}()
|
|
|
|
logger.Info("starting service")
|
2021-07-09 18:01:46 -07:00
|
|
|
}
|
2021-03-23 10:34:03 -07:00
|
|
|
|
2022-05-16 16:36:21 +02:00
|
|
|
func (s *server) Stop(ctx context.Context) {
|
2022-07-27 16:45:06 +08:00
|
|
|
s.cleanup(ctx)
|
2022-05-16 16:36:21 +02:00
|
|
|
err := s.server.Shutdown(ctx)
|
2022-05-05 14:06:18 -07:00
|
|
|
if err != nil {
|
2022-05-16 16:36:21 +02:00
|
|
|
logger.Error(err, "shutting down server")
|
|
|
|
err = s.server.Close()
|
2022-05-05 14:06:18 -07:00
|
|
|
if err != nil {
|
2022-05-16 16:36:21 +02:00
|
|
|
logger.Error(err, "server shut down failed")
|
2022-05-05 14:06:18 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-30 16:13:29 +02:00
|
|
|
func (s *server) Cleanup() <-chan struct{} {
|
|
|
|
return s.cleanUp
|
|
|
|
}
|
|
|
|
|
2022-07-27 16:45:06 +08:00
|
|
|
func (s *server) cleanup(ctx context.Context) {
|
2022-10-12 08:52:42 +02:00
|
|
|
if s.runtime.IsGoingDown() {
|
|
|
|
deleteLease := func(name string) {
|
|
|
|
if err := s.leaseClient.Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
|
|
|
|
logger.Error(err, "failed to clean up lease", "name", name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
deleteVwc := func(name string) {
|
|
|
|
if err := s.vwcClient.Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
|
|
|
|
logger.Error(err, "failed to clean up validating webhook configuration", "name", name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
deleteMwc := func(name string) {
|
|
|
|
if err := s.mwcClient.Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
|
|
|
|
logger.Error(err, "failed to clean up mutating webhook configuration", "name", name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
deleteLease("kyvernopre-lock")
|
2022-11-03 10:19:38 +00:00
|
|
|
deleteLease("kyverno-health")
|
2022-10-12 08:52:42 +02:00
|
|
|
deleteVwc(config.ValidatingWebhookConfigurationName)
|
|
|
|
deleteVwc(config.PolicyValidatingWebhookConfigurationName)
|
|
|
|
deleteMwc(config.MutatingWebhookConfigurationName)
|
|
|
|
deleteMwc(config.PolicyMutatingWebhookConfigurationName)
|
|
|
|
deleteMwc(config.VerifyMutatingWebhookConfigurationName)
|
|
|
|
}
|
2022-07-27 16:45:06 +08:00
|
|
|
close(s.cleanUp)
|
|
|
|
}
|
|
|
|
|
2022-10-21 17:17:49 +01:00
|
|
|
func dumpPayload(logger logr.Logger, request *admissionv1.AdmissionRequest, response *admissionv1.AdmissionResponse) {
|
|
|
|
reqPayload, err := newAdmissionRequestPayload(request)
|
|
|
|
if err != nil {
|
|
|
|
logger.Error(err, "Failed to extract resources")
|
|
|
|
} else {
|
|
|
|
logger.Info("Logging admission request and response payload ", "AdmissionRequest", reqPayload, "AdmissionResponse", response)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func dump(inner handlers.AdmissionHandler, debugModeOpts DebugModeOptions) handlers.AdmissionHandler {
|
|
|
|
// debug mode not enabled, no need to add debug middleware
|
|
|
|
if !debugModeOpts.DumpPayload {
|
|
|
|
return inner
|
|
|
|
}
|
|
|
|
return func(logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
|
|
|
|
response := inner(logger, request, startTime)
|
|
|
|
dumpPayload(logger, request, response)
|
|
|
|
return response
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-06 17:43:04 +02:00
|
|
|
func protect(inner handlers.AdmissionHandler) handlers.AdmissionHandler {
|
2022-10-21 17:17:49 +01:00
|
|
|
if !toggle.ProtectManagedResources.Enabled() {
|
|
|
|
return inner
|
|
|
|
}
|
2022-09-08 09:34:55 +02:00
|
|
|
return func(logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
|
2022-10-21 17:17:49 +01:00
|
|
|
newResource, oldResource, err := utils.ExtractResources(nil, request)
|
|
|
|
if err != nil {
|
|
|
|
logger.Error(err, "Failed to extract resources")
|
|
|
|
return admissionutils.ResponseFailure(err.Error())
|
|
|
|
}
|
|
|
|
for _, resource := range []unstructured.Unstructured{newResource, oldResource} {
|
|
|
|
resLabels := resource.GetLabels()
|
|
|
|
if resLabels[kyvernov1.LabelAppManagedBy] == kyvernov1.ValueKyvernoApp {
|
|
|
|
if request.UserInfo.Username != fmt.Sprintf("system:serviceaccount:%s:%s", config.KyvernoNamespace(), config.KyvernoServiceAccountName()) {
|
|
|
|
logger.Info("Access to the resource not authorized, this is a kyverno managed resource and should be altered only by kyverno")
|
|
|
|
return admissionutils.ResponseFailure("A kyverno managed resource can only be modified by kyverno")
|
2022-09-06 17:43:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-09-08 09:34:55 +02:00
|
|
|
return inner(logger, request, startTime)
|
2022-09-06 17:43:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-16 16:36:21 +02:00
|
|
|
func filter(configuration config.Configuration, inner handlers.AdmissionHandler) handlers.AdmissionHandler {
|
|
|
|
return handlers.Filter(configuration, inner)
|
2019-02-07 19:22:04 +02:00
|
|
|
}
|
|
|
|
|
2022-10-21 17:17:49 +01:00
|
|
|
func admission(logger logr.Logger, inner handlers.AdmissionHandler, debugModeOpts DebugModeOptions) http.HandlerFunc {
|
|
|
|
return handlers.Admission(logger, dump(protect(inner), debugModeOpts))
|
2019-02-07 19:22:04 +02:00
|
|
|
}
|
2022-09-26 17:55:46 +02:00
|
|
|
|
|
|
|
func registerWebhookHandlers(
|
|
|
|
logger logr.Logger,
|
|
|
|
mux *httprouter.Router,
|
|
|
|
basePath string,
|
|
|
|
configuration config.Configuration,
|
|
|
|
handlerFunc func(logr.Logger, *admissionv1.AdmissionRequest, string, time.Time) *admissionv1.AdmissionResponse,
|
2022-10-21 17:17:49 +01:00
|
|
|
debugModeOpts DebugModeOptions,
|
2022-09-26 17:55:46 +02:00
|
|
|
) {
|
2022-10-12 08:52:42 +02:00
|
|
|
mux.HandlerFunc("POST", basePath, admission(logger, filter(
|
2022-09-26 17:55:46 +02:00
|
|
|
configuration,
|
|
|
|
func(logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
|
|
|
|
return handlerFunc(logger, request, "all", startTime)
|
2022-10-21 17:17:49 +01:00
|
|
|
}), debugModeOpts),
|
2022-09-26 17:55:46 +02:00
|
|
|
)
|
2022-10-12 08:52:42 +02:00
|
|
|
mux.HandlerFunc("POST", basePath+"/fail", admission(logger, filter(
|
2022-09-26 17:55:46 +02:00
|
|
|
configuration,
|
|
|
|
func(logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
|
|
|
|
return handlerFunc(logger, request, "fail", startTime)
|
2022-10-21 17:17:49 +01:00
|
|
|
}), debugModeOpts),
|
2022-09-26 17:55:46 +02:00
|
|
|
)
|
2022-10-12 08:52:42 +02:00
|
|
|
mux.HandlerFunc("POST", basePath+"/ignore", admission(logger, filter(
|
2022-09-26 17:55:46 +02:00
|
|
|
configuration,
|
|
|
|
func(logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
|
|
|
|
return handlerFunc(logger, request, "ignore", startTime)
|
2022-10-21 17:17:49 +01:00
|
|
|
}), debugModeOpts),
|
2022-09-26 17:55:46 +02:00
|
|
|
)
|
|
|
|
}
|