From 0583a169d9a2957fdb50d7ad384182548c585cdd Mon Sep 17 00:00:00 2001 From: Martin Weindel Date: Thu, 2 Jan 2025 09:07:25 +0100 Subject: [PATCH] Add controllers to watch garden and certificate resources on runtime cluster; add webhook to patch sniconfig of virtual kube-apiserver deployment --- .../app/app.go | 21 +- .../app/options.go | 55 +++- example/controller-registration.yaml | 2 +- go.mod | 22 +- go.sum | 99 ++++++- pkg/cmd/options.go | 16 +- pkg/controller/healthcheck/add.go | 4 +- .../runtimecluster/certificate/add.go | 74 +++++ .../runtimecluster/certificate/reconciler.go | 174 ++++++++++++ pkg/controller/runtimecluster/garden/add.go | 66 +++++ .../runtimecluster/garden/reconciler.go | 171 +++++++++++ .../{ => shootcertservice}/actuator.go | 14 +- pkg/controller/{ => shootcertservice}/add.go | 2 +- pkg/webhook/sniconfig/add.go | 43 +++ pkg/webhook/sniconfig/handler.go | 184 ++++++++++++ pkg/webhook/sniconfig/handler_test.go | 266 ++++++++++++++++++ pkg/webhook/sniconfig/sniconfig_suite_test.go | 17 ++ 17 files changed, 1193 insertions(+), 37 deletions(-) create mode 100644 pkg/controller/runtimecluster/certificate/add.go create mode 100644 pkg/controller/runtimecluster/certificate/reconciler.go create mode 100644 pkg/controller/runtimecluster/garden/add.go create mode 100644 pkg/controller/runtimecluster/garden/reconciler.go rename pkg/controller/{ => shootcertservice}/actuator.go (97%) rename pkg/controller/{ => shootcertservice}/add.go (99%) create mode 100644 pkg/webhook/sniconfig/add.go create mode 100644 pkg/webhook/sniconfig/handler.go create mode 100644 pkg/webhook/sniconfig/handler_test.go create mode 100644 pkg/webhook/sniconfig/sniconfig_suite_test.go diff --git a/cmd/gardener-extension-shoot-cert-service/app/app.go b/cmd/gardener-extension-shoot-cert-service/app/app.go index b8a5f991..ded5c5b8 100644 --- a/cmd/gardener-extension-shoot-cert-service/app/app.go +++ b/cmd/gardener-extension-shoot-cert-service/app/app.go @@ -12,6 +12,7 @@ import ( extensionscontroller "github.com/gardener/gardener/extensions/pkg/controller" "github.com/gardener/gardener/extensions/pkg/controller/heartbeat" "github.com/gardener/gardener/extensions/pkg/util" + operatorv1alpha1 "github.com/gardener/gardener/pkg/apis/operator/v1alpha1" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" componentbaseconfig "k8s.io/component-base/config" @@ -20,8 +21,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" serviceinstall "github.com/gardener/gardener-extension-shoot-cert-service/pkg/apis/service/install" - "github.com/gardener/gardener-extension-shoot-cert-service/pkg/controller" "github.com/gardener/gardener-extension-shoot-cert-service/pkg/controller/healthcheck" + certificatecontroller "github.com/gardener/gardener-extension-shoot-cert-service/pkg/controller/runtimecluster/certificate" + gardencontroller "github.com/gardener/gardener-extension-shoot-cert-service/pkg/controller/runtimecluster/garden" + "github.com/gardener/gardener-extension-shoot-cert-service/pkg/controller/shootcertservice" ) // NewServiceControllerCommand creates a new command that is used to start the Certificate Service controller. @@ -88,18 +91,28 @@ func (o *Options) run(ctx context.Context) error { return fmt.Errorf("could not update manager scheme: %s", err) } + if err := operatorv1alpha1.AddToScheme(mgr.GetScheme()); err != nil { + return fmt.Errorf("could not update manager scheme: %s", err) + } + ctrlConfig := o.certOptions.Completed() ctrlConfig.ApplyHealthCheckConfig(&healthcheck.DefaultAddOptions.HealthCheckConfig) - ctrlConfig.Apply(&controller.DefaultAddOptions.ServiceConfig) - o.controllerOptions.Completed().Apply(&controller.DefaultAddOptions.ControllerOptions) + ctrlConfig.Apply(&shootcertservice.DefaultAddOptions.ServiceConfig) + o.controllerOptions.Completed().Apply(&shootcertservice.DefaultAddOptions.ControllerOptions) o.healthOptions.Completed().Apply(&healthcheck.DefaultAddOptions.Controller) - o.reconcileOptions.Completed().Apply(&controller.DefaultAddOptions.IgnoreOperationAnnotation, &controller.DefaultAddOptions.ExtensionClass) + o.reconcileOptions.Completed().Apply(&shootcertservice.DefaultAddOptions.IgnoreOperationAnnotation, &shootcertservice.DefaultAddOptions.ExtensionClass) o.heartbeatOptions.Completed().Apply(&heartbeat.DefaultAddOptions) + o.gardenControllerOptions.Completed().Apply(&gardencontroller.DefaultAddOptions) + o.certificateControllerOptions.Completed().Apply(&certificatecontroller.DefaultAddOptions) if err := o.controllerSwitches.Completed().AddToManager(ctx, mgr); err != nil { return fmt.Errorf("could not add controllers to manager: %s", err) } + if _, err := o.webhookOptions.Completed().AddToManager(ctx, mgr, mgr); err != nil { + return fmt.Errorf("could not add webhooks to manager: %s", err) + } + if err := mgr.Start(ctx); err != nil { return fmt.Errorf("error running manager: %s", err) } diff --git a/cmd/gardener-extension-shoot-cert-service/app/options.go b/cmd/gardener-extension-shoot-cert-service/app/options.go index 6041bd25..11e23eec 100644 --- a/cmd/gardener-extension-shoot-cert-service/app/options.go +++ b/cmd/gardener-extension-shoot-cert-service/app/options.go @@ -9,6 +9,8 @@ import ( controllercmd "github.com/gardener/gardener/extensions/pkg/controller/cmd" heartbeatcmd "github.com/gardener/gardener/extensions/pkg/controller/heartbeat/cmd" + extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook" + extensionscmdwebhook "github.com/gardener/gardener/extensions/pkg/webhook/cmd" certificateservicecmd "github.com/gardener/gardener-extension-shoot-cert-service/pkg/cmd" ) @@ -18,20 +20,28 @@ const ExtensionName = "extension-shoot-cert-service" // Options holds configuration passed to the Certificate Service controller. type Options struct { - generalOptions *controllercmd.GeneralOptions - certOptions *certificateservicecmd.CertificateServiceOptions - restOptions *controllercmd.RESTOptions - managerOptions *controllercmd.ManagerOptions - controllerOptions *controllercmd.ControllerOptions - healthOptions *controllercmd.ControllerOptions - heartbeatOptions *heartbeatcmd.Options - controllerSwitches *controllercmd.SwitchOptions - reconcileOptions *controllercmd.ReconcilerOptions - optionAggregator controllercmd.OptionAggregator + generalOptions *controllercmd.GeneralOptions + certOptions *certificateservicecmd.CertificateServiceOptions + restOptions *controllercmd.RESTOptions + managerOptions *controllercmd.ManagerOptions + controllerOptions *controllercmd.ControllerOptions + healthOptions *controllercmd.ControllerOptions + heartbeatOptions *heartbeatcmd.Options + gardenControllerOptions *controllercmd.ControllerOptions + certificateControllerOptions *controllercmd.ControllerOptions + controllerSwitches *controllercmd.SwitchOptions + reconcileOptions *controllercmd.ReconcilerOptions + optionAggregator controllercmd.OptionAggregator + webhookOptions *extensionscmdwebhook.AddToManagerOptions } // NewOptions creates a new Options instance. func NewOptions() *Options { + mode, url := extensionswebhook.ModeService, os.Getenv("WEBHOOK_URL") + if v := os.Getenv("WEBHOOK_MODE"); v != "" { + mode = v + } + options := &Options{ generalOptions: &controllercmd.GeneralOptions{}, certOptions: &certificateservicecmd.CertificateServiceOptions{}, @@ -41,6 +51,9 @@ func NewOptions() *Options { LeaderElection: true, LeaderElectionID: controllercmd.LeaderElectionNameID(ExtensionName), LeaderElectionNamespace: os.Getenv("LEADER_ELECTION_NAMESPACE"), + + // These are default values. + WebhookServerPort: 10250, }, controllerOptions: &controllercmd.ControllerOptions{ // This is a default value. @@ -50,6 +63,14 @@ func NewOptions() *Options { // This is a default value. MaxConcurrentReconciles: 5, }, + gardenControllerOptions: &controllercmd.ControllerOptions{ + // This is a default value. + MaxConcurrentReconciles: 1, + }, + certificateControllerOptions: &controllercmd.ControllerOptions{ + // This is a default value. + MaxConcurrentReconciles: 1, + }, heartbeatOptions: &heartbeatcmd.Options{ // This is a default value. ExtensionName: ExtensionName, @@ -58,6 +79,17 @@ func NewOptions() *Options { }, controllerSwitches: certificateservicecmd.ControllerSwitches(), reconcileOptions: &controllercmd.ReconcilerOptions{}, + webhookOptions: extensionscmdwebhook.NewAddToManagerOptions( + "shoot-cert-service", + "", + nil, + &extensionscmdwebhook.ServerOptions{ + Mode: mode, + URL: url, + ServicePort: 443, + Namespace: "garden", + }, + certificateservicecmd.WebhookSwitches()), } options.optionAggregator = controllercmd.NewOptionAggregator( @@ -68,8 +100,11 @@ func NewOptions() *Options { options.certOptions, controllercmd.PrefixOption("healthcheck-", options.healthOptions), controllercmd.PrefixOption("heartbeat-", options.heartbeatOptions), + controllercmd.PrefixOption("garden-", options.gardenControllerOptions), + controllercmd.PrefixOption("certificate-", options.certificateControllerOptions), options.controllerSwitches, options.reconcileOptions, + options.webhookOptions, ) return options diff --git a/example/controller-registration.yaml b/example/controller-registration.yaml index c3a738b9..063897d5 100644 --- a/example/controller-registration.yaml +++ b/example/controller-registration.yaml @@ -4,7 +4,7 @@ kind: ControllerDeployment metadata: name: extension-shoot-cert-service helm: - rawChart: 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 + rawChart: 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 values: image: tag: v1.48.0-dev diff --git a/go.mod b/go.mod index e2581475..8df0a4cd 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/spf13/pflag v1.0.5 go.uber.org/mock v0.5.0 golang.org/x/tools v0.28.0 + gomodules.xyz/jsonpatch/v2 v2.4.0 k8s.io/api v0.31.3 k8s.io/apimachinery v0.31.3 k8s.io/client-go v0.31.3 @@ -26,6 +27,7 @@ require ( require ( dario.cat/mergo v1.0.1 // indirect + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/BurntSushi/toml v1.4.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.3.1 // indirect @@ -33,6 +35,8 @@ require ( github.com/andybalholm/brotli v1.1.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cert-manager/cert-manager v1.16.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cyphar/filepath-securejoin v0.3.4 // indirect @@ -43,8 +47,14 @@ require ( github.com/fluent/fluent-operator/v2 v2.9.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/gardener/controller-manager-library v0.2.1-0.20241104074533-80cbeddadabc // indirect github.com/gardener/etcd-druid v0.25.0 // indirect + github.com/gardener/external-dns-management v0.22.1 // indirect github.com/gardener/machine-controller-manager v0.55.1 // indirect + github.com/go-acme/lego/v4 v4.20.4 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.6 // indirect + github.com/go-jose/go-jose/v4 v4.0.4 // indirect + github.com/go-ldap/ldap/v3 v3.4.8 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/errors v0.20.4 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect @@ -64,7 +74,7 @@ require ( github.com/gorilla/websocket v1.5.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -77,6 +87,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/miekg/dns v1.1.62 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -86,6 +97,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.78.2 // indirect @@ -97,6 +109,7 @@ require ( github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.7.0 // indirect @@ -108,17 +121,16 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.30.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.32.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.8.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect google.golang.org/protobuf v1.35.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect @@ -143,7 +155,9 @@ require ( k8s.io/metrics v0.31.3 // indirect sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20231015215740-bf15e44028f9 // indirect sigs.k8s.io/controller-tools v0.16.5 // indirect + sigs.k8s.io/gateway-api v1.2.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.3 // indirect sigs.k8s.io/yaml v1.4.0 // indirect + software.sslmate.com/src/go-pkcs12 v0.5.0 // indirect ) diff --git a/go.sum b/go.sum index 66fe5fe9..c8f64833 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,8 @@ github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxB github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= @@ -41,6 +43,8 @@ github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdko github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/ahmetb/gen-crd-api-reference-docs v0.3.0 h1:+XfOU14S4bGuwyvCijJwhhBIjYN+YXS18jrCY2EzJaY= github.com/ahmetb/gen-crd-api-reference-docs v0.3.0/go.mod h1:TdjdkYhlOifCQWPs1UdTma97kQQMozf5h26hTuG70u8= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -49,7 +53,11 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cert-manager/cert-manager v1.16.2 h1:c9UU2E+8XWGruyvC/mdpc1wuLddtgmNr8foKdP7a8Jg= +github.com/cert-manager/cert-manager v1.16.2/go.mod h1:MfLVTL45hFZsqmaT1O0+b2ugaNNQQZttSFV9hASHUb0= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -95,14 +103,27 @@ github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gardener/cert-management v0.17.1 h1:vawZGN+rsCRMviacnnMSWELbuIJsNXHaqaLbZ4hYADw= github.com/gardener/cert-management v0.17.1/go.mod h1:cwSsyN935017HojKVuWqw2TBhiaxSisX132D9Tn+n9I= +github.com/gardener/controller-manager-library v0.2.1-0.20241104074533-80cbeddadabc h1:o4r1a4B2HKwkglGpO74FJ9XnrGK8NAqynTpvKTfapyI= +github.com/gardener/controller-manager-library v0.2.1-0.20241104074533-80cbeddadabc/go.mod h1:fyLOrcaKtGno4McZKW21b6QtwNghCF0IemTLKcwKZlM= github.com/gardener/etcd-druid v0.25.0 h1:mR9/x5r27pO+I+XzpNcN2DDenam+7ITrhc7qKt9rbsI= github.com/gardener/etcd-druid v0.25.0/go.mod h1:6C0eyfdlw6CowLm/l4ZiKwrvkc+5NHrnc/rY2wCUwys= +github.com/gardener/external-dns-management v0.22.1 h1:WEwCDOersJ7ezeDJelbGVac1BTmEveJuds3JlJc84Xg= +github.com/gardener/external-dns-management v0.22.1/go.mod h1:2P7PamBPMKIOZMYRhl/VFhxZEBn4VUTdjESjKPxvOXA= github.com/gardener/gardener v1.110.0 h1:Ix/NeYJyYIIDRHqO0126JYPGNVKy2kDEco7RyXuCYwo= github.com/gardener/gardener v1.110.0/go.mod h1:Ge2wQMWm0NmQZP3L/WMejpfXsnGbfTFBEZud819P3vU= github.com/gardener/machine-controller-manager v0.55.1 h1:d6mTnuYko+jWeIi7tAFWgWnL1nR5hGcI6pRCDcH0TGY= github.com/gardener/machine-controller-manager v0.55.1/go.mod h1:eCng7De6OE15rndmMm6Q1fwMQI39esASCd3WKZ/lLmY= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-acme/lego/v4 v4.20.4 h1:yCQGBX9jOfMbriEQUocdYm7EBapdTp8nLXYG8k6SqSU= +github.com/go-acme/lego/v4 v4.20.4/go.mod h1:foauPlhnhoq8WUphaWx5U04uDc+JGhk4ZZtPz/Vqsjg= +github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-asn1-ber/asn1-ber v1.5.6 h1:CYsqysemXfEaQbyrLJmdsCRuufHoLa3P/gGWGl5TDrM= +github.com/go-asn1-ber/asn1-ber v1.5.6/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= +github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ= +github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= @@ -187,6 +208,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -195,10 +218,13 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= +github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -210,6 +236,18 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/ironcore-dev/vgopath v0.1.5 h1:+I46zEFfbmNIGIGylqedT2bMXw8V7yVP16GJkG64gAw= github.com/ironcore-dev/vgopath v0.1.5/go.mod h1:qbSUA7Eg0SO97OYfkG0DH+DxaPrH6XCiAQHqqs9R63Q= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -249,6 +287,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= +github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= @@ -285,6 +325,8 @@ github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGV github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.36.0 h1:Pb12RlruUtj4XUuPUqeEWc6j5DkVVVA49Uf6YLfC95Y= github.com/onsi/gomega v1.36.0/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 h1:2nosf3P75OZv2/ZO/9Px5ZgZ5gbKrzA3joN1QMfOGMQ= +github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0/go.mod h1:lAVhWwbNaveeJmxrxuSTxMgKpF6DjnuVpn6T8WiBwYQ= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -318,6 +360,8 @@ github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWR github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -340,6 +384,7 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -362,6 +407,7 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -380,8 +426,12 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -405,6 +455,8 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -421,12 +473,20 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -441,6 +501,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -459,16 +521,35 @@ golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -502,6 +583,8 @@ golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -641,6 +724,8 @@ sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20231015215740-bf15e44 sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20231015215740-bf15e44028f9/go.mod h1:TF/lVLWS+JNNaVqJuDDictY2hZSXSsIHCx4FClMvqFg= sigs.k8s.io/controller-tools v0.16.5 h1:5k9FNRqziBPwqr17AMEPPV/En39ZBplLAdOwwQHruP4= sigs.k8s.io/controller-tools v0.16.5/go.mod h1:8vztuRVzs8IuuJqKqbXCSlXcw+lkAv/M2sTpg55qjMY= +sigs.k8s.io/gateway-api v1.2.0 h1:LrToiFwtqKTKZcZtoQPTuo3FxhrrhTgzQG0Te+YGSo8= +sigs.k8s.io/gateway-api v1.2.0/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= @@ -650,3 +735,5 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +software.sslmate.com/src/go-pkcs12 v0.5.0 h1:EC6R394xgENTpZ4RltKydeDUjtlM5drOYIG9c6TVj2M= +software.sslmate.com/src/go-pkcs12 v0.5.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/pkg/cmd/options.go b/pkg/cmd/options.go index de505589..8f9fa1f9 100644 --- a/pkg/cmd/options.go +++ b/pkg/cmd/options.go @@ -12,6 +12,7 @@ import ( "github.com/gardener/gardener/extensions/pkg/controller/cmd" extensionshealthcheckcontroller "github.com/gardener/gardener/extensions/pkg/controller/healthcheck" extensionsheartbeatcontroller "github.com/gardener/gardener/extensions/pkg/controller/heartbeat" + webhookcmd "github.com/gardener/gardener/extensions/pkg/webhook/cmd" "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" @@ -20,9 +21,12 @@ import ( apisconfig "github.com/gardener/gardener-extension-shoot-cert-service/pkg/apis/config" "github.com/gardener/gardener-extension-shoot-cert-service/pkg/apis/config/v1alpha1" "github.com/gardener/gardener-extension-shoot-cert-service/pkg/apis/config/validation" - "github.com/gardener/gardener-extension-shoot-cert-service/pkg/controller" controllerconfig "github.com/gardener/gardener-extension-shoot-cert-service/pkg/controller/config" healthcheckcontroller "github.com/gardener/gardener-extension-shoot-cert-service/pkg/controller/healthcheck" + certificatecontroller "github.com/gardener/gardener-extension-shoot-cert-service/pkg/controller/runtimecluster/certificate" + gardencontroller "github.com/gardener/gardener-extension-shoot-cert-service/pkg/controller/runtimecluster/garden" + "github.com/gardener/gardener-extension-shoot-cert-service/pkg/controller/shootcertservice" + "github.com/gardener/gardener-extension-shoot-cert-service/pkg/webhook/sniconfig" ) var ( @@ -94,9 +98,11 @@ func (c *CertificateServiceConfig) Apply(config *controllerconfig.Config) { // ControllerSwitches are the cmd.SwitchOptions for the provider controllers. func ControllerSwitches() *cmd.SwitchOptions { return cmd.NewSwitchOptions( - cmd.Switch(controller.ControllerName, controller.AddToManager), + cmd.Switch(shootcertservice.ControllerName, shootcertservice.AddToManager), cmd.Switch(extensionshealthcheckcontroller.ControllerName, healthcheckcontroller.AddToManager), cmd.Switch(extensionsheartbeatcontroller.ControllerName, extensionsheartbeatcontroller.AddToManager), + cmd.Switch(gardencontroller.ControllerName, gardencontroller.AddToManager), + cmd.Switch(certificatecontroller.ControllerName, certificatecontroller.AddToManager), ) } @@ -105,3 +111,9 @@ func (c *CertificateServiceConfig) ApplyHealthCheckConfig(config *extensionsapis *config = *c.config.HealthCheckConfig } } + +func WebhookSwitches() *webhookcmd.SwitchOptions { + return webhookcmd.NewSwitchOptions( + webhookcmd.Switch(sniconfig.HandlerName, sniconfig.AddToManager), + ) +} diff --git a/pkg/controller/healthcheck/add.go b/pkg/controller/healthcheck/add.go index eb6c66b7..968d7938 100644 --- a/pkg/controller/healthcheck/add.go +++ b/pkg/controller/healthcheck/add.go @@ -20,7 +20,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" certv1alpha1 "github.com/gardener/gardener-extension-shoot-cert-service/pkg/apis/service/v1alpha1" - certcontroller "github.com/gardener/gardener-extension-shoot-cert-service/pkg/controller" + "github.com/gardener/gardener-extension-shoot-cert-service/pkg/controller/shootcertservice" ) var ( @@ -40,7 +40,7 @@ func RegisterHealthChecks(ctx context.Context, mgr manager.Manager, opts healthc return healthcheck.DefaultRegistration( ctx, - certcontroller.Type, + shootcertservice.Type, extensionsv1alpha1.SchemeGroupVersion.WithKind(extensionsv1alpha1.ExtensionResource), func() client.ObjectList { return &extensionsv1alpha1.ExtensionList{} }, func() extensionsv1alpha1.Object { return &extensionsv1alpha1.Extension{} }, diff --git a/pkg/controller/runtimecluster/certificate/add.go b/pkg/controller/runtimecluster/certificate/add.go new file mode 100644 index 00000000..826a280a --- /dev/null +++ b/pkg/controller/runtimecluster/certificate/add.go @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package certificate + +import ( + "context" + "fmt" + + certv1alpha1 "github.com/gardener/cert-management/pkg/apis/cert/v1alpha1" + extensionspredicate "github.com/gardener/gardener/extensions/pkg/predicate" + v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" + "github.com/gardener/gardener/pkg/client/kubernetes" + "k8s.io/utils/clock" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +// ControllerName is the name of this controller. +const ControllerName = "certificate" + +// DefaultAddOptions are the default AddOptions for AddToManager. +var DefaultAddOptions = controller.Options{ + MaxConcurrentReconciles: 1, +} + +// AddToManager adds a controller with the default Options to the given Controller Manager. +func AddToManager(_ context.Context, mgr manager.Manager) error { + return (&Reconciler{ + ControllerOptions: DefaultAddOptions, + }).AddToManager(mgr) +} + +// AddToManager adds Reconciler to the given manager. +func (r *Reconciler) AddToManager(mgr manager.Manager) error { + var err error + + if r.Manager == nil { + r.Manager = mgr + } + if r.RuntimeClientSet == nil { + r.RuntimeClientSet, err = kubernetes.NewWithConfig( + kubernetes.WithRESTConfig(mgr.GetConfig()), + kubernetes.WithRuntimeAPIReader(mgr.GetAPIReader()), + kubernetes.WithRuntimeClient(mgr.GetClient()), + kubernetes.WithRuntimeCache(mgr.GetCache()), + ) + if err != nil { + return fmt.Errorf("failed creating runtime clientset: %w", err) + } + } + if r.Clock == nil { + r.Clock = clock.RealClock{} + } + if r.GardenNamespace == "" { + r.GardenNamespace = v1beta1constants.GardenNamespace + } + + return builder. + ControllerManagedBy(mgr). + Named(ControllerName). + For(&certv1alpha1.Certificate{}, builder.WithPredicates( + extensionspredicate.IsInGardenNamespacePredicate, + predicate.NewPredicateFuncs(func(obj client.Object) bool { + return obj != nil && obj.GetLabels()[v1beta1constants.GardenRole] == v1beta1constants.GardenRoleControlPlaneWildcardCert + }), + )). + WithOptions(r.ControllerOptions). + Complete(r) +} diff --git a/pkg/controller/runtimecluster/certificate/reconciler.go b/pkg/controller/runtimecluster/certificate/reconciler.go new file mode 100644 index 00000000..9e754e0f --- /dev/null +++ b/pkg/controller/runtimecluster/certificate/reconciler.go @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package certificate + +import ( + "context" + "fmt" + + certv1alpha1 "github.com/gardener/cert-management/pkg/apis/cert/v1alpha1" + "github.com/gardener/cert-management/pkg/controller/issuer/certificate" + "github.com/gardener/gardener/pkg/client/kubernetes" + "github.com/go-logr/logr" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/clock" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const ( + TLSCertAPIServerNamesAnnotation = "service.cert.extensions.gardener.cloud/tls-cert-apiserver-names" + TLSCertRequestedAtAnnotation = "service.cert.extensions.gardener.cloud/tls-cert-requested-at" + TLSCertHashAnnotation = "service.cert.extensions.gardener.cloud/tls-cert-hash" +) + +// Reconciler reconciles Gardens. +type Reconciler struct { + Manager manager.Manager + RuntimeClientSet kubernetes.Interface + ControllerOptions controller.Options + Clock clock.Clock + GardenNamespace string +} + +// Reconcile performs the main reconciliation logic. +func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { + log := logf.FromContext(ctx) + + cert := &certv1alpha1.Certificate{} + if err := r.RuntimeClientSet.Client().Get(ctx, request.NamespacedName, cert); err != nil { + if apierrors.IsNotFound(err) { + log.V(1).Info("Object is gone, stop reconciling") + return reconcile.Result{}, nil + } + return reconcile.Result{}, fmt.Errorf("error retrieving object from store: %w", err) + } + + if cert.DeletionTimestamp != nil { + if result, err := r.delete(ctx, log, cert); err != nil { + return result, err + } + return reconcile.Result{}, nil + } + + if result, err := r.reconcile(ctx, log, cert); err != nil { + return result, err + } + + return reconcile.Result{}, nil +} + +func (r *Reconciler) reconcile( + ctx context.Context, + log logr.Logger, + cert *certv1alpha1.Certificate, +) ( + reconcile.Result, + error, +) { + if cert.Status.State != "Ready" { + log.Info("Certificate is not ready yet") + return reconcile.Result{}, nil + } + + apiDNSNames := cert.GetAnnotations()[TLSCertAPIServerNamesAnnotation] + certHash := cert.GetLabels()[certificate.LabelCertificateNewHashKey] + + secret := &corev1.Secret{} + ns := cert.Namespace + var secretName string + if cert.Spec.SecretRef != nil { + secretName = cert.Spec.SecretRef.Name + if cert.Spec.SecretRef.Namespace != "" { + ns = cert.Spec.SecretRef.Namespace + } + } else if cert.Spec.SecretName != nil { + secretName = *cert.Spec.SecretName + } + if err := r.RuntimeClientSet.Client().Get(ctx, client.ObjectKey{Namespace: ns, Name: secretName}, secret); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to get certificate secret %s: %w", secretName, err) + } + requestedAt := secret.GetAnnotations()[certificate.AnnotationRequestedAt] + + if err := r.updateVirtualGardenDeploymentAnnotation(ctx, log, apiDNSNames, requestedAt, certHash); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to update virtual garden deployment annotations: %w", err) + } + + return reconcile.Result{}, nil +} + +func (r *Reconciler) delete( + ctx context.Context, + log logr.Logger, + _ *certv1alpha1.Certificate, +) ( + reconcile.Result, + error, +) { + if err := r.updateVirtualGardenDeploymentAnnotation(ctx, log, "", "", ""); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to update virtual garden deployment annotations: %w", err) + } + + return reconcile.Result{}, nil +} + +func (r *Reconciler) newCertificate() *certv1alpha1.Certificate { + return &certv1alpha1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tls", + Namespace: r.GardenNamespace, + }, + } +} + +func (r *Reconciler) updateVirtualGardenDeploymentAnnotation( + ctx context.Context, + log logr.Logger, + apiserverNames string, + requestedAt string, + certHash string, +) error { + deploy := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "virtual-garden-kube-apiserver", + Namespace: r.GardenNamespace, + }, + } + if err := r.RuntimeClientSet.Client().Get(ctx, client.ObjectKeyFromObject(deploy), deploy); err != nil { + return fmt.Errorf("failed to get deployment %s: %w", deploy.Name, err) + } + + if deploy.Annotations[TLSCertAPIServerNamesAnnotation] == apiserverNames && + deploy.Annotations[TLSCertRequestedAtAnnotation] == requestedAt && + deploy.Annotations[TLSCertHashAnnotation] == certHash { + return nil + } + + patch := client.MergeFrom(deploy.DeepCopy()) + if apiserverNames != "" { + if deploy.Annotations == nil { + deploy.Annotations = map[string]string{} + } + deploy.Annotations[TLSCertAPIServerNamesAnnotation] = apiserverNames + deploy.Annotations[TLSCertRequestedAtAnnotation] = requestedAt + deploy.Annotations[TLSCertHashAnnotation] = certHash + } else { + delete(deploy.Annotations, TLSCertAPIServerNamesAnnotation) + delete(deploy.Annotations, TLSCertRequestedAtAnnotation) + delete(deploy.Annotations, TLSCertHashAnnotation) + } + if err := r.RuntimeClientSet.Client().Patch(ctx, deploy, patch); err != nil { + return fmt.Errorf("failed to patch virtual garden kube-apisever deployment annotations: %w", err) + } + log.Info("Updated deployment annotations", "name", deploy.Name, "apiserverNames", apiserverNames, "certHash", certHash) + + return nil +} diff --git a/pkg/controller/runtimecluster/garden/add.go b/pkg/controller/runtimecluster/garden/add.go new file mode 100644 index 00000000..46b52a86 --- /dev/null +++ b/pkg/controller/runtimecluster/garden/add.go @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package garden + +import ( + "context" + "fmt" + + v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" + operatorv1alpha1 "github.com/gardener/gardener/pkg/apis/operator/v1alpha1" + "github.com/gardener/gardener/pkg/client/kubernetes" + "k8s.io/utils/clock" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +// ControllerName is the name of this controller. +const ControllerName = "garden" + +// DefaultAddOptions are the default AddOptions for AddToManager. +var DefaultAddOptions = controller.Options{ + MaxConcurrentReconciles: 1, +} + +// AddToManager adds a controller with the default Options to the given Controller Manager. +func AddToManager(_ context.Context, mgr manager.Manager) error { + return (&Reconciler{ + ControllerOptions: DefaultAddOptions, + }).AddToManager(mgr) +} + +// AddToManager adds Reconciler to the given manager. +func (r *Reconciler) AddToManager(mgr manager.Manager) error { + var err error + + if r.Manager == nil { + r.Manager = mgr + } + if r.RuntimeClientSet == nil { + r.RuntimeClientSet, err = kubernetes.NewWithConfig( + kubernetes.WithRESTConfig(mgr.GetConfig()), + kubernetes.WithRuntimeAPIReader(mgr.GetAPIReader()), + kubernetes.WithRuntimeClient(mgr.GetClient()), + kubernetes.WithRuntimeCache(mgr.GetCache()), + ) + if err != nil { + return fmt.Errorf("failed creating runtime clientset: %w", err) + } + } + if r.Clock == nil { + r.Clock = clock.RealClock{} + } + if r.GardenNamespace == "" { + r.GardenNamespace = v1beta1constants.GardenNamespace + } + + return builder. + ControllerManagedBy(mgr). + Named(ControllerName). + For(&operatorv1alpha1.Garden{}). + WithOptions(r.ControllerOptions). + Complete(r) +} diff --git a/pkg/controller/runtimecluster/garden/reconciler.go b/pkg/controller/runtimecluster/garden/reconciler.go new file mode 100644 index 00000000..0b82d59e --- /dev/null +++ b/pkg/controller/runtimecluster/garden/reconciler.go @@ -0,0 +1,171 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package garden + +import ( + "context" + "fmt" + "strings" + + certv1alpha1 "github.com/gardener/cert-management/pkg/apis/cert/v1alpha1" + v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" + operatorv1alpha1 "github.com/gardener/gardener/pkg/apis/operator/v1alpha1" + "github.com/gardener/gardener/pkg/client/kubernetes" + "github.com/gardener/gardener/pkg/controllerutils" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/clock" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/gardener/gardener-extension-shoot-cert-service/pkg/controller/runtimecluster/certificate" +) + +// Reconciler reconciles Gardens. +type Reconciler struct { + Manager manager.Manager + RuntimeClientSet kubernetes.Interface + ControllerOptions controller.Options + Clock clock.Clock + GardenNamespace string +} + +// Reconcile performs the main reconciliation logic. +func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { + log := logf.FromContext(ctx) + + garden := &operatorv1alpha1.Garden{} + if err := r.RuntimeClientSet.Client().Get(ctx, request.NamespacedName, garden); err != nil { + if apierrors.IsNotFound(err) { + log.V(1).Info("Object is gone, stop reconciling") + return reconcile.Result{}, nil + } + return reconcile.Result{}, fmt.Errorf("error retrieving object from store: %w", err) + } + + if err := r.ensureAtMostOneGardenExists(ctx); err != nil { + log.Error(err, "Reconciliation prevented without automatic requeue") + return reconcile.Result{}, nil + } + + if garden.DeletionTimestamp != nil { + if result, err := r.delete(ctx, log, garden); err != nil { + return result, err + } + return reconcile.Result{}, nil + } + + if result, err := r.reconcile(ctx, log, garden); err != nil { + return result, err + } + + return reconcile.Result{}, nil +} + +func (r *Reconciler) ensureAtMostOneGardenExists(ctx context.Context) error { + gardenList := &metav1.PartialObjectMetadataList{} + gardenList.SetGroupVersionKind(operatorv1alpha1.SchemeGroupVersion.WithKind("GardenList")) + if err := r.RuntimeClientSet.Client().List(ctx, gardenList, client.Limit(2)); err != nil { + return err + } + + if len(gardenList.Items) <= 1 { + return nil + } + + return fmt.Errorf("there can be at most one operator.gardener.cloud/v1alpha1.Garden resource in the system at a time") +} + +func (r *Reconciler) reconcile( + ctx context.Context, + log logr.Logger, + garden *operatorv1alpha1.Garden, +) ( + reconcile.Result, + error, +) { + var ( + apiServerNames []string + dnsNames []string + ) + + for _, domain := range garden.Spec.VirtualCluster.DNS.Domains { + apiServerNames = append(apiServerNames, fmt.Sprintf("api.%s", domain.Name)) + dnsNames = append(dnsNames, fmt.Sprintf("*.%s", domain.Name), fmt.Sprintf("*.ingress.%s", domain.Name)) + } + + if len(dnsNames) == 0 { + return r.delete(ctx, log, garden) + } + + cert := r.newCertificate() + result, err := controllerutils.CreateOrGetAndMergePatch(ctx, r.RuntimeClientSet.Client(), cert, func() error { + cert.Spec.DNSNames = dnsNames + cert.Spec.SecretLabels = map[string]string{v1beta1constants.GardenRole: v1beta1constants.GardenRoleControlPlaneWildcardCert} + cert.Spec.SecretRef = &corev1.SecretReference{ + Name: "tls", + Namespace: r.GardenNamespace, + } + if cert.Annotations == nil { + cert.Annotations = map[string]string{} + } + cert.Annotations["cert.gardener.cloud/class"] = "garden" + cert.Annotations[certificate.TLSCertAPIServerNamesAnnotation] = strings.Join(apiServerNames, ",") + if cert.Labels == nil { + cert.Labels = map[string]string{} + } + cert.Labels[v1beta1constants.GardenRole] = v1beta1constants.GardenRoleControlPlaneWildcardCert + return nil + }) + if err != nil { + return reconcile.Result{}, fmt.Errorf("failed to create or update certificate: %w", err) + } + + switch result { + case controllerutil.OperationResultCreated: + log.Info("Created certificate", "name", cert.Name) + case controllerutil.OperationResultUpdated: + log.Info("Updated certificate", "name", cert.Name) + case controllerutil.OperationResultNone: + log.Info("Certificate unchanged", "name", cert.Name) + } + + return reconcile.Result{}, nil +} + +func (r *Reconciler) delete( + ctx context.Context, + log logr.Logger, + _ *operatorv1alpha1.Garden, +) ( + reconcile.Result, + error, +) { + cert := r.newCertificate() + if err := r.RuntimeClientSet.Client().Delete(ctx, cert); err != nil { + if apierrors.IsNotFound(err) { + return reconcile.Result{}, nil + } + return reconcile.Result{}, fmt.Errorf("failed to delete certificate: %w", err) + } + log.Info("Deleted certificate", "name", cert.Name) + + return reconcile.Result{}, nil +} + +func (r *Reconciler) newCertificate() *certv1alpha1.Certificate { + return &certv1alpha1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tls", + Namespace: r.GardenNamespace, + }, + } +} diff --git a/pkg/controller/actuator.go b/pkg/controller/shootcertservice/actuator.go similarity index 97% rename from pkg/controller/actuator.go rename to pkg/controller/shootcertservice/actuator.go index 437da493..3ff19fe3 100644 --- a/pkg/controller/actuator.go +++ b/pkg/controller/shootcertservice/actuator.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package controller +package shootcertservice import ( "context" @@ -71,7 +71,7 @@ type actuator struct { // Reconcile the Extension resource. func (a *actuator) Reconcile(ctx context.Context, log logr.Logger, ex *extensionsv1alpha1.Extension) error { if a.extensionClass == extensionsv1alpha1.ExtensionClassGarden { - return a.reconcileOnRuntimeCluster(ctx, log, ex) + return a.reconcileInGardenNamespace(ctx, log, ex) } namespace := ex.GetNamespace() @@ -367,8 +367,8 @@ func (a *actuator) createSeedResources(ctx context.Context, certConfig *service. resourceName := v1alpha1.CertManagementResourceNameSeed chartName := v1alpha1.CertManagementChartNameSeed if cluster == nil { - resourceName = v1alpha1.CertManagementResourceNameRuntime - chartName = v1alpha1.CertManagementChartNameRuntime + resourceName = v1alpha1.CertManagementResourceNameEmbedded + chartName = v1alpha1.CertManagementChartNameEmbedded } return a.createManagedResource(ctx, namespace, resourceName, "seed", renderer, chartName, namespace, certManagementConfig, nil) } @@ -466,7 +466,7 @@ func (a *actuator) createShootIssuersValues(certConfig *service.CertConfig) map[ } } -func (a *actuator) reconcileOnRuntimeCluster(ctx context.Context, _ logr.Logger, ex *extensionsv1alpha1.Extension) error { +func (a *actuator) reconcileInGardenNamespace(ctx context.Context, _ logr.Logger, ex *extensionsv1alpha1.Extension) error { namespace := ex.GetNamespace() certConfig := &service.CertConfig{} @@ -492,13 +492,13 @@ func (a *actuator) deleteOnRuntimeCluster(ctx context.Context, logger logr.Logge a.logger.Info("Deleting managed resource for runtime", "namespace", namespace) - if err := managedresources.Delete(ctx, a.client, namespace, v1alpha1.CertManagementResourceNameRuntime, false); err != nil { + if err := managedresources.Delete(ctx, a.client, namespace, v1alpha1.CertManagementResourceNameEmbedded, false); err != nil { return err } timeoutCtx, cancel := context.WithTimeout(ctx, 2*time.Minute) defer cancel() - return managedresources.WaitUntilDeleted(timeoutCtx, a.client, namespace, v1alpha1.CertManagementResourceNameRuntime) + return managedresources.WaitUntilDeleted(timeoutCtx, a.client, namespace, v1alpha1.CertManagementResourceNameEmbedded) } func mergeServers(serversList ...string) string { diff --git a/pkg/controller/add.go b/pkg/controller/shootcertservice/add.go similarity index 99% rename from pkg/controller/add.go rename to pkg/controller/shootcertservice/add.go index 9e07e4d6..a224fc57 100644 --- a/pkg/controller/add.go +++ b/pkg/controller/shootcertservice/add.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package controller +package shootcertservice import ( "context" diff --git a/pkg/webhook/sniconfig/add.go b/pkg/webhook/sniconfig/add.go new file mode 100644 index 00000000..c3111025 --- /dev/null +++ b/pkg/webhook/sniconfig/add.go @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package sniconfig + +import ( + extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook" + "github.com/gardener/gardener/pkg/resourcemanager/apis/config" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +const ( + // HandlerName is the name of the webhook handler. + HandlerName = "sni-config" + // WebhookPath is the path at which the handler should be registered. + WebhookPath = "/webhooks/sni-config" +) + +func AddToManager(mgr manager.Manager) (*extensionswebhook.Webhook, error) { + handler := &Handler{ + Logger: mgr.GetLogger().WithName("webhook").WithName(HandlerName), + TargetClient: mgr.GetClient(), + Config: config.HighAvailabilityConfigWebhookConfig{}, + Decoder: admission.NewDecoder(mgr.GetScheme()), + } + return &extensionswebhook.Webhook{ + Name: HandlerName, + Provider: "", + Action: extensionswebhook.ActionMutating, + NamespaceSelector: nil, + ObjectSelector: nil, + Path: WebhookPath, + Target: extensionswebhook.TargetSeed, + Webhook: &admission.Webhook{Handler: handler, RecoverPanic: ptr.To(true)}, + Types: []extensionswebhook.Type{ + {Obj: &appsv1.Deployment{}}, + }, + }, nil +} diff --git a/pkg/webhook/sniconfig/handler.go b/pkg/webhook/sniconfig/handler.go new file mode 100644 index 00000000..e5cd3ced --- /dev/null +++ b/pkg/webhook/sniconfig/handler.go @@ -0,0 +1,184 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package sniconfig + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "reflect" + "strings" + + "github.com/gardener/gardener/pkg/resourcemanager/apis/config" + "github.com/go-logr/logr" + admissionv1 "k8s.io/api/admission/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/gardener/gardener-extension-shoot-cert-service/pkg/controller/runtimecluster/certificate" +) + +// Handler handles admission requests and sets the following fields based on the failure tolerance type and the +// component type: +// - `.spec.replicas` +// - `.spec.template.spec.affinity` +// - `.spec.template.spec.topologySpreadConstraints` +type Handler struct { + Logger logr.Logger + TargetClient client.Reader + Config config.HighAvailabilityConfigWebhookConfig + Decoder admission.Decoder +} + +// Handle defaults the high availability settings of the provided resource. +func (h *Handler) Handle(_ context.Context, req admission.Request) admission.Response { + var ( + requestGK = schema.GroupKind{Group: req.Kind.Group, Kind: req.Kind.Kind} + obj runtime.Object + err error + ) + + switch requestGK { + case appsv1.SchemeGroupVersion.WithKind("Deployment").GroupKind(): + obj, err = h.handleDeployment(req) + default: + return admission.Allowed(fmt.Sprintf("unexpected resource: %s", requestGK)) + } + + if err != nil { + var apiStatus apierrors.APIStatus + if errors.As(err, &apiStatus) { + result := apiStatus.Status() + return admission.Response{AdmissionResponse: admissionv1.AdmissionResponse{Allowed: false, Result: &result}} + } + return admission.Denied(err.Error()) + } + if obj == nil { + return admission.Allowed("no changes required") + } + + marshalled, err := json.Marshal(obj) + if err != nil { + return admission.Errored(http.StatusInternalServerError, err) + } + return admission.PatchResponseFromRaw(req.Object.Raw, marshalled) +} + +func (h *Handler) handleDeployment(req admission.Request) (runtime.Object, error) { + if req.Name != "virtual-garden-kube-apiserver" || req.Namespace != "garden" { + return nil, nil + } + if req.Operation != admissionv1.Update && req.Operation != admissionv1.Create { + return nil, nil + } + + deployment := &appsv1.Deployment{} + if err := h.Decoder.Decode(req, deployment); err != nil { + return nil, err + } + + if err := mutateTLSCertSNI(h.Logger, deployment); err != nil { + return nil, err + } + + return deployment, nil +} + +func mutateTLSCertSNI(log logr.Logger, deployment *appsv1.Deployment) error { + updateTemplateAnnotations(deployment) + + var apiServerNames []string + if deployment.Annotations[certificate.TLSCertAPIServerNamesAnnotation] != "" { + apiServerNames = strings.Split(deployment.Annotations[certificate.TLSCertAPIServerNamesAnnotation], ",") + } + for i := range deployment.Spec.Template.Spec.Containers { + container := &deployment.Spec.Template.Spec.Containers[i] + if container.Name == "kube-apiserver" { + // remove old args + var oldAPIServerNames []string + + for i := len(container.Args) - 1; i >= 0; i-- { + if strings.HasPrefix(container.Args[i], "--tls-sni-cert-key=/srv/kubernetes/tls-sni/shoot-cert-service-injected/") { + container.Args = append(container.Args[:i], container.Args[i+1:]...) + // no break here, as there could be multiple args + } + } + // add new args + for _, apiServerName := range apiServerNames { + container.Args = append(container.Args, fmt.Sprintf( + "--tls-sni-cert-key=/srv/kubernetes/tls-sni/shoot-cert-service-injected/tls.crt,/srv/kubernetes/tls-sni/shoot-cert-service-injected/tls.key:%s", + apiServerName)) + } + if !reflect.DeepEqual(oldAPIServerNames, apiServerNames) { + log.Info("updated tls-cert-sni domain names", "domainNames", strings.Join(apiServerNames, ",")) + } + + // remove old volume mount + for i, volume := range container.VolumeMounts { + if volume.Name == "tls-sni-shoot-cert-service-injected" { + container.VolumeMounts = append(container.VolumeMounts[:i], container.VolumeMounts[i+1:]...) + break + } + } + // add new volume mount + if len(apiServerNames) > 0 { + container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{ + Name: "tls-sni-shoot-cert-service-injected", + MountPath: "/srv/kubernetes/tls-sni/shoot-cert-service-injected", + ReadOnly: true, + }) + } + } + } + // remove old volume + for i, volume := range deployment.Spec.Template.Spec.Volumes { + if volume.Name == "tls-sni-shoot-cert-service-injected" { + deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes[:i], deployment.Spec.Template.Spec.Volumes[i+1:]...) + break + } + } + // add new volume + if len(apiServerNames) > 0 { + deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes, corev1.Volume{ + Name: "tls-sni-shoot-cert-service-injected", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "tls", + DefaultMode: ptr.To(int32(416)), + }, + }, + }) + } + + return nil +} + +func updateTemplateAnnotations(deployment *appsv1.Deployment) { + certHash := deployment.Annotations[certificate.TLSCertHashAnnotation] + certRequestedAt := deployment.Annotations[certificate.TLSCertRequestedAtAnnotation] + + if deployment.Spec.Template.Annotations == nil { + deployment.Spec.Template.Annotations = map[string]string{} + } + + if certHash != "" { + deployment.Spec.Template.Annotations[certificate.TLSCertHashAnnotation] = certHash + } else { + delete(deployment.Spec.Template.Annotations, certificate.TLSCertHashAnnotation) + } + if certRequestedAt != "" { + deployment.Spec.Template.Annotations[certificate.TLSCertRequestedAtAnnotation] = certRequestedAt + } else { + delete(deployment.Spec.Template.Annotations, certificate.TLSCertRequestedAtAnnotation) + } +} diff --git a/pkg/webhook/sniconfig/handler_test.go b/pkg/webhook/sniconfig/handler_test.go new file mode 100644 index 00000000..d517d3f6 --- /dev/null +++ b/pkg/webhook/sniconfig/handler_test.go @@ -0,0 +1,266 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package sniconfig_test + +import ( + "context" + "encoding/json" + "sort" + + "github.com/gardener/gardener/pkg/client/kubernetes" + "github.com/gardener/gardener/pkg/logger" + "github.com/gardener/gardener/pkg/utils/test/matchers" + mockclient "github.com/gardener/gardener/third_party/mock/controller-runtime/client" + "github.com/go-logr/logr" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + gomegatypes "github.com/onsi/gomega/types" + "go.uber.org/mock/gomock" + "gomodules.xyz/jsonpatch/v2" + admissionv1 "k8s.io/api/admission/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" + logzap "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/gardener/gardener-extension-shoot-cert-service/pkg/controller/runtimecluster/certificate" + . "github.com/gardener/gardener-extension-shoot-cert-service/pkg/webhook/sniconfig" +) + +var _ = Describe("handler", func() { + Describe("#Handle", func() { + var ( + ctx = context.TODO() + log logr.Logger + + request admission.Request + decoder admission.Decoder + handler admission.Handler + + ctrl *gomock.Controller + c *mockclient.MockClient + + fooKind = metav1.GroupVersionKind{Group: "foo", Version: "bar", Kind: "Foo"} + deployment *appsv1.Deployment + expectedPatches []jsonpatch.JsonPatchOperation + ) + + BeforeEach(func() { + log = logger.MustNewZapLogger(logger.DebugLevel, logger.FormatJSON, logzap.WriteTo(GinkgoWriter)) + + ctrl = gomock.NewController(GinkgoT()) + c = mockclient.NewMockClient(ctrl) + + request = admission.Request{} + request.Operation = admissionv1.Update + request.Kind = metav1.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"} + + var err error + decoder = admission.NewDecoder(kubernetes.SeedScheme) + Expect(err).NotTo(HaveOccurred()) + + handler = &Handler{Logger: log, TargetClient: c, Decoder: decoder} + + deployment = &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "virtual-garden-kube-apiserver", + Namespace: "garden", + Annotations: map[string]string{ + certificate.TLSCertAPIServerNamesAnnotation: "foo.example.com", + certificate.TLSCertHashAnnotation: "1234", + certificate.TLSCertRequestedAtAnnotation: "2000-01-01T00:00:00Z", + }, + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "kube-apiserver", + Args: []string{ + "--foo=bar", + }, + }}, + }, + }, + }, + } + request.Name = deployment.Name + request.Namespace = deployment.Namespace + + expectedPatches = []jsonpatch.JsonPatchOperation{ + { + Operation: "add", + Path: "/spec/template/metadata/annotations", + Value: map[string]any{ + certificate.TLSCertRequestedAtAnnotation: "2000-01-01T00:00:00Z", + certificate.TLSCertHashAnnotation: "1234", + }, + }, + { + Operation: "add", + Path: "/spec/template/spec/volumes", + Value: []any{map[string]any{ + "name": "tls-sni-shoot-cert-service-injected", + "secret": map[string]any{ + "secretName": "tls", + "defaultMode": float64(416), + }, + }}, + }, + { + Operation: "add", + Path: "/spec/template/spec/containers/0/args/1", + Value: "--tls-sni-cert-key=/srv/kubernetes/tls-sni/shoot-cert-service-injected/tls.crt,/srv/kubernetes/tls-sni/shoot-cert-service-injected/tls.key:foo.example.com", + }, + { + Operation: "add", + Path: "/spec/template/spec/containers/0/volumeMounts", + Value: []any{map[string]any{ + "name": "tls-sni-shoot-cert-service-injected", + "readOnly": true, + "mountPath": "/srv/kubernetes/tls-sni/shoot-cert-service-injected", + }}, + }, + } + }) + + AfterEach(func() { + ctrl.Finish() + }) + + Context("ignored requests", func() { + It("should ignore other operations than CREATE or UPDATE", func() { + for _, op := range []admissionv1.Operation{admissionv1.Delete, admissionv1.Connect} { + request.Operation = op + expectAllowed(handler.Handle(ctx, request), ContainSubstring("no changes required")) + } + }) + + It("should ignore types other than deployment resources", func() { + request.Kind = fooKind + expectAllowed(handler.Handle(ctx, request), ContainSubstring("unexpected resource")) + }) + + It("should ignore other deployments than virtual-garden-kube-apiserver", func() { + deployment.Name = "foo" + prepareRequest(&request, deployment) + expectAllowed(handler.Handle(ctx, request), ContainSubstring("no changes required")) + }) + }) + + Context("mutating requests", func() { + It("should update the deployment on create operation", func() { + prepareRequest(&request, deployment) + request.Operation = admissionv1.Create + expectPatched(handler.Handle(ctx, request), expectedPatches) + }) + It("should update the deployment on update operation", func() { + prepareRequest(&request, deployment) + request.Operation = admissionv1.Update + expectPatched(handler.Handle(ctx, request), expectedPatches) + }) + It("should keep the deployment unchanged", func() { + patchDeployment(deployment) + prepareRequest(&request, deployment) + expectPatched(handler.Handle(ctx, request), nil) + }) + It("should drop additional args", func() { + patchDeployment(deployment) + deployment.Spec.Template.Spec.Containers[0].Args = append(deployment.Spec.Template.Spec.Containers[0].Args, + "--tls-sni-cert-key=/srv/kubernetes/tls-sni/shoot-cert-service-injected/tls.crt,/srv/kubernetes/tls-sni/shoot-cert-service-injected/tls.key:bar.example.com", + "--tls-sni-cert-key=/srv/kubernetes/tls-sni/shoot-cert-service-injected/tls.crt,/srv/kubernetes/tls-sni/shoot-cert-service-injected/tls.key:baz.example.com", + ) + prepareRequest(&request, deployment) + expectPatched(handler.Handle(ctx, request), []jsonpatch.JsonPatchOperation{ + { + Operation: "remove", + Path: "/spec/template/spec/containers/0/args/2", + }, + { + Operation: "remove", + Path: "/spec/template/spec/containers/0/args/3", + }, + }) + }) + It("should remove the injected stuff if deployment annotations are removed", func() { + patchDeployment(deployment) + deployment.Annotations = map[string]string{} + prepareRequest(&request, deployment) + expectPatched(handler.Handle(ctx, request), []jsonpatch.JsonPatchOperation{ + { + Operation: "remove", + Path: "/spec/template/metadata/annotations", + }, + { + Operation: "remove", + Path: "/spec/template/spec/containers/0/args/1", + }, + { + Operation: "remove", + Path: "/spec/template/spec/containers/0/volumeMounts", + }, + { + Operation: "remove", + Path: "/spec/template/spec/volumes", + }, + }) + }) + }) + }) +}) + +func expectAllowed(response admission.Response, reason gomegatypes.GomegaMatcher, optionalDescription ...any) { + ExpectWithOffset(1, response.Allowed).To(BeTrue(), optionalDescription...) + ExpectWithOffset(1, response.Result.Message).To(reason, optionalDescription...) +} + +func expectPatched(response admission.Response, expectedPatches []jsonpatch.JsonPatchOperation, optionalDescription ...any) { + ExpectWithOffset(1, response.Allowed).To(BeTrue(), optionalDescription...) + sort.Slice(response.Patches, func(i, j int) bool { + return response.Patches[i].Path < response.Patches[j].Path + }) + sort.Slice(expectedPatches, func(i, j int) bool { + return expectedPatches[i].Path < expectedPatches[j].Path + }) + ExpectWithOffset(1, response.Patches).To(matchers.DeepEqual(expectedPatches), optionalDescription...) +} + +func prepareRequest(request *admission.Request, obj *appsv1.Deployment) { + objJSON, err := json.Marshal(obj) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + request.Object = runtime.RawExtension{Raw: objJSON} + request.Name = obj.Name + request.Namespace = obj.Namespace +} + +func patchDeployment(deployment *appsv1.Deployment) { + deployment.Spec.Template.Annotations = map[string]string{ + certificate.TLSCertHashAnnotation: "1234", + certificate.TLSCertRequestedAtAnnotation: "2000-01-01T00:00:00Z", + } + deployment.Spec.Template.Spec.Containers[0].Args = append(deployment.Spec.Template.Spec.Containers[0].Args, + "--tls-sni-cert-key=/srv/kubernetes/tls-sni/shoot-cert-service-injected/tls.crt,/srv/kubernetes/tls-sni/shoot-cert-service-injected/tls.key:foo.example.com", + ) + deployment.Spec.Template.Spec.Containers[0].VolumeMounts = append(deployment.Spec.Template.Spec.Containers[0].VolumeMounts, + corev1.VolumeMount{ + Name: "tls-sni-shoot-cert-service-injected", + MountPath: "/srv/kubernetes/tls-sni/shoot-cert-service-injected", + ReadOnly: true, + }) + deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes, + corev1.Volume{ + Name: "tls-sni-shoot-cert-service-injected", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "tls", + DefaultMode: ptr.To(int32(416)), + }, + }, + }) +} diff --git a/pkg/webhook/sniconfig/sniconfig_suite_test.go b/pkg/webhook/sniconfig/sniconfig_suite_test.go new file mode 100644 index 00000000..23c2b963 --- /dev/null +++ b/pkg/webhook/sniconfig/sniconfig_suite_test.go @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package sniconfig_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestCRDDeletionProtection(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "SNI Config Webhook Suite") +}