2023-02-22 18:49:09 +08:00
package generation
import (
"context"
"fmt"
2023-02-23 19:18:05 +08:00
"github.com/go-logr/logr"
2023-07-06 10:00:36 +02:00
"github.com/kyverno/kyverno/api/kyverno"
2023-02-22 18:49:09 +08:00
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
2024-06-20 11:44:43 +02:00
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
2023-02-22 18:49:09 +08:00
"github.com/kyverno/kyverno/pkg/background/common"
generateutils "github.com/kyverno/kyverno/pkg/background/generate"
2023-02-23 19:18:05 +08:00
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
kyvernov1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
2024-06-20 11:44:43 +02:00
kyvernov2listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v2"
2023-02-23 19:18:05 +08:00
"github.com/kyverno/kyverno/pkg/clients/dclient"
2023-02-22 18:49:09 +08:00
"github.com/kyverno/kyverno/pkg/engine"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
2023-06-13 17:12:13 +08:00
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
2023-02-22 18:49:09 +08:00
"github.com/kyverno/kyverno/pkg/event"
2023-02-23 19:18:05 +08:00
"github.com/kyverno/kyverno/pkg/metrics"
2023-06-13 17:12:13 +08:00
utils "github.com/kyverno/kyverno/pkg/utils/engine"
2023-02-23 19:18:05 +08:00
webhookgenerate "github.com/kyverno/kyverno/pkg/webhooks/updaterequest"
2023-12-15 16:42:10 +02:00
webhookutils "github.com/kyverno/kyverno/pkg/webhooks/utils"
2023-02-22 18:49:09 +08:00
admissionv1 "k8s.io/api/admission/v1"
2023-02-23 19:18:05 +08:00
corev1listers "k8s.io/client-go/listers/core/v1"
2023-02-22 18:49:09 +08:00
)
2023-02-23 19:18:05 +08:00
type GenerationHandler interface {
2023-04-04 07:11:18 +02:00
Handle ( context . Context , admissionv1 . AdmissionRequest , [ ] kyvernov1 . PolicyInterface , * engine . PolicyContext )
2023-02-23 19:18:05 +08:00
}
func NewGenerationHandler (
log logr . Logger ,
engine engineapi . Engine ,
client dclient . Interface ,
kyvernoClient versioned . Interface ,
nsLister corev1listers . NamespaceLister ,
2024-06-20 11:44:43 +02:00
urLister kyvernov2listers . UpdateRequestNamespaceLister ,
2023-02-23 19:18:05 +08:00
cpolLister kyvernov1listers . ClusterPolicyLister ,
polLister kyvernov1listers . PolicyLister ,
urGenerator webhookgenerate . Generator ,
eventGen event . Interface ,
metrics metrics . MetricsConfigManager ,
2024-01-04 18:47:58 +08:00
backgroundServiceAccountName string ,
2024-08-20 01:55:32 -07:00
reportsServiceAccountName string ,
2023-02-23 19:18:05 +08:00
) GenerationHandler {
return & generationHandler {
2024-01-04 18:47:58 +08:00
log : log ,
engine : engine ,
client : client ,
kyvernoClient : kyvernoClient ,
nsLister : nsLister ,
urLister : urLister ,
cpolLister : cpolLister ,
polLister : polLister ,
urGenerator : urGenerator ,
eventGen : eventGen ,
metrics : metrics ,
backgroundServiceAccountName : backgroundServiceAccountName ,
2024-08-20 01:55:32 -07:00
reportsServiceAccountName : reportsServiceAccountName ,
2023-02-23 19:18:05 +08:00
}
}
type generationHandler struct {
2024-01-04 18:47:58 +08:00
log logr . Logger
engine engineapi . Engine
client dclient . Interface
kyvernoClient versioned . Interface
nsLister corev1listers . NamespaceLister
2024-06-20 11:44:43 +02:00
urLister kyvernov2listers . UpdateRequestNamespaceLister
2024-01-04 18:47:58 +08:00
cpolLister kyvernov1listers . ClusterPolicyLister
polLister kyvernov1listers . PolicyLister
urGenerator webhookgenerate . Generator
eventGen event . Interface
metrics metrics . MetricsConfigManager
backgroundServiceAccountName string
2024-08-20 01:55:32 -07:00
reportsServiceAccountName string
2023-02-23 19:18:05 +08:00
}
2023-03-11 01:17:10 +08:00
func ( h * generationHandler ) Handle (
2023-02-22 18:49:09 +08:00
ctx context . Context ,
2023-04-04 07:11:18 +02:00
request admissionv1 . AdmissionRequest ,
2023-02-22 18:49:09 +08:00
policies [ ] kyvernov1 . PolicyInterface ,
policyContext * engine . PolicyContext ,
) {
h . log . V ( 6 ) . Info ( "handle admission request for generate" )
if len ( policies ) != 0 {
h . handleTrigger ( ctx , request , policies , policyContext )
}
2024-01-04 18:47:58 +08:00
if h . backgroundServiceAccountName == policyContext . AdmissionInfo ( ) . AdmissionUserInfo . Username {
return
}
2024-08-14 01:14:06 +08:00
h . handleNonTrigger ( ctx , policyContext )
2023-02-22 18:49:09 +08:00
}
2023-03-11 01:17:10 +08:00
func getAppliedRules ( policy kyvernov1 . PolicyInterface , applied [ ] engineapi . RuleResponse ) [ ] kyvernov1 . Rule {
rules := [ ] kyvernov1 . Rule { }
for _ , rule := range policy . GetSpec ( ) . Rules {
if ! rule . HasGenerate ( ) {
continue
}
for _ , applied := range applied {
2023-04-05 12:35:38 +02:00
if applied . Name ( ) == rule . Name && applied . RuleType ( ) == engineapi . Generation {
2023-03-11 01:17:10 +08:00
rules = append ( rules , rule )
}
}
}
return rules
}
2023-02-22 18:49:09 +08:00
func ( h * generationHandler ) handleTrigger (
ctx context . Context ,
2023-04-04 07:11:18 +02:00
request admissionv1 . AdmissionRequest ,
2023-02-22 18:49:09 +08:00
policies [ ] kyvernov1 . PolicyInterface ,
policyContext * engine . PolicyContext ,
) {
2023-03-11 01:17:10 +08:00
h . log . V ( 4 ) . Info ( "handle trigger resource operation for generate" , "policies" , len ( policies ) )
2023-02-22 18:49:09 +08:00
for _ , policy := range policies {
2023-03-11 01:17:10 +08:00
var appliedRules , failedRules [ ] engineapi . RuleResponse
2023-02-22 18:49:09 +08:00
policyContext := policyContext . WithPolicy ( policy )
if request . Kind . Kind != "Namespace" && request . Namespace != "" {
2023-06-13 17:12:13 +08:00
policyContext = policyContext . WithNamespaceLabels ( utils . GetNamespaceSelectorsFromNamespaceLister ( request . Kind . Kind , request . Namespace , h . nsLister , h . log ) )
2023-02-22 18:49:09 +08:00
}
engineResponse := h . engine . ApplyBackgroundChecks ( ctx , policyContext )
for _ , rule := range engineResponse . PolicyResponse . Rules {
2023-04-05 12:35:38 +02:00
if rule . Status ( ) == engineapi . RuleStatusPass {
2023-02-22 18:49:09 +08:00
appliedRules = append ( appliedRules , rule )
2023-04-05 12:35:38 +02:00
} else if rule . Status ( ) == engineapi . RuleStatusFail {
2023-03-11 01:17:10 +08:00
failedRules = append ( failedRules , rule )
2023-02-22 18:49:09 +08:00
}
}
2023-03-11 01:17:10 +08:00
h . applyGeneration ( ctx , request , policy , appliedRules , policyContext )
h . syncTriggerAction ( ctx , request , policy , failedRules , policyContext )
2023-02-22 18:49:09 +08:00
}
}
func ( h * generationHandler ) handleNonTrigger (
ctx context . Context ,
policyContext * engine . PolicyContext ,
) {
resource := policyContext . OldResource ( )
labels := resource . GetLabels ( )
2023-06-07 21:50:47 +08:00
if _ , ok := labels [ common . GenerateTypeCloneSourceLabel ] ; ok || labels [ common . GeneratePolicyLabel ] != "" {
2023-02-22 18:49:09 +08:00
h . log . V ( 4 ) . Info ( "handle non-trigger resource operation for generate" )
2024-08-14 01:14:06 +08:00
if err := h . processRequest ( ctx , policyContext ) ; err != nil {
2023-02-22 18:49:09 +08:00
h . log . Error ( err , "failed to create the UR on non-trigger admission request" )
}
}
}
2023-03-11 01:17:10 +08:00
func ( h * generationHandler ) applyGeneration (
ctx context . Context ,
2023-04-04 07:11:18 +02:00
request admissionv1 . AdmissionRequest ,
2023-03-11 01:17:10 +08:00
policy kyvernov1 . PolicyInterface ,
appliedRules [ ] engineapi . RuleResponse ,
policyContext * engine . PolicyContext ,
) {
if len ( appliedRules ) == 0 {
return
}
pKey := common . PolicyKey ( policy . GetNamespace ( ) , policy . GetName ( ) )
trigger := policyContext . NewResource ( )
triggerSpec := kyvernov1 . ResourceSpec {
APIVersion : trigger . GetAPIVersion ( ) ,
Kind : trigger . GetKind ( ) ,
Namespace : trigger . GetNamespace ( ) ,
Name : trigger . GetName ( ) ,
2023-11-06 16:07:13 +05:30
UID : trigger . GetUID ( ) ,
2023-03-11 01:17:10 +08:00
}
rules := getAppliedRules ( policy , appliedRules )
2024-08-14 01:14:06 +08:00
h . log . V ( 4 ) . Info ( "creating the UR to generate downstream on trigger's operation" , "operation" , request . Operation , "policy" , pKey )
urSpec := buildURSpecNew ( kyvernov2 . Generate , pKey , rules , triggerSpec , false )
urSpec . Context = buildURContext ( request , policyContext )
if err := h . urGenerator . Apply ( ctx , urSpec ) ; err != nil {
h . log . Error ( err , "failed to create the UR to create downstream on trigger's operation" , "operation" , request . Operation , "policy" , pKey )
e := event . NewFailedEvent ( err , pKey , "" , event . GeneratePolicyController ,
kyvernov1 . ResourceSpec { Kind : policy . GetKind ( ) , Namespace : policy . GetNamespace ( ) , Name : policy . GetName ( ) } )
h . eventGen . Add ( e )
2023-03-11 01:17:10 +08:00
}
}
// handleFailedRules sync changes of the trigger to the downstream
2023-06-15 16:32:19 +01:00
// it can be 1. trigger deletion; 2. trigger no longer matches, when a rule fails or is skipped
2023-03-11 01:17:10 +08:00
func ( h * generationHandler ) syncTriggerAction (
ctx context . Context ,
2023-04-04 07:11:18 +02:00
request admissionv1 . AdmissionRequest ,
2023-03-11 01:17:10 +08:00
policy kyvernov1 . PolicyInterface ,
failedRules [ ] engineapi . RuleResponse ,
policyContext * engine . PolicyContext ,
) {
if len ( failedRules ) == 0 {
return
}
pKey := common . PolicyKey ( policy . GetNamespace ( ) , policy . GetName ( ) )
trigger := policyContext . OldResource ( )
2024-08-14 01:14:06 +08:00
triggerSpec := kyvernov1 . ResourceSpec {
2023-03-11 01:17:10 +08:00
APIVersion : trigger . GetAPIVersion ( ) ,
Kind : trigger . GetKind ( ) ,
Namespace : trigger . GetNamespace ( ) ,
Name : trigger . GetName ( ) ,
2023-11-06 16:07:13 +05:30
UID : trigger . GetUID ( ) ,
2023-03-11 01:17:10 +08:00
}
rules := getAppliedRules ( policy , failedRules )
2024-08-14 01:14:06 +08:00
urSpec := kyvernov2 . UpdateRequestSpec {
Type : kyvernov2 . Generate ,
Policy : pKey ,
RuleContext : make ( [ ] kyvernov2 . RuleContext , 0 ) ,
Context : buildURContext ( request , policyContext ) ,
}
2023-03-11 01:17:10 +08:00
for _ , rule := range rules {
// fire generation on trigger deletion
2023-12-15 16:42:10 +02:00
if ( request . Operation == admissionv1 . Delete ) && webhookutils . MatchDeleteOperation ( rule ) {
2024-08-29 19:59:22 +08:00
h . log . V ( 4 ) . Info ( "creating the UR to generate downstream on trigger's deletion" , "operation" , request . Operation , "rule" , rule . Name , "trigger" , triggerSpec . String ( ) )
2024-08-14 01:14:06 +08:00
ruleCtx := buildRuleContext ( rule , triggerSpec , false )
urSpec . RuleContext = append ( urSpec . RuleContext , ruleCtx )
2023-03-11 01:17:10 +08:00
continue
}
// delete downstream on trigger deletion
if rule . Generation . Synchronize {
2024-08-29 19:59:22 +08:00
h . log . V ( 4 ) . Info ( "creating the UR to delete downstream on trigger's event" , "operation" , request . Operation , "rule" , rule . Name , "trigger" , triggerSpec . String ( ) )
2024-08-14 01:14:06 +08:00
ruleCtx := buildRuleContext ( rule , triggerSpec , true )
urSpec . RuleContext = append ( urSpec . RuleContext , ruleCtx )
2023-03-11 01:17:10 +08:00
}
}
2024-08-14 01:14:06 +08:00
if err := h . urGenerator . Apply ( ctx , urSpec ) ; err != nil {
h . log . Error ( err , "failed to create the UR on trigger's event" , "operation" , request . Operation , "policy" , pKey )
e := event . NewFailedEvent ( err , pKey , "" , event . GeneratePolicyController ,
kyvernov1 . ResourceSpec { Kind : policy . GetKind ( ) , Namespace : policy . GetNamespace ( ) , Name : policy . GetName ( ) } )
h . eventGen . Add ( e )
}
2023-03-11 01:17:10 +08:00
}
2023-06-07 21:50:47 +08:00
// processRequest determine if it needs to re-apply the generate rule to the source or the target changes
2024-08-14 01:14:06 +08:00
func ( h * generationHandler ) processRequest ( ctx context . Context , policyContext * engine . PolicyContext ) ( err error ) {
2023-02-22 18:49:09 +08:00
var policy kyvernov1 . PolicyInterface
2023-06-07 21:50:47 +08:00
var labelsList [ ] map [ string ] string
var deleteDownstream bool
2023-02-22 18:49:09 +08:00
new := policyContext . NewResource ( )
old := policyContext . OldResource ( )
2023-06-07 21:50:47 +08:00
labels := old . GetLabels ( )
2023-07-06 10:00:36 +02:00
managedBy := labels [ kyverno . LabelAppManagedBy ] == kyverno . ValueKyvernoApp
2023-02-22 18:49:09 +08:00
2023-06-07 21:50:47 +08:00
// clone source changes
if ! managedBy {
if new . Object == nil {
// clone source deletion
2023-03-01 11:48:18 +08:00
deleteDownstream = true
}
2023-11-06 16:07:13 +05:30
// fetch targets that have the source name label
2023-06-07 21:50:47 +08:00
targetSelector := map [ string ] string {
common . GenerateSourceGroupLabel : old . GroupVersionKind ( ) . Group ,
common . GenerateSourceVersionLabel : old . GroupVersionKind ( ) . Version ,
common . GenerateSourceKindLabel : old . GetKind ( ) ,
common . GenerateSourceNSLabel : old . GetNamespace ( ) ,
common . GenerateSourceNameLabel : old . GetName ( ) ,
}
2023-11-06 16:07:13 +05:30
targets , err := common . FindDownstream ( h . client , old . GetAPIVersion ( ) , old . GetKind ( ) , targetSelector )
if err != nil {
return fmt . Errorf ( "failed to list targets resources: %v" , err )
}
for i := range targets . Items {
l := targets . Items [ i ] . GetLabels ( )
labelsList = append ( labelsList , l )
}
// fetch targets that have the source UID label
targetSelector = map [ string ] string {
common . GenerateSourceGroupLabel : old . GroupVersionKind ( ) . Group ,
common . GenerateSourceVersionLabel : old . GroupVersionKind ( ) . Version ,
common . GenerateSourceKindLabel : old . GetKind ( ) ,
common . GenerateSourceNSLabel : old . GetNamespace ( ) ,
common . GenerateSourceUIDLabel : string ( old . GetUID ( ) ) ,
}
targets , err = common . FindDownstream ( h . client , old . GetAPIVersion ( ) , old . GetKind ( ) , targetSelector )
2023-06-07 21:50:47 +08:00
if err != nil {
return fmt . Errorf ( "failed to list targets resources: %v" , err )
}
2023-02-22 18:49:09 +08:00
2023-06-07 21:50:47 +08:00
for i := range targets . Items {
l := targets . Items [ i ] . GetLabels ( )
labelsList = append ( labelsList , l )
}
2023-02-22 18:49:09 +08:00
} else {
2023-06-07 21:50:47 +08:00
labelsList = append ( labelsList , labels )
2023-02-22 18:49:09 +08:00
}
2023-06-07 21:50:47 +08:00
for _ , labels := range labelsList {
pName := labels [ common . GeneratePolicyLabel ]
pNamespace := labels [ common . GeneratePolicyNamespaceLabel ]
pRuleName := labels [ common . GenerateRuleLabel ]
2023-02-22 18:49:09 +08:00
2023-06-07 21:50:47 +08:00
if pNamespace != "" {
policy , err = h . polLister . Policies ( pNamespace ) . Get ( pName )
} else {
policy , err = h . cpolLister . Get ( pName )
}
if err != nil {
return err
}
pKey := common . PolicyKey ( pNamespace , pName )
2024-08-14 01:14:06 +08:00
urSpec := kyvernov2 . UpdateRequestSpec {
Type : kyvernov2 . Generate ,
Policy : pKey ,
RuleContext : make ( [ ] kyvernov2 . RuleContext , 0 ) ,
}
2023-06-07 21:50:47 +08:00
for _ , rule := range policy . GetSpec ( ) . Rules {
if rule . Name == pRuleName && rule . Generation . Synchronize {
2023-06-13 17:12:13 +08:00
gvk , subresource := policyContext . ResourceKind ( )
if err := engineutils . MatchesResourceDescription (
old ,
rule ,
policyContext . AdmissionInfo ( ) ,
policyContext . NamespaceLabels ( ) ,
policy . GetNamespace ( ) ,
gvk ,
subresource ,
policyContext . Operation ( ) ,
) ; err == nil {
h . log . V ( 4 ) . Info ( "skip creating UR as the admission resource is both the source and the trigger" )
continue
}
2024-08-14 01:14:06 +08:00
ruleCtx := buildRuleContext ( rule , generateutils . TriggerFromLabels ( labels ) , deleteDownstream )
urSpec . RuleContext = append ( urSpec . RuleContext , ruleCtx )
2023-02-22 18:49:09 +08:00
}
}
2024-08-14 01:14:06 +08:00
if err := h . urGenerator . Apply ( ctx , urSpec ) ; err != nil {
e := event . NewBackgroundFailedEvent ( err , policy , "" , event . GeneratePolicyController ,
kyvernov1 . ResourceSpec { Kind : new . GetKind ( ) , Namespace : new . GetNamespace ( ) , Name : new . GetName ( ) } )
h . eventGen . Add ( e ... )
return err
}
2023-02-22 18:49:09 +08:00
}
return nil
}