2022-09-09 16:19:38 +02:00
package generation
2022-05-16 16:36:21 +02:00
import (
2022-05-19 18:06:56 +02:00
"context"
2022-05-16 16:36:21 +02:00
"fmt"
"strings"
"time"
"github.com/go-logr/logr"
2022-05-17 13:12:43 +02:00
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
2022-05-16 16:36:21 +02:00
"github.com/kyverno/kyverno/pkg/autogen"
gen "github.com/kyverno/kyverno/pkg/background/generate"
2022-09-09 16:19:38 +02:00
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
kyvernov1beta1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1beta1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
2022-05-16 16:36:21 +02:00
"github.com/kyverno/kyverno/pkg/common"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/response"
enginutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/event"
2022-09-09 16:19:38 +02:00
"github.com/kyverno/kyverno/pkg/metrics"
webhookgenerate "github.com/kyverno/kyverno/pkg/webhooks/updaterequest"
2022-09-09 06:11:16 +02:00
webhookutils "github.com/kyverno/kyverno/pkg/webhooks/utils"
2022-05-16 16:36:21 +02:00
admissionv1 "k8s.io/api/admission/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
2022-09-09 16:19:38 +02:00
corev1listers "k8s.io/client-go/listers/core/v1"
2022-05-16 16:36:21 +02:00
)
2022-09-09 16:19:38 +02:00
type GenerationHandler interface {
// TODO: why do we need to expose that ?
HandleUpdatesForGenerateRules ( * admissionv1 . AdmissionRequest , [ ] kyvernov1 . PolicyInterface )
Handle (
2022-11-30 14:37:53 +01:00
metrics . MetricsConfigManager ,
2022-09-09 16:19:38 +02:00
* admissionv1 . AdmissionRequest ,
[ ] kyvernov1 . PolicyInterface ,
* engine . PolicyContext ,
time . Time ,
)
}
func NewGenerationHandler (
log logr . Logger ,
client dclient . Interface ,
kyvernoClient versioned . Interface ,
nsLister corev1listers . NamespaceLister ,
urLister kyvernov1beta1listers . UpdateRequestNamespaceLister ,
urGenerator webhookgenerate . Generator ,
urUpdater webhookutils . UpdateRequestUpdater ,
eventGen event . Interface ,
) GenerationHandler {
return & generationHandler {
log : log ,
client : client ,
kyvernoClient : kyvernoClient ,
nsLister : nsLister ,
urLister : urLister ,
urGenerator : urGenerator ,
urUpdater : urUpdater ,
eventGen : eventGen ,
}
}
type generationHandler struct {
log logr . Logger
client dclient . Interface
kyvernoClient versioned . Interface
nsLister corev1listers . NamespaceLister
urLister kyvernov1beta1listers . UpdateRequestNamespaceLister
urGenerator webhookgenerate . Generator
urUpdater webhookutils . UpdateRequestUpdater
eventGen event . Interface
}
// Handle handles admission-requests for policies with generate rules
func ( h * generationHandler ) Handle (
2022-11-30 14:37:53 +01:00
metricsConfig metrics . MetricsConfigManager ,
2022-05-16 16:36:21 +02:00
request * admissionv1 . AdmissionRequest ,
2022-05-17 13:12:43 +02:00
policies [ ] kyvernov1 . PolicyInterface ,
2022-05-16 16:36:21 +02:00
policyContext * engine . PolicyContext ,
2022-08-24 13:08:42 +02:00
admissionRequestTimestamp time . Time ,
2022-05-16 16:36:21 +02:00
) {
2022-11-12 01:00:54 +05:30
h . log . V ( 6 ) . Info ( "update request for generate policy" )
2022-05-16 16:36:21 +02:00
var engineResponses [ ] * response . EngineResponse
if ( request . Operation == admissionv1 . Create || request . Operation == admissionv1 . Update ) && len ( policies ) != 0 {
for _ , policy := range policies {
var rules [ ] response . RuleResponse
2022-12-02 09:14:23 +01:00
policyContext := policyContext . WithPolicy ( policy )
2022-05-16 16:36:21 +02:00
if request . Kind . Kind != "Namespace" && request . Namespace != "" {
2022-12-02 09:14:23 +01:00
policyContext = policyContext . WithNamespaceLabels ( common . GetNamespaceSelectorsFromNamespaceLister ( request . Kind . Kind , request . Namespace , h . nsLister , h . log ) )
2022-05-16 16:36:21 +02:00
}
engineResponse := engine . ApplyBackgroundChecks ( policyContext )
for _ , rule := range engineResponse . PolicyResponse . Rules {
if rule . Status != response . RuleStatusPass {
2022-09-09 16:19:38 +02:00
h . deleteGR ( engineResponse )
2022-05-16 16:36:21 +02:00
continue
}
rules = append ( rules , rule )
}
if len ( rules ) > 0 {
engineResponse . PolicyResponse . Rules = rules
// some generate rules do apply to the resource
engineResponses = append ( engineResponses , engineResponse )
}
// registering the kyverno_policy_results_total metric concurrently
2022-11-28 11:30:14 +01:00
go webhookutils . RegisterPolicyResultsMetricGeneration ( context . TODO ( ) , h . log , metricsConfig , string ( request . Operation ) , policy , * engineResponse )
2022-05-16 16:36:21 +02:00
// registering the kyverno_policy_execution_duration_seconds metric concurrently
2022-11-28 11:30:14 +01:00
go webhookutils . RegisterPolicyExecutionDurationMetricGenerate ( context . TODO ( ) , h . log , metricsConfig , string ( request . Operation ) , policy , * engineResponse )
2022-05-16 16:36:21 +02:00
}
2022-12-02 09:14:23 +01:00
if failedResponse := applyUpdateRequest ( request , kyvernov1beta1 . Generate , h . urGenerator , policyContext . AdmissionInfo ( ) , request . Operation , engineResponses ... ) ; failedResponse != nil {
2022-05-16 16:36:21 +02:00
// report failure event
for _ , failedUR := range failedResponse {
err := fmt . Errorf ( "failed to create Update Request: %v" , failedUR . err )
2022-12-02 09:14:23 +01:00
newResource := policyContext . NewResource ( )
e := event . NewBackgroundFailedEvent ( err , failedUR . ur . Policy , "" , event . GeneratePolicyController , & newResource )
2022-05-16 16:36:21 +02:00
h . eventGen . Add ( e ... )
}
}
}
if request . Operation == admissionv1 . Update {
2022-09-09 16:19:38 +02:00
h . HandleUpdatesForGenerateRules ( request , policies )
2022-05-16 16:36:21 +02:00
}
}
2022-09-09 16:19:38 +02:00
// HandleUpdatesForGenerateRules handles admission-requests for update
func ( h * generationHandler ) HandleUpdatesForGenerateRules ( request * admissionv1 . AdmissionRequest , policies [ ] kyvernov1 . PolicyInterface ) {
2022-05-16 16:36:21 +02:00
if request . Operation != admissionv1 . Update {
return
}
resource , err := enginutils . ConvertToUnstructured ( request . OldObject . Raw )
if err != nil {
2022-09-09 16:19:38 +02:00
h . log . Error ( err , "failed to convert object resource to unstructured format" )
2022-05-16 16:36:21 +02:00
}
resLabels := resource . GetLabels ( )
if resLabels [ "generate.kyverno.io/clone-policy-name" ] != "" {
2022-09-09 16:19:38 +02:00
h . handleUpdateGenerateSourceResource ( resLabels )
2022-05-16 16:36:21 +02:00
}
2022-09-19 11:11:12 +02:00
if resLabels [ kyvernov1 . LabelAppManagedBy ] == kyvernov1 . ValueKyvernoApp && resLabels [ "policy.kyverno.io/synchronize" ] == "enable" && request . Operation == admissionv1 . Update {
2022-09-09 16:19:38 +02:00
h . handleUpdateGenerateTargetResource ( request , policies , resLabels )
2022-05-16 16:36:21 +02:00
}
}
2022-05-17 08:19:03 +02:00
// handleUpdateGenerateSourceResource - handles update of clone source for generate policy
2022-09-09 16:19:38 +02:00
func ( h * generationHandler ) handleUpdateGenerateSourceResource ( resLabels map [ string ] string ) {
2022-05-16 16:36:21 +02:00
policyNames := strings . Split ( resLabels [ "generate.kyverno.io/clone-policy-name" ] , "," )
for _ , policyName := range policyNames {
// check if the policy exists
2022-05-19 18:06:56 +02:00
_ , err := h . kyvernoClient . KyvernoV1 ( ) . ClusterPolicies ( ) . Get ( context . TODO ( ) , policyName , metav1 . GetOptions { } )
2022-05-16 16:36:21 +02:00
if err != nil {
if strings . Contains ( err . Error ( ) , "not found" ) {
2022-09-09 16:19:38 +02:00
h . log . V ( 4 ) . Info ( "skipping update of update request as policy is deleted" )
2022-05-16 16:36:21 +02:00
} else {
2022-09-09 16:19:38 +02:00
h . log . Error ( err , "failed to get generate policy" , "Name" , policyName )
2022-05-16 16:36:21 +02:00
}
} else {
selector := labels . SelectorFromSet ( labels . Set ( map [ string ] string {
2022-05-17 13:12:43 +02:00
kyvernov1beta1 . URGeneratePolicyLabel : policyName ,
2022-05-16 16:36:21 +02:00
} ) )
urList , err := h . urLister . List ( selector )
if err != nil {
2022-09-09 16:19:38 +02:00
h . log . Error ( err , "failed to get update request for the resource" , "label" , kyvernov1beta1 . URGeneratePolicyLabel )
2022-05-16 16:36:21 +02:00
return
}
for _ , ur := range urList {
2022-09-09 16:19:38 +02:00
h . urUpdater . UpdateAnnotation ( h . log , ur . GetName ( ) )
2022-05-16 16:36:21 +02:00
}
}
}
}
2022-05-17 08:19:03 +02:00
// handleUpdateGenerateTargetResource - handles update of target resource for generate policy
2022-09-09 16:19:38 +02:00
func ( h * generationHandler ) handleUpdateGenerateTargetResource ( request * admissionv1 . AdmissionRequest , policies [ ] kyvernov1 . PolicyInterface , resLabels map [ string ] string ) {
2022-05-16 16:36:21 +02:00
enqueueBool := false
newRes , err := enginutils . ConvertToUnstructured ( request . Object . Raw )
if err != nil {
2022-09-09 16:19:38 +02:00
h . log . Error ( err , "failed to convert object resource to unstructured format" )
2022-05-16 16:36:21 +02:00
}
policyName := resLabels [ "policy.kyverno.io/policy-name" ]
targetSourceName := newRes . GetName ( )
targetSourceKind := newRes . GetKind ( )
2022-05-19 18:06:56 +02:00
policy , err := h . kyvernoClient . KyvernoV1 ( ) . ClusterPolicies ( ) . Get ( context . TODO ( ) , policyName , metav1 . GetOptions { } )
2022-05-16 16:36:21 +02:00
if err != nil {
2022-09-09 16:19:38 +02:00
h . log . Error ( err , "failed to get policy from kyverno client." , "policy name" , policyName )
2022-05-16 16:36:21 +02:00
return
}
for _ , rule := range autogen . ComputeRules ( policy ) {
if rule . Generation . Kind == targetSourceKind && rule . Generation . Name == targetSourceName {
2022-09-09 16:19:38 +02:00
updatedRule , err := getGeneratedByResource ( newRes , resLabels , h . client , rule , h . log )
2022-05-16 16:36:21 +02:00
if err != nil {
2022-09-09 16:19:38 +02:00
h . log . V ( 4 ) . Info ( "skipping generate policy and resource pattern validaton" , "error" , err )
2022-05-16 16:36:21 +02:00
} else {
data := updatedRule . Generation . DeepCopy ( ) . GetData ( )
if data != nil {
2022-09-09 16:19:38 +02:00
if _ , err := gen . ValidateResourceWithPattern ( h . log , newRes . Object , data ) ; err != nil {
2022-05-16 16:36:21 +02:00
enqueueBool = true
break
}
}
cloneName := updatedRule . Generation . Clone . Name
if cloneName != "" {
2022-11-29 14:59:40 +01:00
obj , err := h . client . GetResource ( context . TODO ( ) , "" , rule . Generation . Kind , rule . Generation . Clone . Namespace , rule . Generation . Clone . Name )
2022-05-16 16:36:21 +02:00
if err != nil {
2022-09-09 16:19:38 +02:00
h . log . Error ( err , fmt . Sprintf ( "source resource %s/%s/%s not found." , rule . Generation . Kind , rule . Generation . Clone . Namespace , rule . Generation . Clone . Name ) )
2022-05-16 16:36:21 +02:00
continue
}
2022-09-09 16:19:38 +02:00
sourceObj , newResObj := stripNonPolicyFields ( obj . Object , newRes . Object , h . log )
2022-05-16 16:36:21 +02:00
2022-09-09 16:19:38 +02:00
if _ , err := gen . ValidateResourceWithPattern ( h . log , newResObj , sourceObj ) ; err != nil {
2022-05-16 16:36:21 +02:00
enqueueBool = true
break
}
}
}
}
}
if enqueueBool {
urName := resLabels [ "policy.kyverno.io/gr-name" ]
ur , err := h . urLister . Get ( urName )
if err != nil {
2022-09-09 16:19:38 +02:00
h . log . Error ( err , "failed to get update request" , "name" , urName )
2022-05-16 16:36:21 +02:00
return
}
2022-09-09 16:19:38 +02:00
h . urUpdater . UpdateAnnotation ( h . log , ur . GetName ( ) )
2022-05-16 16:36:21 +02:00
}
}
2022-09-09 16:19:38 +02:00
func ( h * generationHandler ) deleteGR ( engineResponse * response . EngineResponse ) {
h . log . V ( 4 ) . Info ( "querying all update requests" )
2022-05-16 16:36:21 +02:00
selector := labels . SelectorFromSet ( labels . Set ( map [ string ] string {
2022-05-24 09:41:12 +02:00
kyvernov1beta1 . URGeneratePolicyLabel : engineResponse . PolicyResponse . Policy . Name ,
kyvernov1beta1 . URGenerateResourceNameLabel : engineResponse . PolicyResponse . Resource . Name ,
kyvernov1beta1 . URGenerateResourceKindLabel : engineResponse . PolicyResponse . Resource . Kind ,
kyvernov1beta1 . URGenerateResourceNSLabel : engineResponse . PolicyResponse . Resource . Namespace ,
2022-05-16 16:36:21 +02:00
} ) )
urList , err := h . urLister . List ( selector )
if err != nil {
2022-09-09 16:19:38 +02:00
h . log . Error ( err , "failed to get update request for the resource" , "kind" , engineResponse . PolicyResponse . Resource . Kind , "name" , engineResponse . PolicyResponse . Resource . Name , "namespace" , engineResponse . PolicyResponse . Resource . Namespace )
2022-05-16 16:36:21 +02:00
return
}
for _ , v := range urList {
2022-05-19 18:06:56 +02:00
err := h . kyvernoClient . KyvernoV1beta1 ( ) . UpdateRequests ( config . KyvernoNamespace ( ) ) . Delete ( context . TODO ( ) , v . GetName ( ) , metav1 . DeleteOptions { } )
2022-05-16 16:36:21 +02:00
if err != nil {
2022-09-09 16:19:38 +02:00
h . log . Error ( err , "failed to update ur" )
2022-05-16 16:36:21 +02:00
}
}
}