mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
feat: add reporting to mutate and generate rules (#11265)
* feat: add reports to standard mutatation Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> * feat: add warnings for permissions Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> * fix: remove unnecessary fields Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> * feat: add reporting to generate and mutate existing Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> * feat: add reporting to generate and mutate existing Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> * fix: codegen and add generate tests Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> * fix: linter Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> * fix: e2e matrix Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> * fix: cleanup Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> --------- Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> Co-authored-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
parent
6b307b90a5
commit
fe49e97fba
41 changed files with 793 additions and 104 deletions
|
@ -68,6 +68,20 @@ rules:
|
||||||
- patch
|
- patch
|
||||||
- update
|
- update
|
||||||
- watch
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- reports.kyverno.io
|
||||||
|
resources:
|
||||||
|
- ephemeralreports
|
||||||
|
- clusterephemeralreports
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
- watch
|
||||||
|
- deletecollection
|
||||||
{{- with .Values.backgroundController.rbac.coreClusterRole.extraResources }}
|
{{- with .Values.backgroundController.rbac.coreClusterRole.extraResources }}
|
||||||
{{- toYaml . | nindent 2 }}
|
{{- toYaml . | nindent 2 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
|
|
||||||
"github.com/kyverno/kyverno/cmd/internal"
|
"github.com/kyverno/kyverno/cmd/internal"
|
||||||
"github.com/kyverno/kyverno/pkg/background"
|
"github.com/kyverno/kyverno/pkg/background"
|
||||||
|
"github.com/kyverno/kyverno/pkg/breaker"
|
||||||
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
|
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
|
||||||
kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions"
|
kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions"
|
||||||
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
||||||
|
@ -54,6 +55,7 @@ func createrLeaderControllers(
|
||||||
jp jmespath.Interface,
|
jp jmespath.Interface,
|
||||||
backgroundScanInterval time.Duration,
|
backgroundScanInterval time.Duration,
|
||||||
urGenerator generator.UpdateRequestGenerator,
|
urGenerator generator.UpdateRequestGenerator,
|
||||||
|
reportsBreaker breaker.Breaker,
|
||||||
) ([]internal.Controller, error) {
|
) ([]internal.Controller, error) {
|
||||||
policyCtrl, err := policy.NewPolicyController(
|
policyCtrl, err := policy.NewPolicyController(
|
||||||
kyvernoClient,
|
kyvernoClient,
|
||||||
|
@ -85,6 +87,7 @@ func createrLeaderControllers(
|
||||||
eventGenerator,
|
eventGenerator,
|
||||||
configuration,
|
configuration,
|
||||||
jp,
|
jp,
|
||||||
|
reportsBreaker,
|
||||||
)
|
)
|
||||||
return []internal.Controller{
|
return []internal.Controller{
|
||||||
internal.NewController("policy-controller", policyCtrl, 2),
|
internal.NewController("policy-controller", policyCtrl, 2),
|
||||||
|
@ -98,12 +101,14 @@ func main() {
|
||||||
maxQueuedEvents int
|
maxQueuedEvents int
|
||||||
omitEvents string
|
omitEvents string
|
||||||
maxAPICallResponseLength int64
|
maxAPICallResponseLength int64
|
||||||
|
maxBackgroundReports int
|
||||||
)
|
)
|
||||||
flagset := flag.NewFlagSet("updaterequest-controller", flag.ExitOnError)
|
flagset := flag.NewFlagSet("updaterequest-controller", flag.ExitOnError)
|
||||||
flagset.IntVar(&genWorkers, "genWorkers", 10, "Workers for the background controller.")
|
flagset.IntVar(&genWorkers, "genWorkers", 10, "Workers for the background controller.")
|
||||||
flagset.IntVar(&maxQueuedEvents, "maxQueuedEvents", 1000, "Maximum events to be queued.")
|
flagset.IntVar(&maxQueuedEvents, "maxQueuedEvents", 1000, "Maximum events to be queued.")
|
||||||
flagset.StringVar(&omitEvents, "omitEvents", "", "Set this flag to a comma sperated list of PolicyViolation, PolicyApplied, PolicyError, PolicySkipped to disable events, e.g. --omitEvents=PolicyApplied,PolicyViolation")
|
flagset.StringVar(&omitEvents, "omitEvents", "", "Set this flag to a comma sperated list of PolicyViolation, PolicyApplied, PolicyError, PolicySkipped to disable events, e.g. --omitEvents=PolicyApplied,PolicyViolation")
|
||||||
flagset.Int64Var(&maxAPICallResponseLength, "maxAPICallResponseLength", 2*1000*1000, "Maximum allowed response size from API Calls. A value of 0 bypasses checks (not recommended).")
|
flagset.Int64Var(&maxAPICallResponseLength, "maxAPICallResponseLength", 2*1000*1000, "Maximum allowed response size from API Calls. A value of 0 bypasses checks (not recommended).")
|
||||||
|
flagset.IntVar(&maxBackgroundReports, "maxBackgroundReports", 10000, "Maximum number of background reports before we stop creating new ones")
|
||||||
// config
|
// config
|
||||||
appConfig := internal.NewConfiguration(
|
appConfig := internal.NewConfiguration(
|
||||||
internal.WithProfiling(),
|
internal.WithProfiling(),
|
||||||
|
@ -131,7 +136,7 @@ func main() {
|
||||||
signalCtx, setup, sdown := internal.Setup(appConfig, "kyverno-background-controller", false)
|
signalCtx, setup, sdown := internal.Setup(appConfig, "kyverno-background-controller", false)
|
||||||
defer sdown()
|
defer sdown()
|
||||||
var err error
|
var err error
|
||||||
bgscanInterval := time.Hour
|
bgscanInterval := 30 * time.Second
|
||||||
val := os.Getenv("BACKGROUND_SCAN_INTERVAL")
|
val := os.Getenv("BACKGROUND_SCAN_INTERVAL")
|
||||||
if val != "" {
|
if val != "" {
|
||||||
if bgscanInterval, err = time.ParseDuration(val); err != nil {
|
if bgscanInterval, err = time.ParseDuration(val); err != nil {
|
||||||
|
@ -198,6 +203,18 @@ func main() {
|
||||||
polexCache,
|
polexCache,
|
||||||
gcstore,
|
gcstore,
|
||||||
)
|
)
|
||||||
|
ephrs, err := breaker.StartBackgroundReportsCounter(signalCtx, setup.MetadataClient)
|
||||||
|
if err != nil {
|
||||||
|
setup.Logger.Error(err, "failed to start background-scan reports watcher")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
reportsBreaker := breaker.NewBreaker("background scan reports", func(context.Context) bool {
|
||||||
|
count, isRunning := ephrs.Count()
|
||||||
|
if !isRunning {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return count > maxBackgroundReports
|
||||||
|
})
|
||||||
// start informers and wait for cache sync
|
// start informers and wait for cache sync
|
||||||
if !internal.StartInformersAndWaitForCacheSync(signalCtx, setup.Logger, kyvernoInformer) {
|
if !internal.StartInformersAndWaitForCacheSync(signalCtx, setup.Logger, kyvernoInformer) {
|
||||||
setup.Logger.Error(errors.New("failed to wait for cache sync"), "failed to wait for cache sync")
|
setup.Logger.Error(errors.New("failed to wait for cache sync"), "failed to wait for cache sync")
|
||||||
|
@ -230,6 +247,7 @@ func main() {
|
||||||
setup.Jp,
|
setup.Jp,
|
||||||
bgscanInterval,
|
bgscanInterval,
|
||||||
urGenerator,
|
urGenerator,
|
||||||
|
reportsBreaker,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err, "failed to create leader controllers")
|
logger.Error(err, "failed to create leader controllers")
|
||||||
|
|
|
@ -49622,6 +49622,20 @@ rules:
|
||||||
- patch
|
- patch
|
||||||
- update
|
- update
|
||||||
- watch
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- reports.kyverno.io
|
||||||
|
resources:
|
||||||
|
- ephemeralreports
|
||||||
|
- clusterephemeralreports
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
- watch
|
||||||
|
- deletecollection
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- networking.k8s.io
|
- networking.k8s.io
|
||||||
resources:
|
resources:
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||||
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
|
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
|
||||||
"github.com/kyverno/kyverno/pkg/background/common"
|
"github.com/kyverno/kyverno/pkg/background/common"
|
||||||
|
"github.com/kyverno/kyverno/pkg/breaker"
|
||||||
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
|
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
|
||||||
kyvernov1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
|
kyvernov1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
|
||||||
kyvernov2listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v2"
|
kyvernov2listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v2"
|
||||||
|
@ -27,6 +28,7 @@ import (
|
||||||
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
|
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
|
||||||
engineutils "github.com/kyverno/kyverno/pkg/utils/engine"
|
engineutils "github.com/kyverno/kyverno/pkg/utils/engine"
|
||||||
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
||||||
|
reportutils "github.com/kyverno/kyverno/pkg/utils/report"
|
||||||
validationpolicy "github.com/kyverno/kyverno/pkg/validation/policy"
|
validationpolicy "github.com/kyverno/kyverno/pkg/validation/policy"
|
||||||
"go.uber.org/multierr"
|
"go.uber.org/multierr"
|
||||||
admissionv1 "k8s.io/api/admission/v1"
|
admissionv1 "k8s.io/api/admission/v1"
|
||||||
|
@ -55,6 +57,8 @@ type GenerateController struct {
|
||||||
|
|
||||||
log logr.Logger
|
log logr.Logger
|
||||||
jp jmespath.Interface
|
jp jmespath.Interface
|
||||||
|
|
||||||
|
reportsBreaker breaker.Breaker
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGenerateController returns an instance of the Generate-Request Controller
|
// NewGenerateController returns an instance of the Generate-Request Controller
|
||||||
|
@ -71,6 +75,7 @@ func NewGenerateController(
|
||||||
eventGen event.Interface,
|
eventGen event.Interface,
|
||||||
log logr.Logger,
|
log logr.Logger,
|
||||||
jp jmespath.Interface,
|
jp jmespath.Interface,
|
||||||
|
reportsBreaker breaker.Breaker,
|
||||||
) *GenerateController {
|
) *GenerateController {
|
||||||
c := GenerateController{
|
c := GenerateController{
|
||||||
client: client,
|
client: client,
|
||||||
|
@ -85,6 +90,7 @@ func NewGenerateController(
|
||||||
eventGen: eventGen,
|
eventGen: eventGen,
|
||||||
log: log,
|
log: log,
|
||||||
jp: jp,
|
jp: jp,
|
||||||
|
reportsBreaker: reportsBreaker,
|
||||||
}
|
}
|
||||||
return &c
|
return &c
|
||||||
}
|
}
|
||||||
|
@ -227,6 +233,11 @@ func (c *GenerateController) applyGenerate(trigger unstructured.Unstructured, ur
|
||||||
logger.V(4).Info(doesNotApply)
|
logger.V(4).Info(doesNotApply)
|
||||||
return nil, errors.New(doesNotApply)
|
return nil, errors.New(doesNotApply)
|
||||||
}
|
}
|
||||||
|
if c.needsReports(trigger) {
|
||||||
|
if err := c.createReports(context.TODO(), policyContext.NewResource(), engineResponse); err != nil {
|
||||||
|
c.log.Error(err, "failed to create report")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var applicableRules []string
|
var applicableRules []string
|
||||||
for _, r := range engineResponse.PolicyResponse.Rules {
|
for _, r := range engineResponse.PolicyResponse.Rules {
|
||||||
|
@ -359,6 +370,34 @@ func (c *GenerateController) GetUnstrResource(genResourceSpec kyvernov1.Resource
|
||||||
return resource, nil
|
return resource, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *GenerateController) needsReports(trigger unstructured.Unstructured) bool {
|
||||||
|
createReport := true
|
||||||
|
// check if the resource supports reporting
|
||||||
|
if !reportutils.IsGvkSupported(trigger.GroupVersionKind()) {
|
||||||
|
createReport = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return createReport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GenerateController) createReports(
|
||||||
|
ctx context.Context,
|
||||||
|
resource unstructured.Unstructured,
|
||||||
|
engineResponses ...engineapi.EngineResponse,
|
||||||
|
) error {
|
||||||
|
report := reportutils.BuildGenerateReport(resource.GetNamespace(), resource.GetName(), resource.GroupVersionKind(), resource.GetName(), resource.GetUID(), engineResponses...)
|
||||||
|
if len(report.GetResults()) > 0 {
|
||||||
|
err := c.reportsBreaker.Do(ctx, func(ctx context.Context) error {
|
||||||
|
_, err := reportutils.CreateReport(ctx, report, c.kyvernoClient)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func updateStatus(statusControl common.StatusControlInterface, ur kyvernov2.UpdateRequest, err error, genResources []kyvernov1.ResourceSpec) error {
|
func updateStatus(statusControl common.StatusControlInterface, ur kyvernov2.UpdateRequest, err error, genResources []kyvernov1.ResourceSpec) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, err := statusControl.Failed(ur.GetName(), err.Error(), genResources); err != nil {
|
if _, err := statusControl.Failed(ur.GetName(), err.Error(), genResources); err != nil {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||||
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
|
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
|
||||||
"github.com/kyverno/kyverno/pkg/background/common"
|
"github.com/kyverno/kyverno/pkg/background/common"
|
||||||
|
"github.com/kyverno/kyverno/pkg/breaker"
|
||||||
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
|
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
|
||||||
kyvernov1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
|
kyvernov1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
|
||||||
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
||||||
|
@ -17,6 +18,7 @@ import (
|
||||||
"github.com/kyverno/kyverno/pkg/event"
|
"github.com/kyverno/kyverno/pkg/event"
|
||||||
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
|
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
|
||||||
engineutils "github.com/kyverno/kyverno/pkg/utils/engine"
|
engineutils "github.com/kyverno/kyverno/pkg/utils/engine"
|
||||||
|
reportutils "github.com/kyverno/kyverno/pkg/utils/report"
|
||||||
"go.uber.org/multierr"
|
"go.uber.org/multierr"
|
||||||
admissionv1 "k8s.io/api/admission/v1"
|
admissionv1 "k8s.io/api/admission/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
@ -44,6 +46,8 @@ type mutateExistingController struct {
|
||||||
|
|
||||||
log logr.Logger
|
log logr.Logger
|
||||||
jp jmespath.Interface
|
jp jmespath.Interface
|
||||||
|
|
||||||
|
reportsBreaker breaker.Breaker
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMutateExistingController returns an instance of the MutateExistingController
|
// NewMutateExistingController returns an instance of the MutateExistingController
|
||||||
|
@ -59,6 +63,7 @@ func NewMutateExistingController(
|
||||||
eventGen event.Interface,
|
eventGen event.Interface,
|
||||||
log logr.Logger,
|
log logr.Logger,
|
||||||
jp jmespath.Interface,
|
jp jmespath.Interface,
|
||||||
|
reportsBreaker breaker.Breaker,
|
||||||
) *mutateExistingController {
|
) *mutateExistingController {
|
||||||
c := mutateExistingController{
|
c := mutateExistingController{
|
||||||
client: client,
|
client: client,
|
||||||
|
@ -72,6 +77,7 @@ func NewMutateExistingController(
|
||||||
eventGen: eventGen,
|
eventGen: eventGen,
|
||||||
log: log,
|
log: log,
|
||||||
jp: jp,
|
jp: jp,
|
||||||
|
reportsBreaker: reportsBreaker,
|
||||||
}
|
}
|
||||||
return &c
|
return &c
|
||||||
}
|
}
|
||||||
|
@ -80,6 +86,7 @@ func (c *mutateExistingController) ProcessUR(ur *kyvernov2.UpdateRequest) error
|
||||||
logger := c.log.WithValues("name", ur.GetName(), "policy", ur.Spec.GetPolicyKey(), "resource", ur.Spec.GetResource().String())
|
logger := c.log.WithValues("name", ur.GetName(), "policy", ur.Spec.GetPolicyKey(), "resource", ur.Spec.GetResource().String())
|
||||||
var errs []error
|
var errs []error
|
||||||
|
|
||||||
|
logger.Info("processing mutate existing")
|
||||||
policy, err := c.getPolicy(ur)
|
policy, err := c.getPolicy(ur)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err, "failed to get policy")
|
logger.Error(err, "failed to get policy")
|
||||||
|
@ -157,6 +164,11 @@ func (c *mutateExistingController) ProcessUR(ur *kyvernov2.UpdateRequest) error
|
||||||
}
|
}
|
||||||
|
|
||||||
er := c.engine.Mutate(context.TODO(), policyContext)
|
er := c.engine.Mutate(context.TODO(), policyContext)
|
||||||
|
if c.needsReports(trigger) {
|
||||||
|
if err := c.createReports(context.TODO(), policyContext.NewResource(), er); err != nil {
|
||||||
|
c.log.Error(err, "failed to create report")
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, r := range er.PolicyResponse.Rules {
|
for _, r := range er.PolicyResponse.Rules {
|
||||||
patched, parentGVR, patchedSubresource := r.PatchedTarget()
|
patched, parentGVR, patchedSubresource := r.PatchedTarget()
|
||||||
switch r.Status() {
|
switch r.Status() {
|
||||||
|
@ -243,6 +255,36 @@ func (c *mutateExistingController) report(err error, policy kyvernov1.PolicyInte
|
||||||
c.eventGen.Add(events...)
|
c.eventGen.Add(events...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *mutateExistingController) needsReports(trigger *unstructured.Unstructured) bool {
|
||||||
|
createReport := true
|
||||||
|
if trigger == nil {
|
||||||
|
return createReport
|
||||||
|
}
|
||||||
|
if !reportutils.IsGvkSupported(trigger.GroupVersionKind()) {
|
||||||
|
createReport = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return createReport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mutateExistingController) createReports(
|
||||||
|
ctx context.Context,
|
||||||
|
resource unstructured.Unstructured,
|
||||||
|
engineResponses ...engineapi.EngineResponse,
|
||||||
|
) error {
|
||||||
|
report := reportutils.BuildMutateExistingReport(resource.GetNamespace(), resource.GetName(), resource.GroupVersionKind(), resource.GetName(), resource.GetUID(), engineResponses...)
|
||||||
|
if len(report.GetResults()) > 0 {
|
||||||
|
err := c.reportsBreaker.Do(ctx, func(ctx context.Context) error {
|
||||||
|
_, err := reportutils.CreateReport(ctx, report, c.kyvernoClient)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func updateURStatus(statusControl common.StatusControlInterface, ur kyvernov2.UpdateRequest, err error) error {
|
func updateURStatus(statusControl common.StatusControlInterface, ur kyvernov2.UpdateRequest, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, err := statusControl.Failed(ur.GetName(), err.Error(), nil); err != nil {
|
if _, err := statusControl.Failed(ur.GetName(), err.Error(), nil); err != nil {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
common "github.com/kyverno/kyverno/pkg/background/common"
|
common "github.com/kyverno/kyverno/pkg/background/common"
|
||||||
"github.com/kyverno/kyverno/pkg/background/generate"
|
"github.com/kyverno/kyverno/pkg/background/generate"
|
||||||
"github.com/kyverno/kyverno/pkg/background/mutate"
|
"github.com/kyverno/kyverno/pkg/background/mutate"
|
||||||
|
"github.com/kyverno/kyverno/pkg/breaker"
|
||||||
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
|
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
|
||||||
kyvernov1informers "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1"
|
kyvernov1informers "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1"
|
||||||
kyvernov2informers "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v2"
|
kyvernov2informers "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v2"
|
||||||
|
@ -60,6 +61,7 @@ type controller struct {
|
||||||
eventGen event.Interface
|
eventGen event.Interface
|
||||||
configuration config.Configuration
|
configuration config.Configuration
|
||||||
jp jmespath.Interface
|
jp jmespath.Interface
|
||||||
|
reportsBreaker breaker.Breaker
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewController returns an instance of the Generate-Request Controller
|
// NewController returns an instance of the Generate-Request Controller
|
||||||
|
@ -74,6 +76,7 @@ func NewController(
|
||||||
eventGen event.Interface,
|
eventGen event.Interface,
|
||||||
configuration config.Configuration,
|
configuration config.Configuration,
|
||||||
jp jmespath.Interface,
|
jp jmespath.Interface,
|
||||||
|
reportsBreaker breaker.Breaker,
|
||||||
) Controller {
|
) Controller {
|
||||||
urLister := urInformer.Lister().UpdateRequests(config.KyvernoNamespace())
|
urLister := urInformer.Lister().UpdateRequests(config.KyvernoNamespace())
|
||||||
c := controller{
|
c := controller{
|
||||||
|
@ -88,6 +91,7 @@ func NewController(
|
||||||
eventGen: eventGen,
|
eventGen: eventGen,
|
||||||
configuration: configuration,
|
configuration: configuration,
|
||||||
jp: jp,
|
jp: jp,
|
||||||
|
reportsBreaker: reportsBreaker,
|
||||||
}
|
}
|
||||||
_, _ = urInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
_, _ = urInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||||
AddFunc: c.addUR,
|
AddFunc: c.addUR,
|
||||||
|
@ -220,10 +224,10 @@ func (c *controller) processUR(ur *kyvernov2.UpdateRequest) error {
|
||||||
statusControl := common.NewStatusControl(c.kyvernoClient, c.urLister)
|
statusControl := common.NewStatusControl(c.kyvernoClient, c.urLister)
|
||||||
switch ur.Spec.GetRequestType() {
|
switch ur.Spec.GetRequestType() {
|
||||||
case kyvernov2.Mutate:
|
case kyvernov2.Mutate:
|
||||||
ctrl := mutate.NewMutateExistingController(c.client, c.kyvernoClient, statusControl, c.engine, c.cpolLister, c.polLister, c.nsLister, c.configuration, c.eventGen, logger, c.jp)
|
ctrl := mutate.NewMutateExistingController(c.client, c.kyvernoClient, statusControl, c.engine, c.cpolLister, c.polLister, c.nsLister, c.configuration, c.eventGen, logger, c.jp, c.reportsBreaker)
|
||||||
return ctrl.ProcessUR(ur)
|
return ctrl.ProcessUR(ur)
|
||||||
case kyvernov2.Generate:
|
case kyvernov2.Generate:
|
||||||
ctrl := generate.NewGenerateController(c.client, c.kyvernoClient, statusControl, c.engine, c.cpolLister, c.polLister, c.urLister, c.nsLister, c.configuration, c.eventGen, logger, c.jp)
|
ctrl := generate.NewGenerateController(c.client, c.kyvernoClient, statusControl, c.engine, c.cpolLister, c.polLister, c.urLister, c.nsLister, c.configuration, c.eventGen, logger, c.jp, c.reportsBreaker)
|
||||||
return ctrl.ProcessUR(ur)
|
return ctrl.ProcessUR(ur)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -108,6 +108,23 @@ func StartAdmissionReportsCounter(ctx context.Context, client metadataclient.Int
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StartBackgroundReportsCounter(ctx context.Context, client metadataclient.Interface) (Counter, error) {
|
||||||
|
tweakListOptions := func(lo *metav1.ListOptions) {
|
||||||
|
lo.LabelSelector = "audit.kyverno.io/source==background-scan"
|
||||||
|
}
|
||||||
|
ephrs, err := StartResourceCounter(ctx, client, reportsv1.SchemeGroupVersion.WithResource("ephemeralreports"), tweakListOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cephrs, err := StartResourceCounter(ctx, client, reportsv1.SchemeGroupVersion.WithResource("clusterephemeralreports"), tweakListOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return composite{
|
||||||
|
inner: []Counter{ephrs, cephrs},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
type composite struct {
|
type composite struct {
|
||||||
inner []Counter
|
inner []Counter
|
||||||
}
|
}
|
||||||
|
|
|
@ -330,27 +330,30 @@ func (c *controller) findResource(ctx context.Context, reportMeta *metav1.Partia
|
||||||
return resource, nil
|
return resource, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) adopt(ctx context.Context, reportMeta *metav1.PartialObjectMetadata) bool {
|
func (c *controller) adopt(ctx context.Context, reportMeta *metav1.PartialObjectMetadata) (bool, bool) {
|
||||||
resource, err := c.findResource(ctx, reportMeta)
|
resource, err := c.findResource(ctx, reportMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
if apierrors.IsForbidden(err) {
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
return false, false
|
||||||
}
|
}
|
||||||
if resource == nil {
|
if resource == nil {
|
||||||
return false
|
return false, false
|
||||||
}
|
}
|
||||||
report, err := c.getEphemeralReport(ctx, reportMeta.GetNamespace(), reportMeta.GetName())
|
report, err := c.getEphemeralReport(ctx, reportMeta.GetNamespace(), reportMeta.GetName())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false, false
|
||||||
}
|
}
|
||||||
if report == nil {
|
if report == nil {
|
||||||
return false
|
return false, false
|
||||||
}
|
}
|
||||||
controllerutils.SetOwner(report, resource.GetAPIVersion(), resource.GetKind(), resource.GetName(), resource.GetUID())
|
controllerutils.SetOwner(report, resource.GetAPIVersion(), resource.GetKind(), resource.GetName(), resource.GetUID())
|
||||||
reportutils.SetResourceUid(report, resource.GetUID())
|
reportutils.SetResourceUid(report, resource.GetUID())
|
||||||
if _, err := updateReport(ctx, report, c.client); err != nil {
|
if _, err := updateReport(ctx, report, c.client); err != nil {
|
||||||
return false
|
return false, false
|
||||||
}
|
}
|
||||||
return true
|
return true, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) frontReconcile(ctx context.Context, logger logr.Logger, _, namespace, name string) error {
|
func (c *controller) frontReconcile(ctx context.Context, logger logr.Logger, _, namespace, name string) error {
|
||||||
|
@ -371,8 +374,11 @@ func (c *controller) frontReconcile(ctx context.Context, logger logr.Logger, _,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// try to find the owner
|
// try to find the owner
|
||||||
if c.adopt(ctx, reportMeta) {
|
if adopted, forbidden := c.adopt(ctx, reportMeta); adopted {
|
||||||
return nil
|
return nil
|
||||||
|
} else if forbidden {
|
||||||
|
logger.Info("deleting because insufficient permission to fetch resource")
|
||||||
|
return c.deleteEphemeralReport(ctx, reportMeta.GetNamespace(), reportMeta.GetName())
|
||||||
}
|
}
|
||||||
// if not found and too old, forget about it
|
// if not found and too old, forget about it
|
||||||
if isTooOld(reportMeta) {
|
if isTooOld(reportMeta) {
|
||||||
|
|
|
@ -16,8 +16,9 @@ type FakeGenerate struct {
|
||||||
// fake/mock implementation for operation access(always returns true)
|
// fake/mock implementation for operation access(always returns true)
|
||||||
func NewFakeGenerate(rule kyvernov1.Generation) *FakeGenerate {
|
func NewFakeGenerate(rule kyvernov1.Generation) *FakeGenerate {
|
||||||
g := FakeGenerate{}
|
g := FakeGenerate{}
|
||||||
g.rule = rule
|
g.rule = &kyvernov1.Rule{Generation: &rule}
|
||||||
g.authChecker = fake.NewFakeAuth()
|
g.authChecker = fake.NewFakeAuth()
|
||||||
|
g.authCheckerReports = fake.NewFakeAuth()
|
||||||
g.log = logging.GlobalLogger()
|
g.log = logging.GlobalLogger()
|
||||||
return &g
|
return &g
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,17 +18,19 @@ import (
|
||||||
// Generate provides implementation to validate 'generate' rule
|
// Generate provides implementation to validate 'generate' rule
|
||||||
type Generate struct {
|
type Generate struct {
|
||||||
user string
|
user string
|
||||||
rule kyvernov1.Generation
|
rule *kyvernov1.Rule
|
||||||
authChecker auth.AuthChecks
|
authChecker auth.AuthChecks
|
||||||
|
authCheckerReports auth.AuthChecks
|
||||||
log logr.Logger
|
log logr.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGenerateFactory returns a new instance of Generate validation checker
|
// NewGenerateFactory returns a new instance of Generate validation checker
|
||||||
func NewGenerateFactory(client dclient.Interface, rule kyvernov1.Generation, user string, log logr.Logger) *Generate {
|
func NewGenerateFactory(client dclient.Interface, rule *kyvernov1.Rule, user, reportsSA string, log logr.Logger) *Generate {
|
||||||
g := Generate{
|
g := Generate{
|
||||||
user: user,
|
user: user,
|
||||||
rule: rule,
|
rule: rule,
|
||||||
authChecker: auth.NewAuth(client, user, log),
|
authChecker: auth.NewAuth(client, user, log),
|
||||||
|
authCheckerReports: auth.NewAuth(client, reportsSA, log),
|
||||||
log: log,
|
log: log,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,13 +40,13 @@ func NewGenerateFactory(client dclient.Interface, rule kyvernov1.Generation, use
|
||||||
// Validate validates the 'generate' rule
|
// Validate validates the 'generate' rule
|
||||||
func (g *Generate) Validate(ctx context.Context, verbs []string) (warnings []string, path string, err error) {
|
func (g *Generate) Validate(ctx context.Context, verbs []string) (warnings []string, path string, err error) {
|
||||||
rule := g.rule
|
rule := g.rule
|
||||||
if rule.CloneList.Selector != nil {
|
if rule.Generation.CloneList.Selector != nil {
|
||||||
if wildcard.ContainsWildcard(rule.CloneList.Selector.String()) {
|
if wildcard.ContainsWildcard(rule.Generation.CloneList.Selector.String()) {
|
||||||
return nil, "selector", fmt.Errorf("wildcard characters `*/?` not supported")
|
return nil, "selector", fmt.Errorf("wildcard characters `*/?` not supported")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if target := rule.GetData(); target != nil {
|
if target := rule.Generation.GetData(); target != nil {
|
||||||
// TODO: is this required ?? as anchors can only be on pattern and not resource
|
// TODO: is this required ?? as anchors can only be on pattern and not resource
|
||||||
// we can add this check by not sure if its needed here
|
// we can add this check by not sure if its needed here
|
||||||
if path, err := common.ValidatePattern(target, "/", nil); err != nil {
|
if path, err := common.ValidatePattern(target, "/", nil); err != nil {
|
||||||
|
@ -57,18 +59,23 @@ func (g *Generate) Validate(ctx context.Context, verbs []string) (warnings []str
|
||||||
// instructions to modify the RBAC for kyverno are mentioned at https://github.com/kyverno/kyverno/blob/master/documentation/installation.md
|
// instructions to modify the RBAC for kyverno are mentioned at https://github.com/kyverno/kyverno/blob/master/documentation/installation.md
|
||||||
// - operations required: create/update/delete/get
|
// - operations required: create/update/delete/get
|
||||||
// If kind and namespace contain variables, then we cannot resolve then so we skip the processing
|
// If kind and namespace contain variables, then we cannot resolve then so we skip the processing
|
||||||
if rule.ForEachGeneration != nil {
|
if rule.Generation.ForEachGeneration != nil {
|
||||||
for _, forEach := range rule.ForEachGeneration {
|
for _, forEach := range rule.Generation.ForEachGeneration {
|
||||||
if err := g.validateAuth(ctx, verbs, forEach.GeneratePattern); err != nil {
|
if err := g.validateAuth(ctx, verbs, forEach.GeneratePattern); err != nil {
|
||||||
return nil, "foreach", err
|
return nil, "foreach", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := g.validateAuth(ctx, verbs, rule.GeneratePattern); err != nil {
|
if err := g.validateAuth(ctx, verbs, rule.Generation.GeneratePattern); err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, "", nil
|
if w, err := g.validateAuthReports(ctx); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
} else if len(w) > 0 {
|
||||||
|
warnings = append(warnings, w...)
|
||||||
|
}
|
||||||
|
return warnings, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Generate) validateAuth(ctx context.Context, verbs []string, generate kyvernov1.GeneratePattern) error {
|
func (g *Generate) validateAuth(ctx context.Context, verbs []string, generate kyvernov1.GeneratePattern) error {
|
||||||
|
@ -92,7 +99,7 @@ func (g *Generate) canIGenerate(ctx context.Context, verbs []string, gvk, namesp
|
||||||
|
|
||||||
if verbs == nil {
|
if verbs == nil {
|
||||||
verbs = []string{"get", "create"}
|
verbs = []string{"get", "create"}
|
||||||
if g.rule.Synchronize {
|
if g.rule.Generation.Synchronize {
|
||||||
verbs = []string{"get", "create", "update", "delete"}
|
verbs = []string{"get", "create", "update", "delete"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,3 +124,23 @@ func parseCloneKind(gvks string) (gvk, sub string) {
|
||||||
}
|
}
|
||||||
return k, sub
|
return k, sub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Generate) validateAuthReports(ctx context.Context) (warnings []string, err error) {
|
||||||
|
kinds := g.rule.MatchResources.GetKinds()
|
||||||
|
for _, k := range kinds {
|
||||||
|
if wildcard.ContainsWildcard(k) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
verbs := []string{"get", "list", "watch"}
|
||||||
|
ok, msg, err := g.authCheckerReports.CanI(ctx, verbs, k, "", "", "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
warnings = append(warnings, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return warnings, nil
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||||
|
"github.com/kyverno/kyverno/ext/wildcard"
|
||||||
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
||||||
"github.com/kyverno/kyverno/pkg/engine/variables/regex"
|
"github.com/kyverno/kyverno/pkg/engine/variables/regex"
|
||||||
"github.com/kyverno/kyverno/pkg/logging"
|
"github.com/kyverno/kyverno/pkg/logging"
|
||||||
|
@ -17,22 +18,26 @@ import (
|
||||||
|
|
||||||
// Mutate provides implementation to validate 'mutate' rule
|
// Mutate provides implementation to validate 'mutate' rule
|
||||||
type Mutate struct {
|
type Mutate struct {
|
||||||
mutation kyvernov1.Mutation
|
rule *kyvernov1.Rule
|
||||||
authChecker auth.AuthChecks
|
authCheckerBackground auth.AuthChecks
|
||||||
|
authCheckerReports auth.AuthChecks
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMutateFactory returns a new instance of Mutate validation checker
|
// NewMutateFactory returns a new instance of Mutate validation checker
|
||||||
func NewMutateFactory(m kyvernov1.Mutation, client dclient.Interface, mock bool, backgroundSA string) *Mutate {
|
func NewMutateFactory(rule *kyvernov1.Rule, client dclient.Interface, mock bool, backgroundSA, reportsSA string) *Mutate {
|
||||||
var authCheck auth.AuthChecks
|
var authCheckBackground, authCheckerReports auth.AuthChecks
|
||||||
if mock {
|
if mock {
|
||||||
authCheck = fake.NewFakeAuth()
|
authCheckBackground = fake.NewFakeAuth()
|
||||||
|
authCheckerReports = fake.NewFakeAuth()
|
||||||
} else {
|
} else {
|
||||||
authCheck = auth.NewAuth(client, backgroundSA, logging.GlobalLogger())
|
authCheckBackground = auth.NewAuth(client, backgroundSA, logging.GlobalLogger())
|
||||||
|
authCheckerReports = auth.NewAuth(client, reportsSA, logging.GlobalLogger())
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Mutate{
|
return &Mutate{
|
||||||
mutation: m,
|
rule: rule,
|
||||||
authChecker: authCheck,
|
authCheckerBackground: authCheckBackground,
|
||||||
|
authCheckerReports: authCheckerReports,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,19 +48,24 @@ func (m *Mutate) Validate(ctx context.Context, _ []string) (warnings []string, p
|
||||||
return nil, "foreach", fmt.Errorf("only one of `foreach`, `patchStrategicMerge`, or `patchesJson6902` is allowed")
|
return nil, "foreach", fmt.Errorf("only one of `foreach`, `patchStrategicMerge`, or `patchesJson6902` is allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.validateForEach("", m.mutation.ForEachMutation)
|
return m.validateForEach("", m.rule.Mutation.ForEachMutation)
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.hasPatchesJSON6902() && m.hasPatchStrategicMerge() {
|
if m.hasPatchesJSON6902() && m.hasPatchStrategicMerge() {
|
||||||
return nil, "foreach", fmt.Errorf("only one of `patchStrategicMerge` or `patchesJson6902` is allowed")
|
return nil, "foreach", fmt.Errorf("only one of `patchStrategicMerge` or `patchesJson6902` is allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.mutation.Targets != nil {
|
if m.rule.Mutation.Targets != nil {
|
||||||
if err := m.validateAuth(ctx, m.mutation.Targets); err != nil {
|
if err := m.validateAuth(ctx, m.rule.Mutation.Targets); err != nil {
|
||||||
return nil, "targets", fmt.Errorf("auth check fails, additional privileges are required for the service account '%s': %v", m.authChecker.User(), err)
|
return nil, "targets", fmt.Errorf("auth check fails, additional privileges are required for the service account '%s': %v", m.authCheckerBackground.User(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, "", nil
|
if w, err := m.validateAuthReports(ctx); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
} else if len(w) > 0 {
|
||||||
|
warnings = append(warnings, w...)
|
||||||
|
}
|
||||||
|
return warnings, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mutate) validateForEach(tag string, foreach []kyvernov1.ForEachMutation) (warnings []string, path string, err error) {
|
func (m *Mutate) validateForEach(tag string, foreach []kyvernov1.ForEachMutation) (warnings []string, path string, err error) {
|
||||||
|
@ -88,18 +98,18 @@ func (m *Mutate) validateNestedForEach(tag string, j []kyvernov1.ForEachMutation
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mutate) hasForEach() bool {
|
func (m *Mutate) hasForEach() bool {
|
||||||
return len(m.mutation.ForEachMutation) > 0
|
return len(m.rule.Mutation.ForEachMutation) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mutate) hasPatchStrategicMerge() bool {
|
func (m *Mutate) hasPatchStrategicMerge() bool {
|
||||||
return m.mutation.GetPatchStrategicMerge() != nil
|
return m.rule.Mutation.GetPatchStrategicMerge() != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mutate) hasPatchesJSON6902() bool {
|
func (m *Mutate) hasPatchesJSON6902() bool {
|
||||||
return m.mutation.PatchesJSON6902 != ""
|
return m.rule.Mutation.PatchesJSON6902 != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mutate) validateAuth(ctx context.Context, targets []kyvernov1.TargetResourceSpec) error {
|
func (m *Mutate) validateAuth(ctx context.Context, targets []kyvernov1.TargetResourceSpec) (err error) {
|
||||||
var errs []error
|
var errs []error
|
||||||
for _, target := range targets {
|
for _, target := range targets {
|
||||||
if regex.IsVariable(target.Kind) {
|
if regex.IsVariable(target.Kind) {
|
||||||
|
@ -108,7 +118,7 @@ func (m *Mutate) validateAuth(ctx context.Context, targets []kyvernov1.TargetRes
|
||||||
_, _, k, sub := kubeutils.ParseKindSelector(target.Kind)
|
_, _, k, sub := kubeutils.ParseKindSelector(target.Kind)
|
||||||
gvk := strings.Join([]string{target.APIVersion, k}, "/")
|
gvk := strings.Join([]string{target.APIVersion, k}, "/")
|
||||||
verbs := []string{"get", "update"}
|
verbs := []string{"get", "update"}
|
||||||
ok, msg, err := m.authChecker.CanI(ctx, verbs, gvk, target.Namespace, target.Name, sub)
|
ok, msg, err := m.authCheckerBackground.CanI(ctx, verbs, gvk, target.Namespace, target.Name, sub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -119,3 +129,23 @@ func (m *Mutate) validateAuth(ctx context.Context, targets []kyvernov1.TargetRes
|
||||||
|
|
||||||
return multierr.Combine(errs...)
|
return multierr.Combine(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Mutate) validateAuthReports(ctx context.Context) (warnings []string, err error) {
|
||||||
|
kinds := m.rule.MatchResources.GetKinds()
|
||||||
|
for _, k := range kinds {
|
||||||
|
if wildcard.ContainsWildcard(k) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
verbs := []string{"get", "list", "watch"}
|
||||||
|
ok, msg, err := m.authCheckerReports.CanI(ctx, verbs, k, "", "", "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
warnings = append(warnings, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return warnings, nil
|
||||||
|
}
|
||||||
|
|
|
@ -54,6 +54,24 @@ func NewBackgroundScanReport(namespace, name string, gvk schema.GroupVersionKind
|
||||||
return report
|
return report
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BuildMutationReport(resource unstructured.Unstructured, request admissionv1.AdmissionRequest, responses ...engineapi.EngineResponse) reportsv1.ReportInterface {
|
||||||
|
report := NewAdmissionReport(resource.GetNamespace(), string(request.UID), schema.GroupVersionResource(request.Resource), schema.GroupVersionKind(request.Kind), resource)
|
||||||
|
SetMutationResponses(report, responses...)
|
||||||
|
return report
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildMutateExistingReport(namespace, name string, gvk schema.GroupVersionKind, owner string, uid types.UID, responses ...engineapi.EngineResponse) reportsv1.ReportInterface {
|
||||||
|
report := NewBackgroundScanReport(namespace, name, gvk, owner, uid)
|
||||||
|
SetMutationResponses(report, responses...)
|
||||||
|
return report
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildGenerateReport(namespace, name string, gvk schema.GroupVersionKind, owner string, uid types.UID, responses ...engineapi.EngineResponse) reportsv1.ReportInterface {
|
||||||
|
report := NewBackgroundScanReport(namespace, name, gvk, owner, uid)
|
||||||
|
SetGenerationResponses(report, responses...)
|
||||||
|
return report
|
||||||
|
}
|
||||||
|
|
||||||
func NewPolicyReport(namespace, name string, scope *corev1.ObjectReference, results ...policyreportv1alpha2.PolicyReportResult) reportsv1.ReportInterface {
|
func NewPolicyReport(namespace, name string, scope *corev1.ObjectReference, results ...policyreportv1alpha2.PolicyReportResult) reportsv1.ReportInterface {
|
||||||
var report reportsv1.ReportInterface
|
var report reportsv1.ReportInterface
|
||||||
if namespace == "" {
|
if namespace == "" {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/kyverno/kyverno/pkg/pss/utils"
|
"github.com/kyverno/kyverno/pkg/pss/utils"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -191,6 +192,42 @@ func EngineResponseToReportResults(response engineapi.EngineResponse) []policyre
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MutationEngineResponseToReportResults(response engineapi.EngineResponse) []policyreportv1alpha2.PolicyReportResult {
|
||||||
|
pol := response.Policy()
|
||||||
|
policyName, _ := cache.MetaNamespaceKeyFunc(pol.AsKyvernoPolicy())
|
||||||
|
policyType := pol.GetType()
|
||||||
|
annotations := pol.GetAnnotations()
|
||||||
|
|
||||||
|
results := make([]policyreportv1alpha2.PolicyReportResult, 0, len(response.PolicyResponse.Rules))
|
||||||
|
for _, ruleResult := range response.PolicyResponse.Rules {
|
||||||
|
result := ToPolicyReportResult(policyType, policyName, ruleResult, annotations, nil)
|
||||||
|
if target, _, _ := ruleResult.PatchedTarget(); target != nil {
|
||||||
|
addProperty("patched-target", getResourceInfo(target.GroupVersionKind(), target.GetName(), target.GetNamespace()), &result)
|
||||||
|
}
|
||||||
|
results = append(results, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerationEngineResponseToReportResults(response engineapi.EngineResponse) []policyreportv1alpha2.PolicyReportResult {
|
||||||
|
pol := response.Policy()
|
||||||
|
policyName, _ := cache.MetaNamespaceKeyFunc(pol.AsKyvernoPolicy())
|
||||||
|
policyType := pol.GetType()
|
||||||
|
annotations := pol.GetAnnotations()
|
||||||
|
|
||||||
|
results := make([]policyreportv1alpha2.PolicyReportResult, 0, len(response.PolicyResponse.Rules))
|
||||||
|
for _, ruleResult := range response.PolicyResponse.Rules {
|
||||||
|
result := ToPolicyReportResult(policyType, policyName, ruleResult, annotations, nil)
|
||||||
|
if generatedResource := ruleResult.GeneratedResource(); generatedResource.GetName() != "" {
|
||||||
|
addProperty("generated-resource", getResourceInfo(generatedResource.GroupVersionKind(), generatedResource.GetName(), generatedResource.GetNamespace()), &result)
|
||||||
|
}
|
||||||
|
results = append(results, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
func SplitResultsByPolicy(logger logr.Logger, results []policyreportv1alpha2.PolicyReportResult) map[string][]policyreportv1alpha2.PolicyReportResult {
|
func SplitResultsByPolicy(logger logr.Logger, results []policyreportv1alpha2.PolicyReportResult) map[string][]policyreportv1alpha2.PolicyReportResult {
|
||||||
resultsMap := map[string][]policyreportv1alpha2.PolicyReportResult{}
|
resultsMap := map[string][]policyreportv1alpha2.PolicyReportResult{}
|
||||||
keysMap := map[string]string{}
|
keysMap := map[string]string{}
|
||||||
|
@ -230,3 +267,31 @@ func SetResponses(report reportsv1.ReportInterface, engineResponses ...engineapi
|
||||||
}
|
}
|
||||||
SetResults(report, ruleResults...)
|
SetResults(report, ruleResults...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetMutationResponses(report reportsv1.ReportInterface, engineResponses ...engineapi.EngineResponse) {
|
||||||
|
var ruleResults []policyreportv1alpha2.PolicyReportResult
|
||||||
|
for _, result := range engineResponses {
|
||||||
|
pol := result.Policy()
|
||||||
|
SetPolicyLabel(report, pol)
|
||||||
|
ruleResults = append(ruleResults, MutationEngineResponseToReportResults(result)...)
|
||||||
|
}
|
||||||
|
SetResults(report, ruleResults...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetGenerationResponses(report reportsv1.ReportInterface, engineResponses ...engineapi.EngineResponse) {
|
||||||
|
var ruleResults []policyreportv1alpha2.PolicyReportResult
|
||||||
|
for _, result := range engineResponses {
|
||||||
|
pol := result.Policy()
|
||||||
|
SetPolicyLabel(report, pol)
|
||||||
|
ruleResults = append(ruleResults, GenerationEngineResponseToReportResults(result)...)
|
||||||
|
}
|
||||||
|
SetResults(report, ruleResults...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getResourceInfo(gvk schema.GroupVersionKind, name, namespace string) string {
|
||||||
|
info := gvk.String() + " Name=" + name
|
||||||
|
if len(namespace) != 0 {
|
||||||
|
info = info + " Namespace=" + namespace
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ func validateActions(idx int, rule *kyvernov1.Rule, client dclient.Interface, mo
|
||||||
|
|
||||||
// Mutate
|
// Mutate
|
||||||
if rule.HasMutate() {
|
if rule.HasMutate() {
|
||||||
checker = mutate.NewMutateFactory(*rule.Mutation, client, mock, backgroundSA)
|
checker = mutate.NewMutateFactory(rule, client, mock, backgroundSA, reportsSA)
|
||||||
if w, path, err := checker.Validate(context.TODO(), nil); err != nil {
|
if w, path, err := checker.Validate(context.TODO(), nil); err != nil {
|
||||||
return nil, fmt.Errorf("path: spec.rules[%d].mutate.%s.: %v", idx, path, err)
|
return nil, fmt.Errorf("path: spec.rules[%d].mutate.%s.: %v", idx, path, err)
|
||||||
} else if w != nil {
|
} else if w != nil {
|
||||||
|
@ -79,14 +79,14 @@ func validateActions(idx int, rule *kyvernov1.Rule, client dclient.Interface, mo
|
||||||
} else {
|
} else {
|
||||||
if rule.Generation.Synchronize {
|
if rule.Generation.Synchronize {
|
||||||
admissionSA := fmt.Sprintf("system:serviceaccount:%s:%s", config.KyvernoNamespace(), config.KyvernoServiceAccountName())
|
admissionSA := fmt.Sprintf("system:serviceaccount:%s:%s", config.KyvernoNamespace(), config.KyvernoServiceAccountName())
|
||||||
checker = generate.NewGenerateFactory(client, *rule.Generation, admissionSA, logging.GlobalLogger())
|
checker = generate.NewGenerateFactory(client, rule, admissionSA, reportsSA, logging.GlobalLogger())
|
||||||
if w, path, err := checker.Validate(context.TODO(), []string{"list", "get"}); err != nil {
|
if w, path, err := checker.Validate(context.TODO(), []string{"list", "get"}); err != nil {
|
||||||
return nil, fmt.Errorf("path: spec.rules[%d].generate.%s.: %v", idx, path, err)
|
return nil, fmt.Errorf("path: spec.rules[%d].generate.%s.: %v", idx, path, err)
|
||||||
} else if warnings != nil {
|
} else if warnings != nil {
|
||||||
warnings = append(warnings, w...)
|
warnings = append(warnings, w...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checker = generate.NewGenerateFactory(client, *rule.Generation, backgroundSA, logging.GlobalLogger())
|
checker = generate.NewGenerateFactory(client, rule, backgroundSA, reportsSA, logging.GlobalLogger())
|
||||||
if w, path, err := checker.Validate(context.TODO(), nil); err != nil {
|
if w, path, err := checker.Validate(context.TODO(), nil); err != nil {
|
||||||
return nil, fmt.Errorf("path: spec.rules[%d].generate.%s.: %v", idx, path, err)
|
return nil, fmt.Errorf("path: spec.rules[%d].generate.%s.: %v", idx, path, err)
|
||||||
} else if warnings != nil {
|
} else if warnings != nil {
|
||||||
|
|
|
@ -200,8 +200,8 @@ func (h *resourceHandlers) Mutate(ctx context.Context, logger logr.Logger, reque
|
||||||
logger.Error(err, "failed to build policy context")
|
logger.Error(err, "failed to build policy context")
|
||||||
return admissionutils.Response(request.UID, err)
|
return admissionutils.Response(request.UID, err)
|
||||||
}
|
}
|
||||||
mh := mutation.NewMutationHandler(logger, h.engine, h.eventGen, h.nsLister, h.metricsConfig)
|
mh := mutation.NewMutationHandler(logger, h.kyvernoClient, h.engine, h.eventGen, h.nsLister, h.metricsConfig, h.admissionReports, h.reportsBreaker)
|
||||||
patches, warnings, err := mh.HandleMutation(ctx, request.AdmissionRequest, mutatePolicies, policyContext, startTime, h.configuration)
|
patches, warnings, err := mh.HandleMutation(ctx, request, mutatePolicies, policyContext, startTime, h.configuration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err, "mutation failed")
|
logger.Error(err, "mutation failed")
|
||||||
return admissionutils.Response(request.UID, err)
|
return admissionutils.Response(request.UID, err)
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||||
|
"github.com/kyverno/kyverno/pkg/breaker"
|
||||||
|
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
|
||||||
"github.com/kyverno/kyverno/pkg/config"
|
"github.com/kyverno/kyverno/pkg/config"
|
||||||
"github.com/kyverno/kyverno/pkg/engine"
|
"github.com/kyverno/kyverno/pkg/engine"
|
||||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||||
|
@ -17,10 +19,13 @@ import (
|
||||||
"github.com/kyverno/kyverno/pkg/tracing"
|
"github.com/kyverno/kyverno/pkg/tracing"
|
||||||
engineutils "github.com/kyverno/kyverno/pkg/utils/engine"
|
engineutils "github.com/kyverno/kyverno/pkg/utils/engine"
|
||||||
jsonutils "github.com/kyverno/kyverno/pkg/utils/json"
|
jsonutils "github.com/kyverno/kyverno/pkg/utils/json"
|
||||||
|
reportutils "github.com/kyverno/kyverno/pkg/utils/report"
|
||||||
|
"github.com/kyverno/kyverno/pkg/webhooks/handlers"
|
||||||
webhookutils "github.com/kyverno/kyverno/pkg/webhooks/utils"
|
webhookutils "github.com/kyverno/kyverno/pkg/webhooks/utils"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
"gomodules.xyz/jsonpatch/v2"
|
"gomodules.xyz/jsonpatch/v2"
|
||||||
admissionv1 "k8s.io/api/admission/v1"
|
admissionv1 "k8s.io/api/admission/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
corev1listers "k8s.io/client-go/listers/core/v1"
|
corev1listers "k8s.io/client-go/listers/core/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,36 +33,45 @@ type MutationHandler interface {
|
||||||
// HandleMutation handles validating webhook admission request
|
// HandleMutation handles validating webhook admission request
|
||||||
// If there are no errors in validating rule we apply generation rules
|
// If there are no errors in validating rule we apply generation rules
|
||||||
// patchedResource is the (resource + patches) after applying mutation rules
|
// patchedResource is the (resource + patches) after applying mutation rules
|
||||||
HandleMutation(context.Context, admissionv1.AdmissionRequest, []kyvernov1.PolicyInterface, *engine.PolicyContext, time.Time, config.Configuration) ([]byte, []string, error)
|
HandleMutation(context.Context, handlers.AdmissionRequest, []kyvernov1.PolicyInterface, *engine.PolicyContext, time.Time, config.Configuration) ([]byte, []string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMutationHandler(
|
func NewMutationHandler(
|
||||||
log logr.Logger,
|
log logr.Logger,
|
||||||
|
kyvernoClient versioned.Interface,
|
||||||
engine engineapi.Engine,
|
engine engineapi.Engine,
|
||||||
eventGen event.Interface,
|
eventGen event.Interface,
|
||||||
nsLister corev1listers.NamespaceLister,
|
nsLister corev1listers.NamespaceLister,
|
||||||
metrics metrics.MetricsConfigManager,
|
metrics metrics.MetricsConfigManager,
|
||||||
|
admissionReports bool,
|
||||||
|
reportsBreaker breaker.Breaker,
|
||||||
) MutationHandler {
|
) MutationHandler {
|
||||||
return &mutationHandler{
|
return &mutationHandler{
|
||||||
log: log,
|
log: log,
|
||||||
|
kyvernoClient: kyvernoClient,
|
||||||
engine: engine,
|
engine: engine,
|
||||||
eventGen: eventGen,
|
eventGen: eventGen,
|
||||||
nsLister: nsLister,
|
nsLister: nsLister,
|
||||||
metrics: metrics,
|
metrics: metrics,
|
||||||
|
admissionReports: admissionReports,
|
||||||
|
reportsBreaker: reportsBreaker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type mutationHandler struct {
|
type mutationHandler struct {
|
||||||
log logr.Logger
|
log logr.Logger
|
||||||
|
kyvernoClient versioned.Interface
|
||||||
engine engineapi.Engine
|
engine engineapi.Engine
|
||||||
eventGen event.Interface
|
eventGen event.Interface
|
||||||
nsLister corev1listers.NamespaceLister
|
nsLister corev1listers.NamespaceLister
|
||||||
metrics metrics.MetricsConfigManager
|
metrics metrics.MetricsConfigManager
|
||||||
|
admissionReports bool
|
||||||
|
reportsBreaker breaker.Breaker
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *mutationHandler) HandleMutation(
|
func (h *mutationHandler) HandleMutation(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request admissionv1.AdmissionRequest,
|
request handlers.AdmissionRequest,
|
||||||
policies []kyvernov1.PolicyInterface,
|
policies []kyvernov1.PolicyInterface,
|
||||||
policyContext *engine.PolicyContext,
|
policyContext *engine.PolicyContext,
|
||||||
admissionRequestTimestamp time.Time,
|
admissionRequestTimestamp time.Time,
|
||||||
|
@ -77,7 +91,7 @@ func (h *mutationHandler) HandleMutation(
|
||||||
// return value: generated patches, triggered policies, engine responses correspdonding to the triggered policies
|
// return value: generated patches, triggered policies, engine responses correspdonding to the triggered policies
|
||||||
func (v *mutationHandler) applyMutations(
|
func (v *mutationHandler) applyMutations(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request admissionv1.AdmissionRequest,
|
request handlers.AdmissionRequest,
|
||||||
policies []kyvernov1.PolicyInterface,
|
policies []kyvernov1.PolicyInterface,
|
||||||
policyContext *engine.PolicyContext,
|
policyContext *engine.PolicyContext,
|
||||||
cfg config.Configuration,
|
cfg config.Configuration,
|
||||||
|
@ -107,7 +121,7 @@ func (v *mutationHandler) applyMutations(
|
||||||
failurePolicy = kyvernov1.Fail
|
failurePolicy = kyvernov1.Fail
|
||||||
}
|
}
|
||||||
|
|
||||||
engineResponse, policyPatches, err := v.applyMutation(ctx, request, currentContext, failurePolicy)
|
engineResponse, policyPatches, err := v.applyMutation(ctx, request.AdmissionRequest, currentContext, failurePolicy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("mutation policy %s error: %v", policy.GetName(), err)
|
return fmt.Errorf("mutation policy %s error: %v", policy.GetName(), err)
|
||||||
}
|
}
|
||||||
|
@ -141,6 +155,12 @@ func (v *mutationHandler) applyMutations(
|
||||||
events := webhookutils.GenerateEvents(engineResponses, false, cfg)
|
events := webhookutils.GenerateEvents(engineResponses, false, cfg)
|
||||||
v.eventGen.Add(events...)
|
v.eventGen.Add(events...)
|
||||||
|
|
||||||
|
if v.needsReports(request, policyContext.NewResource(), v.admissionReports) {
|
||||||
|
if err := v.createReports(ctx, policyContext.NewResource(), request, engineResponses...); err != nil {
|
||||||
|
v.log.Error(err, "failed to create report")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logMutationResponse(patches, engineResponses, v.log)
|
logMutationResponse(patches, engineResponses, v.log)
|
||||||
|
|
||||||
// patches holds all the successful patches, if no patch is created, it returns nil
|
// patches holds all the successful patches, if no patch is created, it returns nil
|
||||||
|
@ -168,6 +188,25 @@ func (h *mutationHandler) applyMutation(ctx context.Context, request admissionv1
|
||||||
return &engineResponse, policyPatches, nil
|
return &engineResponse, policyPatches, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *mutationHandler) createReports(
|
||||||
|
ctx context.Context,
|
||||||
|
resource unstructured.Unstructured,
|
||||||
|
request handlers.AdmissionRequest,
|
||||||
|
engineResponses ...engineapi.EngineResponse,
|
||||||
|
) error {
|
||||||
|
report := reportutils.BuildMutationReport(resource, request.AdmissionRequest, engineResponses...)
|
||||||
|
if len(report.GetResults()) > 0 {
|
||||||
|
err := h.reportsBreaker.Do(ctx, func(ctx context.Context) error {
|
||||||
|
_, err := reportutils.CreateReport(ctx, report, h.kyvernoClient)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func logMutationResponse(patches []jsonpatch.JsonPatchOperation, engineResponses []engineapi.EngineResponse, logger logr.Logger) {
|
func logMutationResponse(patches []jsonpatch.JsonPatchOperation, engineResponses []engineapi.EngineResponse, logger logr.Logger) {
|
||||||
if len(patches) != 0 {
|
if len(patches) != 0 {
|
||||||
logger.V(4).Info("created patches", "count", len(patches))
|
logger.V(4).Info("created patches", "count", len(patches))
|
||||||
|
|
27
pkg/webhooks/resource/mutation/utils.go
Normal file
27
pkg/webhooks/resource/mutation/utils.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package mutation
|
||||||
|
|
||||||
|
import (
|
||||||
|
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
|
||||||
|
reportutils "github.com/kyverno/kyverno/pkg/utils/report"
|
||||||
|
"github.com/kyverno/kyverno/pkg/webhooks/handlers"
|
||||||
|
admissionv1 "k8s.io/api/admission/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *mutationHandler) needsReports(request handlers.AdmissionRequest, resource unstructured.Unstructured, admissionReport bool) bool {
|
||||||
|
createReport := admissionReport
|
||||||
|
if admissionutils.IsDryRun(request.AdmissionRequest) {
|
||||||
|
createReport = false
|
||||||
|
}
|
||||||
|
// we don't need reports for deletions
|
||||||
|
if request.Operation == admissionv1.Delete {
|
||||||
|
createReport = false
|
||||||
|
}
|
||||||
|
// check if the resource supports reporting
|
||||||
|
if !reportutils.IsGvkSupported(schema.GroupVersionKind(request.Kind)) {
|
||||||
|
createReport = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return createReport
|
||||||
|
}
|
|
@ -104,7 +104,8 @@
|
||||||
"^rbac$"
|
"^rbac$"
|
||||||
],
|
],
|
||||||
"reports": [
|
"reports": [
|
||||||
"^reports$"
|
"^reports$/^admission$/^(exception|mutation|namespaceselector|namespaceselector-assert|test-report-admission-mode|test-report-audit-warn|test-report-properties|two-rules-with-different-modes|update|update-deployment)\\[.*\\]$",
|
||||||
|
"^reports$/^background$/^(exception|exception-assert|exception-with-conditions|exception-with-podsecurity|generate|multiple-exceptions-with-pod-security|mutate-existing|report-deletion|test-report-background-mode|two-rules-with-different-modes|verify-image-fail|verify-image-pass)\\[.*\\]$"
|
||||||
],
|
],
|
||||||
"sigstore-custom-tuf": [
|
"sigstore-custom-tuf": [
|
||||||
"^sigstore-custom-tuf$"
|
"^sigstore-custom-tuf$"
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Title
|
||||||
|
|
||||||
|
This is a basic mutation test.
|
|
@ -0,0 +1,19 @@
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: add-labels
|
||||||
|
spec:
|
||||||
|
admission: true
|
||||||
|
background: true
|
||||||
|
rules:
|
||||||
|
- match:
|
||||||
|
any:
|
||||||
|
- resources:
|
||||||
|
kinds:
|
||||||
|
- Pod
|
||||||
|
mutate:
|
||||||
|
patchStrategicMerge:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
foo: bar
|
||||||
|
name: add-labels
|
|
@ -0,0 +1,9 @@
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: add-labels
|
||||||
|
status:
|
||||||
|
conditions:
|
||||||
|
- reason: Succeeded
|
||||||
|
status: "True"
|
||||||
|
type: Ready
|
|
@ -0,0 +1,13 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: good-pod
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
hostNetwork: false
|
||||||
|
containers:
|
||||||
|
- name: nginx1
|
||||||
|
image: nginx
|
||||||
|
args:
|
||||||
|
- sleep
|
||||||
|
- 1d
|
|
@ -0,0 +1,7 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: good-pod
|
||||||
|
namespace: default
|
||||||
|
labels:
|
||||||
|
foo: bar
|
|
@ -0,0 +1,24 @@
|
||||||
|
apiVersion: wgpolicyk8s.io/v1alpha2
|
||||||
|
kind: PolicyReport
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/managed-by: kyverno
|
||||||
|
namespace: default
|
||||||
|
results:
|
||||||
|
- message: mutated Pod/good-pod in namespace default
|
||||||
|
policy: add-labels
|
||||||
|
result: pass
|
||||||
|
rule: add-labels
|
||||||
|
scored: true
|
||||||
|
source: kyverno
|
||||||
|
scope:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
name: good-pod
|
||||||
|
namespace: default
|
||||||
|
summary:
|
||||||
|
error: 0
|
||||||
|
fail: 0
|
||||||
|
pass: 1
|
||||||
|
skip: 0
|
||||||
|
warn: 0
|
22
test/conformance/chainsaw/reports/admission/mutation/chainsaw-test.yaml
Executable file
22
test/conformance/chainsaw/reports/admission/mutation/chainsaw-test.yaml
Executable file
|
@ -0,0 +1,22 @@
|
||||||
|
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||||||
|
kind: Test
|
||||||
|
metadata:
|
||||||
|
name: basic-check-output
|
||||||
|
spec:
|
||||||
|
steps:
|
||||||
|
- name: step-01
|
||||||
|
try:
|
||||||
|
- apply:
|
||||||
|
file: chainsaw-step-01-apply-1-1.yaml
|
||||||
|
- assert:
|
||||||
|
file: chainsaw-step-01-assert-1-1.yaml
|
||||||
|
- name: step-02
|
||||||
|
try:
|
||||||
|
- apply:
|
||||||
|
file: chainsaw-step-02-apply-1-1.yaml
|
||||||
|
- assert:
|
||||||
|
file: chainsaw-step-02-assert-1-1.yaml
|
||||||
|
- name: step-03
|
||||||
|
try:
|
||||||
|
- assert:
|
||||||
|
file: chainsaw-step-03-assert-1-1.yaml
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Title
|
||||||
|
|
||||||
|
This is a generate test to ensure cluster policy report is generated for generate rules.
|
|
@ -0,0 +1,21 @@
|
||||||
|
apiVersion: kyverno.io/v2beta1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: cpol-nosync-clone
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- generate:
|
||||||
|
apiVersion: v1
|
||||||
|
clone:
|
||||||
|
name: regcred
|
||||||
|
namespace: default
|
||||||
|
kind: Secret
|
||||||
|
name: regcred
|
||||||
|
namespace: '{{request.object.metadata.name}}'
|
||||||
|
synchronize: false
|
||||||
|
match:
|
||||||
|
any:
|
||||||
|
- resources:
|
||||||
|
kinds:
|
||||||
|
- Namespace
|
||||||
|
name: clone-secret
|
|
@ -0,0 +1,8 @@
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
foo: YmFy
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: regcred
|
||||||
|
namespace: default
|
||||||
|
type: Opaque
|
|
@ -0,0 +1,9 @@
|
||||||
|
apiVersion: kyverno.io/v2beta1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: cpol-nosync-clone
|
||||||
|
status:
|
||||||
|
conditions:
|
||||||
|
- reason: Succeeded
|
||||||
|
status: "True"
|
||||||
|
type: Ready
|
|
@ -0,0 +1,4 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: cpol-clone-nosync-create-ns
|
|
@ -0,0 +1,25 @@
|
||||||
|
apiVersion: wgpolicyk8s.io/v1alpha2
|
||||||
|
kind: ClusterPolicyReport
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/managed-by: kyverno
|
||||||
|
ownerReferences:
|
||||||
|
- apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
name: cpol-clone-nosync-create-ns
|
||||||
|
results:
|
||||||
|
- policy: cpol-nosync-clone
|
||||||
|
result: pass
|
||||||
|
rule: clone-secret
|
||||||
|
scored: true
|
||||||
|
source: kyverno
|
||||||
|
scope:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
name: cpol-clone-nosync-create-ns
|
||||||
|
summary:
|
||||||
|
error: 0
|
||||||
|
fail: 0
|
||||||
|
pass: 1
|
||||||
|
skip: 0
|
||||||
|
warn: 0
|
26
test/conformance/chainsaw/reports/background/generate/chainsaw-test.yaml
Executable file
26
test/conformance/chainsaw/reports/background/generate/chainsaw-test.yaml
Executable file
|
@ -0,0 +1,26 @@
|
||||||
|
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||||||
|
kind: Test
|
||||||
|
metadata:
|
||||||
|
name: cpol-clone-nosync-create
|
||||||
|
spec:
|
||||||
|
steps:
|
||||||
|
- name: step-01
|
||||||
|
try:
|
||||||
|
- apply:
|
||||||
|
file: permissions.yaml
|
||||||
|
- apply:
|
||||||
|
file: chainsaw-step-01-apply-1-1.yaml
|
||||||
|
- apply:
|
||||||
|
file: chainsaw-step-01-apply-1-2.yaml
|
||||||
|
- assert:
|
||||||
|
file: chainsaw-step-01-assert-1-1.yaml
|
||||||
|
- name: step-02
|
||||||
|
try:
|
||||||
|
- apply:
|
||||||
|
file: chainsaw-step-02-apply-1-1.yaml
|
||||||
|
- sleep:
|
||||||
|
duration: 3s
|
||||||
|
- name: step-03
|
||||||
|
try:
|
||||||
|
- assert:
|
||||||
|
file: chainsaw-step-03-assert-1-1.yaml
|
|
@ -0,0 +1,20 @@
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: kyverno:secrets-kqo1
|
||||||
|
labels:
|
||||||
|
rbac.kyverno.io/aggregate-to-background-controller: "true"
|
||||||
|
rbac.kyverno.io/aggregate-to-reports-controller: "true"
|
||||||
|
rbac.kyverno.io/aggregate-to-admission-controller: "true"
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ''
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- delete
|
|
@ -0,0 +1,7 @@
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This is a basic test for the mutate existing capability which ensures that creating a triggering resource results in the correct mutation of a different resource and correct report being generated
|
||||||
|
|
||||||
|
## Reference Issue(s)
|
||||||
|
|
||||||
|
N/A
|
|
@ -0,0 +1,8 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
cloud.platformzero.com/serviceClass: xl2
|
||||||
|
labels:
|
||||||
|
app-type: corp
|
||||||
|
name: staging
|
|
@ -0,0 +1,8 @@
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
foo: YmFy
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: secret-1
|
||||||
|
namespace: staging
|
||||||
|
type: Opaque
|
|
@ -0,0 +1,25 @@
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: mutate-existing-secret
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- match:
|
||||||
|
any:
|
||||||
|
- resources:
|
||||||
|
kinds:
|
||||||
|
- Pod
|
||||||
|
namespaces:
|
||||||
|
- staging
|
||||||
|
mutate:
|
||||||
|
mutateExistingOnPolicyUpdate: false
|
||||||
|
patchStrategicMerge:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
foo: bar
|
||||||
|
targets:
|
||||||
|
- apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
name: secret-1
|
||||||
|
namespace: '{{ request.object.metadata.namespace }}'
|
||||||
|
name: mutate-secret-on-configmap-create
|
|
@ -0,0 +1,9 @@
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: mutate-existing-secret
|
||||||
|
status:
|
||||||
|
conditions:
|
||||||
|
- reason: Succeeded
|
||||||
|
status: "True"
|
||||||
|
type: Ready
|
|
@ -0,0 +1,11 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: myapp-pod
|
||||||
|
namespace: staging
|
||||||
|
labels:
|
||||||
|
app: myapp
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx:latest
|
|
@ -0,0 +1,22 @@
|
||||||
|
apiVersion: wgpolicyk8s.io/v1alpha2
|
||||||
|
kind: PolicyReport
|
||||||
|
metadata:
|
||||||
|
generation: 1
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/managed-by: kyverno
|
||||||
|
namespace: staging
|
||||||
|
results:
|
||||||
|
- message: mutated Secret/secret-1 in namespace staging
|
||||||
|
policy: mutate-existing-secret
|
||||||
|
properties:
|
||||||
|
patched-target: /v1, Kind=Secret Name=secret-1 Namespace=staging
|
||||||
|
result: pass
|
||||||
|
rule: mutate-secret-on-configmap-create
|
||||||
|
scored: true
|
||||||
|
source: kyverno
|
||||||
|
summary:
|
||||||
|
error: 0
|
||||||
|
fail: 0
|
||||||
|
pass: 1
|
||||||
|
skip: 0
|
||||||
|
warn: 0
|
|
@ -0,0 +1,24 @@
|
||||||
|
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||||||
|
kind: Test
|
||||||
|
metadata:
|
||||||
|
name: basic-create
|
||||||
|
spec:
|
||||||
|
steps:
|
||||||
|
- name: step-01
|
||||||
|
try:
|
||||||
|
- apply:
|
||||||
|
file: chainsaw-step-01-apply-1-1.yaml
|
||||||
|
- apply:
|
||||||
|
file: chainsaw-step-01-apply-1-2.yaml
|
||||||
|
- apply:
|
||||||
|
file: chainsaw-step-01-apply-1-3.yaml
|
||||||
|
- assert:
|
||||||
|
file: chainsaw-step-01-assert-1-1.yaml
|
||||||
|
- name: step-02
|
||||||
|
try:
|
||||||
|
- apply:
|
||||||
|
file: chainsaw-step-02-apply-1-1.yaml
|
||||||
|
- name: step-03
|
||||||
|
try:
|
||||||
|
- assert:
|
||||||
|
file: chainsaw-step-03-assert-1-1.yaml
|
Loading…
Reference in a new issue