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"
2023-08-30 14:24:57 +02:00
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
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"
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"
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
2023-08-30 14:24:57 +02:00
kyvernoClient versioned . 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 ,
2023-08-30 14:24:57 +02:00
kyvernoClient versioned . 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 ,
2023-08-30 14:24:57 +02:00
kyvernoClient : kyvernoClient ,
2023-02-03 06:01:11 +01:00
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 )
2023-08-30 14:24:57 +02:00
if err := common . UpdateRetryAnnotation ( c . kyvernoClient , ur ) ; err != nil {
errs = append ( errs , err )
}
2023-03-29 21:24:42 +05:30
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 )
2023-08-30 14:24:57 +02:00
if err := common . UpdateRetryAnnotation ( c . kyvernoClient , ur ) ; err != nil {
errs = append ( errs , err )
}
2023-03-29 21:24:42 +05:30
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-09-19 14:54:40 +08:00
err := fmt . Errorf ( "failed to mutate existing resource, rule %s, response %v: %s" , r . Name ( ) , r . Status ( ) , r . Message ( ) )
2022-04-25 20:20:40 +08:00
logger . Error ( err , "" )
errs = append ( errs , err )
2023-07-26 17:06:51 +03:00
c . report ( err , policy , rule . Name , patched )
2022-04-25 20:20:40 +08:00
2023-01-30 12:41:09 +01:00
case engineapi . RuleStatusSkip :
2023-09-19 14:54:40 +08:00
err := fmt . Errorf ( "mutate existing rule skipped, rule %s, response %v: %s" , r . Name ( ) , r . Status ( ) , r . Message ( ) )
logger . Error ( err , "" )
2023-07-26 17:06:51 +03:00
c . report ( err , policy , rule . Name , patched )
2022-04-25 20:20:40 +08:00
2023-01-30 12:41:09 +01:00
case engineapi . RuleStatusPass :
2023-06-07 06:51:02 +02:00
patchedNew := patched
2022-04-25 20:20:40 +08:00
if patchedNew == nil {
2023-04-05 12:35:38 +02:00
logger . Error ( ErrEmptyPatch , "" , "rule" , r . Name ( ) , "message" , r . Message ( ) )
2023-09-19 14:54:40 +08:00
errs = append ( errs , ErrEmptyPatch )
2022-04-25 20:20:40 +08:00
continue
}
2023-09-19 14:54:40 +08:00
patchedNew . SetResourceVersion ( patched . GetResourceVersion ( ) )
var updateErr error
if patchedSubresource == "status" {
_ , updateErr = c . client . UpdateStatusResource ( context . TODO ( ) , patchedNew . GetAPIVersion ( ) , patchedNew . GetKind ( ) , patchedNew . GetNamespace ( ) , patchedNew . Object , false )
} else if patchedSubresource != "" {
parentResourceGVR := parentGVR
parentResourceGV := schema . GroupVersion { Group : parentResourceGVR . Group , Version : parentResourceGVR . Version }
parentResourceGVK , err := c . client . Discovery ( ) . GetGVKFromGVR ( parentResourceGV . WithResource ( parentResourceGVR . Resource ) )
if err != nil {
logger . Error ( err , "failed to get GVK from GVR" , "GVR" , parentResourceGVR )
errs = append ( errs , err )
continue
2022-04-25 20:20:40 +08:00
}
2023-09-19 14:54:40 +08:00
_ , updateErr = c . client . UpdateResource ( context . TODO ( ) , parentResourceGV . String ( ) , parentResourceGVK . Kind , patchedNew . GetNamespace ( ) , patchedNew . Object , false , patchedSubresource )
} else {
_ , updateErr = c . client . UpdateResource ( context . TODO ( ) , patchedNew . GetAPIVersion ( ) , patchedNew . GetKind ( ) , patchedNew . GetNamespace ( ) , patchedNew . Object , false )
}
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 ( ) )
2022-04-25 20:20:40 +08:00
}
2023-09-19 14:54:40 +08:00
c . report ( updateErr , policy , rule . Name , patched )
2022-04-25 20:20:40 +08:00
}
}
}
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-07-26 17:06:51 +03:00
func ( c * mutateExistingController ) report ( err error , policy kyvernov1 . PolicyInterface , rule string , target * unstructured . Unstructured ) {
2022-04-25 20:20:40 +08:00
var events [ ] event . Info
if target == nil {
2023-07-26 17:06:51 +03:00
c . log . WithName ( "mutateExisting" ) . Info ( "cannot generate events for empty target resource" , "policy" , policy . GetName ( ) , "rule" , rule )
return
2022-04-25 20:20:40 +08:00
}
if err != nil {
2023-07-26 17:06:51 +03:00
events = event . NewBackgroundFailedEvent ( err , policy , rule , event . MutateExistingController ,
kyvernov1 . ResourceSpec { Kind : target . GetKind ( ) , Namespace : target . GetNamespace ( ) , Name : target . GetName ( ) } )
2022-04-25 20:20:40 +08:00
} else {
2023-07-26 17:06:51 +03:00
events = event . NewBackgroundSuccessEvent ( event . MutateExistingController , policy ,
[ ] kyvernov1 . ResourceSpec { { Kind : target . GetKind ( ) , Namespace : target . GetNamespace ( ) , Name : target . GetName ( ) } } )
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
}