1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 07:57:07 +00:00
kyverno/pkg/policyreport/builder.go
Prateek Pandey 886cb6effe
fix split policyreport name with background scan (#4237) (#4245)
- fix split policyreport name with background scan
- fix the label selector initialising
- refactor the generatePolicyName func

Signed-off-by: prateekpandey14 <prateek.pandey@nirmata.com>
2022-07-21 09:49:24 +00:00

413 lines
10 KiB
Go
Executable file

package policyreport
import (
"encoding/json"
"fmt"
"reflect"
"time"
"github.com/go-logr/logr"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
request "github.com/kyverno/kyverno/api/kyverno/v1alpha2"
report "github.com/kyverno/kyverno/api/policyreport/v1alpha2"
kyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/toggle"
"github.com/kyverno/kyverno/pkg/version"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
)
const (
// appVersion represents which version of Kyverno manages rcr / crcr
appVersion string = "app.kubernetes.io/version"
// the following labels are used to list rcr / crcr
ResourceLabelNamespace string = "kyverno.io/resource.namespace"
policyLabel string = "kyverno.io/policy-name"
deletedLabelPolicy string = "kyverno.io/delete.policy"
deletedLabelRule string = "kyverno.io/delete.rule"
// the following annotations are used to remove entries from polr / cpolr
// there would be a problem if use labels as the value could exceed 63 chars
deletedAnnotationResourceName string = "kyverno.io/delete.resource.name"
deletedAnnotationResourceKind string = "kyverno.io/delete.resource.kind"
inactiveLabelKey string = "kyverno.io/report.status"
inactiveLabelVal string = "inactive"
// SourceValue is the static value for PolicyReportResult.Source
SourceValue = "Kyverno"
)
func GeneratePolicyReportName(ns, policyName string) string {
if ns == "" {
if toggle.SplitPolicyReport() {
return TrimmedName(clusterpolicyreport + "-" + policyName)
}
return clusterpolicyreport
}
var name string
if toggle.SplitPolicyReport() {
name = fmt.Sprintf("polr-ns-%s-%s", ns, policyName)
} else {
name = fmt.Sprintf("polr-ns-%s", ns)
}
if len(name) > 63 {
return name[:63]
}
return name
}
func TrimmedName(s string) string {
if len(s) > 63 {
return s[:63]
}
return s
}
// GeneratePRsFromEngineResponse generate Violations from engine responses
func GeneratePRsFromEngineResponse(ers []*response.EngineResponse, log logr.Logger) (pvInfos []Info) {
for _, er := range ers {
// ignore creation of PV for resources that are yet to be assigned a name
if er.PolicyResponse.Resource.Name == "" {
log.V(4).Info("skipping resource with no name", "resource", er.PolicyResponse.Resource)
continue
}
if len(er.PolicyResponse.Rules) == 0 {
continue
}
if er.Policy != nil && engine.ManagedPodResource(er.Policy, er.PatchedResource) {
continue
}
// build policy violation info
pvInfos = append(pvInfos, buildPVInfo(er))
}
return pvInfos
}
// Builder builds report change request struct
// this is base type of namespaced and cluster policy report
type Builder interface {
build(info Info) (*unstructured.Unstructured, error)
}
type requestBuilder struct {
cpolLister kyvernolister.ClusterPolicyLister
polLister kyvernolister.PolicyLister
}
// NewBuilder ...
func NewBuilder(cpolLister kyvernolister.ClusterPolicyLister, polLister kyvernolister.PolicyLister) Builder {
return &requestBuilder{cpolLister: cpolLister, polLister: polLister}
}
func (builder *requestBuilder) build(info Info) (req *unstructured.Unstructured, err error) {
results := []report.PolicyReportResult{}
req = new(unstructured.Unstructured)
for _, infoResult := range info.Results {
for _, rule := range infoResult.Rules {
if rule.Type != string(response.Validation) && rule.Type != string(response.ImageVerify) {
continue
}
result := builder.buildRCRResult(info.PolicyName, infoResult.Resource, rule)
results = append(results, result)
}
}
if info.Namespace != "" {
rr := &request.ReportChangeRequest{
Summary: calculateSummary(results),
Results: results,
}
gv := report.SchemeGroupVersion
rr.SetGroupVersionKind(schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: "ReportChangeRequest"})
rawRcr, err := json.Marshal(rr)
if err != nil {
return nil, err
}
err = json.Unmarshal(rawRcr, req)
if err != nil {
return nil, err
}
set(req, info)
} else {
rr := &request.ClusterReportChangeRequest{
Summary: calculateSummary(results),
Results: results,
}
gv := report.SchemeGroupVersion
rr.SetGroupVersionKind(schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: "ClusterReportChangeRequest"})
rawRcr, err := json.Marshal(rr)
if err != nil {
return nil, err
}
err = json.Unmarshal(rawRcr, req)
if err != nil {
return nil, err
}
set(req, info)
}
if !setRequestDeletionLabels(req, info) {
if len(results) == 0 {
// return nil on empty result without a deletion
return nil, nil
}
}
req.SetCreationTimestamp(metav1.Now())
return req, nil
}
func (builder *requestBuilder) buildRCRResult(policy string, resource response.ResourceSpec, rule kyverno.ViolatedRule) report.PolicyReportResult {
av := builder.fetchAnnotationValues(policy, resource.Namespace)
result := report.PolicyReportResult{
Policy: policy,
Resources: []v1.ObjectReference{
{
Kind: resource.Kind,
Namespace: resource.Namespace,
APIVersion: resource.APIVersion,
Name: resource.Name,
UID: types.UID(resource.UID),
},
},
Scored: av.scored,
Category: av.category,
Severity: av.severity,
}
result.Rule = rule.Name
result.Message = rule.Message
result.Result = report.PolicyResult(rule.Status)
if result.Result == "fail" && !av.scored {
result.Result = "warn"
}
result.Source = SourceValue
result.Timestamp = metav1.Timestamp{
Seconds: time.Now().Unix(),
}
return result
}
func set(obj *unstructured.Unstructured, info Info) {
obj.SetAPIVersion(request.SchemeGroupVersion.Group + "/" + request.SchemeGroupVersion.Version)
if info.Namespace == "" {
obj.SetGenerateName("crcr-")
obj.SetKind("ClusterReportChangeRequest")
} else {
obj.SetGenerateName("rcr-")
obj.SetKind("ReportChangeRequest")
obj.SetNamespace(config.KyvernoNamespace)
}
obj.SetLabels(map[string]string{
ResourceLabelNamespace: info.Namespace,
policyLabel: TrimmedName(info.PolicyName),
appVersion: version.BuildVersion,
})
}
func setRequestDeletionLabels(req *unstructured.Unstructured, info Info) bool {
switch {
case isResourceDeletion(info):
req.SetAnnotations(map[string]string{
deletedAnnotationResourceName: info.Results[0].Resource.Name,
deletedAnnotationResourceKind: info.Results[0].Resource.Kind,
})
labels := req.GetLabels()
labels[ResourceLabelNamespace] = info.Results[0].Resource.Namespace
req.SetLabels(labels)
return true
case isPolicyDeletion(info):
req.SetKind("ReportChangeRequest")
req.SetGenerateName("rcr-")
labels := req.GetLabels()
labels[deletedLabelPolicy] = info.PolicyName
req.SetLabels(labels)
return true
case isRuleDeletion(info):
req.SetKind("ReportChangeRequest")
req.SetGenerateName("rcr-")
labels := req.GetLabels()
labels[deletedLabelPolicy] = info.PolicyName
labels[deletedLabelRule] = info.Results[0].Rules[0].Name
req.SetLabels(labels)
return true
}
return false
}
func calculateSummary(results []report.PolicyReportResult) (summary report.PolicyReportSummary) {
for _, res := range results {
switch string(res.Result) {
case report.StatusPass:
summary.Pass++
case report.StatusFail:
summary.Fail++
case report.StatusWarn:
summary.Warn++
case report.StatusError:
summary.Error++
case report.StatusSkip:
summary.Skip++
}
}
return
}
func buildPVInfo(er *response.EngineResponse) Info {
info := Info{
PolicyName: er.PolicyResponse.Policy.Name,
Namespace: er.PatchedResource.GetNamespace(),
Results: []EngineResponseResult{
{
Resource: er.GetResourceSpec(),
Rules: buildViolatedRules(er),
},
},
}
return info
}
func buildViolatedRules(er *response.EngineResponse) []kyverno.ViolatedRule {
var violatedRules []kyverno.ViolatedRule
for _, rule := range er.PolicyResponse.Rules {
vrule := kyverno.ViolatedRule{
Name: rule.Name,
Type: string(rule.Type),
Message: rule.Message,
}
vrule.Status = toPolicyResult(rule.Status)
violatedRules = append(violatedRules, vrule)
}
return violatedRules
}
func toPolicyResult(status response.RuleStatus) string {
switch status {
case response.RuleStatusPass:
return report.StatusPass
case response.RuleStatusFail:
return report.StatusFail
case response.RuleStatusError:
return report.StatusError
case response.RuleStatusWarn:
return report.StatusWarn
case response.RuleStatusSkip:
return report.StatusSkip
}
return ""
}
const categoryLabel string = "policies.kyverno.io/category"
const severityLabel string = "policies.kyverno.io/severity"
const ScoredLabel string = "policies.kyverno.io/scored"
type annotationValues struct {
category string
severity report.PolicySeverity
scored bool
}
func (av *annotationValues) setSeverityFromString(severity string) {
switch severity {
case report.SeverityHigh:
av.severity = report.SeverityHigh
case report.SeverityMedium:
av.severity = report.SeverityMedium
case report.SeverityLow:
av.severity = report.SeverityLow
}
}
func (builder *requestBuilder) fetchAnnotationValues(policy, ns string) annotationValues {
av := annotationValues{}
ann := builder.fetchAnnotations(policy, ns)
if category, ok := ann[categoryLabel]; ok {
av.category = category
}
if severity, ok := ann[severityLabel]; ok {
av.setSeverityFromString(severity)
}
if scored, ok := ann[ScoredLabel]; ok {
if scored == "false" {
av.scored = false
} else {
av.scored = true
}
} else {
av.scored = true
}
return av
}
func (builder *requestBuilder) fetchAnnotations(policy, ns string) map[string]string {
cpol, err := builder.cpolLister.Get(policy)
if err == nil {
if ann := cpol.GetAnnotations(); ann != nil {
return ann
}
}
pol, err := builder.polLister.Policies(ns).Get(policy)
if err == nil {
if ann := pol.GetAnnotations(); ann != nil {
return ann
}
}
return make(map[string]string)
}
func isResourceDeletion(info Info) bool {
return info.PolicyName == "" && len(info.Results) == 1 && info.GetRuleLength() == 0
}
func isPolicyDeletion(info Info) bool {
return info.PolicyName != "" && len(info.Results) == 0
}
func isRuleDeletion(info Info) bool {
if info.PolicyName != "" && len(info.Results) == 1 {
result := info.Results[0]
if len(result.Rules) == 1 && reflect.DeepEqual(result.Resource, response.ResourceSpec{}) {
return true
}
}
return false
}