1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-07 00:17:13 +00:00
kyverno/pkg/webhooks/resource/handlers.go
Jim Bugwadia f06399200c
remove wildcard permissions (#10785)
* remove wildcard permissions

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* update codegen

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* codegen

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix tests

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix background controller perms

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* remove secrets perm

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* update tests

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* update tests

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix reports-controller role

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* add wildcard check and limit generate policy checks based on `synchronize`

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* update manifest

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix permissions

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix wildcard check

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* update default QPS and burst for better performance and to prevent test failure

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix test permissions

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix test permissions

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix test permissions

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix test permissions

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix test permissions

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix test permissions

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix test permissions

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix perms

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix perms

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix test permissions

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix test permissions

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix merge issues

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix merge issues

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

---------

Signed-off-by: Jim Bugwadia <jim@nirmata.com>
Co-authored-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
2024-08-20 11:55:32 +03:00

358 lines
14 KiB
Go

package resource
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/alitto/pond"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/breaker"
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
kyvernov1informers "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1"
kyvernov1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
kyvernov2listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v2"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/engine/policycontext"
"github.com/kyverno/kyverno/pkg/event"
"github.com/kyverno/kyverno/pkg/metrics"
"github.com/kyverno/kyverno/pkg/policycache"
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
engineutils "github.com/kyverno/kyverno/pkg/utils/engine"
jsonutils "github.com/kyverno/kyverno/pkg/utils/json"
"github.com/kyverno/kyverno/pkg/webhooks"
"github.com/kyverno/kyverno/pkg/webhooks/handlers"
"github.com/kyverno/kyverno/pkg/webhooks/resource/imageverification"
"github.com/kyverno/kyverno/pkg/webhooks/resource/mutation"
"github.com/kyverno/kyverno/pkg/webhooks/resource/validation"
webhookgenerate "github.com/kyverno/kyverno/pkg/webhooks/updaterequest"
webhookutils "github.com/kyverno/kyverno/pkg/webhooks/utils"
"k8s.io/apimachinery/pkg/runtime/schema"
corev1listers "k8s.io/client-go/listers/core/v1"
)
type resourceHandlers struct {
// clients
client dclient.Interface
kyvernoClient versioned.Interface
engine engineapi.Engine
// config
configuration config.Configuration
metricsConfig metrics.MetricsConfigManager
// cache
pCache policycache.Cache
// listers
nsLister corev1listers.NamespaceLister
urLister kyvernov2listers.UpdateRequestNamespaceLister
cpolLister kyvernov1listers.ClusterPolicyLister
polLister kyvernov1listers.PolicyLister
urGenerator webhookgenerate.Generator
eventGen event.Interface
pcBuilder webhookutils.PolicyContextBuilder
admissionReports bool
backgroundServiceAccountName string
reportsServiceAccountName string
auditPool *pond.WorkerPool
reportsBreaker breaker.Breaker
}
func NewHandlers(
engine engineapi.Engine,
client dclient.Interface,
kyvernoClient versioned.Interface,
configuration config.Configuration,
metricsConfig metrics.MetricsConfigManager,
pCache policycache.Cache,
nsLister corev1listers.NamespaceLister,
urLister kyvernov2listers.UpdateRequestNamespaceLister,
cpolInformer kyvernov1informers.ClusterPolicyInformer,
polInformer kyvernov1informers.PolicyInformer,
urGenerator webhookgenerate.Generator,
eventGen event.Interface,
admissionReports bool,
backgroundServiceAccountName string,
reportsServiceAccountName string,
jp jmespath.Interface,
maxAuditWorkers int,
maxAuditCapacity int,
reportsBreaker breaker.Breaker,
) webhooks.ResourceHandlers {
return &resourceHandlers{
engine: engine,
client: client,
kyvernoClient: kyvernoClient,
configuration: configuration,
metricsConfig: metricsConfig,
pCache: pCache,
nsLister: nsLister,
urLister: urLister,
cpolLister: cpolInformer.Lister(),
polLister: polInformer.Lister(),
urGenerator: urGenerator,
eventGen: eventGen,
pcBuilder: webhookutils.NewPolicyContextBuilder(configuration, jp),
admissionReports: admissionReports,
backgroundServiceAccountName: backgroundServiceAccountName,
reportsServiceAccountName: reportsServiceAccountName,
auditPool: pond.New(maxAuditWorkers, maxAuditCapacity, pond.Strategy(pond.Lazy())),
reportsBreaker: reportsBreaker,
}
}
func (h *resourceHandlers) Validate(ctx context.Context, logger logr.Logger, request handlers.AdmissionRequest, failurePolicy string, startTime time.Time) handlers.AdmissionResponse {
kind := request.Kind.Kind
logger = logger.WithValues("kind", kind).WithValues("URLParams", request.URLParams)
logger.V(4).Info("received an admission request in validating webhook")
policies, mutatePolicies, generatePolicies, _, err := h.retrieveAndCategorizePolicies(ctx, logger, request, failurePolicy, false)
if err != nil {
return errorResponse(logger, request.UID, err, "failed to fetch policy with key")
}
if len(policies) == 0 && len(mutatePolicies) == 0 && len(generatePolicies) == 0 {
logger.V(4).Info("no policies matched admission request")
}
logger.V(4).Info("processing policies for validate admission request", "validate", len(policies), "mutate", len(mutatePolicies), "generate", len(generatePolicies))
vh := validation.NewValidationHandler(
logger,
h.kyvernoClient,
h.engine,
h.pCache,
h.pcBuilder,
h.eventGen,
h.admissionReports,
h.metricsConfig,
h.configuration,
h.nsLister,
h.reportsBreaker,
)
var wg sync.WaitGroup
var ok bool
var msg string
var warnings []string
var enforceResponses []engineapi.EngineResponse
wg.Add(1)
go func() {
defer wg.Done()
ok, msg, warnings, enforceResponses = vh.HandleValidationEnforce(ctx, request, policies, startTime)
}()
if !admissionutils.IsDryRun(request.AdmissionRequest) {
h.handleBackgroundApplies(ctx, logger, request, generatePolicies, mutatePolicies, startTime, nil)
}
wg.Wait()
if !ok {
logger.Info("admission request denied")
events := webhookutils.GenerateEvents(enforceResponses, true)
h.eventGen.Add(events...)
return admissionutils.Response(request.UID, errors.New(msg), warnings...)
}
go h.auditPool.Submit(func() {
auditResponses := vh.HandleValidationAudit(ctx, request)
var events []event.Info
switch {
case len(auditResponses) == 0:
events = webhookutils.GenerateEvents(enforceResponses, false)
case len(enforceResponses) == 0:
events = webhookutils.GenerateEvents(auditResponses, false)
default:
responses := mergeEngineResponses(auditResponses, enforceResponses)
events = webhookutils.GenerateEvents(responses, false)
}
h.eventGen.Add(events...)
})
return admissionutils.ResponseSuccess(request.UID, warnings...)
}
func (h *resourceHandlers) Mutate(ctx context.Context, logger logr.Logger, request handlers.AdmissionRequest, failurePolicy string, startTime time.Time) handlers.AdmissionResponse {
kind := request.Kind.Kind
logger = logger.WithValues("kind", kind).WithValues("URLParams", request.URLParams)
logger.V(4).Info("received an admission request in mutating webhook")
_, mutatePolicies, _, verifyImagesPolicies, err := h.retrieveAndCategorizePolicies(ctx, logger, request, failurePolicy, true)
if err != nil {
return errorResponse(logger, request.UID, err, "failed to fetch policy with key")
}
if len(mutatePolicies) == 0 && len(verifyImagesPolicies) == 0 {
logger.V(4).Info("no policies matched mutate admission request")
return admissionutils.ResponseSuccess(request.UID)
}
logger.V(4).Info("processing policies for mutate admission request", "mutatePolicies", len(mutatePolicies), "verifyImagesPolicies", len(verifyImagesPolicies))
policyContext, err := h.pcBuilder.Build(request.AdmissionRequest, request.Roles, request.ClusterRoles, request.GroupVersionKind)
if err != nil {
logger.Error(err, "failed to build policy context")
return admissionutils.Response(request.UID, err)
}
mh := mutation.NewMutationHandler(logger, h.engine, h.eventGen, h.nsLister, h.metricsConfig)
patches, warnings, err := mh.HandleMutation(ctx, request.AdmissionRequest, mutatePolicies, policyContext, startTime)
if err != nil {
logger.Error(err, "mutation failed")
return admissionutils.Response(request.UID, err)
}
if len(verifyImagesPolicies) != 0 {
newRequest := patchRequest(patches, request.AdmissionRequest, logger)
// rebuild context to process images updated via mutate policies
policyContext, err = h.pcBuilder.Build(newRequest, request.Roles, request.ClusterRoles, request.GroupVersionKind)
if err != nil {
logger.Error(err, "failed to build policy context")
return admissionutils.Response(request.UID, err)
}
ivh := imageverification.NewImageVerificationHandler(
logger,
h.kyvernoClient,
h.engine,
h.eventGen,
h.admissionReports,
h.configuration,
h.nsLister,
h.reportsBreaker,
)
imagePatches, imageVerifyWarnings, err := ivh.Handle(ctx, newRequest, verifyImagesPolicies, policyContext)
if err != nil {
logger.Error(err, "image verification failed")
return admissionutils.Response(request.UID, err)
}
patches = jsonutils.JoinPatches(patches, imagePatches)
warnings = append(warnings, imageVerifyWarnings...)
}
return admissionutils.MutationResponse(request.UID, patches, warnings...)
}
func (h *resourceHandlers) retrieveAndCategorizePolicies(
ctx context.Context, logger logr.Logger, request handlers.AdmissionRequest, failurePolicy string, mutation bool) (
[]kyvernov1.PolicyInterface, []kyvernov1.PolicyInterface, []kyvernov1.PolicyInterface, []kyvernov1.PolicyInterface, error,
) {
var policies, mutatePolicies, generatePolicies, imageVerifyValidatePolicies []kyvernov1.PolicyInterface
if request.URLParams == "" {
gvr := schema.GroupVersionResource(request.Resource)
policies = filterPolicies(ctx, failurePolicy, h.pCache.GetPolicies(policycache.ValidateEnforce, gvr, request.SubResource, request.Namespace)...)
mutatePolicies = filterPolicies(ctx, failurePolicy, h.pCache.GetPolicies(policycache.Mutate, gvr, request.SubResource, request.Namespace)...)
generatePolicies = filterPolicies(ctx, failurePolicy, h.pCache.GetPolicies(policycache.Generate, gvr, request.SubResource, request.Namespace)...)
if mutation {
imageVerifyValidatePolicies = filterPolicies(ctx, failurePolicy, h.pCache.GetPolicies(policycache.VerifyImagesMutate, gvr, request.SubResource, request.Namespace)...)
} else {
imageVerifyValidatePolicies = filterPolicies(ctx, failurePolicy, h.pCache.GetPolicies(policycache.VerifyImagesValidate, gvr, request.SubResource, request.Namespace)...)
policies = append(policies, imageVerifyValidatePolicies...)
}
} else {
meta := strings.Split(request.URLParams, "/")
polName := meta[1]
polNamespace := ""
if len(meta) >= 3 {
polNamespace = meta[1]
polName = meta[2]
}
var policy kyvernov1.PolicyInterface
var err error
if polNamespace == "" {
policy, err = h.cpolLister.Get(polName)
} else {
policy, err = h.polLister.Policies(polNamespace).Get(polName)
}
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("key %s/%s: %v", polNamespace, polName, err)
}
filteredPolicies := filterPolicies(ctx, failurePolicy, policy)
if len(filteredPolicies) == 0 {
logger.V(4).Info("no policy found with key", "namespace", polNamespace, "name", polName)
return nil, nil, nil, nil, nil
}
policy = filteredPolicies[0]
spec := policy.GetSpec()
if spec.HasValidate() {
policies = append(policies, policy)
}
if spec.HasGenerate() {
generatePolicies = append(generatePolicies, policy)
}
if spec.HasMutate() {
mutatePolicies = append(mutatePolicies, policy)
}
if spec.HasVerifyImages() {
policies = append(policies, policy)
}
}
return policies, mutatePolicies, generatePolicies, imageVerifyValidatePolicies, nil
}
func (h *resourceHandlers) buildPolicyContextFromAdmissionRequest(logger logr.Logger, request handlers.AdmissionRequest) (*policycontext.PolicyContext, error) {
policyContext, err := h.pcBuilder.Build(request.AdmissionRequest, request.Roles, request.ClusterRoles, request.GroupVersionKind)
if err != nil {
return nil, err
}
namespaceLabels := make(map[string]string)
if request.Kind.Kind != "Namespace" && request.Namespace != "" {
namespaceLabels = engineutils.GetNamespaceSelectorsFromNamespaceLister(request.Kind.Kind, request.Namespace, h.nsLister, logger)
}
policyContext = policyContext.WithNamespaceLabels(namespaceLabels)
return policyContext, nil
}
func filterPolicies(ctx context.Context, failurePolicy string, policies ...kyvernov1.PolicyInterface) []kyvernov1.PolicyInterface {
var results []kyvernov1.PolicyInterface
for _, policy := range policies {
if failurePolicy == "fail" {
if policy.GetSpec().GetFailurePolicy(ctx) == kyvernov1.Fail {
results = append(results, policy)
}
} else if failurePolicy == "ignore" {
if policy.GetSpec().GetFailurePolicy(ctx) == kyvernov1.Ignore {
results = append(results, policy)
}
} else {
results = append(results, policy)
}
}
return results
}
func mergeEngineResponses(auditResponses, enforceResponses []engineapi.EngineResponse) []engineapi.EngineResponse {
responseMap := make(map[string]engineapi.EngineResponse)
var responses []engineapi.EngineResponse
for _, enforceResponse := range enforceResponses {
responseMap[enforceResponse.Policy().GetName()] = enforceResponse
}
for _, auditResponse := range auditResponses {
policyName := auditResponse.Policy().GetName()
if enforceResponse, exists := responseMap[policyName]; exists {
response := auditResponse
for _, ruleResponse := range enforceResponse.PolicyResponse.Rules {
response.PolicyResponse.Add(ruleResponse.Stats(), ruleResponse)
}
responses = append(responses, response)
delete(responseMap, policyName)
} else {
responses = append(responses, auditResponse)
}
}
if len(responseMap) != 0 {
for _, enforceResponse := range responseMap {
responses = append(responses, enforceResponse)
}
}
return responses
}