package response import ( "fmt" "reflect" "time" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" pssutils "github.com/kyverno/kyverno/pkg/pss/utils" "github.com/kyverno/kyverno/pkg/utils/wildcard" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/pod-security-admission/api" ) // EngineResponse engine response to the action type EngineResponse struct { // Resource patched with the engine action changes PatchedResource unstructured.Unstructured // Original policy Policy kyvernov1.PolicyInterface // Policy Response PolicyResponse PolicyResponse } // PolicyResponse policy application response type PolicyResponse struct { // policy details Policy PolicySpec `json:"policy"` // resource details Resource ResourceSpec `json:"resource"` // policy statistics PolicyStats `json:",inline"` // rule response Rules []RuleResponse `json:"rules"` // ValidationFailureAction: audit (default) or enforce ValidationFailureAction kyvernov1.ValidationFailureAction ValidationFailureActionOverrides []ValidationFailureActionOverride } // PolicySpec policy type PolicySpec struct { Name string `json:"name"` Namespace string `json:"namespace"` } // ResourceSpec resource action applied on type ResourceSpec struct { Kind string `json:"kind"` APIVersion string `json:"apiVersion"` Namespace string `json:"namespace"` Name string `json:"name"` // UID is not used to build the unique identifier // optional UID string `json:"uid"` } // GetKey returns the key func (rs ResourceSpec) GetKey() string { return rs.Kind + "/" + rs.Namespace + "/" + rs.Name } // PolicyStats stores statistics for the single policy application type PolicyStats struct { // time required to process the policy rules on a resource ProcessingTime time.Duration `json:"processingTime"` // Count of rules that were applied successfully RulesAppliedCount int `json:"rulesAppliedCount"` // Count of rules that with execution errors RulesErrorCount int `json:"rulesErrorCount"` // Timestamp of the instant the Policy was triggered PolicyExecutionTimestamp int64 `json:"policyExecutionTimestamp"` } type RuleType string const ( // Mutation type for mutation rule Mutation RuleType = "Mutation" // Validation type for validation rule Validation RuleType = "Validation" // Generation type for generation rule Generation RuleType = "Generation" // ImageVerify type for image verification ImageVerify RuleType = "ImageVerify" ) // RuleResponse details for each rule application type RuleResponse struct { // rule name specified in policy Name string `json:"name"` // rule type (Mutation,Generation,Validation) for Kyverno Policy Type RuleType `json:"type"` // message response from the rule application Message string `json:"message"` // JSON patches, for mutation rules Patches [][]byte `json:"patches,omitempty"` // Resource generated by the generate rules of a policy GeneratedResource unstructured.Unstructured `json:"generatedResource,omitempty"` // rule status Status RuleStatus `json:"status"` // statistics RuleStats `json:",inline"` // PatchedTarget is the patched resource for mutate.targets PatchedTarget *unstructured.Unstructured // PatchedTargetSubresourceName is the name of the subresource which is patched, empty if the resource patched is // not a subresource. PatchedTargetSubresourceName string // PatchedTargetParentResourceGVR is the GVR of the parent resource of the PatchedTarget. This is only populated // when PatchedTarget is a subresource. PatchedTargetParentResourceGVR metav1.GroupVersionResource // PodSecurityChecks contains pod security checks (only if this is a pod security rule) PodSecurityChecks *PodSecurityChecks } type PodSecurityChecks struct { Level api.Level Version string Checks []pssutils.PSSCheckResult } // ToString ... func (rr RuleResponse) ToString() string { return fmt.Sprintf("rule %s (%s): %v", rr.Name, rr.Type, rr.Message) } // RuleStats stores the statistics for the single rule application type RuleStats struct { // time required to apply the rule on the resource ProcessingTime time.Duration `json:"processingTime"` // Timestamp of the instant the rule got triggered RuleExecutionTimestamp int64 `json:"ruleExecutionTimestamp"` } // IsSuccessful checks if any rule has failed or produced an error during execution func (er EngineResponse) IsSuccessful() bool { for _, r := range er.PolicyResponse.Rules { if r.Status == RuleStatusFail || r.Status == RuleStatusError { return false } } return true } // IsSkipped checks if any rule has skipped resource or not. func (er EngineResponse) IsSkipped() bool { for _, r := range er.PolicyResponse.Rules { if r.Status == RuleStatusSkip { return true } } return false } // IsFailed checks if any rule created a policy violation func (er EngineResponse) IsFailed() bool { for _, r := range er.PolicyResponse.Rules { if r.Status == RuleStatusFail { return true } } return false } // IsError checks if any rule resulted in a processing error func (er EngineResponse) IsError() bool { for _, r := range er.PolicyResponse.Rules { if r.Status == RuleStatusError { return true } } return false } // IsEmpty checks if any rule results are present func (er EngineResponse) IsEmpty() bool { return len(er.PolicyResponse.Rules) == 0 } // isNil checks if rule is an empty rule func (er EngineResponse) IsNil() bool { return reflect.DeepEqual(er, EngineResponse{}) } // GetPatches returns all the patches joined func (er EngineResponse) GetPatches() [][]byte { var patches [][]byte for _, r := range er.PolicyResponse.Rules { if r.Patches != nil { patches = append(patches, r.Patches...) } } return patches } // GetFailedRules returns failed rules func (er EngineResponse) GetFailedRules() []string { return er.getRules(func(status RuleStatus) bool { return status == RuleStatusFail || status == RuleStatusError }) } // GetSuccessRules returns success rules func (er EngineResponse) GetSuccessRules() []string { return er.getRules(func(status RuleStatus) bool { return status == RuleStatusPass }) } // GetResourceSpec returns resourceSpec of er func (er EngineResponse) GetResourceSpec() ResourceSpec { return ResourceSpec{ Kind: er.PatchedResource.GetKind(), APIVersion: er.PatchedResource.GetAPIVersion(), Namespace: er.PatchedResource.GetNamespace(), Name: er.PatchedResource.GetName(), UID: string(er.PatchedResource.GetUID()), } } func (er EngineResponse) getRules(predicate func(RuleStatus) bool) []string { var rules []string for _, r := range er.PolicyResponse.Rules { if predicate(r.Status) { rules = append(rules, r.Name) } } return rules } func (er *EngineResponse) GetValidationFailureAction() kyvernov1.ValidationFailureAction { for _, v := range er.PolicyResponse.ValidationFailureActionOverrides { for _, ns := range v.Namespaces { if wildcard.Match(ns, er.PatchedResource.GetNamespace()) { return v.Action } } } return er.PolicyResponse.ValidationFailureAction } type ValidationFailureActionOverride struct { Action kyvernov1.ValidationFailureAction `json:"action"` Namespaces []string `json:"namespaces"` }