Files
capsule/cmd/controller/main.go
T
77d1810bb9 feat: add metadata enforcement (#1990)
* fix(controller): decode old object for delete requests

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: modernize golang

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: modernize golang

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: modernize golang

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* fix: preserve ca-bundles injected from external providers

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add metadata enforcement

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add metadata enforcement

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* fix: add resourcepoolclaim validation

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* fix: add resourcepoolclaim validation

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* fix: add resourcepoolclaim validation

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix: add resourcepoolclaim validation

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

---------

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-02 13:42:08 +02:00

850 lines
28 KiB
Go

// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package main
import (
"crypto/tls"
goflag "flag"
"fmt"
"os"
"path/filepath"
goRuntime "runtime"
"time"
flag "github.com/spf13/pflag"
_ "go.uber.org/automaxprocs"
"go.uber.org/zap/zapcore"
admissionv1 "k8s.io/api/admissionregistration/v1"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
utilVersion "k8s.io/apimachinery/pkg/util/version"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
"k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
ctrlwebhook "sigs.k8s.io/controller-runtime/pkg/webhook"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
capsulev1beta1 "github.com/projectcapsule/capsule/api/v1beta1"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
"github.com/projectcapsule/capsule/internal/cache"
"github.com/projectcapsule/capsule/internal/controllers/admission"
cacheinvalidator "github.com/projectcapsule/capsule/internal/controllers/cfg/invalidator"
configcontroller "github.com/projectcapsule/capsule/internal/controllers/cfg/status"
customquotacontroller "github.com/projectcapsule/capsule/internal/controllers/customquotas"
podlabelscontroller "github.com/projectcapsule/capsule/internal/controllers/pod"
"github.com/projectcapsule/capsule/internal/controllers/pv"
rbaccontroller "github.com/projectcapsule/capsule/internal/controllers/rbac"
"github.com/projectcapsule/capsule/internal/controllers/resourcepools"
"github.com/projectcapsule/capsule/internal/controllers/resources"
rulestatuscontroller "github.com/projectcapsule/capsule/internal/controllers/rulestatus"
servicelabelscontroller "github.com/projectcapsule/capsule/internal/controllers/servicelabels"
tenantcontroller "github.com/projectcapsule/capsule/internal/controllers/tenant"
tenantownercontroller "github.com/projectcapsule/capsule/internal/controllers/tenantowner"
tlscontroller "github.com/projectcapsule/capsule/internal/controllers/tls"
utilscontroller "github.com/projectcapsule/capsule/internal/controllers/utils"
"github.com/projectcapsule/capsule/internal/metrics"
capsuleversion "github.com/projectcapsule/capsule/internal/version"
"github.com/projectcapsule/capsule/internal/webhook"
cfgvalidation "github.com/projectcapsule/capsule/internal/webhook/cfg"
customquotavalidation "github.com/projectcapsule/capsule/internal/webhook/customquota"
"github.com/projectcapsule/capsule/internal/webhook/defaults"
"github.com/projectcapsule/capsule/internal/webhook/dra"
"github.com/projectcapsule/capsule/internal/webhook/gateway"
"github.com/projectcapsule/capsule/internal/webhook/generic"
"github.com/projectcapsule/capsule/internal/webhook/ingress"
namespacemutation "github.com/projectcapsule/capsule/internal/webhook/namespace/mutation"
namespacevalidation "github.com/projectcapsule/capsule/internal/webhook/namespace/validation"
"github.com/projectcapsule/capsule/internal/webhook/node"
"github.com/projectcapsule/capsule/internal/webhook/owners"
"github.com/projectcapsule/capsule/internal/webhook/pod"
"github.com/projectcapsule/capsule/internal/webhook/pvc"
"github.com/projectcapsule/capsule/internal/webhook/resourcepool"
"github.com/projectcapsule/capsule/internal/webhook/route"
rulesgenericvalidation "github.com/projectcapsule/capsule/internal/webhook/rules/generic/validation"
podrules "github.com/projectcapsule/capsule/internal/webhook/rules/pods/validation"
servicerules "github.com/projectcapsule/capsule/internal/webhook/rules/services/validation"
"github.com/projectcapsule/capsule/internal/webhook/service"
"github.com/projectcapsule/capsule/internal/webhook/serviceaccounts"
tenantmutation "github.com/projectcapsule/capsule/internal/webhook/tenant/mutation"
tenantvalidation "github.com/projectcapsule/capsule/internal/webhook/tenant/validation"
"github.com/projectcapsule/capsule/pkg/runtime/configuration"
evt "github.com/projectcapsule/capsule/pkg/runtime/events"
"github.com/projectcapsule/capsule/pkg/runtime/handlers"
"github.com/projectcapsule/capsule/pkg/runtime/indexers"
"github.com/projectcapsule/capsule/pkg/utils"
)
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(capsulev1beta1.AddToScheme(scheme))
utilruntime.Must(capsulev1beta2.AddToScheme(scheme))
utilruntime.Must(apiextensionsv1.AddToScheme(scheme))
utilruntime.Must(gatewayv1.Install(scheme))
utilruntime.Must(admissionv1.AddToScheme(scheme))
}
func printVersion() {
setupLog.Info(fmt.Sprintf("Capsule Version %s %s%s", capsuleversion.GitTag, capsuleversion.GitCommit, capsuleversion.GitDirty))
setupLog.Info(fmt.Sprintf("Build from: %s", capsuleversion.GitRepo))
setupLog.Info(fmt.Sprintf("Build date: %s", capsuleversion.BuildTime))
setupLog.Info(fmt.Sprintf("Go Version: %s", goRuntime.Version()))
setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", goRuntime.GOOS, goRuntime.GOARCH))
}
//nolint:maintidx,gocyclo,cyclop,gocognit
func main() {
controllerConfig := utilscontroller.ControllerOptions{}
var (
metricsAddr, metricsCertPath, metricsCertName, metricsCertKey string
webhookCertPath, webhookCertName, webhookCertKey string
enableLeaderElection bool
enablePprof bool
version bool
secureMetrics bool
enableHTTP2 bool
clientConnectionQPS float32
clientConnectionBurst int32
webhookPort int
cacheSyncTimeout time.Duration
)
var goFlagSet goflag.FlagSet
var tlsOpts []func(*tls.Config)
flag.StringVar(
&controllerConfig.ConfigurationName,
"configuration-name",
"default",
"The CapsuleConfiguration resource name to use",
)
flag.BoolVar(
&enableLeaderElection,
"enable-leader-election",
false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.",
)
flag.IntVar(
&controllerConfig.Runtime.MaxConcurrentReconciles,
"workers",
1,
"MaxConcurrentReconciles is the maximum number of concurrent Reconciles which can be run.",
)
flag.DurationVar(
&cacheSyncTimeout,
"cache-sync-timeout",
0,
"The timeout used when waiting for controller cache synchronization. If unset or 0, the controller-runtime default is used.",
)
flag.StringVar(
&metricsAddr,
"metrics-addr",
":8080",
"The address the metric endpoint binds to.",
)
flag.BoolVar(
&secureMetrics,
"metrics-secure",
false,
"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.",
)
flag.StringVar(
&metricsCertPath,
"metrics-cert-path",
"",
"The directory that contains the metrics server certificate.",
)
flag.StringVar(
&metricsCertName,
"metrics-cert-name",
"tls.crt",
"The name of the metrics server certificate file.",
)
flag.StringVar(
&metricsCertKey,
"metrics-cert-key",
"tls.key",
"The name of the metrics server key file.",
)
flag.IntVar(
&webhookPort,
"webhook-port",
9443,
"The port the webhook server binds to.",
)
flag.StringVar(
&webhookCertPath,
"webhook-cert-path",
"/tmp/k8s-webhook-server/serving-certs",
"The directory that contains the webhook certificate.",
)
flag.StringVar(
&webhookCertName,
"webhook-cert-name",
"tls.crt",
"The name of the webhook certificate file.",
)
flag.StringVar(
&webhookCertKey,
"webhook-cert-key",
"tls.key",
"The name of the webhook key file.",
)
flag.BoolVar(
&enableHTTP2,
"enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers",
)
flag.Float32Var(
&clientConnectionQPS,
"client-connection-qps",
20.0,
"QPS to use for interacting with kubernetes apiserver.",
)
flag.Int32Var(
&clientConnectionBurst,
"client-connection-burst",
30,
"Burst to use for interacting with kubernetes apiserver.",
)
flag.BoolVar(
&enablePprof,
"enable-pprof",
false,
"Enables Pprof endpoint for profiling (not recommend in production)",
)
flag.BoolVar(
&version,
"version",
false,
"Print the Capsule version and exit",
)
opts := zap.Options{
EncoderConfigOptions: append([]zap.EncoderConfigOption{}, func(config *zapcore.EncoderConfig) {
config.EncodeTime = zapcore.ISO8601TimeEncoder
}),
}
opts.BindFlags(&goFlagSet)
flag.CommandLine.AddGoFlagSet(&goFlagSet)
flag.Parse()
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
printVersion()
if version {
os.Exit(0)
}
ctx := ctrl.SetupSignalHandler()
setupLog.V(5).Info("Controller", "Options", controllerConfig)
var ns string
if ns = os.Getenv(configuration.EnvironmentControllerNamespace); len(ns) == 0 {
setupLog.Error(fmt.Errorf("unable to determinate the Namespace Capsule is running on. Please export %s", configuration.EnvironmentControllerNamespace), "unable to start manager")
os.Exit(1)
}
if serviceAccountName := os.Getenv(configuration.EnvironmentServiceaccountName); len(serviceAccountName) == 0 {
setupLog.Error(fmt.Errorf("unable to determinate the ServiceAccount Capsule is running with. Please export %s", configuration.EnvironmentServiceaccountName), "unable to start manager")
os.Exit(1)
}
if cacheSyncTimeout > 0 {
controllerConfig.Runtime.CacheSyncTimeout = cacheSyncTimeout
}
if len(controllerConfig.ConfigurationName) == 0 {
setupLog.Error(fmt.Errorf("missing CapsuleConfiguration resource name"), "unable to start manager")
os.Exit(1)
}
restConfig, err := ctrl.GetConfig()
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}
// Suppress Warnings
restConfig.WarningHandler = rest.NoWarnings{}
restConfig.QPS = clientConnectionQPS
restConfig.Burst = int(clientConnectionBurst)
directClient, err := client.New(ctrl.GetConfigOrDie(), client.Options{
Scheme: scheme,
})
if err != nil {
setupLog.Error(err, "unable to create the direct client")
os.Exit(1)
}
directCfg := configuration.NewCapsuleConfiguration(ctx, directClient, directClient, restConfig, controllerConfig.ConfigurationName)
tlsReconciler := &tlscontroller.Reconciler{}
if directCfg.EnableTLSConfiguration() {
tlsReconciler = &tlscontroller.Reconciler{
Client: directClient,
Log: ctrl.Log.WithName("capsule.ctrl").WithName("tls"),
Namespace: ns,
Configuration: directCfg,
}
tlsCert := &corev1.Secret{}
if err = directClient.Get(ctx, types.NamespacedName{
Namespace: ns,
Name: directCfg.TLSSecretName(),
}, tlsCert); err != nil {
if !apierrors.IsNotFound(err) {
setupLog.Error(err, "unable to get Capsule TLS secret")
os.Exit(1)
}
tlsCert = &corev1.Secret{}
tlsCert.Name = directCfg.TLSSecretName()
tlsCert.Namespace = ns
tlsCert.Data = map[string][]byte{}
}
// Reconcile TLS certificates before starting controllers and webhooks
if err = tlsReconciler.ReconcileCertificates(ctx, ctrl.Log.WithName("capsule.setup").WithName("tls"), tlsCert); err != nil {
setupLog.Error(err, "unable to reconcile Capsule TLS secret")
os.Exit(1)
}
}
// if the enable-http2 flag is false (the default), http/2 should be disabled
// due to its vulnerabilities. More specifically, disabling http/2 will
// prevent from being vulnerable to the HTTP/2 Stream Cancellation and
// Rapid Reset CVEs. For more information see:
// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
// - https://github.com/advisories/GHSA-4374-p667-p6c8
disableHTTP2 := func(c *tls.Config) {
setupLog.Info("disabling http/2")
c.NextProtos = []string{"http/1.1"}
}
if !enableHTTP2 {
tlsOpts = append(tlsOpts, disableHTTP2)
}
// Create watchers for metrics and webhooks certificates
var metricsCertWatcher, webhookCertWatcher *certwatcher.CertWatcher
// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.
// More info:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/metrics/server
// - https://book.kubebuilder.io/reference/metrics.html
metricsServerOptions := metricsserver.Options{
BindAddress: metricsAddr,
SecureServing: secureMetrics,
TLSOpts: tlsOpts,
}
if secureMetrics {
// FilterProvider is used to protect the metrics endpoint with authn/authz.
// These configurations ensure that only authorized users and service accounts
// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:
// https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/metrics/filters#WithAuthenticationAndAuthorization
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
}
// If the certificate is not specified, controller-runtime will automatically
// generate self-signed certificates for the metrics server. While convenient for development and testing,
// this setup is not recommended for production.
//
if len(metricsCertPath) > 0 {
setupLog.Info(
"Initializing metrics certificate watcher using provided certificates",
"metrics-cert-path",
metricsCertPath,
"metrics-cert-name",
metricsCertName,
"metrics-cert-key",
metricsCertKey,
)
var err error
metricsCertWatcher, err = certwatcher.New(
filepath.Join(metricsCertPath, metricsCertName),
filepath.Join(metricsCertPath, metricsCertKey),
)
if err != nil {
setupLog.Error(err, "to initialize metrics certificate watcher", "error", err)
os.Exit(1)
}
metricsServerOptions.TLSOpts = append(
metricsServerOptions.TLSOpts,
func(config *tls.Config) {
config.GetCertificate = metricsCertWatcher.GetCertificate
},
)
}
// Initial webhook TLS options
webhookTLSOpts := tlsOpts
if len(webhookCertPath) > 0 {
setupLog.Info(
"Initializing webhook certificate watcher using provided certificates",
"webhook-cert-path",
webhookCertPath,
"webhook-cert-name",
webhookCertName,
"webhook-cert-key",
webhookCertKey,
)
var err error
webhookCertWatcher, err = certwatcher.New(
filepath.Join(webhookCertPath, webhookCertName),
filepath.Join(webhookCertPath, webhookCertKey),
)
if err != nil {
setupLog.Error(err, "Failed to initialize webhook certificate watcher")
os.Exit(1)
}
webhookTLSOpts = append(webhookTLSOpts, func(config *tls.Config) {
config.GetCertificate = webhookCertWatcher.GetCertificate
})
}
ctrlOpts := ctrl.Options{
Scheme: scheme,
Metrics: metricsServerOptions,
WebhookServer: ctrlwebhook.NewServer(ctrlwebhook.Options{
Port: webhookPort,
TLSOpts: webhookTLSOpts,
}),
LeaderElection: enableLeaderElection,
LeaderElectionID: "42c733ea.clastix.capsule.io",
HealthProbeBindAddress: ":10080",
NewClient: func(config *rest.Config, options client.Options) (client.Client, error) {
options.Cache.Unstructured = true
return client.New(config, options)
},
}
if enablePprof {
ctrlOpts.PprofBindAddress = ":8082"
}
setupLog.Info("initializing manager")
// Suppress Warnings
restConfig.WarningHandler = rest.NoWarnings{}
restConfig.QPS = clientConnectionQPS
restConfig.Burst = int(clientConnectionBurst)
manager, err := ctrl.NewManager(restConfig, ctrlOpts)
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}
if metricsCertWatcher != nil {
if err := manager.Add(metricsCertWatcher); err != nil {
setupLog.Error(err, "unable to add metrics certificate watcher")
os.Exit(1)
}
}
if webhookCertWatcher != nil {
if err := manager.Add(webhookCertWatcher); err != nil {
setupLog.Error(err, "unable to add webhook certificate watcher")
os.Exit(1)
}
}
_ = manager.AddReadyzCheck("ping", healthz.Ping)
_ = manager.AddHealthzCheck("ping", healthz.Ping)
dc, err := discovery.NewDiscoveryClientForConfig(manager.GetConfig())
if err != nil {
setupLog.Error(err, "unable to create discovery client")
os.Exit(1)
}
dynamicClient, err := dynamic.NewForConfig(manager.GetConfig())
if err != nil {
setupLog.Error(err, "unable to create dynamic client")
os.Exit(1)
}
setupLog.Info("initializing capsule configuration")
cfg := configuration.NewCapsuleConfiguration(ctx, manager.GetClient(), manager.GetAPIReader(), manager.GetConfig(), controllerConfig.ConfigurationName)
setupLog.Info("initializing caches")
// Initialize Notifiers (Channels)
customQuotaCh := make(chan event.TypedGenericEvent[*capsulev1beta2.CustomQuota], 1024)
globalCustomQuotaCh := make(chan event.TypedGenericEvent[*capsulev1beta2.GlobalCustomQuota], 1024)
// Initialize Caches
impersonationCache := cache.NewImpersonationCache()
regexCache := cache.NewRegexCache()
registryCache := cache.NewRegistryRuleSetCache(regexCache)
customQuotaQuantityCache := cache.NewQuantityCache[string]()
jsonPathCache := cache.NewJSONPathCache()
targetsCache := cache.NewCompiledTargetsCache[string]()
if directCfg.EnableTLSConfiguration() {
if err = tlsReconciler.SetupWithManager(manager, controllerConfig); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Namespace")
os.Exit(1)
}
}
if err = (&tenantcontroller.Manager{
RESTConfig: manager.GetConfig(),
Client: manager.GetClient(),
DynamicClient: dynamicClient,
DiscoveryClient: dc,
Metrics: metrics.MustMakeTenantRecorder(),
Log: ctrl.Log.WithName("capsule.ctrl").WithName("tenant"),
Recorder: manager.GetEventRecorder("tenant-controller"),
Configuration: cfg,
}).SetupWithManager(manager, controllerConfig); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Tenant")
os.Exit(1)
}
if err = (&capsulev1beta1.Tenant{}).SetupWebhookWithManager(manager); err != nil {
setupLog.Error(err, "unable to create conversion webhook", "webhook", "capsulev1beta1.Tenant")
os.Exit(1)
}
setupLog.Info("registering indexers")
if err = indexers.AddToManager(ctx, setupLog, manager); err != nil {
setupLog.Error(err, "unable to setup indexers")
os.Exit(1)
}
var kubeVersion *utilVersion.Version
if kubeVersion, err = utils.GetK8sVersionFromConfig(dc); err != nil {
setupLog.Error(err, "unable to get kubernetes version")
os.Exit(1)
}
setupLog.Info("registering webhooks")
// webhooks: the order matters, don't change it and just append
webhooksList := append(
make([]handlers.Webhook, 0),
rulesgenericvalidation.Register(regexCache),
route.GenericReplicasHandler(),
route.GenericManagedHandler(cfg),
route.Pod(
pod.Handler(
podrules.PodRules(regexCache, registryCache),
pod.ImagePullPolicy(),
pod.ContainerRegistryLegacy(cfg),
pod.PriorityClass(),
pod.RuntimeClass(),
),
),
route.Ingress(ingress.Class(cfg, kubeVersion), ingress.Hostnames(cfg), ingress.Collision(cfg), ingress.Wildcard()),
route.PVCValidating(
pvc.Handler(
pvc.PersistentVolumeValidatingVolume(),
pvc.PersistentVolumeValidatingClass(),
),
),
route.PVCMutating(
pvc.Handler(
pvc.PersistentVolumeMutatingVolume(),
),
),
route.Service(
service.Handler(
servicerules.ServiceRules(regexCache),
service.Validating(),
),
),
route.Node(handlers.InCapsuleGroups(cfg, node.UserMetadataHandler(cfg, kubeVersion))),
route.Cordoning(handlers.InCapsuleGroups(cfg, generic.CordoningHandler(cfg))),
route.ServiceAccounts(
serviceaccounts.Handler(
cfg,
serviceaccounts.Promotion(cfg),
serviceaccounts.OwnerPromotion(cfg),
),
),
route.GenericCustomResources(generic.ResourceCounterHandler(manager.GetClient())),
route.Gateway(gateway.Class(cfg)),
route.DeviceClass(dra.DeviceClass()),
route.Defaults(defaults.Handler(cfg, kubeVersion)),
route.TenantMutation(
tenantmutation.MetaHandler(),
),
route.TenantValidation(
tenantvalidation.Handler(cfg,
tenantvalidation.NameHandler(),
tenantvalidation.NamespaceMetadataHandler(),
tenantvalidation.RoleBindingRegexHandler(),
tenantvalidation.IngressClassRegexHandler(),
tenantvalidation.StorageClassRegexHandler(),
tenantvalidation.ContainerRegistryRegexHandler(),
tenantvalidation.RuleHandler(manager.GetRESTMapper()),
tenantvalidation.HostnameRegexHandler(),
tenantvalidation.FreezedEmitter(),
tenantvalidation.OwnersHandler(),
tenantvalidation.ForbiddenAnnotationsRegexHandler(),
tenantvalidation.ProtectedHandler(),
tenantvalidation.RequiredMetadataHandler(),
// Must run last, because always returns response
tenantvalidation.WarningHandler(cfg),
),
),
route.TenantOwnersValidation(
owners.UserMetadataHandler(),
),
route.NamespaceValidation(
namespacevalidation.NamespaceHandler(
cfg,
namespacevalidation.CordoningHandler(cfg),
namespacevalidation.QuotaHandler(),
namespacevalidation.PrefixHandler(cfg),
namespacevalidation.UserMetadataHandler(),
namespacevalidation.RequiredMetadataHandler(),
),
),
route.NamespaceMutation(
namespacemutation.NamespaceHandler(
cfg,
namespacemutation.OwnerReferenceHandler(cfg),
namespacemutation.MetadataHandler(cfg),
namespacemutation.NamespacePatchGuardHandler(cfg),
),
),
route.ResourcePoolMutation(resourcepool.PoolMutationHandler(ctrl.Log.WithName("webhooks").WithName("resourcepool"))),
route.ResourcePoolValidation(resourcepool.PoolValidationHandler(ctrl.Log.WithName("webhooks").WithName("resourcepool"))),
route.ResourcePoolClaimMutation(resourcepool.ClaimMutationHandler(ctrl.Log.WithName("webhooks").WithName("resourcepoolclaims"))),
route.ResourcePoolClaimValidation(resourcepool.ClaimValidationHandler(ctrl.Log.WithName("webhooks").WithName("resourcepoolclaims"))),
route.CustomQuotaValidation(customquotavalidation.CustomQuotaValidationHandler(
targetsCache,
jsonPathCache,
)),
route.GlobalCustomQuotaValidation(customquotavalidation.GlobalCustomQuotaValidationHandler(
targetsCache,
jsonPathCache,
)),
route.CalculationCustomQuotas(
customquotavalidation.ObjectCalculationHandler(
targetsCache,
jsonPathCache,
),
),
route.GenericTenantAssignment(
generic.TenantAssignmentHandler(),
),
route.ConfigValidation(
cfgvalidation.Handler(cfg,
cfgvalidation.ValidationHandler(regexCache),
cfgvalidation.ServiceAccountHandler(),
cfgvalidation.OwnerHandler(),
// Must run last, because always returns response
cfgvalidation.WarningHandler(),
),
),
route.RulesValidating(manager.GetRESTMapper(), cfg),
)
nodeWebhookSupported, _ := utils.NodeWebhookSupported(kubeVersion)
if !nodeWebhookSupported {
setupLog.Info("disabling node labels verification webhook as current Kubernetes version doesn't have fix for CVE-2021-25735")
}
if err = webhook.Register(
manager,
evt.NewEventRecorder(
manager.GetClient(),
ctrl.Log.WithName("capsule.ctrl").WithName("events"),
manager.GetEventRecorder("tenant-controller"),
cfg,
), webhooksList...); err != nil {
setupLog.Error(err, "unable to setup webhooks")
os.Exit(1)
}
rbacManager := &rbaccontroller.Manager{
Log: ctrl.Log.WithName("capsule.ctrl").WithName("rbac"),
Client: manager.GetClient(),
Configuration: cfg,
}
if err = manager.Add(rbacManager); err != nil {
setupLog.Error(err, "unable to create cluster roles")
os.Exit(1)
}
if err = rbacManager.SetupWithManager(ctx, manager, controllerConfig); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Rbac")
os.Exit(1)
}
if err = (&tenantownercontroller.TenantOwnerManager{
Log: ctrl.Log.WithName("capsule.ctrl").WithName("tenantowners"),
Client: manager.GetClient(),
}).SetupWithManager(manager, controllerConfig); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "TenantOwners")
os.Exit(1)
}
if err = (&servicelabelscontroller.ServicesLabelsReconciler{
Log: ctrl.Log.WithName("capsule.ctrl").WithName("services"),
}).SetupWithManager(ctx, manager, controllerConfig); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ServiceLabels")
os.Exit(1)
}
if err = (&servicelabelscontroller.EndpointSlicesLabelsReconciler{
Log: ctrl.Log.WithName("capsule.ctrl").WithName("endpointslices"),
}).SetupWithManager(ctx, manager, controllerConfig); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "EndpointSliceLabels")
}
if err = (&podlabelscontroller.MetadataReconciler{
Client: manager.GetClient(),
Log: ctrl.Log.WithName("capsule.ctrl").WithName("pods"),
}).SetupWithManager(ctx, manager, controllerConfig); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "PodLabels")
os.Exit(1)
}
if err = (&pv.Controller{}).SetupWithManager(manager, controllerConfig); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "PersistentVolume")
os.Exit(1)
}
if err = (&configcontroller.Manager{
Rest: manager.GetConfig(),
Client: manager.GetClient(),
Log: ctrl.Log.WithName("capsule.ctrl").WithName("configuration"),
}).SetupWithManager(manager, controllerConfig); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "CapsuleConfiguration")
os.Exit(1)
}
if err = (&rulestatuscontroller.Manager{
Client: manager.GetClient(),
Log: ctrl.Log.WithName("capsule.ctrl").WithName("ruleset"),
}).SetupWithManager(manager, controllerConfig); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "RuleSet")
os.Exit(1)
}
setupLog.Info("initializing controllers")
localInvalidator := &cacheinvalidator.CacheInvalidator{
Log: ctrl.Log.WithName("capsule.ctrl").WithName("invalidator"),
Client: manager.GetClient(),
Configuration: directCfg,
ImpersonationCache: impersonationCache,
RegistryCache: registryCache,
JSONPathCache: jsonPathCache,
TargetsCache: targetsCache,
RegexCache: regexCache,
}
if err := localInvalidator.SetupWithManager(manager, controllerConfig); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "invalidator")
os.Exit(1)
}
if err := resources.Add(
ctrl.Log.WithName("controllers").WithName("TenantResources"),
manager,
cfg,
controllerConfig,
impersonationCache,
); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "tenantresources")
os.Exit(1)
}
if err := admission.Add(
ctrl.Log.WithName("capsule.ctrl").WithName("admission"),
manager,
manager.GetEventRecorder("admission-ctrl"),
controllerConfig,
directCfg,
); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "admission")
os.Exit(1)
}
if err := resourcepools.Add(
ctrl.Log.WithName("capsule.ctrl").WithName("resourcepools"),
manager,
manager.GetEventRecorder("pools-ctrl"),
controllerConfig,
); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "resourcepools")
os.Exit(1)
}
if err = customquotacontroller.Add(ctrl.Log.WithName("controllers").WithName("CustomQuotas"),
manager,
manager.GetEventRecorder("customquotas-ctrl"),
controllerConfig,
customQuotaQuantityCache,
jsonPathCache,
targetsCache,
customQuotaCh,
globalCustomQuotaCh,
); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "customquotas")
os.Exit(1)
}
setupLog.Info("starting manager")
if err = manager.Start(ctx); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}