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"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"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"
2020-10-07 11:12:31 -07:00
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
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"
2021-02-04 02:39:42 +05:30
"github.com/kyverno/kyverno/pkg/common"
2020-10-07 11:12:31 -07:00
"github.com/kyverno/kyverno/pkg/config"
client "github.com/kyverno/kyverno/pkg/dclient"
2021-02-16 19:17:20 -08:00
enginectx "github.com/kyverno/kyverno/pkg/engine/context"
2021-05-15 23:33:41 +05:30
"github.com/kyverno/kyverno/pkg/engine/response"
2020-10-07 11:12:31 -07:00
"github.com/kyverno/kyverno/pkg/event"
2020-12-16 19:44:28 +05:30
"github.com/kyverno/kyverno/pkg/generate"
2021-05-05 00:41:13 +05:30
"github.com/kyverno/kyverno/pkg/metrics"
2021-05-15 23:33:41 +05:30
admissionReviewLatency "github.com/kyverno/kyverno/pkg/metrics/admissionreviewlatency"
2020-10-07 11:12:31 -07:00
"github.com/kyverno/kyverno/pkg/openapi"
"github.com/kyverno/kyverno/pkg/policycache"
2020-11-09 11:26:12 -08:00
"github.com/kyverno/kyverno/pkg/policyreport"
2020-10-07 11:12:31 -07:00
"github.com/kyverno/kyverno/pkg/policystatus"
2020-11-09 11:26:12 -08:00
"github.com/kyverno/kyverno/pkg/resourcecache"
2021-03-16 11:31:04 -07:00
ktls "github.com/kyverno/kyverno/pkg/tls"
2020-10-07 11:12:31 -07:00
tlsutils "github.com/kyverno/kyverno/pkg/tls"
userinfo "github.com/kyverno/kyverno/pkg/userinfo"
"github.com/kyverno/kyverno/pkg/utils"
"github.com/kyverno/kyverno/pkg/webhookconfig"
2020-12-16 19:44:28 +05:30
webhookgenerate "github.com/kyverno/kyverno/pkg/webhooks/generate"
2021-03-23 10:34:03 -07:00
"github.com/pkg/errors"
2019-03-04 20:40:02 +02:00
v1beta1 "k8s.io/api/admission/v1beta1"
2019-08-23 18:34:23 -07:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2021-02-04 02:39:42 +05:30
informers "k8s.io/client-go/informers/core/v1"
2020-03-29 07:25:13 +05:30
rbacinformer "k8s.io/client-go/informers/rbac/v1"
2021-02-04 02:39:42 +05:30
listerv1 "k8s.io/client-go/listers/core/v1"
2019-11-11 15:43:13 -08:00
rbaclister "k8s.io/client-go/listers/rbac/v1"
2019-08-12 10:02:07 -07:00
"k8s.io/client-go/tools/cache"
2019-02-07 19:22:04 +02:00
)
2019-03-04 20:40:02 +02:00
// WebhookServer contains configured TLS server with MutationWebhook.
2019-02-07 19:22:04 +02:00
type WebhookServer struct {
2020-10-22 00:41:25 -07:00
server * http . Server
2020-03-29 07:25:13 +05:30
client * client . Client
kyvernoClient * kyvernoclient . Clientset
2020-05-26 10:36:56 -07:00
2020-12-15 04:22:13 +05:30
// grLister can list/get generate request from the shared informer's store
grLister kyvernolister . GenerateRequestNamespaceLister
// grSynced returns true if the Generate Request store has been synced at least once
grSynced cache . InformerSynced
2019-11-15 15:59:37 -08:00
// list/get cluster policy resource
2020-03-29 07:25:13 +05:30
pLister kyvernolister . ClusterPolicyLister
2020-05-26 10:36:56 -07:00
2019-11-15 15:59:37 -08:00
// returns true if the cluster policy store has synced atleast
2020-03-29 07:25:13 +05:30
pSynced cache . InformerSynced
2020-05-26 10:36:56 -07:00
2019-11-15 15:59:37 -08:00
// list/get role binding resource
2020-03-29 07:25:13 +05:30
rbLister rbaclister . RoleBindingLister
2020-05-26 10:36:56 -07:00
2020-07-10 11:48:27 -07:00
// list/get role binding resource
rLister rbaclister . RoleLister
// list/get role binding resource
crLister rbaclister . ClusterRoleLister
2019-11-15 15:59:37 -08:00
// return true if role bining store has synced atleast once
2020-03-29 07:25:13 +05:30
rbSynced cache . InformerSynced
2020-05-26 10:36:56 -07:00
2020-07-10 16:59:17 -07:00
// return true if role store has synced atleast once
rSynced cache . InformerSynced
2019-11-15 15:59:37 -08:00
// list/get cluster role binding resource
2020-03-29 07:25:13 +05:30
crbLister rbaclister . ClusterRoleBindingLister
2020-05-26 10:36:56 -07:00
2019-11-15 15:59:37 -08:00
// return true if cluster role binding store has synced atleast once
2020-03-29 07:25:13 +05:30
crbSynced cache . InformerSynced
2020-05-26 10:36:56 -07:00
2020-07-10 16:59:17 -07:00
// return true if cluster role store has synced atleast once
crSynced cache . InformerSynced
2019-11-15 15:59:37 -08:00
// generate events
2020-03-29 07:25:13 +05:30
eventGen event . Interface
2020-05-26 10:36:56 -07:00
2020-07-02 12:49:10 -07:00
// policy cache
pCache policycache . Interface
2019-11-15 15:59:37 -08:00
// webhook registration client
2020-11-26 16:07:06 -08:00
webhookRegister * webhookconfig . Register
2020-05-26 10:36:56 -07:00
2019-08-20 12:51:25 -07:00
// API to send policy stats for aggregation
2020-03-29 07:25:13 +05:30
statusListener policystatus . Listener
2020-05-26 10:36:56 -07:00
2019-10-18 17:38:46 -07:00
// helpers to validate against current loaded configuration
2020-03-29 07:25:13 +05:30
configHandler config . Interface
2020-05-26 10:36:56 -07:00
2019-10-18 17:38:46 -07:00
// channel for cleanup notification
2020-03-29 07:25:13 +05:30
cleanUp chan <- struct { }
2020-05-26 10:36:56 -07:00
2019-10-30 13:39:19 -07:00
// last request time
2020-11-26 16:07:06 -08:00
webhookMonitor * webhookconfig . Monitor
2020-05-26 10:36:56 -07:00
2021-03-16 11:31:04 -07:00
certRenewer * ktls . CertRenewer
2020-11-09 11:26:12 -08:00
// policy report generator
prGenerator policyreport . GeneratorInterface
2020-05-26 10:36:56 -07:00
2020-01-07 10:33:28 -08:00
// generate request generator
2020-12-16 19:44:28 +05:30
grGenerator * webhookgenerate . Generator
2020-05-26 10:36:56 -07:00
2021-02-04 02:39:42 +05:30
nsLister listerv1 . NamespaceLister
// nsListerSynced returns true if the namespace store has been synced at least once
nsListerSynced cache . InformerSynced
2020-07-09 11:48:34 -07:00
auditHandler AuditHandler
2020-12-03 23:13:43 +05:30
log logr . Logger
2020-11-26 16:07:06 -08:00
2020-07-09 11:48:34 -07:00
openAPIController * openapi . Controller
2020-07-02 12:49:10 -07:00
2020-09-23 02:41:49 +05:30
// resCache - controls creation and fetching of resource informer cache
2021-01-29 17:38:23 -08:00
resCache resourcecache . ResourceCache
2020-12-16 19:44:28 +05:30
grController * generate . Controller
2021-01-20 15:25:27 -08:00
debug bool
2021-05-05 00:41:13 +05:30
promConfig * metrics . PromConfig
2019-03-04 20:40:02 +02:00
}
// NewWebhookServer creates new instance of WebhookServer accordingly to given configuration
// Policy Controller and Kubernetes Client should be initialized in configuration
2019-05-13 21:27:47 +03:00
func NewWebhookServer (
2020-03-29 07:25:13 +05:30
kyvernoClient * kyvernoclient . Clientset ,
client * client . Client ,
2020-11-17 13:07:30 -08:00
tlsPair * tlsutils . PemPair ,
2020-12-15 04:22:13 +05:30
grInformer kyvernoinformer . GenerateRequestInformer ,
2020-03-29 07:25:13 +05:30
pInformer kyvernoinformer . ClusterPolicyInformer ,
rbInformer rbacinformer . RoleBindingInformer ,
crbInformer rbacinformer . ClusterRoleBindingInformer ,
2020-07-10 11:48:27 -07:00
rInformer rbacinformer . RoleInformer ,
crInformer rbacinformer . ClusterRoleInformer ,
2021-02-04 02:39:42 +05:30
namespace informers . NamespaceInformer ,
2020-03-29 07:25:13 +05:30
eventGen event . Interface ,
2020-07-02 12:49:10 -07:00
pCache policycache . Interface ,
2020-11-26 16:07:06 -08:00
webhookRegistrationClient * webhookconfig . Register ,
webhookMonitor * webhookconfig . Monitor ,
2021-03-16 11:31:04 -07:00
certRenewer * ktls . CertRenewer ,
2020-03-29 07:25:13 +05:30
statusSync policystatus . Listener ,
configHandler config . Interface ,
2020-11-09 11:26:12 -08:00
prGenerator policyreport . GeneratorInterface ,
2020-12-16 19:44:28 +05:30
grGenerator * webhookgenerate . Generator ,
2020-07-09 11:48:34 -07:00
auditHandler AuditHandler ,
2020-03-17 11:05:20 -07:00
cleanUp chan <- struct { } ,
log logr . Logger ,
2020-03-27 19:06:06 +05:30
openAPIController * openapi . Controller ,
2021-01-29 17:38:23 -08:00
resCache resourcecache . ResourceCache ,
2020-12-16 19:44:28 +05:30
grc * generate . Controller ,
2021-01-20 15:25:27 -08:00
debug bool ,
2021-05-05 00:41:13 +05:30
promConfig * metrics . PromConfig ,
2020-03-17 11:05:20 -07:00
) ( * WebhookServer , error ) {
2019-03-04 20:40:02 +02:00
2019-05-13 21:27:47 +03:00
if tlsPair == nil {
2019-03-22 22:11:55 +02:00
return nil , errors . New ( "NewWebhookServer is not initialized properly" )
2019-03-04 20:40:02 +02:00
}
2020-03-29 07:25:13 +05:30
2019-03-04 20:40:02 +02:00
var tlsConfig tls . Config
2019-03-22 22:11:55 +02:00
pair , err := tls . X509KeyPair ( tlsPair . Certificate , tlsPair . PrivateKey )
2019-03-04 20:40:02 +02:00
if err != nil {
return nil , err
}
tlsConfig . Certificates = [ ] tls . Certificate { pair }
2020-03-29 07:25:13 +05:30
ws := & WebhookServer {
2021-02-04 02:39:42 +05:30
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 ,
2020-07-10 16:59:17 -07:00
2021-03-23 10:34:03 -07:00
crbLister : crbInformer . Lister ( ) ,
crLister : crInformer . Lister ( ) ,
crbSynced : crbInformer . Informer ( ) . HasSynced ,
crSynced : crInformer . Informer ( ) . HasSynced ,
eventGen : eventGen ,
pCache : pCache ,
webhookRegister : webhookRegistrationClient ,
statusListener : statusSync ,
configHandler : configHandler ,
cleanUp : cleanUp ,
webhookMonitor : webhookMonitor ,
certRenewer : certRenewer ,
prGenerator : prGenerator ,
grGenerator : grGenerator ,
grController : grc ,
auditHandler : auditHandler ,
log : log ,
openAPIController : openAPIController ,
resCache : resCache ,
debug : debug ,
2021-05-05 00:41:13 +05:30
promConfig : promConfig ,
2020-03-29 07:25:13 +05:30
}
2020-05-18 17:00:52 -07:00
2020-04-27 18:38:03 +05:30
mux := httprouter . New ( )
2020-08-05 23:53:27 +05:30
mux . HandlerFunc ( "POST" , config . MutatingWebhookServicePath , ws . handlerFunc ( ws . ResourceMutation , true ) )
2020-05-19 00:14:23 -07:00
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 ) )
2020-05-18 17:00:52 -07:00
2020-05-27 06:33:32 +05:30
// Handle Liveness responds to a Kubernetes Liveness probe
// Fail this request if Kubernetes should restart this instance
2020-06-01 19:37:48 -07:00
mux . HandlerFunc ( "GET" , config . LivenessServicePath , func ( w http . ResponseWriter , r * http . Request ) {
2020-05-27 06:33:32 +05:30
defer r . Body . Close ( )
2021-03-31 15:44:56 -04:00
if err := ws . webhookRegister . Check ( ) ; err != nil {
w . WriteHeader ( http . StatusInternalServerError )
} else {
w . WriteHeader ( http . StatusOK )
}
2020-05-27 06:33:32 +05:30
} )
// Handle Readiness responds to a Kubernetes Readiness probe
// Fail this request if this instance can't accept traffic, but Kubernetes shouldn't restart it
2020-06-01 19:37:48 -07:00
mux . HandlerFunc ( "GET" , config . ReadinessServicePath , func ( w http . ResponseWriter , r * http . Request ) {
2020-05-27 06:33:32 +05:30
defer r . Body . Close ( )
w . WriteHeader ( http . StatusOK )
} )
2020-10-22 00:41:25 -07:00
ws . server = & http . Server {
Addr : ":9443" , // Listen on port for HTTPS requests
2019-03-04 20:40:02 +02:00
TLSConfig : & tlsConfig ,
Handler : mux ,
ReadTimeout : 15 * time . Second ,
WriteTimeout : 15 * time . Second ,
}
return ws , nil
2019-02-21 20:31:18 +02:00
}
2020-03-29 07:36:18 +05:30
func ( ws * WebhookServer ) handlerFunc ( handler func ( request * v1beta1 . AdmissionRequest ) * v1beta1 . AdmissionResponse , filter bool ) http . HandlerFunc {
2020-05-17 14:37:05 -07:00
return func ( rw http . ResponseWriter , r * http . Request ) {
2020-03-29 07:36:18 +05:30
startTime := time . Now ( )
2020-11-26 16:07:06 -08:00
ws . webhookMonitor . SetTime ( startTime )
2020-05-17 14:37:05 -07:00
admissionReview := ws . bodyToAdmissionReview ( r , rw )
2020-03-29 07:36:18 +05:30
if admissionReview == nil {
2020-05-17 14:37:05 -07:00
ws . log . Info ( "failed to parse admission review request" , "request" , r )
2020-03-29 07:36:18 +05:30
return
2020-03-25 10:53:03 +05:30
}
2020-05-17 14:37:05 -07:00
2020-08-17 11:17:07 -07:00
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 )
2020-03-29 07:36:18 +05:30
admissionReview . Response = & v1beta1 . AdmissionResponse {
Allowed : true ,
2020-05-17 14:37:05 -07:00
UID : admissionReview . Request . UID ,
2020-03-25 10:53:03 +05:30
}
2020-03-29 07:36:18 +05:30
// Do not process the admission requests for kinds that are in filterKinds for filtering
request := admissionReview . Request
2020-05-17 14:37:05 -07:00
if filter && ws . configHandler . ToFilter ( request . Kind . Kind , request . Namespace , request . Name ) {
writeResponse ( rw , admissionReview )
2020-03-29 07:36:18 +05:30
return
2020-03-29 07:25:13 +05:30
}
2020-04-22 20:15:16 +05:30
2020-05-17 14:37:05 -07:00
admissionReview . Response = handler ( request )
writeResponse ( rw , admissionReview )
2021-01-06 16:32:02 -08:00
logger . V ( 4 ) . Info ( "admission review request processed" , "time" , time . Since ( startTime ) . String ( ) )
2020-05-18 18:30:39 -07:00
2020-05-17 14:37:05 -07:00
return
}
}
2020-03-29 07:25:13 +05:30
2020-05-17 14:37:05 -07:00
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
}
2020-03-29 07:25:13 +05:30
2020-05-17 14:37:05 -07:00
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 )
2019-03-04 20:40:02 +02:00
}
2019-02-15 20:00:49 +02:00
}
2020-11-17 13:07:30 -08:00
// ResourceMutation mutates resource
2020-08-05 23:53:27 +05:30
func ( ws * WebhookServer ) ResourceMutation ( request * v1beta1 . AdmissionRequest ) * v1beta1 . AdmissionResponse {
2021-05-13 12:03:13 -07:00
logger := ws . log . WithName ( "ResourceMutation" ) . WithValues ( "uid" , request . UID , "kind" , request . Kind . Kind , "namespace" , request . Namespace , "name" , request . Name , "operation" , request . Operation , "gvk" , request . Kind . String ( ) )
2020-07-08 14:22:32 -07:00
2020-05-18 20:01:20 -07:00
if excludeKyvernoResources ( request . Kind . Kind ) {
return & v1beta1 . AdmissionResponse {
Allowed : true ,
Result : & metav1 . Status {
Status : "Success" ,
} ,
}
}
2021-03-23 10:34:03 -07:00
2020-08-17 11:17:07 -07:00
logger . V ( 6 ) . Info ( "received an admission request in mutating webhook" )
2021-05-15 19:15:04 +05:30
// timestamp at which this admission request got triggered
admissionRequestTimestamp := time . Now ( ) . Unix ( )
2021-05-07 18:07:41 -07:00
mutatePolicies := ws . pCache . GetPolicyObject ( policycache . Mutate , request . Kind . Kind , "" )
generatePolicies := ws . pCache . GetPolicyObject ( policycache . Generate , request . Kind . Kind , "" )
2020-08-17 11:17:07 -07:00
2020-08-19 21:37:23 +05:30
// Get namespace policies from the cache for the requested resource namespace
2021-05-07 18:07:41 -07:00
nsMutatePolicies := ws . pCache . GetPolicyObject ( policycache . Mutate , request . Kind . Kind , request . Namespace )
2020-08-19 21:37:23 +05:30
mutatePolicies = append ( mutatePolicies , nsMutatePolicies ... )
2019-11-07 12:13:16 -08:00
2020-01-06 19:24:24 -08:00
// convert RAW to unstructured
2020-05-18 18:05:22 -07:00
resource , err := utils . ConvertResource ( request . Object . Raw , request . Kind . Group , request . Kind . Version , request . Kind . Kind , request . Namespace )
2020-01-06 19:24:24 -08:00
if err != nil {
2020-03-17 11:05:20 -07:00
logger . Error ( err , "failed to convert RAW resource to unstructured format" )
2020-01-06 19:24:24 -08:00
return & v1beta1 . AdmissionResponse {
Allowed : false ,
Result : & metav1 . Status {
Status : "Failure" ,
2020-01-07 11:32:52 -08:00
Message : err . Error ( ) ,
2020-01-06 19:24:24 -08:00
} ,
}
}
2021-03-23 10:34:03 -07:00
var roles , clusterRoles [ ] string
// getRoleRef only if policy has roles/clusterroles defined
if containRBACInfo ( mutatePolicies , generatePolicies ) {
if roles , clusterRoles , err = userinfo . GetRoleRef ( ws . rbLister , ws . crbLister , request , ws . configHandler ) ; err != nil {
logger . Error ( err , "failed to get RBAC information for request" )
}
}
2020-04-10 23:24:54 +05:30
userRequestInfo := v1 . RequestInfo {
Roles : roles ,
ClusterRoles : clusterRoles ,
2021-03-23 10:34:03 -07:00
AdmissionUserInfo : * request . UserInfo . DeepCopy ( ) ,
2020-04-15 21:17:14 +05:30
}
2020-04-10 23:24:54 +05:30
2021-03-23 10:34:03 -07:00
ctx , err := newVariablesContext ( request , & userRequestInfo )
2020-04-10 23:24:54 +05:30
if err != nil {
2021-03-23 10:34:03 -07:00
logger . Error ( err , "unable to build variable context" )
2020-04-10 23:24:54 +05:30
}
2021-03-23 10:34:03 -07:00
if err := ctx . AddImageInfo ( & resource ) ; err != nil {
logger . Error ( err , "unable to add image info to variables context" )
2020-04-10 23:24:54 +05:30
}
2020-05-18 18:05:22 -07:00
var patches [ ] byte
patchedResource := request . Object . Raw
2020-05-18 17:00:52 -07:00
2020-11-26 16:07:06 -08:00
// MUTATION
2021-05-15 23:33:41 +05:30
var triggeredMutatePolicies [ ] v1 . ClusterPolicy
var mutateEngineResponses [ ] * response . EngineResponse
patches , triggeredMutatePolicies , mutateEngineResponses = ws . HandleMutation ( request , resource , mutatePolicies , ctx , userRequestInfo , admissionRequestTimestamp )
2021-03-23 10:34:03 -07:00
logger . V ( 6 ) . Info ( "" , "generated patches" , string ( patches ) )
// patch the resource with patches before handling validation rules
patchedResource = processResourceWithPatches ( patches , request . Object . Raw , logger )
logger . V ( 6 ) . Info ( "" , "patchedResource" , string ( patchedResource ) )
2021-05-15 23:33:41 +05:30
admissionReviewLatencyDuration := int64 ( time . Since ( time . Unix ( admissionRequestTimestamp , 0 ) ) )
// registering the kyverno_admission_review_latency_milliseconds metric concurrently
go registerAdmissionReviewLatencyMetricMutate ( logger , * ws . promConfig . Metrics , string ( request . Operation ) , mutateEngineResponses , triggeredMutatePolicies , admissionReviewLatencyDuration )
2019-08-14 11:51:01 -07:00
2020-01-07 10:33:28 -08:00
// GENERATE
2021-03-23 10:34:03 -07:00
newRequest := request . DeepCopy ( )
newRequest . Object . Raw = patchedResource
2020-05-18 17:00:52 -07:00
2021-05-15 23:33:41 +05:30
// this channel will be used to transmit the admissionReviewLatency from ws.HandleGenerate(..,) goroutine to registeGeneraterPolicyAdmissionReviewLatencyMetric(...) goroutine
admissionReviewCompletionLatencyChannel := make ( chan int64 , 1 )
go ws . HandleGenerate ( newRequest , generatePolicies , ctx , userRequestInfo , ws . configHandler , admissionRequestTimestamp , & admissionReviewCompletionLatencyChannel )
// registering the kyverno_admission_review_latency_milliseconds metric concurrently
go registerAdmissionReviewLatencyMetricGenerate ( logger , * ws . promConfig . Metrics , string ( request . Operation ) , mutateEngineResponses , triggeredMutatePolicies , & admissionReviewCompletionLatencyChannel )
2019-08-23 18:34:23 -07:00
patchType := v1beta1 . PatchTypeJSONPatch
return & v1beta1 . AdmissionResponse {
Allowed : true ,
Result : & metav1 . Status {
Status : "Success" ,
} ,
Patch : patches ,
PatchType : & patchType ,
}
2019-08-14 11:51:01 -07:00
}
2021-05-15 23:33:41 +05:30
func registerAdmissionReviewLatencyMetricMutate ( logger logr . Logger , promMetrics metrics . PromMetrics , requestOperation string , engineResponses [ ] * response . EngineResponse , triggeredPolicies [ ] v1 . ClusterPolicy , admissionReviewLatencyDuration int64 ) {
resourceRequestOperationPromAlias , err := admissionReviewLatency . ParseResourceRequestOperation ( requestOperation )
if err != nil {
logger . Error ( err , "error occurred while registering kyverno_admission_review_latency_milliseconds metrics" )
}
if err := admissionReviewLatency . ParsePromMetrics ( promMetrics ) . ProcessEngineResponses ( engineResponses , triggeredPolicies , admissionReviewLatencyDuration , resourceRequestOperationPromAlias ) ; err != nil {
logger . Error ( err , "error occurred while registering kyverno_admission_review_latency_milliseconds metrics" )
}
}
func registerAdmissionReviewLatencyMetricGenerate ( logger logr . Logger , promMetrics metrics . PromMetrics , requestOperation string , engineResponses [ ] * response . EngineResponse , triggeredPolicies [ ] v1 . ClusterPolicy , latencyReceiver * chan int64 ) {
defer close ( * latencyReceiver )
resourceRequestOperationPromAlias , err := admissionReviewLatency . ParseResourceRequestOperation ( requestOperation )
if err != nil {
logger . Error ( err , "error occurred while registering kyverno_admission_review_latency_milliseconds 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 := admissionReviewLatency . ParsePromMetrics ( promMetrics ) . ProcessEngineResponses ( engineResponses , triggeredPolicies , admissionReviewLatencyDuration , resourceRequestOperationPromAlias ) ; err != nil {
logger . Error ( err , "error occurred while registering kyverno_admission_review_latency_milliseconds metrics" )
}
}
2020-04-10 23:24:54 +05:30
func ( ws * WebhookServer ) resourceValidation ( request * v1beta1 . AdmissionRequest ) * v1beta1 . AdmissionResponse {
2020-12-08 19:09:59 -08:00
logger := ws . log . WithName ( "Validate" ) . WithValues ( "uid" , request . UID , "kind" , request . Kind . Kind , "namespace" , request . Namespace , "name" , request . Name , "operation" , request . Operation )
2020-12-30 00:12:36 +05:30
if request . Operation == v1beta1 . Delete {
ws . handleDelete ( request )
2020-12-16 19:44:28 +05:30
}
2020-07-10 16:59:17 -07:00
2020-05-18 20:01:20 -07:00
if excludeKyvernoResources ( request . Kind . Kind ) {
return & v1beta1 . AdmissionResponse {
Allowed : true ,
Result : & metav1 . Status {
Status : "Success" ,
} ,
}
}
2020-08-17 11:17:07 -07:00
logger . V ( 6 ) . Info ( "received an admission request in validating webhook" )
2021-05-15 19:15:04 +05:30
// timestamp at which this admission request got triggered
admissionRequestTimestamp := time . Now ( ) . Unix ( )
2020-08-17 11:17:07 -07:00
2021-05-07 18:07:41 -07:00
policies := ws . pCache . GetPolicyObject ( policycache . ValidateEnforce , request . Kind . Kind , "" )
2020-08-19 21:37:23 +05:30
// Get namespace policies from the cache for the requested resource namespace
2021-05-07 18:07:41 -07:00
nsPolicies := ws . pCache . GetPolicyObject ( policycache . ValidateEnforce , request . Kind . Kind , request . Namespace )
2020-08-19 21:37:23 +05:30
policies = append ( policies , nsPolicies ... )
2020-07-02 12:49:10 -07:00
if len ( policies ) == 0 {
2021-02-22 12:08:26 -08:00
// push admission request to audit handler, this won't block the admission request
ws . auditHandler . Add ( request . DeepCopy ( ) )
2021-01-02 01:10:14 -08:00
logger . V ( 4 ) . Info ( "no enforce validation policies; returning AdmissionResponse.Allowed: true" )
2020-01-11 18:33:11 +05:30
return & v1beta1 . AdmissionResponse { Allowed : true }
}
var roles , clusterRoles [ ] string
2020-07-10 16:59:17 -07:00
var err error
2020-01-11 18:33:11 +05:30
// getRoleRef only if policy has roles/clusterroles defined
2020-12-01 22:50:40 -08:00
if containRBACInfo ( policies ) {
2020-08-14 12:21:06 -07:00
roles , clusterRoles , err = userinfo . GetRoleRef ( ws . rbLister , ws . crbLister , request , ws . configHandler )
2020-01-11 18:33:11 +05:30
if err != nil {
2020-07-09 11:48:34 -07:00
return & v1beta1 . AdmissionResponse {
Allowed : false ,
Result : & metav1 . Status {
Status : "Failure" ,
Message : err . Error ( ) ,
} ,
}
2020-01-11 18:33:11 +05:30
}
}
2020-04-10 23:24:54 +05:30
userRequestInfo := v1 . RequestInfo {
Roles : roles ,
ClusterRoles : clusterRoles ,
2021-03-23 10:34:03 -07:00
AdmissionUserInfo : * request . UserInfo . DeepCopy ( ) ,
2020-04-15 21:17:14 +05:30
}
2020-04-10 23:24:54 +05:30
2021-03-23 10:34:03 -07:00
ctx , err := newVariablesContext ( request , & userRequestInfo )
2020-04-10 23:24:54 +05:30
if err != nil {
2021-03-23 10:34:03 -07:00
return & v1beta1 . AdmissionResponse {
Allowed : false ,
Result : & metav1 . Status {
Status : "Failure" ,
Message : err . Error ( ) ,
} ,
}
2020-04-10 23:24:54 +05:30
}
2021-02-04 02:39:42 +05:30
namespaceLabels := make ( map [ string ] string )
if request . Kind . Kind != "Namespace" && request . Namespace != "" {
namespaceLabels = common . GetNamespaceSelectorsFromNamespaceLister ( request . Kind . Kind , request . Namespace , ws . nsLister , logger )
}
2021-05-15 19:15:04 +05:30
ok , msg := HandleValidation ( ws . promConfig , request , policies , nil , ctx , userRequestInfo , ws . statusListener , ws . eventGen , ws . prGenerator , ws . log , ws . configHandler , ws . resCache , ws . client , namespaceLabels , admissionRequestTimestamp )
2020-05-20 17:08:30 -07:00
if ! ok {
logger . Info ( "admission request denied" )
return & v1beta1 . AdmissionResponse {
Allowed : false ,
Result : & metav1 . Status {
Status : "Failure" ,
Message : msg ,
} ,
2020-01-11 18:33:11 +05:30
}
}
return & v1beta1 . AdmissionResponse {
Allowed : true ,
Result : & metav1 . Status {
Status : "Success" ,
} ,
}
}
2019-05-14 18:10:25 +03:00
// RunAsync TLS server in separate thread and returns control immediately
2019-10-25 16:55:48 -05:00
func ( ws * WebhookServer ) RunAsync ( stopCh <- chan struct { } ) {
2020-03-17 11:05:20 -07:00
logger := ws . log
2020-12-15 04:22:13 +05:30
if ! cache . WaitForCacheSync ( stopCh , ws . grSynced , ws . pSynced , ws . rbSynced , ws . crbSynced , ws . rSynced , ws . crSynced ) {
2020-03-17 11:05:20 -07:00
logger . Info ( "failed to sync informer cache" )
2019-11-15 15:59:37 -08:00
}
2020-11-09 11:26:12 -08:00
go func ( ) {
2020-03-17 11:05:20 -07:00
logger . V ( 3 ) . Info ( "started serving requests" , "addr" , ws . server . Addr )
2019-08-27 17:00:16 -07:00
if err := ws . server . ListenAndServeTLS ( "" , "" ) ; err != http . ErrServerClosed {
2020-03-17 11:05:20 -07:00
logger . Error ( err , "failed to listen to requests" )
2019-03-04 20:40:02 +02:00
}
2020-10-22 00:41:25 -07:00
} ( )
2020-05-17 14:37:05 -07:00
2020-11-26 16:07:06 -08:00
logger . Info ( "starting service" )
2021-01-20 15:25:27 -08:00
if ! ws . debug {
2021-03-16 11:31:04 -07:00
go ws . webhookMonitor . Run ( ws . webhookRegister , ws . certRenewer , ws . eventGen , stopCh )
2021-01-20 15:25:27 -08:00
}
2019-02-07 19:22:04 +02:00
}
2019-05-14 18:10:25 +03:00
// Stop TLS server and returns control after the server is shut down
2019-11-18 11:41:37 -08:00
func ( ws * WebhookServer ) Stop ( ctx context . Context ) {
2020-03-17 11:05:20 -07:00
logger := ws . log
2020-11-26 16:07:06 -08:00
// remove the static webhook configurations
go ws . webhookRegister . Remove ( ws . cleanUp )
2019-11-18 11:41:37 -08:00
// shutdown http.Server with context timeout
err := ws . server . Shutdown ( ctx )
2019-03-04 20:40:02 +02:00
if err != nil {
// Error from closing listeners, or context timeout:
2020-03-17 11:05:20 -07:00
logger . Error ( err , "shutting down server" )
2019-03-04 20:40:02 +02:00
ws . server . Close ( )
}
2019-02-07 19:22:04 +02:00
}
2019-05-13 21:27:47 +03:00
2019-05-14 18:10:25 +03:00
// 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 {
2020-03-17 11:05:20 -07:00
logger := ws . log
2020-05-26 10:36:56 -07:00
if request . Body == nil {
logger . Info ( "empty body" , "req" , request . URL . String ( ) )
2019-05-14 18:10:25 +03:00
http . Error ( writer , "empty body" , http . StatusBadRequest )
return nil
}
2020-05-26 10:36:56 -07:00
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 )
}
2019-05-14 18:10:25 +03:00
contentType := request . Header . Get ( "Content-Type" )
if contentType != "application/json" {
2020-03-17 11:05:20 -07:00
logger . Info ( "invalid Content-Type" , "contextType" , contentType )
2019-05-14 18:10:25 +03:00
http . Error ( writer , "invalid Content-Type, expect `application/json`" , http . StatusUnsupportedMediaType )
return nil
}
admissionReview := & v1beta1 . AdmissionReview { }
if err := json . Unmarshal ( body , & admissionReview ) ; err != nil {
2020-03-17 11:05:20 -07:00
logger . Error ( err , "failed to decode request body to type 'AdmissionReview" )
2019-05-14 18:10:25 +03:00
http . Error ( writer , "Can't decode body as AdmissionReview" , http . StatusExpectationFailed )
return nil
}
2019-06-05 17:43:59 -07:00
return admissionReview
2019-05-13 21:27:47 +03:00
}
2021-03-23 10:34:03 -07:00
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
}