2019-07-15 16:07:56 -07:00
package webhooks
import (
2019-08-09 20:08:22 -07:00
"fmt"
2019-08-09 19:12:50 -07:00
"reflect"
2019-07-15 16:07:56 -07:00
"strings"
2019-07-17 17:53:13 -07:00
"github.com/nirmata/kyverno/pkg/annotations"
2019-08-09 19:12:50 -07:00
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
kyvernoclient "github.com/nirmata/kyverno/pkg/clientNew/clientset/versioned"
2019-08-09 20:08:22 -07:00
lister "github.com/nirmata/kyverno/pkg/clientNew/listers/kyverno/v1alpha1"
2019-07-15 19:14:42 -07:00
"github.com/nirmata/kyverno/pkg/violation"
2019-08-09 20:08:22 -07:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2019-07-15 19:14:42 -07:00
2019-07-15 16:07:56 -07:00
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/info"
2019-08-09 19:12:50 -07:00
"github.com/nirmata/kyverno/pkg/policyviolation"
2019-08-12 10:02:07 -07:00
"k8s.io/client-go/tools/cache"
2019-07-15 16:07:56 -07:00
)
2019-07-16 15:53:14 -07:00
//TODO: change validation from bool -> enum(validation, mutation)
2019-08-09 16:55:43 -07:00
func newEventInfoFromPolicyInfo ( policyInfoList [ ] info . PolicyInfo , onUpdate bool , ruleType info . RuleType ) ( [ ] * event . Info , [ ] * violation . Info ) {
2019-07-15 16:07:56 -07:00
var eventsInfo [ ] * event . Info
2019-07-15 19:14:42 -07:00
var violations [ ] * violation . Info
2019-07-15 16:07:56 -07:00
ok , msg := isAdmSuccesful ( policyInfoList )
// Some policies failed to apply succesfully
if ! ok {
for _ , pi := range policyInfoList {
2019-07-16 15:53:14 -07:00
if pi . IsSuccessful ( ) {
continue
}
2019-07-15 16:07:56 -07:00
rules := pi . FailedRules ( )
ruleNames := strings . Join ( rules , ";" )
if ! onUpdate {
// CREATE
eventsInfo = append ( eventsInfo ,
event . NewEvent ( policyKind , "" , pi . Name , event . RequestBlocked , event . FPolicyApplyBlockCreate , pi . RName , ruleNames ) )
glog . V ( 3 ) . Infof ( "Rule(s) %s of policy %s blocked resource creation, error: %s\n" , ruleNames , pi . Name , msg )
} else {
// UPDATE
eventsInfo = append ( eventsInfo ,
event . NewEvent ( pi . RKind , pi . RNamespace , pi . RName , event . RequestBlocked , event . FPolicyApplyBlockUpdate , ruleNames , pi . Name ) )
eventsInfo = append ( eventsInfo ,
event . NewEvent ( policyKind , "" , pi . Name , event . RequestBlocked , event . FPolicyBlockResourceUpdate , pi . RName , ruleNames ) )
glog . V ( 3 ) . Infof ( "Request blocked events info has prepared for %s/%s and %s/%s\n" , policyKind , pi . Name , pi . RKind , pi . RName )
}
2019-07-15 19:14:42 -07:00
// if report flag is set
2019-07-18 10:22:20 -07:00
if pi . ValidationFailureAction == ReportViolation && ruleType == info . Validation {
2019-07-15 19:14:42 -07:00
// Create Violations
v := violation . BuldNewViolation ( pi . Name , pi . RKind , pi . RNamespace , pi . RName , event . PolicyViolation . String ( ) , pi . GetFailedRules ( ) )
violations = append ( violations , v )
}
2019-07-15 16:07:56 -07:00
}
} else {
if ! onUpdate {
// All policies were applied succesfully
// CREATE
for _ , pi := range policyInfoList {
rules := pi . SuccessfulRules ( )
ruleNames := strings . Join ( rules , ";" )
eventsInfo = append ( eventsInfo ,
event . NewEvent ( pi . RKind , pi . RNamespace , pi . RName , event . PolicyApplied , event . SRulesApply , ruleNames , pi . Name ) )
glog . V ( 3 ) . Infof ( "Success event info has prepared for %s/%s\n" , pi . RKind , pi . RName )
}
}
}
2019-07-15 19:14:42 -07:00
return eventsInfo , violations
2019-07-15 16:07:56 -07:00
}
2019-07-17 17:53:13 -07:00
func addAnnotationsToResource ( rawResource [ ] byte , pi * info . PolicyInfo , ruleType info . RuleType ) [ ] byte {
2019-07-17 23:13:28 -07:00
if len ( pi . Rules ) == 0 {
return nil
}
2019-07-17 17:53:13 -07:00
// get annotations
ann := annotations . ParseAnnotationsFromObject ( rawResource )
ann , patch , err := annotations . AddPolicyJSONPatch ( ann , pi , ruleType )
if err != nil {
glog . Error ( err )
return nil
}
return patch
}
2019-08-09 19:12:50 -07:00
//buildAnnotation we add annotations for the successful application of JSON patches
//TODO
func buildAnnotation ( mAnn map [ string ] string , pi * info . PolicyInfo ) {
if len ( pi . Rules ) == 0 {
return
}
var mchanges [ ] string
for _ , r := range pi . Rules {
if r . Changes != "" {
// the rule generate a patch
// key policy name will be updated to right format during creation of annotations
mchanges = append ( mchanges )
}
}
}
// buildPolicyViolationsForAPolicy returns a policy violation object if there are any rules that fail
func buildPolicyViolationsForAPolicy ( pi info . PolicyInfo ) kyverno . PolicyViolation {
var fRules [ ] kyverno . ViolatedRule
var pv kyverno . PolicyViolation
for _ , r := range pi . Rules {
if ! r . IsSuccessful ( ) {
fRules = append ( fRules , kyverno . ViolatedRule { Name : r . Name , Message : r . GetErrorString ( ) , Type : r . RuleType . String ( ) } )
}
}
if len ( fRules ) > 0 {
glog . V ( 4 ) . Infof ( "building policy violation for policy %s on resource %s/%s/%s" , pi . Name , pi . RKind , pi . RNamespace , pi . RName )
// there is an error
pv = policyviolation . BuildPolicyViolation ( pi . Name , kyverno . ResourceSpec {
Kind : pi . RKind ,
Namespace : pi . RNamespace ,
Name : pi . RName ,
} ,
fRules ,
)
}
return pv
}
//generatePolicyViolations generate policyViolation resources for the rules that failed
2019-08-12 10:02:07 -07:00
func generatePolicyViolations ( pvListerSynced cache . InformerSynced , pvLister lister . PolicyViolationLister , client * kyvernoclient . Clientset , policyInfos [ ] info . PolicyInfo ) {
2019-08-09 19:12:50 -07:00
var pvs [ ] kyverno . PolicyViolation
for _ , policyInfo := range policyInfos {
if ! policyInfo . IsSuccessful ( ) {
if pv := buildPolicyViolationsForAPolicy ( policyInfo ) ; ! reflect . DeepEqual ( pv , kyverno . PolicyViolation { } ) {
pvs = append ( pvs , pv )
}
}
}
if len ( pvs ) > 0 {
for _ , newPv := range pvs {
// generate PolicyViolation objects
glog . V ( 4 ) . Infof ( "creating policyViolation resource for policy %s and resource %s/%s/%s" , newPv . Spec . Policy , newPv . Spec . Kind , newPv . Spec . Namespace , newPv . Spec . Name )
// check if there was a previous violation for policy & resource combination
2019-08-12 10:02:07 -07:00
curPv , err := getExistingPolicyViolationIfAny ( pvListerSynced , pvLister , newPv )
2019-08-09 20:08:22 -07:00
if err != nil {
continue
}
if curPv == nil {
// no existing policy violation, create a new one
_ , err := client . KyvernoV1alpha1 ( ) . PolicyViolations ( ) . Create ( & newPv )
if err != nil {
glog . Error ( err )
}
continue
}
// compare the policyviolation spec for existing resource if present else
if reflect . DeepEqual ( curPv . Spec , newPv . Spec ) {
// if they are equal there has been no change so dont update the polivy violation
glog . Infof ( "policy violation spec %v did not change so not updating it" , newPv . Spec )
continue
}
// spec changed so update the policyviolation
//TODO: wont work, as name is not defined yet
_ , err = client . KyvernoV1alpha1 ( ) . PolicyViolations ( ) . Update ( & newPv )
if err != nil {
glog . Error ( err )
continue
}
2019-08-09 19:12:50 -07:00
}
}
}
2019-08-09 20:08:22 -07:00
//TODO: change the name
2019-08-12 10:02:07 -07:00
func getExistingPolicyViolationIfAny ( pvListerSynced cache . InformerSynced , pvLister lister . PolicyViolationLister , newPv kyverno . PolicyViolation ) ( * kyverno . PolicyViolation , error ) {
2019-08-09 20:08:22 -07:00
// TODO: check for existing ov using label selectors on resource and policy
2019-08-12 10:02:07 -07:00
labelMap := map [ string ] string { "policy" : newPv . Spec . Policy , "resource" : newPv . Spec . ResourceSpec . ToKey ( ) }
2019-08-09 20:08:22 -07:00
ls := & metav1 . LabelSelector { }
err := metav1 . Convert_Map_string_To_string_To_v1_LabelSelector ( & labelMap , ls , nil )
if err != nil {
glog . Errorf ( "failed to generate label sector of Policy name %s: %v" , newPv . Spec . Policy , err )
return nil , err
}
policyViolationSelector , err := metav1 . LabelSelectorAsSelector ( ls )
if err != nil {
glog . Errorf ( "invalid label selector: %v" , err )
return nil , err
}
2019-08-12 10:02:07 -07:00
//TODO: sync the cache before reading from it ?
// check is this is needed ?
// stopCh := make(chan struct{}, 0)
// if !cache.WaitForCacheSync(stopCh, pvListerSynced) {
// //TODO: can this be handled or avoided ?
// glog.Info("unable to sync policy violation shared informer cache, might be out of sync")
// }
2019-08-09 20:08:22 -07:00
pvs , err := pvLister . List ( policyViolationSelector )
if err != nil {
glog . Errorf ( "unable to list policy violations with label selector %v: %v" , policyViolationSelector , err )
return nil , err
}
//TODO: ideally there should be only one policy violation returned
if len ( pvs ) > 1 {
glog . Errorf ( "more than one policy violation exists with labels %v" , labelMap )
return nil , fmt . Errorf ( "more than one policy violation exists with labels %v" , labelMap )
}
if len ( pvs ) == 0 {
glog . Infof ( "policy violation does not exist with labels %v" , labelMap )
return nil , nil
}
return pvs [ 0 ] , nil
}