2022-04-25 20:20:40 +08:00
package mutate
import (
2022-11-29 14:59:40 +01:00
"context"
2022-04-25 20:20:40 +08:00
"fmt"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
2022-05-17 13:12:43 +02:00
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
2022-04-25 20:20:40 +08:00
"github.com/kyverno/kyverno/pkg/background/common"
2022-05-18 06:02:31 +02:00
kyvernov1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
2022-08-31 14:03:47 +08:00
"github.com/kyverno/kyverno/pkg/clients/dclient"
2022-04-25 20:20:40 +08:00
"github.com/kyverno/kyverno/pkg/config"
2023-01-30 12:41:09 +01:00
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
2023-04-13 13:29:40 +02:00
"github.com/kyverno/kyverno/pkg/engine/jmespath"
2022-04-25 20:20:40 +08:00
"github.com/kyverno/kyverno/pkg/event"
"github.com/kyverno/kyverno/pkg/utils"
2023-04-04 16:31:06 +05:30
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
2023-02-02 16:56:00 +08:00
engineutils "github.com/kyverno/kyverno/pkg/utils/engine"
2022-08-02 07:54:02 -07:00
"go.uber.org/multierr"
2022-04-25 20:20:40 +08:00
yamlv2 "gopkg.in/yaml.v2"
2023-03-29 21:24:42 +05:30
admissionv1 "k8s.io/api/admission/v1"
2022-04-25 20:20:40 +08:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2022-12-09 22:15:23 +05:30
"k8s.io/apimachinery/pkg/runtime/schema"
2023-02-02 16:56:00 +08:00
corev1listers "k8s.io/client-go/listers/core/v1"
2022-12-09 22:15:23 +05:30
"k8s.io/client-go/tools/cache"
2022-04-25 20:20:40 +08:00
)
2022-05-01 22:14:32 -07:00
var ErrEmptyPatch error = fmt . Errorf ( "empty resource to patch" )
2023-04-13 13:29:40 +02:00
type mutateExistingController struct {
2022-05-24 12:27:26 +02:00
// clients
client dclient . Interface
2022-04-25 20:20:40 +08:00
statusControl common . StatusControlInterface
2023-02-02 11:58:34 +01:00
engine engineapi . Engine
2023-01-31 15:30:40 +01:00
2022-05-24 12:27:26 +02:00
// listers
policyLister kyvernov1listers . ClusterPolicyLister
2022-05-18 06:02:31 +02:00
npolicyLister kyvernov1listers . PolicyLister
2023-02-02 16:56:00 +08:00
nsLister corev1listers . NamespaceLister
2022-04-25 20:20:40 +08:00
2023-02-03 06:01:11 +01:00
configuration config . Configuration
eventGen event . Interface
2022-08-31 14:03:47 +08:00
log logr . Logger
2023-04-13 13:29:40 +02:00
jp jmespath . Interface
2022-04-25 20:20:40 +08:00
}
// NewMutateExistingController returns an instance of the MutateExistingController
func NewMutateExistingController (
2022-05-03 07:30:04 +02:00
client dclient . Interface ,
2022-05-24 12:27:26 +02:00
statusControl common . StatusControlInterface ,
2023-02-02 11:58:34 +01:00
engine engineapi . Engine ,
2022-05-18 06:02:31 +02:00
policyLister kyvernov1listers . ClusterPolicyLister ,
npolicyLister kyvernov1listers . PolicyLister ,
2023-02-02 16:56:00 +08:00
nsLister corev1listers . NamespaceLister ,
2022-05-24 12:27:26 +02:00
dynamicConfig config . Configuration ,
2022-04-25 20:20:40 +08:00
eventGen event . Interface ,
log logr . Logger ,
2023-04-13 13:29:40 +02:00
jp jmespath . Interface ,
) * mutateExistingController {
c := mutateExistingController {
2023-02-03 06:01:11 +01:00
client : client ,
statusControl : statusControl ,
engine : engine ,
policyLister : policyLister ,
npolicyLister : npolicyLister ,
nsLister : nsLister ,
configuration : dynamicConfig ,
eventGen : eventGen ,
log : log ,
2023-04-13 13:29:40 +02:00
jp : jp ,
2022-04-25 20:20:40 +08:00
}
2022-05-24 12:27:26 +02:00
return & c
2022-04-25 20:20:40 +08:00
}
2023-04-13 13:29:40 +02:00
func ( c * mutateExistingController ) ProcessUR ( ur * kyvernov1beta1 . UpdateRequest ) error {
2023-02-10 22:56:17 +08:00
logger := c . log . WithValues ( "name" , ur . GetName ( ) , "policy" , ur . Spec . GetPolicyKey ( ) , "resource" , ur . Spec . GetResource ( ) . String ( ) )
2022-04-25 20:20:40 +08:00
var errs [ ] error
2023-05-19 05:57:57 +08:00
policy , err := c . getPolicy ( ur )
2022-04-25 20:20:40 +08:00
if err != nil {
logger . Error ( err , "failed to get policy" )
return err
}
for _ , rule := range policy . GetSpec ( ) . Rules {
2023-05-19 05:57:57 +08:00
if ! rule . IsMutateExisting ( ) || ur . Spec . Rule != rule . Name {
2022-04-25 20:20:40 +08:00
continue
}
2023-03-29 21:24:42 +05:30
var trigger * unstructured . Unstructured
admissionRequest := ur . Spec . Context . AdmissionRequestInfo . AdmissionRequest
if admissionRequest == nil {
trigger , err = common . GetResource ( c . client , ur . Spec , c . log )
if err != nil || trigger == nil {
logger . WithName ( rule . Name ) . Error ( err , "failed to get trigger resource" )
errs = append ( errs , err )
continue
}
} else {
if admissionRequest . Operation == admissionv1 . Create {
trigger , err = common . GetResource ( c . client , ur . Spec , c . log )
if err != nil || trigger == nil {
if admissionRequest . SubResource == "" {
logger . WithName ( rule . Name ) . Error ( err , "failed to get trigger resource" )
errs = append ( errs , err )
continue
} else {
logger . WithName ( rule . Name ) . Info ( "trigger resource not found for subresource, reverting to resource in AdmissionReviewRequest" , "subresource" , admissionRequest . SubResource )
2023-04-04 16:31:06 +05:30
newResource , _ , err := admissionutils . ExtractResources ( nil , * admissionRequest )
if err != nil {
logger . WithName ( rule . Name ) . Error ( err , "failed to extract resources from admission review request" )
2023-03-29 21:24:42 +05:30
errs = append ( errs , err )
continue
}
2023-04-04 16:31:06 +05:30
trigger = & newResource
2023-03-29 21:24:42 +05:30
}
}
} else {
2023-04-04 16:31:06 +05:30
newResource , oldResource , err := admissionutils . ExtractResources ( nil , * admissionRequest )
if err != nil {
logger . WithName ( rule . Name ) . Error ( err , "failed to extract resources from admission review request" )
2023-03-29 21:24:42 +05:30
errs = append ( errs , err )
continue
}
2023-04-04 16:31:06 +05:30
trigger = & newResource
if newResource . Object == nil {
trigger = & oldResource
}
2023-03-29 21:24:42 +05:30
}
2022-04-25 20:20:40 +08:00
}
2023-02-02 16:56:00 +08:00
namespaceLabels := engineutils . GetNamespaceSelectorsFromNamespaceLister ( trigger . GetKind ( ) , trigger . GetNamespace ( ) , c . nsLister , logger )
2023-04-13 13:29:40 +02:00
policyContext , err := common . NewBackgroundContext ( logger , c . client , ur , policy , trigger , c . configuration , c . jp , namespaceLabels )
2022-04-25 20:20:40 +08:00
if err != nil {
logger . WithName ( rule . Name ) . Error ( err , "failed to build policy context" )
errs = append ( errs , err )
continue
}
2023-03-29 21:24:42 +05:30
if admissionRequest != nil {
var gvk schema . GroupVersionKind
gvk , err = c . client . Discovery ( ) . GetGVKFromGVR ( schema . GroupVersionResource ( admissionRequest . Resource ) )
if err != nil {
logger . WithName ( rule . Name ) . Error ( err , "failed to get GVK from GVR" , "GVR" , admissionRequest . Resource )
errs = append ( errs , err )
continue
}
policyContext = policyContext . WithResourceKind ( gvk , admissionRequest . SubResource )
}
2022-04-25 20:20:40 +08:00
2023-02-03 06:01:11 +01:00
er := c . engine . Mutate ( context . TODO ( ) , policyContext )
2022-04-25 20:20:40 +08:00
for _ , r := range er . PolicyResponse . Rules {
2023-04-05 12:35:38 +02:00
patched , parentGVR , patchedSubresource := r . PatchedTarget ( )
switch r . Status ( ) {
2023-01-30 12:41:09 +01:00
case engineapi . RuleStatusFail , engineapi . RuleStatusError , engineapi . RuleStatusWarn :
2023-04-05 12:35:38 +02:00
err := fmt . Errorf ( "failed to mutate existing resource, rule response%v: %s" , r . Status ( ) , r . Message ( ) )
2022-04-25 20:20:40 +08:00
logger . Error ( err , "" )
errs = append ( errs , err )
c . report ( err , ur . Spec . Policy , rule . Name , patched )
2023-01-30 12:41:09 +01:00
case engineapi . RuleStatusSkip :
2023-04-05 12:35:38 +02:00
logger . Info ( "mutate existing rule skipped" , "rule" , r . Name ( ) , "message" , r . Message ( ) )
2022-04-25 20:20:40 +08:00
c . report ( err , ur . Spec . Policy , rule . Name , patched )
2023-01-30 12:41:09 +01:00
case engineapi . RuleStatusPass :
2022-04-25 20:20:40 +08:00
patchedNew , err := addAnnotation ( policy , patched , r )
if err != nil {
logger . Error ( err , "failed to apply patches" )
errs = append ( errs , err )
}
if patchedNew == nil {
2023-04-05 12:35:38 +02:00
logger . Error ( ErrEmptyPatch , "" , "rule" , r . Name ( ) , "message" , r . Message ( ) )
2022-04-25 20:20:40 +08:00
errs = append ( errs , err )
continue
}
2023-04-05 12:35:38 +02:00
if r . Status ( ) == engineapi . RuleStatusPass {
2023-01-06 20:58:54 +08:00
patchedNew . SetResourceVersion ( patched . GetResourceVersion ( ) )
2022-12-09 22:15:23 +05:30
var updateErr error
2023-04-05 12:35:38 +02:00
if patchedSubresource == "status" {
2022-12-09 22:15:23 +05:30
_ , updateErr = c . client . UpdateStatusResource ( context . TODO ( ) , patchedNew . GetAPIVersion ( ) , patchedNew . GetKind ( ) , patchedNew . GetNamespace ( ) , patchedNew . Object , false )
2023-04-05 12:35:38 +02:00
} else if patchedSubresource != "" {
parentResourceGVR := parentGVR
2022-12-09 22:15:23 +05:30
parentResourceGV := schema . GroupVersion { Group : parentResourceGVR . Group , Version : parentResourceGVR . Version }
2023-03-09 15:52:44 +01:00
parentResourceGVK , err := c . client . Discovery ( ) . GetGVKFromGVR ( parentResourceGV . WithResource ( parentResourceGVR . Resource ) )
2022-12-09 22:15:23 +05:30
if err != nil {
logger . Error ( err , "failed to get GVK from GVR" , "GVR" , parentResourceGVR )
errs = append ( errs , err )
continue
}
2023-04-05 12:35:38 +02:00
_ , updateErr = c . client . UpdateResource ( context . TODO ( ) , parentResourceGV . String ( ) , parentResourceGVK . Kind , patchedNew . GetNamespace ( ) , patchedNew . Object , false , patchedSubresource )
2022-12-09 22:15:23 +05:30
} else {
_ , updateErr = c . client . UpdateResource ( context . TODO ( ) , patchedNew . GetAPIVersion ( ) , patchedNew . GetKind ( ) , patchedNew . GetNamespace ( ) , patchedNew . Object , false )
}
2022-04-25 20:20:40 +08:00
if updateErr != nil {
errs = append ( errs , updateErr )
logger . WithName ( rule . Name ) . Error ( updateErr , "failed to update target resource" , "namespace" , patchedNew . GetNamespace ( ) , "name" , patchedNew . GetName ( ) )
} else {
logger . WithName ( rule . Name ) . V ( 4 ) . Info ( "successfully mutated existing resource" , "namespace" , patchedNew . GetNamespace ( ) , "name" , patchedNew . GetName ( ) )
}
c . report ( updateErr , ur . Spec . Policy , rule . Name , patched )
}
}
}
}
2022-08-02 07:54:02 -07:00
err = multierr . Combine ( errs ... )
return updateURStatus ( c . statusControl , * ur , err )
2022-04-25 20:20:40 +08:00
}
2023-05-19 05:57:57 +08:00
func ( c * mutateExistingController ) getPolicy ( ur * kyvernov1beta1 . UpdateRequest ) ( policy kyvernov1 . PolicyInterface , err error ) {
pNamespace , pName , err := cache . SplitMetaNamespaceKey ( ur . Spec . Policy )
2022-04-25 20:20:40 +08:00
if err != nil {
return nil , err
}
if pNamespace != "" {
return c . npolicyLister . Policies ( pNamespace ) . Get ( pName )
}
return c . policyLister . Get ( pName )
}
2023-04-13 13:29:40 +02:00
func ( c * mutateExistingController ) report ( err error , policy , rule string , target * unstructured . Unstructured ) {
2022-04-25 20:20:40 +08:00
var events [ ] event . Info
if target == nil {
2022-12-08 16:32:44 +08:00
c . log . WithName ( "mutateExisting" ) . Info ( "cannot generate events for empty target resource" , "policy" , policy , "rule" , rule )
2022-04-25 20:20:40 +08:00
}
if err != nil {
2022-05-01 22:14:32 -07:00
events = event . NewBackgroundFailedEvent ( err , policy , rule , event . MutateExistingController , target )
2022-04-25 20:20:40 +08:00
} else {
2022-05-01 22:14:32 -07:00
events = event . NewBackgroundSuccessEvent ( policy , rule , event . MutateExistingController , target )
2022-04-25 20:20:40 +08:00
}
c . eventGen . Add ( events ... )
}
2022-05-17 13:12:43 +02:00
func updateURStatus ( statusControl common . StatusControlInterface , ur kyvernov1beta1 . UpdateRequest , err error ) error {
2022-04-25 20:20:40 +08:00
if err != nil {
2022-05-19 18:06:56 +02:00
if _ , err := statusControl . Failed ( ur . GetName ( ) , err . Error ( ) , nil ) ; err != nil {
return err
}
} else {
if _ , err := statusControl . Success ( ur . GetName ( ) , nil ) ; err != nil {
return err
}
2022-04-25 20:20:40 +08:00
}
2022-05-19 18:06:56 +02:00
return nil
2022-04-25 20:20:40 +08:00
}
2023-01-30 12:41:09 +01:00
func addAnnotation ( policy kyvernov1 . PolicyInterface , patched * unstructured . Unstructured , r engineapi . RuleResponse ) ( patchedNew * unstructured . Unstructured , err error ) {
2022-04-25 20:20:40 +08:00
if patched == nil {
return
}
patchedNew = patched
var rulePatches [ ] utils . RulePatch
2023-04-05 12:35:38 +02:00
for _ , patch := range r . Patches ( ) {
2023-05-13 10:56:54 +02:00
rulePatches = append ( rulePatches , utils . RulePatch {
2023-04-05 12:35:38 +02:00
RuleName : r . Name ( ) ,
2023-05-13 10:56:54 +02:00
Op : patch . Operation ,
Path : patch . Path ,
} )
2022-04-25 20:20:40 +08:00
}
2022-05-17 08:19:03 +02:00
annotationContent := make ( map [ string ] string )
2022-04-25 20:20:40 +08:00
policyName := policy . GetName ( )
if policy . GetNamespace ( ) != "" {
policyName = policy . GetNamespace ( ) + "/" + policy . GetName ( )
}
for _ , rulePatch := range rulePatches {
annotationContent [ rulePatch . RuleName + "." + policyName + ".kyverno.io" ] = utils . OperationToPastTense [ rulePatch . Op ] + " " + rulePatch . Path
}
if len ( annotationContent ) == 0 {
return
}
result , _ := yamlv2 . Marshal ( annotationContent )
ann := patchedNew . GetAnnotations ( )
if ann == nil {
ann = make ( map [ string ] string )
}
ann [ utils . PolicyAnnotation ] = string ( result )
patchedNew . SetAnnotations ( ann )
return
}