1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00

fix: display a message when the controller has no permissions for VAPs (#8776) (#8814)

* fix: display a message when the controller has no permissions for VAPs



* fix: add a warning when a Kyverno policy is created



---------

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
Co-authored-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
This commit is contained in:
gcp-cherry-pick-bot[bot] 2023-11-01 14:40:20 +00:00 committed by GitHub
parent ef90f0b07a
commit 3de7c54a86
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 109 additions and 24 deletions

View file

@ -12,6 +12,7 @@ import (
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/cmd/internal"
"github.com/kyverno/kyverno/pkg/auth/checker"
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions"
"github.com/kyverno/kyverno/pkg/clients/dclient"
@ -181,6 +182,7 @@ func createrLeaderControllers(
leaderControllers = append(leaderControllers, internal.NewController(exceptionWebhookControllerName, exceptionWebhookController, 1))
if generateVAPs {
checker := checker.NewSelfChecker(kubeClient.AuthorizationV1().SelfSubjectAccessReviews())
vapController := vapcontroller.NewController(
kubeClient,
kyvernoClient,
@ -190,6 +192,7 @@ func createrLeaderControllers(
kubeInformer.Admissionregistration().V1alpha1().ValidatingAdmissionPolicies(),
kubeInformer.Admissionregistration().V1alpha1().ValidatingAdmissionPolicyBindings(),
eventGenerator,
checker,
)
leaderControllers = append(leaderControllers, internal.NewController(vapcontroller.ControllerName, vapController, vapcontroller.Workers))
}
@ -200,17 +203,16 @@ func main() {
var (
// TODO: this has been added to backward support command line arguments
// will be removed in future and the configuration will be set only via configmaps
serverIP string
webhookTimeout int
maxQueuedEvents int
omitEvents string
autoUpdateWebhooks bool
webhookRegistrationTimeout time.Duration
admissionReports bool
dumpPayload bool
servicePort int
backgroundServiceAccountName string
generateValidatingAdmissionPolicy bool
serverIP string
webhookTimeout int
maxQueuedEvents int
omitEvents string
autoUpdateWebhooks bool
webhookRegistrationTimeout time.Duration
admissionReports bool
dumpPayload bool
servicePort int
backgroundServiceAccountName string
)
flagset := flag.NewFlagSet("kyverno", flag.ExitOnError)
flagset.BoolVar(&dumpPayload, "dumpPayload", false, "Set this flag to activate/deactivate debug mode.")
@ -222,8 +224,8 @@ func main() {
flagset.DurationVar(&webhookRegistrationTimeout, "webhookRegistrationTimeout", 120*time.Second, "Timeout for webhook registration, e.g., 30s, 1m, 5m.")
flagset.Func(toggle.ProtectManagedResourcesFlagName, toggle.ProtectManagedResourcesDescription, toggle.ProtectManagedResources.Parse)
flagset.Func(toggle.ForceFailurePolicyIgnoreFlagName, toggle.ForceFailurePolicyIgnoreDescription, toggle.ForceFailurePolicyIgnore.Parse)
flagset.Func(toggle.GenerateValidatingAdmissionPolicyFlagName, toggle.GenerateValidatingAdmissionPolicyDescription, toggle.GenerateValidatingAdmissionPolicy.Parse)
flagset.BoolVar(&admissionReports, "admissionReports", true, "Enable or disable admission reports.")
flagset.BoolVar(&generateValidatingAdmissionPolicy, "generateValidatingAdmissionPolicy", false, "Set this flag 'true' to generate validating admission policies.")
flagset.IntVar(&servicePort, "servicePort", 443, "Port used by the Kyverno Service resource and for webhook configurations.")
flagset.StringVar(&backgroundServiceAccountName, "backgroundServiceAccountName", "", "Background service account name.")
flagset.StringVar(&caSecretName, "caSecretName", "", "Name of the secret containing CA.")
@ -261,6 +263,7 @@ func main() {
os.Exit(1)
}
// check if validating admission policies are registered in the API server
generateValidatingAdmissionPolicy := toggle.FromContext(context.TODO()).GenerateValidatingAdmissionPolicy()
if generateValidatingAdmissionPolicy {
groupVersion := schema.GroupVersion{Group: "admissionregistration.k8s.io", Version: "v1alpha1"}
if _, err := setup.KyvernoDynamicClient.GetKubeClient().Discovery().ServerResourcesForGroupVersion(groupVersion.String()); err != nil {

View file

@ -6,6 +6,7 @@ import (
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/auth/checker"
"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"
@ -16,6 +17,7 @@ import (
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
"github.com/kyverno/kyverno/pkg/utils/validatingadmissionpolicy"
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -48,6 +50,7 @@ type controller struct {
queue workqueue.RateLimitingInterface
eventGen event.Interface
checker checker.AuthChecker
}
func NewController(
@ -59,6 +62,7 @@ func NewController(
vapInformer admissionregistrationv1alpha1informers.ValidatingAdmissionPolicyInformer,
vapbindingInformer admissionregistrationv1alpha1informers.ValidatingAdmissionPolicyBindingInformer,
eventGen event.Interface,
checker checker.AuthChecker,
) controllers.Controller {
queue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), ControllerName)
c := &controller{
@ -70,6 +74,7 @@ func NewController(
vapbindingLister: vapbindingInformer.Lister(),
queue: queue,
eventGen: eventGen,
checker: checker,
}
// Set up an event handler for when Kyverno policies change
@ -307,6 +312,20 @@ func (c *controller) reconcile(ctx context.Context, logger logr.Logger, key, nam
return nil
}
// check if the controller has the required permissions to generate validating admission policies.
if !validatingadmissionpolicy.HasValidatingAdmissionPolicyPermission(c.checker) {
logger.Info("doesn't have required permissions for generating ValidatingAdmissionPolicies")
c.updateClusterPolicyStatus(ctx, *policy, false, "doesn't have required permissions for generating ValidatingAdmissionPolicies")
return nil
}
// check if the controller has the required permissions to generate validating admission policy bindings.
if !validatingadmissionpolicy.HasValidatingAdmissionPolicyBindingPermission(c.checker) {
logger.Info("doesn't have required permissions for generating ValidatingAdmissionPolicyBindings")
c.updateClusterPolicyStatus(ctx, *policy, false, "doesn't have required permissions for generating ValidatingAdmissionPolicyBindings")
return nil
}
if ok, msg := canGenerateVAP(spec); !ok {
c.updateClusterPolicyStatus(ctx, *policy, false, msg)
return nil

View file

@ -10,6 +10,7 @@ type Toggles interface {
ProtectManagedResources() bool
ForceFailurePolicyIgnore() bool
EnableDeferredLoading() bool
GenerateValidatingAdmissionPolicy() bool
}
type defaultToggles struct{}
@ -26,6 +27,10 @@ func (defaultToggles) EnableDeferredLoading() bool {
return EnableDeferredLoading.enabled()
}
func (defaultToggles) GenerateValidatingAdmissionPolicy() bool {
return GenerateValidatingAdmissionPolicy.enabled()
}
type contextKey struct{}
func NewContext(ctx context.Context, toggles Toggles) context.Context {

View file

@ -21,12 +21,18 @@ const (
EnableDeferredLoadingDescription = "enable deferred loading of context variables"
enableDeferredLoadingEnvVar = "FLAG_ENABLE_DEFERRED_LOADING"
defaultEnableDeferredLoading = true
// generate validating admission policies
GenerateValidatingAdmissionPolicyFlagName = "generateValidatingAdmissionPolicy"
GenerateValidatingAdmissionPolicyDescription = "Set the flag to 'true', to generate validating admission policies."
generateValidatingAdmissionPolicyEnvVar = "FLAG_GENERATE_VALIDATING_ADMISSION_POLICY"
defaultGenerateValidatingAdmissionPolicy = false
)
var (
ProtectManagedResources = newToggle(defaultProtectManagedResources, protectManagedResourcesEnvVar)
ForceFailurePolicyIgnore = newToggle(defaultForceFailurePolicyIgnore, forceFailurePolicyIgnoreEnvVar)
EnableDeferredLoading = newToggle(defaultEnableDeferredLoading, enableDeferredLoadingEnvVar)
ProtectManagedResources = newToggle(defaultProtectManagedResources, protectManagedResourcesEnvVar)
ForceFailurePolicyIgnore = newToggle(defaultForceFailurePolicyIgnore, forceFailurePolicyIgnoreEnvVar)
EnableDeferredLoading = newToggle(defaultEnableDeferredLoading, enableDeferredLoadingEnvVar)
GenerateValidatingAdmissionPolicy = newToggle(defaultGenerateValidatingAdmissionPolicy, generateValidatingAdmissionPolicyEnvVar)
)
type ToggleFlag interface {

View file

@ -0,0 +1,30 @@
package validatingadmissionpolicy
import (
"context"
"github.com/kyverno/kyverno/pkg/auth/checker"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func hasPermissions(resource schema.GroupVersionResource, s checker.AuthChecker) bool {
can, err := checker.Check(context.TODO(), s, resource.Group, resource.Version, resource.Resource, "", "", "create", "update", "list", "delete")
if err != nil {
return false
}
return can
}
// HasValidatingAdmissionPolicyPermission check if the admission controller has the required permissions to generate
// Kubernetes ValidatingAdmissionPolicy
func HasValidatingAdmissionPolicyPermission(s checker.AuthChecker) bool {
gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Resource: "validatingadmissionpolicies"}
return hasPermissions(gvr, s)
}
// HasValidatingAdmissionPolicyBindingPermission check if the admission controller has the required permissions to generate
// Kubernetes ValidatingAdmissionPolicyBinding
func HasValidatingAdmissionPolicyBindingPermission(s checker.AuthChecker) bool {
gvr := schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Resource: "validatingadmissionpolicybindings"}
return hasPermissions(gvr, s)
}

View file

@ -6,11 +6,14 @@ import (
"slices"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
authChecker "github.com/kyverno/kyverno/pkg/auth/checker"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/logging"
"github.com/kyverno/kyverno/pkg/policy/generate"
"github.com/kyverno/kyverno/pkg/policy/mutate"
"github.com/kyverno/kyverno/pkg/policy/validate"
"github.com/kyverno/kyverno/pkg/toggle"
"github.com/kyverno/kyverno/pkg/utils/validatingadmissionpolicy"
)
// Validation provides methods to validate a rule
@ -22,9 +25,9 @@ type Validation interface {
// - Mutate
// - Validation
// - Generate
func validateActions(idx int, rule *kyvernov1.Rule, client dclient.Interface, mock bool, username string) error {
func validateActions(idx int, rule *kyvernov1.Rule, client dclient.Interface, mock bool, username string) (string, error) {
if rule == nil {
return nil
return "", nil
}
var checker Validation
@ -32,7 +35,7 @@ func validateActions(idx int, rule *kyvernov1.Rule, client dclient.Interface, mo
if rule.HasMutate() {
checker = mutate.NewMutateFactory(rule.Mutation, client, username)
if path, err := checker.Validate(context.TODO()); err != nil {
return fmt.Errorf("path: spec.rules[%d].mutate.%s.: %v", idx, path, err)
return "", fmt.Errorf("path: spec.rules[%d].mutate.%s.: %v", idx, path, err)
}
}
@ -40,7 +43,21 @@ func validateActions(idx int, rule *kyvernov1.Rule, client dclient.Interface, mo
if rule.HasValidate() {
checker = validate.NewValidateFactory(&rule.Validation)
if path, err := checker.Validate(context.TODO()); err != nil {
return fmt.Errorf("path: spec.rules[%d].validate.%s.: %v", idx, path, err)
return "", fmt.Errorf("path: spec.rules[%d].validate.%s.: %v", idx, path, err)
}
// In case generateValidatingAdmissionPolicy flag is set to true, check the required permissions.
if toggle.FromContext(context.TODO()).GenerateValidatingAdmissionPolicy() {
authCheck := authChecker.NewSelfChecker(client.GetKubeClient().AuthorizationV1().SelfSubjectAccessReviews())
// check if the controller has the required permissions to generate validating admission policies.
if !validatingadmissionpolicy.HasValidatingAdmissionPolicyPermission(authCheck) {
return "doesn't have required permissions for generating ValidatingAdmissionPolicies", nil
}
// check if the controller has the required permissions to generate validating admission policy bindings.
if !validatingadmissionpolicy.HasValidatingAdmissionPolicyBindingPermission(authCheck) {
return "doesn't have required permissions for generating ValidatingAdmissionPolicyBindings", nil
}
}
}
@ -52,19 +69,19 @@ func validateActions(idx int, rule *kyvernov1.Rule, client dclient.Interface, mo
if mock {
checker = generate.NewFakeGenerate(rule.Generation)
if path, err := checker.Validate(context.TODO()); err != nil {
return fmt.Errorf("path: spec.rules[%d].generate.%s.: %v", idx, path, err)
return "", fmt.Errorf("path: spec.rules[%d].generate.%s.: %v", idx, path, err)
}
} else {
checker = generate.NewGenerateFactory(client, rule.Generation, username, logging.GlobalLogger())
if path, err := checker.Validate(context.TODO()); err != nil {
return fmt.Errorf("path: spec.rules[%d].generate.%s.: %v", idx, path, err)
return "", fmt.Errorf("path: spec.rules[%d].generate.%s.: %v", idx, path, err)
}
}
if slices.Contains(rule.MatchResources.Kinds, rule.Generation.Kind) {
return fmt.Errorf("generation kind and match resource kind should not be the same")
return "", fmt.Errorf("generation kind and match resource kind should not be the same")
}
}
return nil
return "", nil
}

View file

@ -299,8 +299,13 @@ func Validate(policy, oldPolicy kyvernov1.PolicyInterface, client dclient.Interf
}
}
if err := validateActions(i, &rules[i], client, mock, username); err != nil {
msg, err := validateActions(i, &rules[i], client, mock, username)
if err != nil {
return warnings, err
} else {
if len(msg) != 0 {
warnings = append(warnings, msg)
}
}
if rule.HasVerifyImages() {