2020-11-09 11:26:12 -08:00
|
|
|
package policyreport
|
|
|
|
|
|
|
|
import (
|
2021-11-08 15:53:21 -08:00
|
|
|
"encoding/json"
|
2020-11-09 11:26:12 -08:00
|
|
|
"fmt"
|
|
|
|
"reflect"
|
2021-08-21 19:35:17 +02:00
|
|
|
"time"
|
2020-11-09 11:26:12 -08:00
|
|
|
|
|
|
|
"github.com/go-logr/logr"
|
2021-10-29 18:13:20 +02:00
|
|
|
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
|
|
|
request "github.com/kyverno/kyverno/api/kyverno/v1alpha2"
|
|
|
|
report "github.com/kyverno/kyverno/api/policyreport/v1alpha2"
|
2020-11-09 11:26:12 -08:00
|
|
|
kyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
|
|
|
|
"github.com/kyverno/kyverno/pkg/config"
|
2021-12-30 00:34:43 +08:00
|
|
|
"github.com/kyverno/kyverno/pkg/engine"
|
2020-11-09 11:26:12 -08:00
|
|
|
"github.com/kyverno/kyverno/pkg/engine/response"
|
|
|
|
"github.com/kyverno/kyverno/pkg/engine/utils"
|
2021-09-14 01:06:56 -07:00
|
|
|
"github.com/kyverno/kyverno/pkg/version"
|
2020-11-09 11:26:12 -08:00
|
|
|
v1 "k8s.io/api/core/v1"
|
2021-02-07 19:46:50 -08:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2020-11-09 11:26:12 -08:00
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
2021-11-08 15:53:21 -08:00
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
2020-12-21 11:04:19 -08:00
|
|
|
"k8s.io/apimachinery/pkg/types"
|
2020-11-09 11:26:12 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2021-09-14 01:06:56 -07:00
|
|
|
// appVersion represents which version of Kyverno manages rcr / crcr
|
|
|
|
appVersion string = "app.kubernetes.io/version"
|
|
|
|
|
2021-02-19 09:09:41 -08:00
|
|
|
// the following labels are used to list rcr / crcr
|
|
|
|
resourceLabelNamespace string = "kyverno.io/resource.namespace"
|
|
|
|
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"
|
2021-08-21 19:35:17 +02:00
|
|
|
|
2021-10-29 16:06:03 +01:00
|
|
|
// SourceValue is the static value for PolicyReportResult.Source
|
2021-08-21 19:35:17 +02:00
|
|
|
SourceValue = "Kyverno"
|
2020-11-09 11:26:12 -08:00
|
|
|
)
|
|
|
|
|
2021-12-17 06:03:52 +01:00
|
|
|
func GeneratePolicyReportName(ns string) string {
|
2020-11-09 11:26:12 -08:00
|
|
|
if ns == "" {
|
|
|
|
return clusterpolicyreport
|
|
|
|
}
|
2020-11-20 13:32:16 -08:00
|
|
|
|
2021-01-02 01:10:14 -08:00
|
|
|
name := fmt.Sprintf("polr-ns-%s", ns)
|
2020-11-20 13:32:16 -08:00
|
|
|
if len(name) > 63 {
|
|
|
|
return name[:63]
|
|
|
|
}
|
|
|
|
|
|
|
|
return name
|
2020-11-09 11:26:12 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
//GeneratePRsFromEngineResponse generate Violations from engine responses
|
2020-12-23 15:10:07 -08:00
|
|
|
func GeneratePRsFromEngineResponse(ers []*response.EngineResponse, log logr.Logger) (pvInfos []Info) {
|
2020-11-09 11:26:12 -08:00
|
|
|
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("resource does no have a name assigned yet, not creating a policy violation", "resource", er.PolicyResponse.Resource)
|
|
|
|
continue
|
|
|
|
}
|
2020-12-08 23:04:16 -08:00
|
|
|
|
2021-01-21 18:58:53 -08:00
|
|
|
if len(er.PolicyResponse.Rules) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-12-30 00:34:43 +08:00
|
|
|
if er.Policy != nil && engine.ManagedPodResource(*er.Policy, er.PatchedResource) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-11-09 11:26:12 -08:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2020-11-17 13:07:30 -08:00
|
|
|
// NewBuilder ...
|
2021-02-07 19:46:50 -08:00
|
|
|
func NewBuilder(cpolLister kyvernolister.ClusterPolicyLister, polLister kyvernolister.PolicyLister) Builder {
|
2020-11-09 11:26:12 -08:00
|
|
|
return &requestBuilder{cpolLister: cpolLister, polLister: polLister}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (builder *requestBuilder) build(info Info) (req *unstructured.Unstructured, err error) {
|
|
|
|
results := []*report.PolicyReportResult{}
|
2021-11-08 15:53:21 -08:00
|
|
|
req = new(unstructured.Unstructured)
|
2020-12-21 11:04:19 -08:00
|
|
|
for _, infoResult := range info.Results {
|
|
|
|
for _, rule := range infoResult.Rules {
|
|
|
|
if rule.Type != utils.Validation.String() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
result := builder.buildRCRResult(info.PolicyName, infoResult.Resource, rule)
|
|
|
|
results = append(results, result)
|
2020-11-09 11:26:12 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-21 11:04:19 -08:00
|
|
|
if info.Namespace != "" {
|
2020-11-09 11:26:12 -08:00
|
|
|
rr := &request.ReportChangeRequest{
|
|
|
|
Summary: calculateSummary(results),
|
|
|
|
Results: results,
|
|
|
|
}
|
|
|
|
|
2021-11-08 15:53:21 -08:00
|
|
|
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)
|
2020-11-09 11:26:12 -08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-11-18 14:31:43 -08:00
|
|
|
set(req, info)
|
2020-11-09 11:26:12 -08:00
|
|
|
} else {
|
|
|
|
rr := &request.ClusterReportChangeRequest{
|
|
|
|
Summary: calculateSummary(results),
|
|
|
|
Results: results,
|
|
|
|
}
|
|
|
|
|
2021-11-08 15:53:21 -08:00
|
|
|
gv := report.SchemeGroupVersion
|
|
|
|
rr.SetGroupVersionKind(schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: "ClusterReportChangeRequest"})
|
|
|
|
|
|
|
|
rawRcr, err := json.Marshal(rr)
|
2020-11-09 11:26:12 -08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-11-08 15:53:21 -08:00
|
|
|
|
|
|
|
err = json.Unmarshal(rawRcr, req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-11-18 14:31:43 -08:00
|
|
|
set(req, info)
|
2020-11-09 11:26:12 -08:00
|
|
|
}
|
|
|
|
|
2021-10-27 22:59:59 -07:00
|
|
|
if !setRequestDeletionLabels(req, info) {
|
2020-12-21 11:04:19 -08:00
|
|
|
if len(results) == 0 {
|
|
|
|
// return nil on empty result without a deletion
|
|
|
|
return nil, nil
|
2020-11-09 11:26:12 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:46:50 -08:00
|
|
|
req.SetCreationTimestamp(metav1.Now())
|
2020-11-09 11:26:12 -08:00
|
|
|
return req, nil
|
|
|
|
}
|
|
|
|
|
2020-12-21 11:04:19 -08:00
|
|
|
func (builder *requestBuilder) buildRCRResult(policy string, resource response.ResourceSpec, rule kyverno.ViolatedRule) *report.PolicyReportResult {
|
2021-04-14 01:41:07 +02:00
|
|
|
av := builder.fetchAnnotationValues(policy, resource.Namespace)
|
2021-04-07 23:56:27 +02:00
|
|
|
|
2020-12-21 11:04:19 -08:00
|
|
|
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),
|
|
|
|
},
|
|
|
|
},
|
2021-06-22 07:07:20 +05:30
|
|
|
Scored: av.scored,
|
2021-04-07 23:56:27 +02:00
|
|
|
Category: av.category,
|
|
|
|
Severity: av.severity,
|
2020-12-21 11:04:19 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
result.Rule = rule.Name
|
|
|
|
result.Message = rule.Message
|
2021-10-03 01:31:05 -07:00
|
|
|
result.Result = report.PolicyResult(rule.Status)
|
2021-08-21 19:35:17 +02:00
|
|
|
if result.Result == "fail" && !av.scored {
|
|
|
|
result.Result = "warn"
|
|
|
|
}
|
|
|
|
result.Source = SourceValue
|
|
|
|
result.Timestamp = metav1.Timestamp{
|
|
|
|
Seconds: time.Now().Unix(),
|
2021-06-22 07:07:20 +05:30
|
|
|
}
|
2020-12-21 11:04:19 -08:00
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2020-11-18 14:31:43 -08:00
|
|
|
func set(obj *unstructured.Unstructured, info Info) {
|
2020-11-09 11:26:12 -08:00
|
|
|
obj.SetAPIVersion(request.SchemeGroupVersion.Group + "/" + request.SchemeGroupVersion.Version)
|
2020-12-21 11:04:19 -08:00
|
|
|
|
|
|
|
if info.Namespace == "" {
|
2021-02-07 19:46:50 -08:00
|
|
|
obj.SetGenerateName("crcr-")
|
2020-11-09 11:26:12 -08:00
|
|
|
obj.SetKind("ClusterReportChangeRequest")
|
|
|
|
} else {
|
2020-12-21 11:04:19 -08:00
|
|
|
obj.SetGenerateName("rcr-")
|
2020-11-09 11:26:12 -08:00
|
|
|
obj.SetKind("ReportChangeRequest")
|
2020-12-21 11:04:19 -08:00
|
|
|
obj.SetNamespace(config.KyvernoNamespace)
|
2020-11-09 11:26:12 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
obj.SetLabels(map[string]string{
|
2020-12-21 11:04:19 -08:00
|
|
|
resourceLabelNamespace: info.Namespace,
|
2021-09-14 01:06:56 -07:00
|
|
|
appVersion: version.BuildVersion,
|
2020-11-09 11:26:12 -08:00
|
|
|
})
|
2020-12-21 11:04:19 -08:00
|
|
|
}
|
2020-11-09 11:26:12 -08:00
|
|
|
|
2021-10-27 22:59:59 -07:00
|
|
|
func setRequestDeletionLabels(req *unstructured.Unstructured, info Info) bool {
|
2020-12-21 11:04:19 -08:00
|
|
|
switch {
|
|
|
|
case isResourceDeletion(info):
|
2021-02-19 09:09:41 -08:00
|
|
|
req.SetAnnotations(map[string]string{
|
|
|
|
deletedAnnotationResourceName: info.Results[0].Resource.Name,
|
|
|
|
deletedAnnotationResourceKind: info.Results[0].Resource.Kind,
|
|
|
|
})
|
|
|
|
|
2021-10-27 22:59:59 -07:00
|
|
|
labels := req.GetLabels()
|
|
|
|
labels[resourceLabelNamespace] = info.Results[0].Resource.Namespace
|
|
|
|
req.SetLabels(labels)
|
2020-12-21 11:04:19 -08:00
|
|
|
return true
|
|
|
|
|
|
|
|
case isPolicyDeletion(info):
|
|
|
|
req.SetKind("ReportChangeRequest")
|
|
|
|
req.SetGenerateName("rcr-")
|
2021-10-27 22:59:59 -07:00
|
|
|
|
|
|
|
labels := req.GetLabels()
|
|
|
|
labels[deletedLabelPolicy] = info.PolicyName
|
|
|
|
req.SetLabels(labels)
|
2020-12-21 11:04:19 -08:00
|
|
|
return true
|
|
|
|
|
|
|
|
case isRuleDeletion(info):
|
|
|
|
req.SetKind("ReportChangeRequest")
|
|
|
|
req.SetGenerateName("rcr-")
|
2021-10-27 22:59:59 -07:00
|
|
|
|
|
|
|
labels := req.GetLabels()
|
|
|
|
labels[deletedLabelPolicy] = info.PolicyName
|
|
|
|
labels[deletedLabelRule] = info.Results[0].Rules[0].Name
|
|
|
|
req.SetLabels(labels)
|
2020-12-21 11:04:19 -08:00
|
|
|
return true
|
2020-11-09 11:26:12 -08:00
|
|
|
}
|
2020-12-21 11:04:19 -08:00
|
|
|
|
|
|
|
return false
|
2020-11-09 11:26:12 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
func calculateSummary(results []*report.PolicyReportResult) (summary report.PolicyReportSummary) {
|
|
|
|
for _, res := range results {
|
2021-08-21 19:35:17 +02:00
|
|
|
switch string(res.Result) {
|
2020-11-09 11:26:12 -08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-12-23 15:10:07 -08:00
|
|
|
func buildPVInfo(er *response.EngineResponse) Info {
|
2020-11-09 11:26:12 -08:00
|
|
|
info := Info{
|
2021-06-30 00:43:11 +03:00
|
|
|
PolicyName: er.PolicyResponse.Policy.Name,
|
2020-12-21 11:04:19 -08:00
|
|
|
Namespace: er.PatchedResource.GetNamespace(),
|
|
|
|
Results: []EngineResponseResult{
|
|
|
|
{
|
|
|
|
Resource: er.GetResourceSpec(),
|
|
|
|
Rules: buildViolatedRules(er),
|
|
|
|
},
|
|
|
|
},
|
2020-11-09 11:26:12 -08:00
|
|
|
}
|
|
|
|
return info
|
|
|
|
}
|
|
|
|
|
2020-12-23 15:10:07 -08:00
|
|
|
func buildViolatedRules(er *response.EngineResponse) []kyverno.ViolatedRule {
|
2020-11-09 11:26:12 -08:00
|
|
|
var violatedRules []kyverno.ViolatedRule
|
|
|
|
for _, rule := range er.PolicyResponse.Rules {
|
|
|
|
vrule := kyverno.ViolatedRule{
|
|
|
|
Name: rule.Name,
|
|
|
|
Type: rule.Type,
|
|
|
|
Message: rule.Message,
|
|
|
|
}
|
2021-10-03 01:31:05 -07:00
|
|
|
|
|
|
|
vrule.Status = toPolicyResult(rule.Status)
|
2020-11-09 11:26:12 -08:00
|
|
|
violatedRules = append(violatedRules, vrule)
|
|
|
|
}
|
2021-10-03 01:31:05 -07:00
|
|
|
|
2020-11-09 11:26:12 -08:00
|
|
|
return violatedRules
|
|
|
|
}
|
|
|
|
|
2021-10-03 01:31:05 -07:00
|
|
|
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 ""
|
|
|
|
}
|
|
|
|
|
2020-11-09 11:26:12 -08:00
|
|
|
const categoryLabel string = "policies.kyverno.io/category"
|
2021-04-07 23:56:27 +02:00
|
|
|
const severityLabel string = "policies.kyverno.io/severity"
|
2021-06-22 07:07:20 +05:30
|
|
|
const scoredLabel string = "policies.kyverno.io/scored"
|
2021-04-07 23:56:27 +02:00
|
|
|
|
|
|
|
type annotationValues struct {
|
|
|
|
category string
|
|
|
|
severity report.PolicySeverity
|
2021-06-22 07:07:20 +05:30
|
|
|
scored bool
|
2021-04-07 23:56:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-14 01:41:07 +02:00
|
|
|
func (builder *requestBuilder) fetchAnnotationValues(policy, ns string) annotationValues {
|
2021-04-07 23:56:27 +02:00
|
|
|
av := annotationValues{}
|
2021-04-14 01:41:07 +02:00
|
|
|
ann := builder.fetchAnnotations(policy, ns)
|
2021-04-07 23:56:27 +02:00
|
|
|
|
|
|
|
if category, ok := ann[categoryLabel]; ok {
|
|
|
|
av.category = category
|
|
|
|
}
|
|
|
|
if severity, ok := ann[severityLabel]; ok {
|
|
|
|
av.setSeverityFromString(severity)
|
|
|
|
}
|
2021-06-22 07:07:20 +05:30
|
|
|
if scored, ok := ann[scoredLabel]; ok {
|
|
|
|
if scored == "false" {
|
|
|
|
av.scored = false
|
|
|
|
} else {
|
|
|
|
av.scored = true
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
av.scored = true
|
|
|
|
}
|
2021-04-07 23:56:27 +02:00
|
|
|
|
|
|
|
return av
|
|
|
|
}
|
2020-11-09 11:26:12 -08:00
|
|
|
|
2021-04-14 01:41:07 +02:00
|
|
|
func (builder *requestBuilder) fetchAnnotations(policy, ns string) map[string]string {
|
2020-11-09 11:26:12 -08:00
|
|
|
cpol, err := builder.cpolLister.Get(policy)
|
|
|
|
if err == nil {
|
|
|
|
if ann := cpol.GetAnnotations(); ann != nil {
|
2021-04-07 23:56:27 +02:00
|
|
|
return ann
|
2020-11-09 11:26:12 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-18 14:31:43 -08:00
|
|
|
pol, err := builder.polLister.Policies(ns).Get(policy)
|
2020-11-09 11:26:12 -08:00
|
|
|
if err == nil {
|
|
|
|
if ann := pol.GetAnnotations(); ann != nil {
|
2021-04-07 23:56:27 +02:00
|
|
|
return ann
|
2020-11-09 11:26:12 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-11 23:43:09 +02:00
|
|
|
return make(map[string]string)
|
2020-11-09 11:26:12 -08:00
|
|
|
}
|
2020-12-21 11:04:19 -08:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|