package webhooks import ( "encoding/json" "fmt" "reflect" "strings" "github.com/golang/glog" "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1" "github.com/nirmata/kyverno/pkg/info" v1beta1 "k8s.io/api/admission/v1beta1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) const policyKind = "Policy" func isAdmSuccesful(policyInfos []*info.PolicyInfo) (bool, string) { var admSuccess = true var errMsgs []string for _, pi := range policyInfos { if !pi.IsSuccessful() { admSuccess = false errMsgs = append(errMsgs, fmt.Sprintf("\nPolicy %s failed with following rules", pi.Name)) // Get the error rules errorRules := pi.ErrorRules() errMsgs = append(errMsgs, errorRules) } } return admSuccess, strings.Join(errMsgs, ";") } //StringInSlice checks if string is present in slice of strings func StringInSlice(kind string, list []string) bool { for _, b := range list { if b == kind { return true } } return false } //parseKinds parses the kinds if a single string contains comma seperated kinds // {"1,2,3","4","5"} => {"1","2","3","4","5"} func parseKinds(list []string) []string { kinds := []string{} for _, k := range list { args := strings.Split(k, ",") for _, arg := range args { if arg != "" { kinds = append(kinds, strings.TrimSpace(arg)) } } } return kinds } //ArrayFlags to store filterkinds type ArrayFlags []string func (i *ArrayFlags) String() string { var sb strings.Builder for _, str := range *i { sb.WriteString(str) } return sb.String() } //Set setter for array flags func (i *ArrayFlags) Set(value string) error { *i = append(*i, value) return nil } // extract the kinds that the policy rules apply to func getApplicableKindsForPolicy(p *v1alpha1.Policy) []string { kindsMap := map[string]interface{}{} kinds := []string{} // iterate over the rules an identify all kinds // Matching for _, rule := range p.Spec.Rules { for _, k := range rule.MatchResources.Kinds { kindsMap[k] = nil } // remove excluded ones for _, k := range rule.ExcludeResources.Kinds { if _, ok := kindsMap[k]; ok { // delete kind delete(kindsMap, k) } } } // get the kinds for k := range kindsMap { kinds = append(kinds, k) } return kinds } // Policy Reporting Modes const ( BlockChanges = "enforce" ReportViolation = "audit" ) // returns true -> if there is even one policy that blocks resource requst // returns false -> if all the policies are meant to report only, we dont block resource request func toBlock(pis []*info.PolicyInfo) bool { for _, pi := range pis { if pi.ValidationFailureAction != ReportViolation { return true } } return false } func checkIfOnlyAnnotationsUpdate(request *v1beta1.AdmissionRequest) bool { var err error // process only if its for existing resources if request.Operation != v1beta1.Update { return false } // approach : we only compare if the addition contains annotations the are added with prefix "policies.kyverno.io" // get annotations for the old resource oldObj := request.OldObject oldObjUnstr := unstructured.Unstructured{} // need to set kind as some request dont contain kind meta-data raw resource but in the api request oldObj.Raw = setKindForObject(oldObj.Raw, request.Kind.Kind) err = oldObjUnstr.UnmarshalJSON(oldObj.Raw) if err != nil { glog.Error(err) return false } oldAnn := oldObjUnstr.GetAnnotations() // get annotations for the new resource newObj := request.Object newObjUnstr := unstructured.Unstructured{} // need to set kind as some request dont contain kind meta-data raw resource but in the api request newObj.Raw = setKindForObject(newObj.Raw, request.Kind.Kind) err = newObjUnstr.UnmarshalJSON(newObj.Raw) if err != nil { glog.Error(err) return false } newAnn := newObjUnstr.GetAnnotations() policiesAppliedNew := 0 newAnnPolicy := map[string]string{} // check if annotations changed // assuming that we only add an annotation with the given prefix for k, v := range newAnn { // check prefix policyName := strings.Split(k, "/") if len(policyName) == 1 { continue } if policyName[0] == "policies.kyverno.io" { newAnnPolicy[policyName[1]] = v policiesAppliedNew++ } } oldAnnPolicy := map[string]string{} policiesAppliedOld := 0 // check if annotations changed // assuming that we only add an annotation with the given prefix for k, v := range oldAnn { // check prefix policyName := strings.Split(k, "/") if len(policyName) == 1 { continue } if policyName[0] == "policies.kyverno.io" { oldAnnPolicy[policyName[1]] = v policiesAppliedOld++ } } diffCount := policiesAppliedNew - policiesAppliedOld switch diffCount { case 1: // policy applied return true case -1: // policy removed return true case 0: // no new policy added or remove // need to check if the policy was updated if !reflect.DeepEqual(newAnnPolicy, oldAnnPolicy) { return true } } //TODO: Hack if there an update on self link then we ignore if oldObjUnstr.GetSelfLink() != newObjUnstr.GetSelfLink() { return true } // then there is some other change and we should process it return false } func setKindForObject(bytes []byte, kind string) []byte { var objectJSON map[string]interface{} json.Unmarshal(bytes, &objectJSON) objectJSON["kind"] = kind data, err := json.Marshal(objectJSON) if err != nil { glog.Error(err) glog.Error("unable to marshall, not setting the kind") return bytes } return data }