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"
"errors"
"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"
"github.com/kyverno/kyverno/pkg/checker"
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/config"
client "github.com/kyverno/kyverno/pkg/dclient"
context2 "github.com/kyverno/kyverno/pkg/engine/context"
enginutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/event"
"github.com/kyverno/kyverno/pkg/openapi"
"github.com/kyverno/kyverno/pkg/policycache"
2020-10-21 17:56:17 -07:00
"github.com/kyverno/kyverno/pkg/policyreport"
2020-10-07 11:12:31 -07:00
"github.com/kyverno/kyverno/pkg/policystatus"
2020-10-21 17:56:17 -07:00
"github.com/kyverno/kyverno/pkg/resourcecache"
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"
"github.com/kyverno/kyverno/pkg/webhooks/generate"
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"
2020-10-21 17:56:17 -07:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2020-03-29 07:25:13 +05:30
rbacinformer "k8s.io/client-go/informers/rbac/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
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-03-29 07:25:13 +05:30
webhookRegistrationClient * webhookconfig . WebhookRegistrationClient
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-03-29 07:25:13 +05:30
lastReqTime * checker . LastReqTime
2020-05-26 10:36:56 -07:00
2020-09-15 06:59:05 -07:00
// policy report generator
prGenerator policyreport . GeneratorInterface
2020-01-07 10:33:28 -08:00
// generate request generator
2020-06-01 19:37:48 -07:00
grGenerator * generate . Generator
2020-05-26 10:36:56 -07:00
2020-03-29 07:25:13 +05:30
resourceWebhookWatcher * webhookconfig . ResourceWebhookRegister
2020-07-09 11:48:34 -07:00
auditHandler AuditHandler
log logr . Logger
openAPIController * openapi . Controller
2020-07-02 12:49:10 -07:00
2020-08-05 23:53:27 +05:30
supportMutateValidate bool
2020-09-23 02:41:49 +05:30
// resCache - controls creation and fetching of resource informer cache
resCache resourcecache . ResourceCacheIface
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 ,
tlsPair * tlsutils . TlsPemPair ,
pInformer kyvernoinformer . ClusterPolicyInformer ,
rbInformer rbacinformer . RoleBindingInformer ,
crbInformer rbacinformer . ClusterRoleBindingInformer ,
2020-07-10 11:48:27 -07:00
rInformer rbacinformer . RoleInformer ,
crInformer rbacinformer . ClusterRoleInformer ,
2020-03-29 07:25:13 +05:30
eventGen event . Interface ,
2020-07-02 12:49:10 -07:00
pCache policycache . Interface ,
2020-03-29 07:25:13 +05:30
webhookRegistrationClient * webhookconfig . WebhookRegistrationClient ,
statusSync policystatus . Listener ,
configHandler config . Interface ,
2020-09-15 06:59:05 -07:00
prGenerator policyreport . GeneratorInterface ,
2020-03-29 07:25:13 +05:30
grGenerator * generate . Generator ,
resourceWebhookWatcher * webhookconfig . ResourceWebhookRegister ,
2020-07-09 11:48:34 -07:00
auditHandler AuditHandler ,
2020-08-05 23:53:27 +05:30
supportMutateValidate bool ,
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 ,
2020-09-23 02:41:49 +05:30
resCache resourcecache . ResourceCacheIface ,
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 {
2020-07-10 16:59:17 -07:00
client : client ,
kyvernoClient : kyvernoClient ,
pLister : pInformer . Lister ( ) ,
pSynced : pInformer . Informer ( ) . HasSynced ,
rbLister : rbInformer . Lister ( ) ,
rbSynced : rbInformer . Informer ( ) . HasSynced ,
rLister : rInformer . Lister ( ) ,
rSynced : rInformer . Informer ( ) . HasSynced ,
2020-03-29 07:25:13 +05:30
crbLister : crbInformer . Lister ( ) ,
2020-07-10 11:48:27 -07:00
crLister : crInformer . Lister ( ) ,
2020-03-29 07:25:13 +05:30
crbSynced : crbInformer . Informer ( ) . HasSynced ,
2020-07-10 16:59:17 -07:00
crSynced : crInformer . Informer ( ) . HasSynced ,
2020-03-29 07:25:13 +05:30
eventGen : eventGen ,
2020-07-02 12:49:10 -07:00
pCache : pCache ,
2020-03-29 07:25:13 +05:30
webhookRegistrationClient : webhookRegistrationClient ,
statusListener : statusSync ,
configHandler : configHandler ,
cleanUp : cleanUp ,
lastReqTime : resourceWebhookWatcher . LastReqTime ,
2020-09-15 06:59:05 -07:00
prGenerator : prGenerator ,
2020-03-29 07:25:13 +05:30
grGenerator : grGenerator ,
resourceWebhookWatcher : resourceWebhookWatcher ,
2020-07-09 11:48:34 -07:00
auditHandler : auditHandler ,
2020-03-17 11:05:20 -07:00
log : log ,
2020-03-27 19:06:06 +05:30
openAPIController : openAPIController ,
2020-08-05 23:53:27 +05:30
supportMutateValidate : supportMutateValidate ,
2020-09-23 02:41:49 +05:30
resCache : resCache ,
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 ( )
w . WriteHeader ( http . StatusOK )
} )
// 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-05-17 14:37:05 -07:00
ws . lastReqTime . SetTime ( startTime )
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 )
2020-07-09 11:48:34 -07:00
logger . V ( 4 ) . Info ( "request processed" , "processingTime" , 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-08-05 23:53:27 +05:30
func ( ws * WebhookServer ) ResourceMutation ( request * v1beta1 . AdmissionRequest ) * v1beta1 . AdmissionResponse {
2020-06-22 18:49:43 -07:00
2020-08-05 23:53:27 +05:30
logger := ws . log . WithName ( "ResourceMutation" ) . WithValues ( "uid" , request . UID , "kind" , request . Kind . Kind , "namespace" , request . Namespace , "name" , request . Name , "operation" , request . Operation )
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" ,
} ,
}
}
2020-08-17 11:17:07 -07:00
logger . V ( 6 ) . Info ( "received an admission request in mutating webhook" )
2020-08-19 21:37:23 +05:30
mutatePolicies := ws . pCache . Get ( policycache . Mutate , nil )
validatePolicies := ws . pCache . Get ( policycache . ValidateEnforce , nil )
generatePolicies := ws . pCache . Get ( policycache . Generate , nil )
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
nsMutatePolicies := ws . pCache . Get ( policycache . Mutate , & request . Namespace )
mutatePolicies = append ( mutatePolicies , nsMutatePolicies ... )
2019-11-07 12:13:16 -08:00
2019-11-11 14:52:09 -08:00
// getRoleRef only if policy has roles/clusterroles defined
2020-04-10 23:24:54 +05:30
var roles , clusterRoles [ ] string
2020-07-02 12:49:10 -07:00
var err error
if containRBACinfo ( mutatePolicies , validatePolicies , generatePolicies ) {
2020-08-14 12:21:06 -07:00
roles , clusterRoles , err = userinfo . GetRoleRef ( ws . rbLister , ws . crbLister , request , ws . configHandler )
2019-11-11 14:52:09 -08:00
if err != nil {
// TODO(shuting): continue apply policy if error getting roleRef?
2020-08-30 20:43:05 +05:30
logger . Error ( err , "failed to get RBAC information for request" )
2019-11-11 14:52:09 -08:00
}
2019-11-08 18:56:24 -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
} ,
}
}
2020-04-10 23:24:54 +05:30
userRequestInfo := v1 . RequestInfo {
Roles : roles ,
ClusterRoles : clusterRoles ,
2020-07-09 11:48:34 -07:00
AdmissionUserInfo : * request . UserInfo . DeepCopy ( ) }
2020-04-10 23:24:54 +05:30
// build context
ctx := context2 . NewContext ( )
2020-04-15 21:17:14 +05:30
err = ctx . AddRequest ( request )
if err != nil {
logger . Error ( err , "failed to load incoming request in context" )
}
2020-04-10 23:24:54 +05:30
err = ctx . AddUserInfo ( userRequestInfo )
if err != nil {
logger . Error ( err , "failed to load userInfo in context" )
}
err = ctx . AddSA ( userRequestInfo . AdmissionUserInfo . Username )
if err != nil {
logger . Error ( err , "failed to load service account in context" )
}
2020-05-18 18:05:22 -07:00
var patches [ ] byte
patchedResource := request . Object . Raw
2020-05-18 17:00:52 -07:00
2020-08-05 23:53:27 +05:30
if ws . supportMutateValidate {
2020-05-18 17:00:52 -07:00
// MUTATION
// mutation failure should not block the resource creation
// any mutation failure is reported as the violation
2020-08-17 11:17:07 -07:00
if resource . GetDeletionTimestamp ( ) == nil {
2020-07-02 12:49:10 -07:00
patches = ws . HandleMutation ( request , resource , mutatePolicies , ctx , userRequestInfo )
logger . V ( 6 ) . Info ( "" , "generated patches" , string ( patches ) )
2020-05-18 17:00:52 -07:00
2020-08-17 11:17:07 -07:00
// patch the resource with patches before handling validation rules
patchedResource = processResourceWithPatches ( patches , request . Object . Raw , logger )
logger . V ( 6 ) . Info ( "" , "patchedResource" , string ( patchedResource ) )
}
2020-05-18 17:00:52 -07:00
if ws . resourceWebhookWatcher != nil && ws . resourceWebhookWatcher . RunValidationInMutatingWebhook == "true" {
2020-07-09 11:48:34 -07:00
// push admission request to audit handler, this won't block the admission request
ws . auditHandler . Add ( request . DeepCopy ( ) )
2020-05-18 17:00:52 -07:00
// VALIDATION
2020-11-02 18:09:20 -08:00
ok , msg := HandleValidation ( request , validatePolicies , nil , ctx , userRequestInfo , ws . statusListener , ws . eventGen , ws . prGenerator , ws . log , ws . configHandler , ws . resCache )
2020-05-18 17:00:52 -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
}
2019-08-23 18:34:23 -07:00
}
2020-05-20 17:08:30 -07:00
} else {
2020-05-21 08:29:35 -07:00
logger . Info ( "mutate and validate rules are not supported prior to Kubernetes 1.14.0" )
2019-08-14 11:51:01 -07:00
}
2020-01-07 10:33:28 -08:00
// GENERATE
2020-06-22 18:49:43 -07:00
// Only applied during resource creation and update
2020-07-09 11:48:34 -07:00
// Success -> Generate Request CR created successfully
2020-01-07 10:33:28 -08:00
// Failed -> Failed to create Generate Request CR
2020-07-08 14:22:32 -07:00
2020-08-11 22:59:50 +05:30
if request . Operation == v1beta1 . Create || request . Operation == v1beta1 . Update {
go ws . HandleGenerate ( request . DeepCopy ( ) , generatePolicies , ctx , userRequestInfo , ws . configHandler )
}
2020-05-18 17:00:52 -07:00
2020-07-09 11:48:34 -07:00
// Succesful processing of mutation & validation rules in policy
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 ,
}
2020-05-18 17:00:52 -07:00
2019-08-14 11:51:01 -07:00
}
2020-04-10 23:24:54 +05:30
func ( ws * WebhookServer ) resourceValidation ( request * v1beta1 . AdmissionRequest ) * v1beta1 . AdmissionResponse {
2020-05-20 17:08:30 -07:00
logger := ws . log . WithName ( "resourceValidation" ) . WithValues ( "uid" , request . UID , "kind" , request . Kind . Kind , "namespace" , request . Namespace , "name" , request . Name , "operation" , request . Operation )
2020-07-10 16:59:17 -07:00
if request . Operation == v1beta1 . Delete || request . Operation == v1beta1 . Update {
if err := ws . excludeKyvernoResources ( request ) ; err != nil {
return & v1beta1 . AdmissionResponse {
Allowed : false ,
Result : & metav1 . Status {
Status : "Failure" ,
Message : err . Error ( ) ,
} ,
2020-07-09 05:48:35 -07:00
}
2020-07-10 11:48:27 -07:00
}
2020-07-09 05:48:35 -07:00
}
2020-07-10 16:59:17 -07:00
2020-08-05 23:53:27 +05:30
if ! ws . supportMutateValidate {
2020-05-21 08:29:35 -07:00
logger . Info ( "mutate and validate rules are not supported prior to Kubernetes 1.14.0" )
2020-05-20 17:08:30 -07:00
return & v1beta1 . AdmissionResponse {
Allowed : true ,
Result : & metav1 . Status {
Status : "Success" ,
} ,
}
}
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" )
2020-07-09 11:48:34 -07:00
// push admission request to audit handler, this won't block the admission request
ws . auditHandler . Add ( request . DeepCopy ( ) )
2020-08-19 21:37:23 +05:30
policies := ws . pCache . Get ( policycache . ValidateEnforce , nil )
// Get namespace policies from the cache for the requested resource namespace
nsPolicies := ws . pCache . Get ( policycache . ValidateEnforce , & request . Namespace )
policies = append ( policies , nsPolicies ... )
2020-07-02 12:49:10 -07:00
if len ( policies ) == 0 {
logger . V ( 4 ) . Info ( "No enforce Validation policy found, returning" )
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
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-05-26 10:36:56 -07:00
logger . Error ( err , "failed to get RBAC information for request" )
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-07-14 20:23:30 -07:00
logger = logger . WithValues ( "username" , request . UserInfo . Username ,
"groups" , request . UserInfo . Groups , "roles" , roles , "clusterRoles" , clusterRoles )
2020-01-11 18:33:11 +05:30
}
2020-04-10 23:24:54 +05:30
userRequestInfo := v1 . RequestInfo {
Roles : roles ,
ClusterRoles : clusterRoles ,
AdmissionUserInfo : request . UserInfo }
// build context
ctx := context2 . NewContext ( )
2020-04-15 21:17:14 +05:30
err = ctx . AddRequest ( request )
if err != nil {
logger . Error ( err , "failed to load incoming request in context" )
}
2020-04-10 23:24:54 +05:30
err = ctx . AddUserInfo ( userRequestInfo )
if err != nil {
logger . Error ( err , "failed to load userInfo in context" )
}
err = ctx . AddSA ( userRequestInfo . AdmissionUserInfo . Username )
if err != nil {
logger . Error ( err , "failed to load service account in context" )
}
2020-11-02 18:09:20 -08:00
ok , msg := HandleValidation ( request , policies , nil , ctx , userRequestInfo , ws . statusListener , ws . eventGen , ws . prGenerator , ws . log , ws . configHandler , ws . resCache )
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-07-10 16:59:17 -07:00
if ! cache . WaitForCacheSync ( stopCh , 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-02 18:09:20 -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-03-17 11:05:20 -07:00
logger . Info ( "starting" )
2020-05-17 14:37:05 -07:00
2020-08-30 20:43:05 +05:30
// verifies if the admission control is enabled and active
2019-10-30 13:39:19 -07:00
// resync: 60 seconds
// deadline: 60 seconds (send request)
// max deadline: deadline*3 (set the deployment annotation as false)
2020-03-29 07:25:13 +05:30
go ws . lastReqTime . Run ( ws . pLister , ws . eventGen , ws . client , checker . DefaultResync , checker . DefaultDeadline , stopCh )
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
2019-11-18 11:41:37 -08:00
// cleanUp
// remove the static webhookconfigurations
2020-03-29 07:25:13 +05:30
go ws . webhookRegistrationClient . RemoveWebhookConfigurations ( 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
}
2020-07-10 16:59:17 -07:00
// excludeKyvernoResources will check resource can have acces or not
func ( ws * WebhookServer ) excludeKyvernoResources ( request * v1beta1 . AdmissionRequest ) error {
logger := ws . log . WithName ( "resourceValidation" ) . WithValues ( "uid" , request . UID , "kind" , request . Kind . Kind , "namespace" , request . Namespace , "name" , request . Name , "operation" , request . Operation )
2020-07-13 13:42:11 -07:00
var resource * unstructured . Unstructured
var err error
var isManagedResourceCheck bool
if request . Operation == v1beta1 . Delete {
resource , err = enginutils . ConvertToUnstructured ( request . OldObject . Raw )
isManagedResourceCheck = true
2020-07-14 20:23:30 -07:00
} else if request . Operation == v1beta1 . Update {
2020-07-13 13:42:11 -07:00
resource , err = enginutils . ConvertToUnstructured ( request . Object . Raw )
isManagedResourceCheck = true
}
2020-07-10 16:59:17 -07:00
if err != nil {
2020-07-13 13:42:11 -07:00
logger . Error ( err , "failed to convert object resource to unstructured format" )
return err
2020-07-10 16:59:17 -07:00
}
2020-07-13 13:42:11 -07:00
if isManagedResourceCheck {
2020-07-10 17:01:48 -07:00
labels := resource . GetLabels ( )
2020-07-10 16:59:17 -07:00
if labels != nil {
2020-07-15 17:19:20 -07:00
if labels [ "app.kubernetes.io/managed-by" ] == "kyverno" && labels [ "policy.kyverno.io/synchronize" ] == "enable" {
2020-08-14 12:21:06 -07:00
isAuthorized , err := userinfo . IsRoleAuthorize ( ws . rbLister , ws . crbLister , ws . rLister , ws . crLister , request , ws . configHandler )
2020-07-13 13:42:11 -07:00
if err != nil {
2020-07-14 20:23:30 -07:00
return fmt . Errorf ( "failed to get RBAC infromation for request %v" , err )
2020-07-13 13:42:11 -07:00
}
if ! isAuthorized {
// convert RAW to unstructured
2020-08-14 12:21:06 -07:00
return fmt . Errorf ( "resource is managed by a Kyverno policy and cannot be update manually. You can edit the policy %s to update this resource." , labels [ "policy.kyverno.io/policy-name" ] )
2020-07-13 13:42:11 -07:00
}
2020-07-10 16:59:17 -07:00
}
}
}
2020-07-13 13:42:11 -07:00
2020-07-10 16:59:17 -07:00
return nil
}