2019-05-13 18:33:01 +00:00
package webhooks
2019-02-07 17:22:04 +00:00
import (
2019-03-04 18:40:02 +00:00
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"time"
2020-03-17 18:05:20 +00:00
"github.com/go-logr/logr"
2020-04-27 13:08:03 +00:00
"github.com/julienschmidt/httprouter"
2020-04-10 17:54:54 +00:00
v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
2019-10-30 20:39:19 +00:00
"github.com/nirmata/kyverno/pkg/checker"
2020-07-10 23:59:17 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2019-08-17 16:58:14 +00:00
kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned"
2020-03-29 01:55:13 +00:00
kyvernoinformer "github.com/nirmata/kyverno/pkg/client/informers/externalversions/kyverno/v1"
2019-11-13 21:41:08 +00:00
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1"
2019-05-22 17:29:10 +00:00
"github.com/nirmata/kyverno/pkg/config"
2019-05-29 21:12:09 +00:00
client "github.com/nirmata/kyverno/pkg/dclient"
2020-05-19 01:05:22 +00:00
context2 "github.com/nirmata/kyverno/pkg/engine/context"
2020-07-10 22:25:05 +00:00
enginutils "github.com/nirmata/kyverno/pkg/engine/utils"
2019-06-27 01:04:50 +00:00
"github.com/nirmata/kyverno/pkg/event"
2020-05-19 01:05:22 +00:00
"github.com/nirmata/kyverno/pkg/openapi"
2020-07-02 19:49:10 +00:00
"github.com/nirmata/kyverno/pkg/policycache"
2020-03-07 07:23:37 +00:00
"github.com/nirmata/kyverno/pkg/policystatus"
2019-11-12 22:41:29 +00:00
"github.com/nirmata/kyverno/pkg/policyviolation"
2019-05-22 17:29:10 +00:00
tlsutils "github.com/nirmata/kyverno/pkg/tls"
2019-11-11 22:52:09 +00:00
userinfo "github.com/nirmata/kyverno/pkg/userinfo"
2020-05-19 01:05:22 +00:00
"github.com/nirmata/kyverno/pkg/utils"
2019-08-21 08:07:32 +00:00
"github.com/nirmata/kyverno/pkg/webhookconfig"
2020-01-07 18:33:28 +00:00
"github.com/nirmata/kyverno/pkg/webhooks/generate"
2019-03-04 18:40:02 +00:00
v1beta1 "k8s.io/api/admission/v1beta1"
2019-08-24 01:34:23 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020-03-29 01:55:13 +00:00
rbacinformer "k8s.io/client-go/informers/rbac/v1"
2019-11-11 23:43:13 +00:00
rbaclister "k8s.io/client-go/listers/rbac/v1"
2019-08-12 17:02:07 +00:00
"k8s.io/client-go/tools/cache"
2019-02-07 17:22:04 +00:00
)
2019-03-04 18:40:02 +00:00
// WebhookServer contains configured TLS server with MutationWebhook.
2019-02-07 17:22:04 +00:00
type WebhookServer struct {
2019-11-15 23:59:37 +00:00
server http . Server
2020-03-29 01:55:13 +00:00
client * client . Client
kyvernoClient * kyvernoclient . Clientset
2020-05-26 17:36:56 +00:00
2019-11-15 23:59:37 +00:00
// list/get cluster policy resource
2020-03-29 01:55:13 +00:00
pLister kyvernolister . ClusterPolicyLister
2020-05-26 17:36:56 +00:00
2019-11-15 23:59:37 +00:00
// returns true if the cluster policy store has synced atleast
2020-03-29 01:55:13 +00:00
pSynced cache . InformerSynced
2020-05-26 17:36:56 +00:00
2019-11-15 23:59:37 +00:00
// list/get role binding resource
2020-03-29 01:55:13 +00:00
rbLister rbaclister . RoleBindingLister
2020-05-26 17:36:56 +00:00
2020-07-10 18:48:27 +00:00
// list/get role binding resource
rLister rbaclister . RoleLister
// list/get role binding resource
crLister rbaclister . ClusterRoleLister
2019-11-15 23:59:37 +00:00
// return true if role bining store has synced atleast once
2020-03-29 01:55:13 +00:00
rbSynced cache . InformerSynced
2020-05-26 17:36:56 +00:00
2020-07-10 23:59:17 +00:00
// return true if role store has synced atleast once
rSynced cache . InformerSynced
2019-11-15 23:59:37 +00:00
// list/get cluster role binding resource
2020-03-29 01:55:13 +00:00
crbLister rbaclister . ClusterRoleBindingLister
2020-05-26 17:36:56 +00:00
2019-11-15 23:59:37 +00:00
// return true if cluster role binding store has synced atleast once
2020-03-29 01:55:13 +00:00
crbSynced cache . InformerSynced
2020-05-26 17:36:56 +00:00
2020-07-10 23:59:17 +00:00
// return true if cluster role store has synced atleast once
crSynced cache . InformerSynced
2019-11-15 23:59:37 +00:00
// generate events
2020-03-29 01:55:13 +00:00
eventGen event . Interface
2020-05-26 17:36:56 +00:00
2020-07-02 19:49:10 +00:00
// policy cache
pCache policycache . Interface
2019-11-15 23:59:37 +00:00
// webhook registration client
2020-03-29 01:55:13 +00:00
webhookRegistrationClient * webhookconfig . WebhookRegistrationClient
2020-05-26 17:36:56 +00:00
2019-08-20 19:51:25 +00:00
// API to send policy stats for aggregation
2020-03-29 01:55:13 +00:00
statusListener policystatus . Listener
2020-05-26 17:36:56 +00:00
2019-10-19 00:38:46 +00:00
// helpers to validate against current loaded configuration
2020-03-29 01:55:13 +00:00
configHandler config . Interface
2020-05-26 17:36:56 +00:00
2019-10-19 00:38:46 +00:00
// channel for cleanup notification
2020-03-29 01:55:13 +00:00
cleanUp chan <- struct { }
2020-05-26 17:36:56 +00:00
2019-10-30 20:39:19 +00:00
// last request time
2020-03-29 01:55:13 +00:00
lastReqTime * checker . LastReqTime
2020-05-26 17:36:56 +00:00
2019-11-12 22:41:29 +00:00
// policy violation generator
2020-03-29 01:55:13 +00:00
pvGenerator policyviolation . GeneratorInterface
2020-05-26 17:36:56 +00:00
2020-01-07 18:33:28 +00:00
// generate request generator
2020-06-02 02:37:48 +00:00
grGenerator * generate . Generator
2020-05-26 17:36:56 +00:00
2020-03-29 01:55:13 +00:00
resourceWebhookWatcher * webhookconfig . ResourceWebhookRegister
2020-07-09 18:48:34 +00:00
auditHandler AuditHandler
log logr . Logger
openAPIController * openapi . Controller
2020-07-02 19:49:10 +00:00
supportMudateValidate bool
2019-03-04 18:40:02 +00:00
}
// NewWebhookServer creates new instance of WebhookServer accordingly to given configuration
// Policy Controller and Kubernetes Client should be initialized in configuration
2019-05-13 18:27:47 +00:00
func NewWebhookServer (
2020-03-29 01:55:13 +00:00
kyvernoClient * kyvernoclient . Clientset ,
client * client . Client ,
tlsPair * tlsutils . TlsPemPair ,
pInformer kyvernoinformer . ClusterPolicyInformer ,
rbInformer rbacinformer . RoleBindingInformer ,
crbInformer rbacinformer . ClusterRoleBindingInformer ,
2020-07-10 18:48:27 +00:00
rInformer rbacinformer . RoleInformer ,
crInformer rbacinformer . ClusterRoleInformer ,
2020-03-29 01:55:13 +00:00
eventGen event . Interface ,
2020-07-02 19:49:10 +00:00
pCache policycache . Interface ,
2020-03-29 01:55:13 +00:00
webhookRegistrationClient * webhookconfig . WebhookRegistrationClient ,
statusSync policystatus . Listener ,
configHandler config . Interface ,
pvGenerator policyviolation . GeneratorInterface ,
grGenerator * generate . Generator ,
resourceWebhookWatcher * webhookconfig . ResourceWebhookRegister ,
2020-07-09 18:48:34 +00:00
auditHandler AuditHandler ,
2020-07-02 19:49:10 +00:00
supportMudateValidate bool ,
2020-03-17 18:05:20 +00:00
cleanUp chan <- struct { } ,
log logr . Logger ,
2020-03-27 13:36:06 +00:00
openAPIController * openapi . Controller ,
2020-03-17 18:05:20 +00:00
) ( * WebhookServer , error ) {
2019-03-04 18:40:02 +00:00
2019-05-13 18:27:47 +00:00
if tlsPair == nil {
2019-03-22 20:11:55 +00:00
return nil , errors . New ( "NewWebhookServer is not initialized properly" )
2019-03-04 18:40:02 +00:00
}
2020-03-29 01:55:13 +00:00
2019-03-04 18:40:02 +00:00
var tlsConfig tls . Config
2019-03-22 20:11:55 +00:00
pair , err := tls . X509KeyPair ( tlsPair . Certificate , tlsPair . PrivateKey )
2019-03-04 18:40:02 +00:00
if err != nil {
return nil , err
}
tlsConfig . Certificates = [ ] tls . Certificate { pair }
2020-03-29 01:55:13 +00:00
ws := & WebhookServer {
2020-07-10 23:59:17 +00: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 01:55:13 +00:00
crbLister : crbInformer . Lister ( ) ,
2020-07-10 18:48:27 +00:00
crLister : crInformer . Lister ( ) ,
2020-03-29 01:55:13 +00:00
crbSynced : crbInformer . Informer ( ) . HasSynced ,
2020-07-10 23:59:17 +00:00
crSynced : crInformer . Informer ( ) . HasSynced ,
2020-03-29 01:55:13 +00:00
eventGen : eventGen ,
2020-07-02 19:49:10 +00:00
pCache : pCache ,
2020-03-29 01:55:13 +00:00
webhookRegistrationClient : webhookRegistrationClient ,
statusListener : statusSync ,
configHandler : configHandler ,
cleanUp : cleanUp ,
lastReqTime : resourceWebhookWatcher . LastReqTime ,
pvGenerator : pvGenerator ,
grGenerator : grGenerator ,
resourceWebhookWatcher : resourceWebhookWatcher ,
2020-07-09 18:48:34 +00:00
auditHandler : auditHandler ,
2020-03-17 18:05:20 +00:00
log : log ,
2020-03-27 13:36:06 +00:00
openAPIController : openAPIController ,
2020-07-02 19:49:10 +00:00
supportMudateValidate : supportMudateValidate ,
2020-03-29 01:55:13 +00:00
}
2020-05-19 00:00:52 +00:00
2020-04-27 13:08:03 +00:00
mux := httprouter . New ( )
2020-05-19 07:14:23 +00:00
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 ) )
2020-05-19 00:00:52 +00:00
2020-05-27 01:03:32 +00:00
// Handle Liveness responds to a Kubernetes Liveness probe
// Fail this request if Kubernetes should restart this instance
2020-06-02 02:37:48 +00:00
mux . HandlerFunc ( "GET" , config . LivenessServicePath , func ( w http . ResponseWriter , r * http . Request ) {
2020-05-27 01:03:32 +00:00
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-02 02:37:48 +00:00
mux . HandlerFunc ( "GET" , config . ReadinessServicePath , func ( w http . ResponseWriter , r * http . Request ) {
2020-05-27 01:03:32 +00:00
defer r . Body . Close ( )
w . WriteHeader ( http . StatusOK )
} )
2019-03-04 18:40:02 +00:00
ws . server = http . Server {
Addr : ":443" , // Listen on port for HTTPS requests
TLSConfig : & tlsConfig ,
Handler : mux ,
ReadTimeout : 15 * time . Second ,
WriteTimeout : 15 * time . Second ,
}
return ws , nil
2019-02-21 18:31:18 +00:00
}
2020-03-29 02:06:18 +00:00
func ( ws * WebhookServer ) handlerFunc ( handler func ( request * v1beta1 . AdmissionRequest ) * v1beta1 . AdmissionResponse , filter bool ) http . HandlerFunc {
2020-05-17 21:37:05 +00:00
return func ( rw http . ResponseWriter , r * http . Request ) {
2020-03-29 02:06:18 +00:00
startTime := time . Now ( )
2020-05-17 21:37:05 +00:00
ws . lastReqTime . SetTime ( startTime )
admissionReview := ws . bodyToAdmissionReview ( r , rw )
2020-03-29 02:06:18 +00:00
if admissionReview == nil {
2020-05-17 21:37:05 +00:00
ws . log . Info ( "failed to parse admission review request" , "request" , r )
2020-03-29 02:06:18 +00:00
return
2020-03-25 05:23:03 +00:00
}
2020-05-17 21:37:05 +00:00
2020-08-17 18:17:07 +00: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 02:06:18 +00:00
admissionReview . Response = & v1beta1 . AdmissionResponse {
Allowed : true ,
2020-05-17 21:37:05 +00:00
UID : admissionReview . Request . UID ,
2020-03-25 05:23:03 +00:00
}
2020-03-29 02:06:18 +00:00
// Do not process the admission requests for kinds that are in filterKinds for filtering
request := admissionReview . Request
2020-05-17 21:37:05 +00:00
if filter && ws . configHandler . ToFilter ( request . Kind . Kind , request . Namespace , request . Name ) {
writeResponse ( rw , admissionReview )
2020-03-29 02:06:18 +00:00
return
2020-03-29 01:55:13 +00:00
}
2020-04-22 14:45:16 +00:00
2020-05-17 21:37:05 +00:00
admissionReview . Response = handler ( request )
writeResponse ( rw , admissionReview )
2020-07-09 18:48:34 +00:00
logger . V ( 4 ) . Info ( "request processed" , "processingTime" , time . Since ( startTime ) . String ( ) )
2020-05-19 01:30:39 +00:00
2020-05-17 21:37:05 +00:00
return
}
}
2020-03-29 01:55:13 +00:00
2020-05-17 21:37:05 +00: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 01:55:13 +00:00
2020-05-17 21:37:05 +00: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 18:40:02 +00:00
}
2019-02-15 18:00:49 +00:00
}
2020-04-10 17:54:54 +00:00
func ( ws * WebhookServer ) resourceMutation ( request * v1beta1 . AdmissionRequest ) * v1beta1 . AdmissionResponse {
2020-06-23 01:49:43 +00:00
2020-07-08 21:22:32 +00:00
logger := ws . log . WithName ( "resourceMutation" ) . WithValues ( "uid" , request . UID , "kind" , request . Kind . Kind , "namespace" , request . Namespace , "name" , request . Name , "operation" , request . Operation )
2020-05-19 03:01:20 +00:00
if excludeKyvernoResources ( request . Kind . Kind ) {
return & v1beta1 . AdmissionResponse {
Allowed : true ,
Result : & metav1 . Status {
Status : "Success" ,
} ,
}
}
2020-08-17 18:17:07 +00:00
logger . V ( 6 ) . Info ( "received an admission request in mutating webhook" )
2020-08-19 16:07:23 +00:00
mutatePolicies := ws . pCache . Get ( policycache . Mutate , nil )
validatePolicies := ws . pCache . Get ( policycache . ValidateEnforce , nil )
generatePolicies := ws . pCache . Get ( policycache . Generate , nil )
2020-08-17 18:17:07 +00:00
2020-08-19 16:07:23 +00:00
// 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 20:13:16 +00:00
2019-11-11 22:52:09 +00:00
// getRoleRef only if policy has roles/clusterroles defined
2020-04-10 17:54:54 +00:00
var roles , clusterRoles [ ] string
2020-07-02 19:49:10 +00:00
var err error
if containRBACinfo ( mutatePolicies , validatePolicies , generatePolicies ) {
2020-08-14 19:21:06 +00:00
roles , clusterRoles , err = userinfo . GetRoleRef ( ws . rbLister , ws . crbLister , request , ws . configHandler )
2019-11-11 22:52:09 +00:00
if err != nil {
// TODO(shuting): continue apply policy if error getting roleRef?
2020-03-17 18:05:20 +00:00
logger . Error ( err , "failed to get RBAC infromation for request" )
2019-11-11 22:52:09 +00:00
}
2019-11-09 02:56:24 +00:00
}
2020-01-07 03:24:24 +00:00
// convert RAW to unstructured
2020-05-19 01:05:22 +00:00
resource , err := utils . ConvertResource ( request . Object . Raw , request . Kind . Group , request . Kind . Version , request . Kind . Kind , request . Namespace )
2020-01-07 03:24:24 +00:00
if err != nil {
2020-03-17 18:05:20 +00:00
logger . Error ( err , "failed to convert RAW resource to unstructured format" )
2020-01-07 03:24:24 +00:00
return & v1beta1 . AdmissionResponse {
Allowed : false ,
Result : & metav1 . Status {
Status : "Failure" ,
2020-01-07 19:32:52 +00:00
Message : err . Error ( ) ,
2020-01-07 03:24:24 +00:00
} ,
}
}
2020-04-10 17:54:54 +00:00
userRequestInfo := v1 . RequestInfo {
Roles : roles ,
ClusterRoles : clusterRoles ,
2020-07-09 18:48:34 +00:00
AdmissionUserInfo : * request . UserInfo . DeepCopy ( ) }
2020-04-10 17:54:54 +00:00
// build context
ctx := context2 . NewContext ( )
2020-04-15 15:47:14 +00:00
err = ctx . AddRequest ( request )
if err != nil {
logger . Error ( err , "failed to load incoming request in context" )
}
2020-04-10 17:54:54 +00:00
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-19 01:05:22 +00:00
var patches [ ] byte
patchedResource := request . Object . Raw
2020-05-19 00:00:52 +00:00
2020-07-02 19:49:10 +00:00
if ws . supportMudateValidate {
2020-05-19 00:00:52 +00:00
// MUTATION
// mutation failure should not block the resource creation
// any mutation failure is reported as the violation
2020-08-17 18:17:07 +00:00
if resource . GetDeletionTimestamp ( ) == nil {
2020-07-02 19:49:10 +00:00
patches = ws . HandleMutation ( request , resource , mutatePolicies , ctx , userRequestInfo )
logger . V ( 6 ) . Info ( "" , "generated patches" , string ( patches ) )
2020-05-19 00:00:52 +00:00
2020-08-17 18:17:07 +00: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-19 00:00:52 +00:00
if ws . resourceWebhookWatcher != nil && ws . resourceWebhookWatcher . RunValidationInMutatingWebhook == "true" {
2020-07-09 18:48:34 +00:00
// push admission request to audit handler, this won't block the admission request
ws . auditHandler . Add ( request . DeepCopy ( ) )
2020-05-19 00:00:52 +00:00
// VALIDATION
2020-08-14 19:21:06 +00:00
ok , msg := HandleValidation ( request , validatePolicies , nil , ctx , userRequestInfo , ws . statusListener , ws . eventGen , ws . pvGenerator , ws . log , ws . configHandler )
2020-05-19 00:00:52 +00:00
if ! ok {
logger . Info ( "admission request denied" )
return & v1beta1 . AdmissionResponse {
Allowed : false ,
Result : & metav1 . Status {
Status : "Failure" ,
Message : msg ,
} ,
}
2020-01-11 13:03:11 +00:00
}
2019-08-24 01:34:23 +00:00
}
2020-05-21 00:08:30 +00:00
} else {
2020-05-21 15:29:35 +00:00
logger . Info ( "mutate and validate rules are not supported prior to Kubernetes 1.14.0" )
2019-08-14 18:51:01 +00:00
}
2020-01-07 18:33:28 +00:00
// GENERATE
2020-06-23 01:49:43 +00:00
// Only applied during resource creation and update
2020-07-09 18:48:34 +00:00
// Success -> Generate Request CR created successfully
2020-01-07 18:33:28 +00:00
// Failed -> Failed to create Generate Request CR
2020-07-08 21:22:32 +00:00
2020-08-14 19:21:06 +00:00
go ws . HandleGenerate ( request . DeepCopy ( ) , generatePolicies , ctx , userRequestInfo , ws . configHandler )
2020-05-19 00:00:52 +00:00
2020-07-09 18:48:34 +00:00
// Succesful processing of mutation & validation rules in policy
2019-08-24 01:34:23 +00:00
patchType := v1beta1 . PatchTypeJSONPatch
return & v1beta1 . AdmissionResponse {
Allowed : true ,
Result : & metav1 . Status {
Status : "Success" ,
} ,
Patch : patches ,
PatchType : & patchType ,
}
2020-05-19 00:00:52 +00:00
2019-08-14 18:51:01 +00:00
}
2020-04-10 17:54:54 +00:00
func ( ws * WebhookServer ) resourceValidation ( request * v1beta1 . AdmissionRequest ) * v1beta1 . AdmissionResponse {
2020-05-21 00:08:30 +00: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 23:59:17 +00: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 12:48:35 +00:00
}
2020-07-10 18:48:27 +00:00
}
2020-07-09 12:48:35 +00:00
}
2020-07-10 23:59:17 +00:00
2020-07-02 19:49:10 +00:00
if ! ws . supportMudateValidate {
2020-05-21 15:29:35 +00:00
logger . Info ( "mutate and validate rules are not supported prior to Kubernetes 1.14.0" )
2020-05-21 00:08:30 +00:00
return & v1beta1 . AdmissionResponse {
Allowed : true ,
Result : & metav1 . Status {
Status : "Success" ,
} ,
}
}
2020-05-19 03:01:20 +00:00
if excludeKyvernoResources ( request . Kind . Kind ) {
return & v1beta1 . AdmissionResponse {
Allowed : true ,
Result : & metav1 . Status {
Status : "Success" ,
} ,
}
}
2020-08-17 18:17:07 +00:00
logger . V ( 6 ) . Info ( "received an admission request in validating webhook" )
2020-07-09 18:48:34 +00:00
// push admission request to audit handler, this won't block the admission request
ws . auditHandler . Add ( request . DeepCopy ( ) )
2020-08-19 16:07:23 +00:00
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 19:49:10 +00:00
if len ( policies ) == 0 {
logger . V ( 4 ) . Info ( "No enforce Validation policy found, returning" )
2020-01-11 13:03:11 +00:00
return & v1beta1 . AdmissionResponse { Allowed : true }
}
var roles , clusterRoles [ ] string
2020-07-10 23:59:17 +00:00
var err error
2020-01-11 13:03:11 +00:00
// getRoleRef only if policy has roles/clusterroles defined
if containRBACinfo ( policies ) {
2020-08-14 19:21:06 +00:00
roles , clusterRoles , err = userinfo . GetRoleRef ( ws . rbLister , ws . crbLister , request , ws . configHandler )
2020-01-11 13:03:11 +00:00
if err != nil {
2020-05-26 17:36:56 +00:00
logger . Error ( err , "failed to get RBAC information for request" )
2020-07-09 18:48:34 +00:00
return & v1beta1 . AdmissionResponse {
Allowed : false ,
Result : & metav1 . Status {
Status : "Failure" ,
Message : err . Error ( ) ,
} ,
}
2020-01-11 13:03:11 +00:00
}
2020-07-15 03:23:30 +00:00
logger = logger . WithValues ( "username" , request . UserInfo . Username ,
"groups" , request . UserInfo . Groups , "roles" , roles , "clusterRoles" , clusterRoles )
2020-01-11 13:03:11 +00:00
}
2020-04-10 17:54:54 +00:00
userRequestInfo := v1 . RequestInfo {
Roles : roles ,
ClusterRoles : clusterRoles ,
AdmissionUserInfo : request . UserInfo }
// build context
ctx := context2 . NewContext ( )
2020-04-15 15:47:14 +00:00
err = ctx . AddRequest ( request )
if err != nil {
logger . Error ( err , "failed to load incoming request in context" )
}
2020-04-10 17:54:54 +00:00
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-08-14 19:21:06 +00:00
ok , msg := HandleValidation ( request , policies , nil , ctx , userRequestInfo , ws . statusListener , ws . eventGen , ws . pvGenerator , ws . log , ws . configHandler )
2020-05-21 00:08:30 +00:00
if ! ok {
logger . Info ( "admission request denied" )
return & v1beta1 . AdmissionResponse {
Allowed : false ,
Result : & metav1 . Status {
Status : "Failure" ,
Message : msg ,
} ,
2020-01-11 13:03:11 +00:00
}
}
return & v1beta1 . AdmissionResponse {
Allowed : true ,
Result : & metav1 . Status {
Status : "Success" ,
} ,
}
}
2019-05-14 15:10:25 +00:00
// RunAsync TLS server in separate thread and returns control immediately
2019-10-25 21:55:48 +00:00
func ( ws * WebhookServer ) RunAsync ( stopCh <- chan struct { } ) {
2020-03-17 18:05:20 +00:00
logger := ws . log
2020-07-10 23:59:17 +00:00
if ! cache . WaitForCacheSync ( stopCh , ws . pSynced , ws . rbSynced , ws . crbSynced , ws . rSynced , ws . crSynced ) {
2020-03-17 18:05:20 +00:00
logger . Info ( "failed to sync informer cache" )
2019-11-15 23:59:37 +00:00
}
2019-03-04 18:40:02 +00:00
go func ( ws * WebhookServer ) {
2020-03-17 18:05:20 +00:00
logger . V ( 3 ) . Info ( "started serving requests" , "addr" , ws . server . Addr )
2019-08-28 00:00:16 +00:00
if err := ws . server . ListenAndServeTLS ( "" , "" ) ; err != http . ErrServerClosed {
2020-03-17 18:05:20 +00:00
logger . Error ( err , "failed to listen to requests" )
2019-03-04 18:40:02 +00:00
}
} ( ws )
2020-03-17 18:05:20 +00:00
logger . Info ( "starting" )
2020-05-17 21:37:05 +00:00
2019-10-30 20:39:19 +00:00
// verifys if the admission control is enabled and active
// resync: 60 seconds
// deadline: 60 seconds (send request)
// max deadline: deadline*3 (set the deployment annotation as false)
2020-03-29 01:55:13 +00:00
go ws . lastReqTime . Run ( ws . pLister , ws . eventGen , ws . client , checker . DefaultResync , checker . DefaultDeadline , stopCh )
2019-02-07 17:22:04 +00:00
}
2019-05-14 15:10:25 +00:00
// Stop TLS server and returns control after the server is shut down
2019-11-18 19:41:37 +00:00
func ( ws * WebhookServer ) Stop ( ctx context . Context ) {
2020-03-17 18:05:20 +00:00
logger := ws . log
2019-11-18 19:41:37 +00:00
// cleanUp
// remove the static webhookconfigurations
2020-03-29 01:55:13 +00:00
go ws . webhookRegistrationClient . RemoveWebhookConfigurations ( ws . cleanUp )
2019-11-18 19:41:37 +00:00
// shutdown http.Server with context timeout
err := ws . server . Shutdown ( ctx )
2019-03-04 18:40:02 +00:00
if err != nil {
// Error from closing listeners, or context timeout:
2020-03-17 18:05:20 +00:00
logger . Error ( err , "shutting down server" )
2019-03-04 18:40:02 +00:00
ws . server . Close ( )
}
2019-02-07 17:22:04 +00:00
}
2019-05-13 18:27:47 +00:00
2019-05-14 15:10:25 +00: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 18:05:20 +00:00
logger := ws . log
2020-05-26 17:36:56 +00:00
if request . Body == nil {
logger . Info ( "empty body" , "req" , request . URL . String ( ) )
2019-05-14 15:10:25 +00:00
http . Error ( writer , "empty body" , http . StatusBadRequest )
return nil
}
2020-05-26 17:36:56 +00: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 15:10:25 +00:00
contentType := request . Header . Get ( "Content-Type" )
if contentType != "application/json" {
2020-03-17 18:05:20 +00:00
logger . Info ( "invalid Content-Type" , "contextType" , contentType )
2019-05-14 15:10:25 +00: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 18:05:20 +00:00
logger . Error ( err , "failed to decode request body to type 'AdmissionReview" )
2019-05-14 15:10:25 +00:00
http . Error ( writer , "Can't decode body as AdmissionReview" , http . StatusExpectationFailed )
return nil
}
2019-06-06 00:43:59 +00:00
return admissionReview
2019-05-13 18:27:47 +00:00
}
2020-07-10 23:59:17 +00: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 20:42:11 +00: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-15 03:23:30 +00:00
} else if request . Operation == v1beta1 . Update {
2020-07-13 20:42:11 +00:00
resource , err = enginutils . ConvertToUnstructured ( request . Object . Raw )
isManagedResourceCheck = true
}
2020-07-10 23:59:17 +00:00
if err != nil {
2020-07-13 20:42:11 +00:00
logger . Error ( err , "failed to convert object resource to unstructured format" )
return err
2020-07-10 23:59:17 +00:00
}
2020-07-13 20:42:11 +00:00
if isManagedResourceCheck {
2020-07-11 00:01:48 +00:00
labels := resource . GetLabels ( )
2020-07-10 23:59:17 +00:00
if labels != nil {
2020-07-16 00:19:20 +00:00
if labels [ "app.kubernetes.io/managed-by" ] == "kyverno" && labels [ "policy.kyverno.io/synchronize" ] == "enable" {
2020-08-14 19:21:06 +00:00
isAuthorized , err := userinfo . IsRoleAuthorize ( ws . rbLister , ws . crbLister , ws . rLister , ws . crLister , request , ws . configHandler )
2020-07-13 20:42:11 +00:00
if err != nil {
2020-07-15 03:23:30 +00:00
return fmt . Errorf ( "failed to get RBAC infromation for request %v" , err )
2020-07-13 20:42:11 +00:00
}
if ! isAuthorized {
// convert RAW to unstructured
2020-08-14 19:21:06 +00: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 20:42:11 +00:00
}
2020-07-10 23:59:17 +00:00
}
}
}
2020-07-13 20:42:11 +00:00
2020-07-10 23:59:17 +00:00
return nil
}