diff --git a/cmd/webhook/app/BUILD.bazel b/cmd/webhook/app/BUILD.bazel index 12f803dcd..d031fdbc8 100644 --- a/cmd/webhook/app/BUILD.bazel +++ b/cmd/webhook/app/BUILD.bazel @@ -1,4 +1,4 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", @@ -8,17 +8,21 @@ go_library( deps = [ "//cmd/util:go_default_library", "//cmd/webhook/app/options:go_default_library", + "//internal/apis/config/webhook:go_default_library", "//pkg/logs:go_default_library", "//pkg/util:go_default_library", "//pkg/webhook:go_default_library", "//pkg/webhook/authority:go_default_library", + "//pkg/webhook/configfile:go_default_library", "//pkg/webhook/handlers:go_default_library", "//pkg/webhook/server:go_default_library", "//pkg/webhook/server/tls:go_default_library", "@com_github_go_logr_logr//:go_default_library", "@com_github_spf13_cobra//:go_default_library", + "@com_github_spf13_pflag//:go_default_library", "@io_k8s_client_go//kubernetes:go_default_library", "@io_k8s_client_go//tools/clientcmd:go_default_library", + "@io_k8s_component_base//cli/flag:go_default_library", ], ) @@ -39,3 +43,10 @@ filegroup( tags = ["automanaged"], visibility = ["//visibility:public"], ) + +go_test( + name = "go_default_test", + srcs = ["webhook_test.go"], + embed = [":go_default_library"], + deps = ["//cmd/webhook/app/options:go_default_library"], +) diff --git a/cmd/webhook/app/options/BUILD.bazel b/cmd/webhook/app/options/BUILD.bazel index 1d01de9a4..10f13e741 100644 --- a/cmd/webhook/app/options/BUILD.bazel +++ b/cmd/webhook/app/options/BUILD.bazel @@ -2,11 +2,17 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", - srcs = ["options.go"], + srcs = [ + "globalflags.go", + "options.go", + ], importpath = "github.com/jetstack/cert-manager/cmd/webhook/app/options", visibility = ["//visibility:public"], deps = [ - "//cmd/util:go_default_library", + "//internal/apis/config/webhook:go_default_library", + "//internal/apis/config/webhook/scheme:go_default_library", + "//pkg/apis/config/webhook/v1alpha1:go_default_library", + "//pkg/logs:go_default_library", "@com_github_spf13_pflag//:go_default_library", "@io_k8s_component_base//cli/flag:go_default_library", ], diff --git a/cmd/webhook/app/options/globalflags.go b/cmd/webhook/app/options/globalflags.go new file mode 100644 index 000000000..d442f267c --- /dev/null +++ b/cmd/webhook/app/options/globalflags.go @@ -0,0 +1,36 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package options + +import ( + "flag" + "os" + + "github.com/spf13/pflag" + + "github.com/jetstack/cert-manager/pkg/logs" +) + +func AddGlobalFlags(fs *pflag.FlagSet) { + addKlogFlags(fs) +} + +func addKlogFlags(fs *pflag.FlagSet) { + local := flag.NewFlagSet(os.Args[0], flag.ExitOnError) + logs.InitLogs(local) + fs.AddGoFlagSet(local) +} diff --git a/cmd/webhook/app/options/options.go b/cmd/webhook/app/options/options.go index f94efa15a..a683009c2 100644 --- a/cmd/webhook/app/options/options.go +++ b/cmd/webhook/app/options/options.go @@ -22,97 +22,71 @@ import ( "github.com/spf13/pflag" cliflag "k8s.io/component-base/cli/flag" - cmdutil "github.com/jetstack/cert-manager/cmd/util" + config "github.com/jetstack/cert-manager/internal/apis/config/webhook" + configscheme "github.com/jetstack/cert-manager/internal/apis/config/webhook/scheme" + configv1alpha1 "github.com/jetstack/cert-manager/pkg/apis/config/webhook/v1alpha1" ) -const ( - // Default port on which /validate, /mutate, /convert endpoints will be served - defaultListeningPort = 6443 - // Default health check port - defaultHealthPort = 6080 -) - -type WebhookOptions struct { - ListenPort int - HealthzPort int - - // Path to TLS certificate and private key on disk. - // Both must be specified if either is. - // May not be specified if DynamicServingCASecretNamespace and - // DynamicServingCASecretName are set. - TLSCertFile string - TLSKeyFile string - - // Namespace and name of the Secret resource containing the TLS certificate - // used as a CA to sign dynamic serving certificates. - // Both must be specified if either is. - // May not be specified if TLSCertFile and TLSKeyFile are set. - DynamicServingCASecretNamespace string - DynamicServingCASecretName string - // List of DNSNames that must be present on serving certificates. - DynamicServingDNSNames []string - - // Optional path to the kubeconfig used to connect to the apiserver when - // using the 'dynamic serving' certificate sources. - // If not specified, in cluster config will be used. - Kubeconfig string - APIServerHost string - - // TLSCipherSuites is the list of allowed cipher suites for the server. - // Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). - TLSCipherSuites []string - - // MinTLSVersion is the minimum TLS version supported. - // Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). - MinTLSVersion string - - // EnablePprof determines whether pprof is enabled. - EnablePprof bool - - // Address on which /debug/pprof endpoint will be served if enabled. Default is - // localhost:6060. - PprofAddress string +// WebhookFlags defines options that can only be configured via flags. +type WebhookFlags struct { + // Path to a file containing a WebhookConfiguration resource + Config string } -func (o *WebhookOptions) AddFlags(fs *pflag.FlagSet) { - // TODO: rename secure-port to listen-port - fs.IntVar(&o.ListenPort, "secure-port", defaultListeningPort, "port number to listen on for secure TLS connections") - fs.IntVar(&o.HealthzPort, "healthz-port", defaultHealthPort, "port number to listen on for insecure healthz connections") - fs.StringVar(&o.TLSCertFile, "tls-cert-file", "", "path to the file containing the TLS certificate to serve with") - fs.StringVar(&o.TLSKeyFile, "tls-private-key-file", "", "path to the file containing the TLS private key to serve with") - fs.StringVar(&o.DynamicServingCASecretNamespace, "dynamic-serving-ca-secret-namespace", "", "namespace of the secret used to store the CA that signs serving certificates") - fs.StringVar(&o.DynamicServingCASecretName, "dynamic-serving-ca-secret-name", "", "name of the secret used to store the CA that signs serving certificates certificates") - fs.StringSliceVar(&o.DynamicServingDNSNames, "dynamic-serving-dns-names", []string{""}, "DNS names that should be present on certificates generated by the dynamic serving CA") - fs.StringVar(&o.Kubeconfig, "kubeconfig", "", "optional path to the kubeconfig used to connect to the apiserver. If not specified, in-cluster-config will be used") - fs.StringVar(&o.APIServerHost, "api-server-host", "", ""+ +func NewWebhookFlags() *WebhookFlags { + return &WebhookFlags{} +} + +func (f *WebhookFlags) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&f.Config, "config", "", "Path to a file containing a WebhookConfiguration object used to configure the webhook") +} + +func ValidateWebhookFlags(f *WebhookFlags) error { + // No validation needed today + return nil +} + +func NewWebhookConfiguration() (*config.WebhookConfiguration, error) { + scheme, _, err := configscheme.NewSchemeAndCodecs() + if err != nil { + return nil, err + } + versioned := &configv1alpha1.WebhookConfiguration{} + scheme.Default(versioned) + config := &config.WebhookConfiguration{} + if err := scheme.Convert(versioned, config, nil); err != nil { + return nil, err + } + return config, nil +} + +func AddConfigFlags(fs *pflag.FlagSet, c *config.WebhookConfiguration) { + fs.IntVar(c.SecurePort, "secure-port", *c.SecurePort, "port number to listen on for secure TLS connections") + fs.IntVar(c.HealthzPort, "healthz-port", *c.HealthzPort, "port number to listen on for insecure healthz connections") + + fs.StringVar(&c.TLSConfig.Filesystem.CertFile, "tls-cert-file", c.TLSConfig.Filesystem.CertFile, "path to the file containing the TLS certificate to serve with") + fs.StringVar(&c.TLSConfig.Filesystem.KeyFile, "tls-private-key-file", c.TLSConfig.Filesystem.KeyFile, "path to the file containing the TLS private key to serve with") + + fs.StringVar(&c.TLSConfig.Dynamic.SecretNamespace, "dynamic-serving-ca-secret-namespace", c.TLSConfig.Dynamic.SecretNamespace, "namespace of the secret used to store the CA that signs serving certificates") + fs.StringVar(&c.TLSConfig.Dynamic.SecretName, "dynamic-serving-ca-secret-name", c.TLSConfig.Dynamic.SecretName, "name of the secret used to store the CA that signs serving certificates certificates") + fs.StringSliceVar(&c.TLSConfig.Dynamic.DNSNames, "dynamic-serving-dns-names", c.TLSConfig.Dynamic.DNSNames, "DNS names that should be present on certificates generated by the dynamic serving CA") + + fs.StringVar(&c.KubeConfig, "kubeconfig", c.KubeConfig, "optional path to the kubeconfig used to connect to the apiserver. If not specified, in-cluster-config will be used") + fs.StringVar(&c.APIServerHost, "api-server-host", c.APIServerHost, ""+ "Optional apiserver host address to connect to. If not specified, autoconfiguration "+ "will be attempted.") - fs.BoolVar(&o.EnablePprof, "enable-profiling", cmdutil.DefaultEnableProfiling, ""+ + fs.BoolVar(&c.EnablePprof, "enable-profiling", c.EnablePprof, ""+ "Enable profiling for controller.") - fs.StringVar(&o.PprofAddress, "profiler-address", cmdutil.DefaultProfilerAddr, + fs.StringVar(&c.PprofAddress, "profiler-address", c.PprofAddress, "Address of the Go profiler (pprof). This should never be exposed on a public interface. If this flag is not set, the profiler is not run.") tlsCipherPossibleValues := cliflag.TLSCipherPossibleValues() - fs.StringSliceVar(&o.TLSCipherSuites, "tls-cipher-suites", o.TLSCipherSuites, + fs.StringSliceVar(&c.TLSConfig.CipherSuites, "tls-cipher-suites", c.TLSConfig.CipherSuites, "Comma-separated list of cipher suites for the server. "+ "If omitted, the default Go cipher suites will be use. "+ "Possible values: "+strings.Join(tlsCipherPossibleValues, ",")) tlsPossibleVersions := cliflag.TLSPossibleVersions() - fs.StringVar(&o.MinTLSVersion, "tls-min-version", o.MinTLSVersion, + fs.StringVar(&c.TLSConfig.MinTLSVersion, "tls-min-version", c.TLSConfig.MinTLSVersion, "Minimum TLS version supported. "+ "Possible values: "+strings.Join(tlsPossibleVersions, ", ")) } - -func FileTLSSourceEnabled(o WebhookOptions) bool { - if o.TLSCertFile != "" || o.TLSKeyFile != "" { - return true - } - return false -} - -func DynamicTLSSourceEnabled(o WebhookOptions) bool { - if o.DynamicServingCASecretNamespace != "" || o.DynamicServingCASecretName != "" { - return true - } - return false -} diff --git a/cmd/webhook/app/testing/BUILD.bazel b/cmd/webhook/app/testing/BUILD.bazel index 96040395d..af0f15c3b 100644 --- a/cmd/webhook/app/testing/BUILD.bazel +++ b/cmd/webhook/app/testing/BUILD.bazel @@ -13,6 +13,7 @@ go_library( "//pkg/webhook/server:go_default_library", "@com_github_spf13_pflag//:go_default_library", "@io_k8s_apimachinery//pkg/util/wait:go_default_library", + "@io_k8s_utils//pointer:go_default_library", ], ) diff --git a/cmd/webhook/app/testing/testwebhook.go b/cmd/webhook/app/testing/testwebhook.go index 12ca600a8..1f116db1c 100644 --- a/cmd/webhook/app/testing/testwebhook.go +++ b/cmd/webhook/app/testing/testwebhook.go @@ -33,6 +33,7 @@ import ( "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/utils/pointer" "github.com/jetstack/cert-manager/cmd/webhook/app" "github.com/jetstack/cert-manager/cmd/webhook/app/options" @@ -58,10 +59,14 @@ type ServerOptions struct { } func StartWebhookServer(t *testing.T, ctx context.Context, args []string) (ServerOptions, StopFunc) { - // Allow user to override options using flags - var opts options.WebhookOptions fs := pflag.NewFlagSet("testset", pflag.ExitOnError) - opts.AddFlags(fs) + webhookFlags := options.NewWebhookFlags() + webhookConfig, err := options.NewWebhookConfiguration() + if err != nil { + t.Fatalf("Failed building test webhook config: %v", err) + } + webhookFlags.AddFlags(fs) + options.AddConfigFlags(fs, webhookConfig) // Parse the arguments passed in into the WebhookOptions struct fs.Parse(args) @@ -70,7 +75,7 @@ func StartWebhookServer(t *testing.T, ctx context.Context, args []string) (Serve if err != nil { t.Fatal(err) } - if !options.FileTLSSourceEnabled(opts) && !options.DynamicTLSSourceEnabled(opts) { + if !webhookConfig.TLSConfig.FilesystemConfigProvided() && !webhookConfig.TLSConfig.DynamicConfigProvided() { // Generate a CA and serving certificate ca, certificatePEM, privateKeyPEM, err := generateTLSAssets() if err != nil { @@ -85,17 +90,17 @@ func StartWebhookServer(t *testing.T, ctx context.Context, args []string) (Serve t.Fatal(err) } - opts.TLSKeyFile = filepath.Join(tempDir, "tls.key") - opts.TLSCertFile = filepath.Join(tempDir, "tls.crt") + webhookConfig.TLSConfig.Filesystem.KeyFile = filepath.Join(tempDir, "tls.key") + webhookConfig.TLSConfig.Filesystem.CertFile = filepath.Join(tempDir, "tls.crt") } // Listen on a random port number - opts.ListenPort = 0 - opts.HealthzPort = 0 + webhookConfig.SecurePort = pointer.Int(0) + webhookConfig.HealthzPort = pointer.Int(0) stopCh := make(chan struct{}) errCh := make(chan error) - srv, err := app.NewServerWithOptions(log, opts) + srv, err := app.NewServerWithOptions(log, *webhookFlags, *webhookConfig) if err != nil { t.Fatal(err) } diff --git a/cmd/webhook/app/webhook.go b/cmd/webhook/app/webhook.go index bf2c56791..696c32f5a 100644 --- a/cmd/webhook/app/webhook.go +++ b/cmd/webhook/app/webhook.go @@ -19,18 +19,24 @@ package app import ( "context" "fmt" + "os" + "path/filepath" "github.com/go-logr/logr" "github.com/spf13/cobra" + "github.com/spf13/pflag" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" + cliflag "k8s.io/component-base/cli/flag" cmdutil "github.com/jetstack/cert-manager/cmd/util" "github.com/jetstack/cert-manager/cmd/webhook/app/options" + config "github.com/jetstack/cert-manager/internal/apis/config/webhook" logf "github.com/jetstack/cert-manager/pkg/logs" "github.com/jetstack/cert-manager/pkg/util" "github.com/jetstack/cert-manager/pkg/webhook" "github.com/jetstack/cert-manager/pkg/webhook/authority" + "github.com/jetstack/cert-manager/pkg/webhook/configfile" "github.com/jetstack/cert-manager/pkg/webhook/handlers" "github.com/jetstack/cert-manager/pkg/webhook/server" "github.com/jetstack/cert-manager/pkg/webhook/server/tls" @@ -40,8 +46,8 @@ var validationHook handlers.ValidatingAdmissionHook = handlers.NewRegistryBacked var mutationHook handlers.MutatingAdmissionHook = handlers.NewRegistryBackedMutator(logf.Log, webhook.Scheme, webhook.MutationRegistry) var conversionHook handlers.ConversionHook = handlers.NewSchemeBackedConverter(logf.Log, webhook.Scheme) -func NewServerWithOptions(log logr.Logger, opts options.WebhookOptions) (*server.Server, error) { - restcfg, err := clientcmd.BuildConfigFromFlags(opts.APIServerHost, opts.Kubeconfig) +func NewServerWithOptions(log logr.Logger, _ options.WebhookFlags, opts config.WebhookConfiguration) (*server.Server, error) { + restcfg, err := clientcmd.BuildConfigFromFlags(opts.APIServerHost, opts.KubeConfig) if err != nil { return nil, err } @@ -54,25 +60,25 @@ func NewServerWithOptions(log logr.Logger, opts options.WebhookOptions) (*server var source tls.CertificateSource switch { - case options.FileTLSSourceEnabled(opts): - log.V(logf.InfoLevel).Info("using TLS certificate from local filesystem", "private_key_path", opts.TLSKeyFile, "certificate", opts.TLSCertFile) + case opts.TLSConfig.FilesystemConfigProvided(): + log.V(logf.InfoLevel).Info("using TLS certificate from local filesystem", "private_key_path", opts.TLSConfig.Filesystem.KeyFile, "certificate", opts.TLSConfig.Filesystem.CertFile) source = &tls.FileCertificateSource{ - CertPath: opts.TLSCertFile, - KeyPath: opts.TLSKeyFile, + CertPath: opts.TLSConfig.Filesystem.CertFile, + KeyPath: opts.TLSConfig.Filesystem.KeyFile, Log: log, } - case options.DynamicTLSSourceEnabled(opts): - restcfg, err := clientcmd.BuildConfigFromFlags("", opts.Kubeconfig) + case opts.TLSConfig.DynamicConfigProvided(): + restcfg, err := clientcmd.BuildConfigFromFlags("", opts.KubeConfig) if err != nil { return nil, err } - log.V(logf.InfoLevel).Info("using dynamic certificate generating using CA stored in Secret resource", "secret_namespace", opts.DynamicServingCASecretNamespace, "secret_name", opts.DynamicServingCASecretName) + log.V(logf.InfoLevel).Info("using dynamic certificate generating using CA stored in Secret resource", "secret_namespace", opts.TLSConfig.Dynamic.SecretNamespace, "secret_name", opts.TLSConfig.Dynamic.SecretName) source = &tls.DynamicSource{ - DNSNames: opts.DynamicServingDNSNames, + DNSNames: opts.TLSConfig.Dynamic.DNSNames, Authority: &authority.DynamicAuthority{ - SecretNamespace: opts.DynamicServingCASecretNamespace, - SecretName: opts.DynamicServingCASecretName, + SecretNamespace: opts.TLSConfig.Dynamic.SecretNamespace, + SecretName: opts.TLSConfig.Dynamic.SecretName, RESTConfig: restcfg, Log: log, }, @@ -83,13 +89,13 @@ func NewServerWithOptions(log logr.Logger, opts options.WebhookOptions) (*server } return &server.Server{ - ListenAddr: fmt.Sprintf(":%d", opts.ListenPort), - HealthzAddr: fmt.Sprintf(":%d", opts.HealthzPort), - PprofAddr: opts.PprofAddress, + ListenAddr: fmt.Sprintf(":%d", *opts.SecurePort), + HealthzAddr: fmt.Sprintf(":%d", *opts.HealthzPort), EnablePprof: opts.EnablePprof, + PprofAddr: opts.PprofAddress, CertificateSource: source, - CipherSuites: opts.TLSCipherSuites, - MinTLSVersion: opts.MinTLSVersion, + CipherSuites: opts.TLSConfig.CipherSuites, + MinTLSVersion: opts.TLSConfig.MinTLSVersion, ValidationWebhook: validationHook, MutationWebhook: mutationHook, ConversionWebhook: conversionHook, @@ -97,27 +103,163 @@ func NewServerWithOptions(log logr.Logger, opts options.WebhookOptions) (*server }, nil } +const componentWebhook = "webhook" + func NewServerCommand(stopCh <-chan struct{}) *cobra.Command { - var opts options.WebhookOptions + ctx := cmdutil.ContextWithStopCh(context.Background(), stopCh) + ctx = logf.NewContext(ctx, nil, "webhook") + log := logf.FromContext(ctx) + + cleanFlagSet := pflag.NewFlagSet(componentWebhook, pflag.ContinueOnError) + // Replaces all instances of `_` in flag names with `-` + cleanFlagSet.SetNormalizeFunc(cliflag.WordSepNormalizeFunc) + webhookFlags := options.NewWebhookFlags() + webhookConfig, err := options.NewWebhookConfiguration() + if err != nil { + log.Error(err, "Failed to create new webhook configuration") + os.Exit(1) + } cmd := &cobra.Command{ - Use: "webhook", - Short: fmt.Sprintf("Webhook component providing API validation, mutation and conversion functionality for cert-manager (%s) (%s)", util.AppVersion, util.AppGitCommit), - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmdutil.ContextWithStopCh(context.Background(), stopCh) - ctx = logf.NewContext(ctx, nil, "webhook") - log := logf.FromContext(ctx) - - srv, err := NewServerWithOptions(log, opts) - if err != nil { - return err + Use: componentWebhook, + Long: fmt.Sprintf("Webhook component providing API validation, mutation and conversion functionality for cert-manager (%s) (%s)", util.AppVersion, util.AppGitCommit), + // The webhook has special flag parsing requirements to handle precedence of providing + // configuration via versioned configuration files and flag values. + // Setting DisableFlagParsing=true prevents Cobra from interfering with flag parsing + // at all, and instead we handle it all in the RunE below. + DisableFlagParsing: true, + Run: func(cmd *cobra.Command, args []string) { + // initial flag parse, since we disable cobra's flag parsing + if err := cleanFlagSet.Parse(args); err != nil { + log.Error(err, "Failed to parse webhook flag") + cmd.Usage() + os.Exit(1) } - return srv.Run(stopCh) + // check if there are non-flag arguments in the command line + cmds := cleanFlagSet.Args() + if len(cmds) > 0 { + log.Error(nil, "Unknown command", "command", cmds[0]) + cmd.Usage() + os.Exit(1) + } + + // short-circuit on help + help, err := cleanFlagSet.GetBool("help") + if err != nil { + log.Info(`"help" flag is non-bool, programmer error, please correct`) + os.Exit(1) + } + if help { + cmd.Help() + return + } + + if err := options.ValidateWebhookFlags(webhookFlags); err != nil { + log.Error(err, "Failed to validate webhook flags") + os.Exit(1) + } + + if configFile := webhookFlags.Config; len(configFile) > 0 { + webhookConfig, err = loadConfigFile(configFile) + if err != nil { + log.Error(err, "Failed to load webhook config file", "path", configFile) + os.Exit(1) + } + + if err := webhookConfigFlagPrecedence(webhookConfig, args); err != nil { + log.Error(err, "Failed to merge flags with config file values") + os.Exit(1) + } + } + + srv, err := NewServerWithOptions(log, *webhookFlags, *webhookConfig) + if err != nil { + log.Error(err, "Failed initialising server") + os.Exit(1) + } + + if err := srv.Run(stopCh); err != nil { + log.Error(err, "Failed running server") + os.Exit(1) + } }, } - opts.AddFlags(cmd.Flags()) + webhookFlags.AddFlags(cleanFlagSet) + options.AddConfigFlags(cleanFlagSet, webhookConfig) + options.AddGlobalFlags(cleanFlagSet) + + cleanFlagSet.BoolP("help", "h", false, fmt.Sprintf("help for %s", cmd.Name())) + + // ugly, but necessary, because Cobra's default UsageFunc and HelpFunc pollute the flagset with global flags + const usageFmt = "Usage:\n %s\n\nFlags:\n%s" + cmd.SetUsageFunc(func(cmd *cobra.Command) error { + fmt.Fprintf(cmd.OutOrStderr(), usageFmt, cmd.UseLine(), cleanFlagSet.FlagUsagesWrapped(2)) + return nil + }) + cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { + fmt.Fprintf(cmd.OutOrStdout(), "%s\n\n"+usageFmt, cmd.Long, cmd.UseLine(), cleanFlagSet.FlagUsagesWrapped(2)) + }) return cmd } + +// newFlagSetWithGlobals constructs a new pflag.FlagSet with global flags registered +// on it. +func newFlagSetWithGlobals() *pflag.FlagSet { + fs := pflag.NewFlagSet("", pflag.ExitOnError) + // set the normalize func, similar to k8s.io/component-base/cli//flags.go:InitFlags + fs.SetNormalizeFunc(cliflag.WordSepNormalizeFunc) + // explicitly add flags from libs that register global flags + options.AddGlobalFlags(fs) + return fs +} + +// newFakeFlagSet constructs a pflag.FlagSet with the same flags as fs, but where +// all values have noop Set implementations +func newFakeFlagSet(fs *pflag.FlagSet) *pflag.FlagSet { + ret := pflag.NewFlagSet("", pflag.ExitOnError) + ret.SetNormalizeFunc(fs.GetNormalizeFunc()) + fs.VisitAll(func(f *pflag.Flag) { + ret.VarP(cliflag.NoOp{}, f.Name, f.Shorthand, f.Usage) + }) + return ret +} + +// webhookConfigFlagPrecedence re-parses flags over the WebhookConfiguration object. +// We must enforce flag precedence by re-parsing the command line into the new object. +// This is necessary to preserve backwards-compatibility across binary upgrades. +// See issue #56171 for more details. +func webhookConfigFlagPrecedence(cfg *config.WebhookConfiguration, args []string) error { + // We use a throwaway webhookFlags and a fake global flagset to avoid double-parses, + // as some Set implementations accumulate values from multiple flag invocations. + fs := newFakeFlagSet(newFlagSetWithGlobals()) + // register throwaway KubeletFlags + options.NewWebhookFlags().AddFlags(fs) + // register new WebhookConfiguration + options.AddConfigFlags(fs, cfg) + // re-parse flags + if err := fs.Parse(args); err != nil { + return err + } + return nil +} + +func loadConfigFile(name string) (*config.WebhookConfiguration, error) { + const errFmt = "failed to load webhook config file %s, error %v" + // compute absolute path based on current working dir + webhookConfigFile, err := filepath.Abs(name) + if err != nil { + return nil, fmt.Errorf(errFmt, name, err) + } + loader, err := configfile.NewFSLoader(configfile.NewRealFS(), webhookConfigFile) + if err != nil { + return nil, fmt.Errorf(errFmt, name, err) + } + cfg, err := loader.Load() + if err != nil { + return nil, fmt.Errorf(errFmt, name, err) + } + return cfg, nil +} diff --git a/cmd/webhook/app/webhook_test.go b/cmd/webhook/app/webhook_test.go new file mode 100644 index 000000000..7535dcb4f --- /dev/null +++ b/cmd/webhook/app/webhook_test.go @@ -0,0 +1,57 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package app + +import ( + "testing" + + "github.com/jetstack/cert-manager/cmd/webhook/app/options" +) + +// Test to ensure flags take precedence over config options. +func TestWebhookConfigFlagPrecedence_FlagsTakePrecedence(t *testing.T) { + cfg, err := options.NewWebhookConfiguration() + if err != nil { + t.Fatal(err) + } + + cfg.KubeConfig = "" + if err := webhookConfigFlagPrecedence(cfg, []string{"--kubeconfig=valid"}); err != nil { + t.Fatal(err) + } + + if cfg.KubeConfig != "valid" { + t.Errorf("unexpected field value %q, expected %q", cfg.KubeConfig, "valid") + } +} + +// Test to ensure that when flags are not provided, config provided values are preserved. +func TestWebhookConfigFlagPrecedence_ConfigPersistsWithoutFlags(t *testing.T) { + cfg, err := options.NewWebhookConfiguration() + if err != nil { + t.Fatal(err) + } + + cfg.KubeConfig = "valid" + if err := webhookConfigFlagPrecedence(cfg, []string{}); err != nil { + t.Fatal(err) + } + + if cfg.KubeConfig != "valid" { + t.Errorf("unexpected field value %q, expected %q", cfg.KubeConfig, "valid") + } +} diff --git a/deploy/charts/cert-manager/README.template.md b/deploy/charts/cert-manager/README.template.md index ce662168f..eff2eac42 100644 --- a/deploy/charts/cert-manager/README.template.md +++ b/deploy/charts/cert-manager/README.template.md @@ -137,6 +137,7 @@ The following table lists the configurable parameters of the cert-manager chart | `webhook.mutatingWebhookConfigurationAnnotations` | Annotations to add to the mutating webhook configuration | `{}` | | `webhook.validatingWebhookConfigurationAnnotations` | Annotations to add to the validating webhook configuration | `{}` | | `webhook.serviceAnnotations` | Annotations to add to the webhook service | `{}` | +| `webhook.config` | WebhookConfiguration YAML used to configure flags for the webhook. Generates a ConfigMap containing contents of the field. See `values.yaml` for example. | `{}` | | `webhook.extraArgs` | Optional flags for cert-manager webhook component | `[]` | | `webhook.serviceAccount.create` | If `true`, create a new service account for the webhook component | `true` | | `webhook.serviceAccount.name` | Service account for the webhook component to be used. If not set and `webhook.serviceAccount.create` is `true`, a name is generated using the fullname template | | diff --git a/deploy/charts/cert-manager/templates/webhook-config.yaml b/deploy/charts/cert-manager/templates/webhook-config.yaml new file mode 100644 index 000000000..29558e91a --- /dev/null +++ b/deploy/charts/cert-manager/templates/webhook-config.yaml @@ -0,0 +1,24 @@ +{{- if .Values.webhook.config -}} + {{- if not .Values.webhook.config.apiVersion -}} + {{- fail "webhook.config.apiVersion must be set" -}} + {{- end -}} + + {{- if not .Values.webhook.config.kind -}} + {{- fail "webhook.config.kind must be set" -}} + {{- end -}} +{{- end -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "webhook.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + app: {{ include "webhook.name" . }} + app.kubernetes.io/name: {{ include "webhook.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "webhook" +data: + {{- if .Values.webhook.config }} + config.yaml: | + {{ .Values.webhook.config | toYaml | nindent 4 }} + {{- end }} diff --git a/deploy/charts/cert-manager/templates/webhook-deployment.yaml b/deploy/charts/cert-manager/templates/webhook-deployment.yaml index 4822fdbff..0de1df2f0 100644 --- a/deploy/charts/cert-manager/templates/webhook-deployment.yaml +++ b/deploy/charts/cert-manager/templates/webhook-deployment.yaml @@ -61,21 +61,40 @@ spec: {{- if .Values.global.logLevel }} - --v={{ .Values.global.logLevel }} {{- end }} + {{- if .Values.webhook.config }} + - --config=/var/cert-manager/config/config.yaml + {{- end }} + {{- $config := default .Values.webhook.config "" }} + {{ if not $config.securePort -}} - --secure-port={{ .Values.webhook.securePort }} + {{- end }} + {{- $tlsConfig := default $config.tlsConfig "" }} + {{ if or (not $config.tlsConfig) (and (not $tlsConfig.dynamic) (not $tlsConfig.filesystem) ) -}} - --dynamic-serving-ca-secret-namespace=$(POD_NAMESPACE) - --dynamic-serving-ca-secret-name={{ template "webhook.fullname" . }}-ca - --dynamic-serving-dns-names={{ template "webhook.fullname" . }},{{ template "webhook.fullname" . }}.{{ .Release.Namespace }},{{ template "webhook.fullname" . }}.{{ .Release.Namespace }}.svc{{ if .Values.webhook.url.host }},{{ .Values.webhook.url.host }}{{ end }} + {{- end }} {{- with .Values.webhook.extraArgs }} {{- toYaml . | nindent 10 }} {{- end }} ports: - name: https protocol: TCP + {{- if $config.securePort }} + containerPort: {{ $config.securePort }} + {{- else if .Values.webhook.securePort }} containerPort: {{ .Values.webhook.securePort }} + {{- else }} + containerPort: 6443 + {{- end }} livenessProbe: httpGet: path: /livez + {{- if $config.healthzPort }} + port: {{ $config.healthzPort }} + {{- else }} port: 6080 + {{- end }} scheme: HTTP initialDelaySeconds: {{ .Values.webhook.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.webhook.livenessProbe.periodSeconds }} @@ -85,7 +104,11 @@ spec: readinessProbe: httpGet: path: /healthz + {{- if $config.healthzPort }} + port: {{ $config.healthzPort }} + {{- else }} port: 6080 + {{- end }} scheme: HTTP initialDelaySeconds: {{ .Values.webhook.readinessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.webhook.readinessProbe.periodSeconds }} @@ -105,6 +128,11 @@ spec: resources: {{- toYaml . | nindent 12 }} {{- end }} + {{- if .Values.webhook.config }} + volumeMounts: + - name: config + mountPath: /var/cert-manager/config + {{- end }} {{- with .Values.webhook.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} @@ -117,4 +145,9 @@ spec: tolerations: {{- toYaml . | nindent 8 }} {{- end }} - + {{- if .Values.webhook.config }} + volumes: + - name: config + configMap: + name: {{ include "webhook.fullname" . }} + {{- end }} diff --git a/deploy/charts/cert-manager/templates/webhook-service.yaml b/deploy/charts/cert-manager/templates/webhook-service.yaml index c6a4f0a30..ca3f7152c 100644 --- a/deploy/charts/cert-manager/templates/webhook-service.yaml +++ b/deploy/charts/cert-manager/templates/webhook-service.yaml @@ -25,7 +25,7 @@ spec: - name: https port: 443 protocol: TCP - targetPort: {{ .Values.webhook.securePort }} + targetPort: "https" selector: app.kubernetes.io/name: {{ include "webhook.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} diff --git a/deploy/charts/cert-manager/values.yaml b/deploy/charts/cert-manager/values.yaml index ff4081656..312f72289 100644 --- a/deploy/charts/cert-manager/values.yaml +++ b/deploy/charts/cert-manager/values.yaml @@ -207,6 +207,23 @@ webhook: replicaCount: 1 timeoutSeconds: 10 + # Used to configure options for the webhook pod. + # This allows setting options that'd usually be provided via flags. + # An APIVersion and Kind must be specified in your values.yaml file. + # Flags will override options that are set here. + config: + # apiVersion: webhook.config.cert-manager.io/v1alpha1 + # kind: WebhookConfiguration + + # The port that the webhook should listen on for requests. + # In GKE private clusters, by default kubernetes apiservers are allowed to + # talk to the cluster nodes only on 443 and 10250. so configuring + # securePort: 10250, will work out of the box without needing to add firewall + # rules or requiring NET_BIND_SERVICE capabilities to bind port numbers <1000. + # This should be uncommented and set as a default by the chart once we graduate + # the apiVersion of WebhookConfiguration past v1alpha1. + # securePort: 10250 + strategy: {} # type: RollingUpdate # rollingUpdate: diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 6461f1e82..491f32842 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -44,6 +44,8 @@ deepcopy_inputs=( pkg/apis/acme/v1beta1 \ pkg/apis/acme/v1 \ internal/apis/acme \ + pkg/apis/config/webhook/v1alpha1 \ + internal/apis/config/webhook \ pkg/apis/meta/v1 \ internal/apis/meta \ pkg/webhook/handlers/testdata/apis/testgroup/v2 \ @@ -76,6 +78,7 @@ defaulter_inputs=( internal/apis/acme/v1alpha3 \ internal/apis/acme/v1beta1 \ internal/apis/acme/v1 \ + internal/apis/config/webhook/v1alpha1 \ internal/apis/meta/v1 \ pkg/webhook/handlers/testdata/apis/testgroup/v2 \ pkg/webhook/handlers/testdata/apis/testgroup/v1 \ @@ -91,6 +94,7 @@ conversion_inputs=( internal/apis/acme/v1alpha3 \ internal/apis/acme/v1beta1 \ internal/apis/acme/v1 \ + internal/apis/config/webhook/v1alpha1 \ internal/apis/meta/v1 \ pkg/webhook/handlers/testdata/apis/testgroup/v2 \ pkg/webhook/handlers/testdata/apis/testgroup/v1 \ diff --git a/internal/BUILD.bazel b/internal/BUILD.bazel index 5f55442e9..158de8e87 100644 --- a/internal/BUILD.bazel +++ b/internal/BUILD.bazel @@ -13,6 +13,7 @@ filegroup( "//internal/api/validation:all-srcs", "//internal/apis/acme:all-srcs", "//internal/apis/certmanager:all-srcs", + "//internal/apis/config/webhook:all-srcs", "//internal/apis/meta:all-srcs", "//internal/ingress:all-srcs", "//internal/vault:all-srcs", diff --git a/internal/apis/config/webhook/BUILD.bazel b/internal/apis/config/webhook/BUILD.bazel new file mode 100644 index 000000000..c243de4b7 --- /dev/null +++ b/internal/apis/config/webhook/BUILD.bazel @@ -0,0 +1,40 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "register.go", + "types.go", + "zz_generated.deepcopy.go", + ], + importpath = "github.com/jetstack/cert-manager/internal/apis/config/webhook", + visibility = ["//:__subpackages__"], + deps = [ + "//pkg/apis/config/webhook:go_default_library", + "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", + "@io_k8s_apimachinery//pkg/runtime:go_default_library", + "@io_k8s_apimachinery//pkg/runtime/schema:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//internal/apis/config/webhook/fuzzer:all-srcs", + "//internal/apis/config/webhook/install:all-srcs", + "//internal/apis/config/webhook/scheme:all-srcs", + "//internal/apis/config/webhook/v1alpha1:all-srcs", + "//internal/apis/config/webhook/validation:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/internal/apis/config/webhook/doc.go b/internal/apis/config/webhook/doc.go new file mode 100644 index 000000000..6026527b7 --- /dev/null +++ b/internal/apis/config/webhook/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// +k8s:deepcopy-gen=package,register + +// Package webhook is the internal version of the webhook config API. +// +groupName=webhook.config.cert-manager.io +package webhook diff --git a/internal/apis/config/webhook/fuzzer/BUILD.bazel b/internal/apis/config/webhook/fuzzer/BUILD.bazel new file mode 100644 index 000000000..e20e9353d --- /dev/null +++ b/internal/apis/config/webhook/fuzzer/BUILD.bazel @@ -0,0 +1,28 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["fuzzer.go"], + importpath = "github.com/jetstack/cert-manager/internal/apis/config/webhook/fuzzer", + visibility = ["//:__subpackages__"], + deps = [ + "//internal/apis/config/webhook:go_default_library", + "@com_github_google_gofuzz//:go_default_library", + "@io_k8s_apimachinery//pkg/runtime/serializer:go_default_library", + "@io_k8s_utils//pointer:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/internal/apis/config/webhook/fuzzer/fuzzer.go b/internal/apis/config/webhook/fuzzer/fuzzer.go new file mode 100644 index 000000000..879e4eb6a --- /dev/null +++ b/internal/apis/config/webhook/fuzzer/fuzzer.go @@ -0,0 +1,41 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fuzzer + +import ( + fuzz "github.com/google/gofuzz" + runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/utils/pointer" + + "github.com/jetstack/cert-manager/internal/apis/config/webhook" +) + +// Funcs returns the fuzzer functions for the webhook config api group. +var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { + return []interface{}{ + func(s *webhook.WebhookConfiguration, c fuzz.Continue) { + c.FuzzNoCustom(s) // fuzz self without calling this function again + + if s.HealthzPort == nil { + s.HealthzPort = pointer.Int(12) + } + if s.SecurePort == nil { + s.SecurePort = pointer.Int(123) + } + }, + } +} diff --git a/internal/apis/config/webhook/install/BUILD.bazel b/internal/apis/config/webhook/install/BUILD.bazel new file mode 100644 index 000000000..513f9a130 --- /dev/null +++ b/internal/apis/config/webhook/install/BUILD.bazel @@ -0,0 +1,41 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["install.go"], + importpath = "github.com/jetstack/cert-manager/internal/apis/config/webhook/install", + visibility = ["//:__subpackages__"], + deps = [ + "//internal/apis/config/webhook:go_default_library", + "//internal/apis/config/webhook/v1alpha1:go_default_library", + "@io_k8s_apimachinery//pkg/runtime:go_default_library", + "@io_k8s_apimachinery//pkg/util/runtime:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_test", + srcs = ["roundtrip_test.go"], + data = [ + "//deploy/crds:templated_files", + ], + embed = [":go_default_library"], + deps = [ + "//internal/apis/config/webhook/fuzzer:go_default_library", + "@io_k8s_apimachinery//pkg/api/apitesting/roundtrip:go_default_library", + ], +) diff --git a/internal/apis/config/webhook/install/install.go b/internal/apis/config/webhook/install/install.go new file mode 100644 index 000000000..426dcc8f7 --- /dev/null +++ b/internal/apis/config/webhook/install/install.go @@ -0,0 +1,33 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package install installs the API group, making it available as an option to +// all of the API encoding/decoding machinery. +package install + +import ( + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + "github.com/jetstack/cert-manager/internal/apis/config/webhook" + "github.com/jetstack/cert-manager/internal/apis/config/webhook/v1alpha1" +) + +// Install registers the API group and adds types to a scheme +func Install(scheme *runtime.Scheme) { + utilruntime.Must(webhook.AddToScheme(scheme)) + utilruntime.Must(v1alpha1.AddToScheme(scheme)) +} diff --git a/internal/apis/config/webhook/install/roundtrip_test.go b/internal/apis/config/webhook/install/roundtrip_test.go new file mode 100644 index 000000000..6175d8b9f --- /dev/null +++ b/internal/apis/config/webhook/install/roundtrip_test.go @@ -0,0 +1,29 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package install + +import ( + "testing" + + "k8s.io/apimachinery/pkg/api/apitesting/roundtrip" + + configfuzzer "github.com/jetstack/cert-manager/internal/apis/config/webhook/fuzzer" +) + +func TestRoundTripTypes(t *testing.T) { + roundtrip.RoundTripTestForAPIGroup(t, Install, configfuzzer.Funcs) +} diff --git a/internal/apis/config/webhook/register.go b/internal/apis/config/webhook/register.go new file mode 100644 index 000000000..0fa94f50e --- /dev/null +++ b/internal/apis/config/webhook/register.go @@ -0,0 +1,46 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/jetstack/cert-manager/pkg/apis/config/webhook" +) + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: webhook.GroupName, Version: runtime.APIVersionInternal} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &WebhookConfiguration{}, + // Add new kinds to be registered here + ) + return nil +} diff --git a/internal/apis/config/webhook/scheme/BUILD.bazel b/internal/apis/config/webhook/scheme/BUILD.bazel new file mode 100644 index 000000000..bb3921a3f --- /dev/null +++ b/internal/apis/config/webhook/scheme/BUILD.bazel @@ -0,0 +1,28 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["scheme.go"], + importpath = "github.com/jetstack/cert-manager/internal/apis/config/webhook/scheme", + visibility = ["//:__subpackages__"], + deps = [ + "//internal/apis/config/webhook:go_default_library", + "//internal/apis/config/webhook/v1alpha1:go_default_library", + "@io_k8s_apimachinery//pkg/runtime:go_default_library", + "@io_k8s_apimachinery//pkg/runtime/serializer:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/internal/apis/config/webhook/scheme/scheme.go b/internal/apis/config/webhook/scheme/scheme.go new file mode 100644 index 000000000..e5b607768 --- /dev/null +++ b/internal/apis/config/webhook/scheme/scheme.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scheme + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + + config "github.com/jetstack/cert-manager/internal/apis/config/webhook" + configv1alpha1 "github.com/jetstack/cert-manager/internal/apis/config/webhook/v1alpha1" +) + +// NewSchemeAndCodecs is a utility function that returns a Scheme and CodecFactory +// that understand the types in the config.cert-manager.io API group. Passing mutators allows +// for adjusting the behavior of the CodecFactory, for example enable strict decoding. +func NewSchemeAndCodecs(mutators ...serializer.CodecFactoryOptionsMutator) (*runtime.Scheme, *serializer.CodecFactory, error) { + scheme := runtime.NewScheme() + if err := config.AddToScheme(scheme); err != nil { + return nil, nil, err + } + if err := configv1alpha1.AddToScheme(scheme); err != nil { + return nil, nil, err + } + codecs := serializer.NewCodecFactory(scheme, mutators...) + return scheme, &codecs, nil +} diff --git a/internal/apis/config/webhook/types.go b/internal/apis/config/webhook/types.go new file mode 100644 index 000000000..4eac795fb --- /dev/null +++ b/internal/apis/config/webhook/types.go @@ -0,0 +1,116 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type WebhookConfiguration struct { + metav1.TypeMeta + + // securePort is the port number to listen on for secure TLS connections from the kube-apiserver. + // Defaults to 6443. + SecurePort *int + + // healthzPort is the port number to listen on (using plaintext HTTP) for healthz connections. + // Defaults to 6080. + HealthzPort *int + + // tlsConfig is used to configure the secure listener's TLS settings. + TLSConfig TLSConfig + + // kubeConfig is the kubeconfig file used to connect to the Kubernetes apiserver. + // If not specified, the webhook will attempt to load the in-cluster-config. + KubeConfig string + + // apiServerHost is used to override the API server connection address. + // Deprecated: use `kubeConfig` instead. + APIServerHost string + + // enablePprof configures whether pprof is enabled. + EnablePprof bool + + // pprofAddress configures the address on which /debug/pprof endpoint will be served if enabled. + // Defaults to 'localhost:6060'. + PprofAddress string +} + +// TLSConfig configures how TLS certificates are sourced for serving. +// Only one of 'filesystem' or 'dynamic' may be specified. +type TLSConfig struct { + // cipherSuites is the list of allowed cipher suites for the server. + // Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). + // If not specified, the default for the Go version will be used and may change over time. + CipherSuites []string + + // minTLSVersion is the minimum TLS version supported. + // Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). + // If not specified, the default for the Go version will be used and may change over time. + MinTLSVersion string + + // Filesystem enables using a certificate and private key found on the local filesystem. + // These files will be periodically polled in case they have changed, and dynamically reloaded. + Filesystem FilesystemServingConfig + + // When Dynamic serving is enabled, the webhook will generate a CA used to sign webhook + // certificates and persist it into a Kubernetes Secret resource (for other replicas of the + // webhook to consume). + // It will then generate a certificate in-memory for itself using this CA to serve with. + // The CAs certificate can then be copied into the appropriate Validating, Mutating and Conversion + // webhook configuration objects (typically by cainjector). + Dynamic DynamicServingConfig +} + +func (c *TLSConfig) FilesystemConfigProvided() bool { + if c.Filesystem.KeyFile != "" || c.Filesystem.CertFile != "" { + return true + } + return false +} + +func (c *TLSConfig) DynamicConfigProvided() bool { + if c.Dynamic.SecretNamespace != "" || c.Dynamic.SecretName != "" || len(c.Dynamic.DNSNames) > 0 { + return true + } + return false +} + +// DynamicServingConfig makes the webhook generate a CA and persist it into Secret resources. +// This CA will be used by all instances of the webhook for signing serving certificates. +type DynamicServingConfig struct { + // Namespace of the Kubernetes Secret resource containing the TLS certificate + // used as a CA to sign dynamic serving certificates. + SecretNamespace string + + // Namespace of the Kubernetes Secret resource containing the TLS certificate + // used as a CA to sign dynamic serving certificates. + SecretName string + + // DNSNames that must be present on serving certificates signed by the CA. + DNSNames []string +} + +// FilesystemServingConfig enables using a certificate and private key found on the local filesystem. +// These files will be periodically polled in case they have changed, and dynamically reloaded. +type FilesystemServingConfig struct { + // Path to a file containing TLS certificate & chain to serve with + CertFile string + + // Path to a file containing a TLS private key to server with + KeyFile string +} diff --git a/internal/apis/config/webhook/v1alpha1/BUILD.bazel b/internal/apis/config/webhook/v1alpha1/BUILD.bazel new file mode 100644 index 000000000..b9e93ab06 --- /dev/null +++ b/internal/apis/config/webhook/v1alpha1/BUILD.bazel @@ -0,0 +1,38 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "conversion.go", + "defaults.go", + "doc.go", + "register.go", + "zz_generated.conversion.go", + "zz_generated.defaults.go", + ], + importpath = "github.com/jetstack/cert-manager/internal/apis/config/webhook/v1alpha1", + visibility = ["//:__subpackages__"], + deps = [ + "//internal/apis/config/webhook:go_default_library", + "//pkg/apis/config/webhook:go_default_library", + "//pkg/apis/config/webhook/v1alpha1:go_default_library", + "@io_k8s_apimachinery//pkg/conversion:go_default_library", + "@io_k8s_apimachinery//pkg/runtime:go_default_library", + "@io_k8s_apimachinery//pkg/runtime/schema:go_default_library", + "@io_k8s_utils//pointer:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/internal/apis/config/webhook/v1alpha1/conversion.go b/internal/apis/config/webhook/v1alpha1/conversion.go new file mode 100644 index 000000000..335956697 --- /dev/null +++ b/internal/apis/config/webhook/v1alpha1/conversion.go @@ -0,0 +1,17 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 diff --git a/internal/apis/config/webhook/v1alpha1/defaults.go b/internal/apis/config/webhook/v1alpha1/defaults.go new file mode 100644 index 000000000..136ec1033 --- /dev/null +++ b/internal/apis/config/webhook/v1alpha1/defaults.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/pointer" + + "github.com/jetstack/cert-manager/pkg/apis/config/webhook/v1alpha1" +) + +func addDefaultingFuncs(scheme *runtime.Scheme) error { + return RegisterDefaults(scheme) +} + +func SetDefaults_WebhookConfiguration(obj *v1alpha1.WebhookConfiguration) { + if obj.SecurePort == nil { + obj.SecurePort = pointer.Int(6443) + } + if obj.HealthzPort == nil { + obj.HealthzPort = pointer.Int(6080) + } + if obj.PprofAddress == "" { + obj.PprofAddress = "localhost:6060" + } +} diff --git a/internal/apis/config/webhook/v1alpha1/doc.go b/internal/apis/config/webhook/v1alpha1/doc.go new file mode 100644 index 000000000..7271e6668 --- /dev/null +++ b/internal/apis/config/webhook/v1alpha1/doc.go @@ -0,0 +1,23 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// +k8s:conversion-gen=github.com/jetstack/cert-manager/internal/apis/config/webhook +// +k8s:conversion-gen-external-types=github.com/jetstack/cert-manager/pkg/apis/config/webhook/v1alpha1 +// +k8s:defaulter-gen=TypeMeta +// +k8s:defaulter-gen-input=../../../../../pkg/apis/config/webhook/v1alpha1 + +// +groupName=webhook.config.cert-manager.io +package v1alpha1 diff --git a/internal/apis/config/webhook/v1alpha1/register.go b/internal/apis/config/webhook/v1alpha1/register.go new file mode 100644 index 000000000..117d5699d --- /dev/null +++ b/internal/apis/config/webhook/v1alpha1/register.go @@ -0,0 +1,44 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/jetstack/cert-manager/pkg/apis/config/webhook" + "github.com/jetstack/cert-manager/pkg/apis/config/webhook/v1alpha1" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: webhook.GroupName, Version: "v1alpha1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + localSchemeBuilder = &v1alpha1.SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addDefaultingFuncs) +} diff --git a/internal/apis/config/webhook/v1alpha1/zz_generated.conversion.go b/internal/apis/config/webhook/v1alpha1/zz_generated.conversion.go new file mode 100644 index 000000000..bcbba6ce8 --- /dev/null +++ b/internal/apis/config/webhook/v1alpha1/zz_generated.conversion.go @@ -0,0 +1,197 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by conversion-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + unsafe "unsafe" + + webhook "github.com/jetstack/cert-manager/internal/apis/config/webhook" + v1alpha1 "github.com/jetstack/cert-manager/pkg/apis/config/webhook/v1alpha1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*v1alpha1.DynamicServingConfig)(nil), (*webhook.DynamicServingConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_DynamicServingConfig_To_webhook_DynamicServingConfig(a.(*v1alpha1.DynamicServingConfig), b.(*webhook.DynamicServingConfig), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*webhook.DynamicServingConfig)(nil), (*v1alpha1.DynamicServingConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_webhook_DynamicServingConfig_To_v1alpha1_DynamicServingConfig(a.(*webhook.DynamicServingConfig), b.(*v1alpha1.DynamicServingConfig), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1alpha1.FilesystemServingConfig)(nil), (*webhook.FilesystemServingConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_FilesystemServingConfig_To_webhook_FilesystemServingConfig(a.(*v1alpha1.FilesystemServingConfig), b.(*webhook.FilesystemServingConfig), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*webhook.FilesystemServingConfig)(nil), (*v1alpha1.FilesystemServingConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_webhook_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig(a.(*webhook.FilesystemServingConfig), b.(*v1alpha1.FilesystemServingConfig), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1alpha1.TLSConfig)(nil), (*webhook.TLSConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_TLSConfig_To_webhook_TLSConfig(a.(*v1alpha1.TLSConfig), b.(*webhook.TLSConfig), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*webhook.TLSConfig)(nil), (*v1alpha1.TLSConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_webhook_TLSConfig_To_v1alpha1_TLSConfig(a.(*webhook.TLSConfig), b.(*v1alpha1.TLSConfig), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1alpha1.WebhookConfiguration)(nil), (*webhook.WebhookConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_WebhookConfiguration_To_webhook_WebhookConfiguration(a.(*v1alpha1.WebhookConfiguration), b.(*webhook.WebhookConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*webhook.WebhookConfiguration)(nil), (*v1alpha1.WebhookConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_webhook_WebhookConfiguration_To_v1alpha1_WebhookConfiguration(a.(*webhook.WebhookConfiguration), b.(*v1alpha1.WebhookConfiguration), scope) + }); err != nil { + return err + } + return nil +} + +func autoConvert_v1alpha1_DynamicServingConfig_To_webhook_DynamicServingConfig(in *v1alpha1.DynamicServingConfig, out *webhook.DynamicServingConfig, s conversion.Scope) error { + out.SecretNamespace = in.SecretNamespace + out.SecretName = in.SecretName + out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) + return nil +} + +// Convert_v1alpha1_DynamicServingConfig_To_webhook_DynamicServingConfig is an autogenerated conversion function. +func Convert_v1alpha1_DynamicServingConfig_To_webhook_DynamicServingConfig(in *v1alpha1.DynamicServingConfig, out *webhook.DynamicServingConfig, s conversion.Scope) error { + return autoConvert_v1alpha1_DynamicServingConfig_To_webhook_DynamicServingConfig(in, out, s) +} + +func autoConvert_webhook_DynamicServingConfig_To_v1alpha1_DynamicServingConfig(in *webhook.DynamicServingConfig, out *v1alpha1.DynamicServingConfig, s conversion.Scope) error { + out.SecretNamespace = in.SecretNamespace + out.SecretName = in.SecretName + out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) + return nil +} + +// Convert_webhook_DynamicServingConfig_To_v1alpha1_DynamicServingConfig is an autogenerated conversion function. +func Convert_webhook_DynamicServingConfig_To_v1alpha1_DynamicServingConfig(in *webhook.DynamicServingConfig, out *v1alpha1.DynamicServingConfig, s conversion.Scope) error { + return autoConvert_webhook_DynamicServingConfig_To_v1alpha1_DynamicServingConfig(in, out, s) +} + +func autoConvert_v1alpha1_FilesystemServingConfig_To_webhook_FilesystemServingConfig(in *v1alpha1.FilesystemServingConfig, out *webhook.FilesystemServingConfig, s conversion.Scope) error { + out.CertFile = in.CertFile + out.KeyFile = in.KeyFile + return nil +} + +// Convert_v1alpha1_FilesystemServingConfig_To_webhook_FilesystemServingConfig is an autogenerated conversion function. +func Convert_v1alpha1_FilesystemServingConfig_To_webhook_FilesystemServingConfig(in *v1alpha1.FilesystemServingConfig, out *webhook.FilesystemServingConfig, s conversion.Scope) error { + return autoConvert_v1alpha1_FilesystemServingConfig_To_webhook_FilesystemServingConfig(in, out, s) +} + +func autoConvert_webhook_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig(in *webhook.FilesystemServingConfig, out *v1alpha1.FilesystemServingConfig, s conversion.Scope) error { + out.CertFile = in.CertFile + out.KeyFile = in.KeyFile + return nil +} + +// Convert_webhook_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig is an autogenerated conversion function. +func Convert_webhook_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig(in *webhook.FilesystemServingConfig, out *v1alpha1.FilesystemServingConfig, s conversion.Scope) error { + return autoConvert_webhook_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig(in, out, s) +} + +func autoConvert_v1alpha1_TLSConfig_To_webhook_TLSConfig(in *v1alpha1.TLSConfig, out *webhook.TLSConfig, s conversion.Scope) error { + out.CipherSuites = *(*[]string)(unsafe.Pointer(&in.CipherSuites)) + out.MinTLSVersion = in.MinTLSVersion + if err := Convert_v1alpha1_FilesystemServingConfig_To_webhook_FilesystemServingConfig(&in.Filesystem, &out.Filesystem, s); err != nil { + return err + } + if err := Convert_v1alpha1_DynamicServingConfig_To_webhook_DynamicServingConfig(&in.Dynamic, &out.Dynamic, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_TLSConfig_To_webhook_TLSConfig is an autogenerated conversion function. +func Convert_v1alpha1_TLSConfig_To_webhook_TLSConfig(in *v1alpha1.TLSConfig, out *webhook.TLSConfig, s conversion.Scope) error { + return autoConvert_v1alpha1_TLSConfig_To_webhook_TLSConfig(in, out, s) +} + +func autoConvert_webhook_TLSConfig_To_v1alpha1_TLSConfig(in *webhook.TLSConfig, out *v1alpha1.TLSConfig, s conversion.Scope) error { + out.CipherSuites = *(*[]string)(unsafe.Pointer(&in.CipherSuites)) + out.MinTLSVersion = in.MinTLSVersion + if err := Convert_webhook_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig(&in.Filesystem, &out.Filesystem, s); err != nil { + return err + } + if err := Convert_webhook_DynamicServingConfig_To_v1alpha1_DynamicServingConfig(&in.Dynamic, &out.Dynamic, s); err != nil { + return err + } + return nil +} + +// Convert_webhook_TLSConfig_To_v1alpha1_TLSConfig is an autogenerated conversion function. +func Convert_webhook_TLSConfig_To_v1alpha1_TLSConfig(in *webhook.TLSConfig, out *v1alpha1.TLSConfig, s conversion.Scope) error { + return autoConvert_webhook_TLSConfig_To_v1alpha1_TLSConfig(in, out, s) +} + +func autoConvert_v1alpha1_WebhookConfiguration_To_webhook_WebhookConfiguration(in *v1alpha1.WebhookConfiguration, out *webhook.WebhookConfiguration, s conversion.Scope) error { + out.SecurePort = (*int)(unsafe.Pointer(in.SecurePort)) + out.HealthzPort = (*int)(unsafe.Pointer(in.HealthzPort)) + if err := Convert_v1alpha1_TLSConfig_To_webhook_TLSConfig(&in.TLSConfig, &out.TLSConfig, s); err != nil { + return err + } + out.KubeConfig = in.KubeConfig + out.APIServerHost = in.APIServerHost + out.EnablePprof = in.EnablePprof + out.PprofAddress = in.PprofAddress + return nil +} + +// Convert_v1alpha1_WebhookConfiguration_To_webhook_WebhookConfiguration is an autogenerated conversion function. +func Convert_v1alpha1_WebhookConfiguration_To_webhook_WebhookConfiguration(in *v1alpha1.WebhookConfiguration, out *webhook.WebhookConfiguration, s conversion.Scope) error { + return autoConvert_v1alpha1_WebhookConfiguration_To_webhook_WebhookConfiguration(in, out, s) +} + +func autoConvert_webhook_WebhookConfiguration_To_v1alpha1_WebhookConfiguration(in *webhook.WebhookConfiguration, out *v1alpha1.WebhookConfiguration, s conversion.Scope) error { + out.SecurePort = (*int)(unsafe.Pointer(in.SecurePort)) + out.HealthzPort = (*int)(unsafe.Pointer(in.HealthzPort)) + if err := Convert_webhook_TLSConfig_To_v1alpha1_TLSConfig(&in.TLSConfig, &out.TLSConfig, s); err != nil { + return err + } + out.KubeConfig = in.KubeConfig + out.APIServerHost = in.APIServerHost + out.EnablePprof = in.EnablePprof + out.PprofAddress = in.PprofAddress + return nil +} + +// Convert_webhook_WebhookConfiguration_To_v1alpha1_WebhookConfiguration is an autogenerated conversion function. +func Convert_webhook_WebhookConfiguration_To_v1alpha1_WebhookConfiguration(in *webhook.WebhookConfiguration, out *v1alpha1.WebhookConfiguration, s conversion.Scope) error { + return autoConvert_webhook_WebhookConfiguration_To_v1alpha1_WebhookConfiguration(in, out, s) +} diff --git a/internal/apis/config/webhook/v1alpha1/zz_generated.defaults.go b/internal/apis/config/webhook/v1alpha1/zz_generated.defaults.go new file mode 100644 index 000000000..786446d27 --- /dev/null +++ b/internal/apis/config/webhook/v1alpha1/zz_generated.defaults.go @@ -0,0 +1,39 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by defaulter-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/jetstack/cert-manager/pkg/apis/config/webhook/v1alpha1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// RegisterDefaults adds defaulters functions to the given scheme. +// Public to allow building arbitrary schemes. +// All generated defaulters are covering - they call all nested defaulters. +func RegisterDefaults(scheme *runtime.Scheme) error { + scheme.AddTypeDefaultingFunc(&v1alpha1.WebhookConfiguration{}, func(obj interface{}) { SetObjectDefaults_WebhookConfiguration(obj.(*v1alpha1.WebhookConfiguration)) }) + return nil +} + +func SetObjectDefaults_WebhookConfiguration(in *v1alpha1.WebhookConfiguration) { + SetDefaults_WebhookConfiguration(in) +} diff --git a/internal/apis/config/webhook/validation/BUILD.bazel b/internal/apis/config/webhook/validation/BUILD.bazel new file mode 100644 index 000000000..2b21a8ee8 --- /dev/null +++ b/internal/apis/config/webhook/validation/BUILD.bazel @@ -0,0 +1,26 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["validation.go"], + importpath = "github.com/jetstack/cert-manager/internal/apis/config/webhook/validation", + visibility = ["//:__subpackages__"], + deps = [ + "//internal/apis/config/webhook:go_default_library", + "@io_k8s_apimachinery//pkg/util/errors:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/internal/apis/config/webhook/validation/validation.go b/internal/apis/config/webhook/validation/validation.go new file mode 100644 index 000000000..689aea827 --- /dev/null +++ b/internal/apis/config/webhook/validation/validation.go @@ -0,0 +1,58 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validation + +import ( + "fmt" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + + config "github.com/jetstack/cert-manager/internal/apis/config/webhook" +) + +func ValidateWebhookConfiguration(cfg *config.WebhookConfiguration) error { + var allErrors []error + if cfg.TLSConfig.FilesystemConfigProvided() && cfg.TLSConfig.DynamicConfigProvided() { + allErrors = append(allErrors, fmt.Errorf("invalid configuration: cannot specify both filesystem based and dynamic TLS configuration")) + } else { + if cfg.TLSConfig.FilesystemConfigProvided() { + if cfg.TLSConfig.Filesystem.KeyFile == "" { + allErrors = append(allErrors, fmt.Errorf("invalid configuration: tlsConfig.filesystem.keyFile (--tls-private-key-file) must be specified when using filesystem based TLS config")) + } + if cfg.TLSConfig.Filesystem.CertFile == "" { + allErrors = append(allErrors, fmt.Errorf("invalid configuration: tlsConfig.filesystem.certFile (--tls-cert-file) must be specified when using filesystem based TLS config")) + } + } else if cfg.TLSConfig.DynamicConfigProvided() { + if cfg.TLSConfig.Dynamic.SecretNamespace == "" { + allErrors = append(allErrors, fmt.Errorf("invalid configuration: tlsConfig.dynamic.secretNamespace (--dynamic-serving-ca-secret-namespace) must be specified when using dynamic TLS config")) + } + if cfg.TLSConfig.Dynamic.SecretName == "" { + allErrors = append(allErrors, fmt.Errorf("invalid configuration: tlsConfig.dynamic.secretName (--dynamic-serving-ca-secret-name) must be specified when using dynamic TLS config")) + } + if len(cfg.TLSConfig.Dynamic.DNSNames) == 0 { + allErrors = append(allErrors, fmt.Errorf("invalid configuration: tlsConfig.dynamic.dnsNames (--dynamic-serving-dns-names) must be specified when using dynamic TLS config")) + } + } + } + if cfg.HealthzPort == nil { + allErrors = append(allErrors, fmt.Errorf("invalid configuration: healthzPort must be specified")) + } + if cfg.SecurePort == nil { + allErrors = append(allErrors, fmt.Errorf("invalid configuration: securePort must be specified")) + } + return utilerrors.NewAggregate(allErrors) +} diff --git a/internal/apis/config/webhook/zz_generated.deepcopy.go b/internal/apis/config/webhook/zz_generated.deepcopy.go new file mode 100644 index 000000000..862f4692d --- /dev/null +++ b/internal/apis/config/webhook/zz_generated.deepcopy.go @@ -0,0 +1,122 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package webhook + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DynamicServingConfig) DeepCopyInto(out *DynamicServingConfig) { + *out = *in + if in.DNSNames != nil { + in, out := &in.DNSNames, &out.DNSNames + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicServingConfig. +func (in *DynamicServingConfig) DeepCopy() *DynamicServingConfig { + if in == nil { + return nil + } + out := new(DynamicServingConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FilesystemServingConfig) DeepCopyInto(out *FilesystemServingConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FilesystemServingConfig. +func (in *FilesystemServingConfig) DeepCopy() *FilesystemServingConfig { + if in == nil { + return nil + } + out := new(FilesystemServingConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSConfig) DeepCopyInto(out *TLSConfig) { + *out = *in + if in.CipherSuites != nil { + in, out := &in.CipherSuites, &out.CipherSuites + *out = make([]string, len(*in)) + copy(*out, *in) + } + out.Filesystem = in.Filesystem + in.Dynamic.DeepCopyInto(&out.Dynamic) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSConfig. +func (in *TLSConfig) DeepCopy() *TLSConfig { + if in == nil { + return nil + } + out := new(TLSConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WebhookConfiguration) DeepCopyInto(out *WebhookConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.SecurePort != nil { + in, out := &in.SecurePort, &out.SecurePort + *out = new(int) + **out = **in + } + if in.HealthzPort != nil { + in, out := &in.HealthzPort, &out.HealthzPort + *out = new(int) + **out = **in + } + in.TLSConfig.DeepCopyInto(&out.TLSConfig) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookConfiguration. +func (in *WebhookConfiguration) DeepCopy() *WebhookConfiguration { + if in == nil { + return nil + } + out := new(WebhookConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *WebhookConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/pkg/apis/BUILD.bazel b/pkg/apis/BUILD.bazel index f045e68a8..92d78d3b3 100644 --- a/pkg/apis/BUILD.bazel +++ b/pkg/apis/BUILD.bazel @@ -20,6 +20,7 @@ filegroup( ":package-srcs", "//pkg/apis/acme:all-srcs", "//pkg/apis/certmanager:all-srcs", + "//pkg/apis/config/webhook:all-srcs", "//pkg/apis/experimental:all-srcs", "//pkg/apis/meta:all-srcs", ], diff --git a/pkg/apis/config/webhook/BUILD.bazel b/pkg/apis/config/webhook/BUILD.bazel new file mode 100644 index 000000000..4faa3628d --- /dev/null +++ b/pkg/apis/config/webhook/BUILD.bazel @@ -0,0 +1,25 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["doc.go"], + importpath = "github.com/jetstack/cert-manager/pkg/apis/config/webhook", + visibility = ["//visibility:public"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/apis/config/webhook/v1alpha1:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/apis/config/webhook/doc.go b/pkg/apis/config/webhook/doc.go new file mode 100644 index 000000000..d90c90388 --- /dev/null +++ b/pkg/apis/config/webhook/doc.go @@ -0,0 +1,22 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// +groupName=webhook.config.cert-manager.io + +// Package webhook contains types used to configure the webhook +package webhook + +const GroupName = "webhook.config.cert-manager.io" diff --git a/pkg/apis/config/webhook/v1alpha1/BUILD.bazel b/pkg/apis/config/webhook/v1alpha1/BUILD.bazel new file mode 100644 index 000000000..151a284e8 --- /dev/null +++ b/pkg/apis/config/webhook/v1alpha1/BUILD.bazel @@ -0,0 +1,33 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "register.go", + "types.go", + "zz_generated.deepcopy.go", + ], + importpath = "github.com/jetstack/cert-manager/pkg/apis/config/webhook/v1alpha1", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/config/webhook:go_default_library", + "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", + "@io_k8s_apimachinery//pkg/runtime:go_default_library", + "@io_k8s_apimachinery//pkg/runtime/schema:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/apis/config/webhook/v1alpha1/doc.go b/pkg/apis/config/webhook/v1alpha1/doc.go new file mode 100644 index 000000000..50a51f182 --- /dev/null +++ b/pkg/apis/config/webhook/v1alpha1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1alpha1 is the v1alpha1 version of the webhook config API. +// +k8s:deepcopy-gen=package,register +// +groupName=webhook.config.cert-manager.io +package v1alpha1 diff --git a/pkg/apis/config/webhook/v1alpha1/register.go b/pkg/apis/config/webhook/v1alpha1/register.go new file mode 100644 index 000000000..df9f247eb --- /dev/null +++ b/pkg/apis/config/webhook/v1alpha1/register.go @@ -0,0 +1,56 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/jetstack/cert-manager/pkg/apis/config/webhook" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: webhook.GroupName, Version: "v1alpha1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder runtime.SchemeBuilder + localSchemeBuilder = &SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addKnownTypes) +} + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &WebhookConfiguration{}, + // Add new kinds to be registered here + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/pkg/apis/config/webhook/v1alpha1/types.go b/pkg/apis/config/webhook/v1alpha1/types.go new file mode 100644 index 000000000..441bcbd9d --- /dev/null +++ b/pkg/apis/config/webhook/v1alpha1/types.go @@ -0,0 +1,102 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type WebhookConfiguration struct { + metav1.TypeMeta `json:",inline"` + + // securePort is the port number to listen on for secure TLS connections from the kube-apiserver. + // Defaults to 6443. + SecurePort *int `json:"securePort,omitempty"` + + // healthzPort is the port number to listen on (using plaintext HTTP) for healthz connections. + // Defaults to 6080. + HealthzPort *int `json:"healthzPort,omitempty"` + + // tlsConfig is used to configure the secure listener's TLS settings. + TLSConfig TLSConfig `json:"tlsConfig"` + + // kubeConfig is the kubeconfig file used to connect to the Kubernetes apiserver. + // If not specified, the webhook will attempt to load the in-cluster-config. + KubeConfig string `json:"kubeConfig,omitempty"` + + // apiServerHost is used to override the API server connection address. + // Deprecated: use `kubeConfig` instead. + APIServerHost string `json:"apiServerHost,omitempty"` + + // enablePprof configures whether pprof is enabled. + EnablePprof bool `json:"enablePprof"` + + // pprofAddress configures the address on which /debug/pprof endpoint will be served if enabled. + // Defaults to 'localhost:6060'. + PprofAddress string `json:"pprofAddress,omitempty"` +} + +// TLSConfig configures how TLS certificates are sourced for serving. +// Only one of 'filesystem' or 'dynamic' may be specified. +type TLSConfig struct { + // cipherSuites is the list of allowed cipher suites for the server. + // Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). + // If not specified, the default for the Go version will be used and may change over time. + CipherSuites []string `json:"cipherSuites,omitempty"` + + // minTLSVersion is the minimum TLS version supported. + // Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). + // If not specified, the default for the Go version will be used and may change over time. + MinTLSVersion string `json:"minTLSVersion,omitempty"` + + // Filesystem enables using a certificate and private key found on the local filesystem. + // These files will be periodically polled in case they have changed, and dynamically reloaded. + Filesystem FilesystemServingConfig `json:"filesystem"` + + // When Dynamic serving is enabled, the webhook will generate a CA used to sign webhook + // certificates and persist it into a Kubernetes Secret resource (for other replicas of the + // webhook to consume). + // It will then generate a certificate in-memory for itself using this CA to serve with. + // The CAs certificate can then be copied into the appropriate Validating, Mutating and Conversion + // webhook configuration objects (typically by cainjector). + Dynamic DynamicServingConfig `json:"dynamic"` +} + +// DynamicServingConfig makes the webhook generate a CA and persist it into Secret resources. +// This CA will be used by all instances of the webhook for signing serving certificates. +type DynamicServingConfig struct { + // Namespace of the Kubernetes Secret resource containing the TLS certificate + // used as a CA to sign dynamic serving certificates. + SecretNamespace string `json:"secretNamespace,omitempty"` + + // Namespace of the Kubernetes Secret resource containing the TLS certificate + // used as a CA to sign dynamic serving certificates. + SecretName string `json:"secretName,omitempty"` + + // DNSNames that must be present on serving certificates signed by the CA. + DNSNames []string `json:"dnsNames,omitempty"` +} + +// FilesystemServingConfig enables using a certificate and private key found on the local filesystem. +// These files will be periodically polled in case they have changed, and dynamically reloaded. +type FilesystemServingConfig struct { + // Path to a file containing TLS certificate & chain to serve with + CertFile string `json:"certFile,omitempty"` + + // Path to a file containing a TLS private key to server with + KeyFile string `json:"keyFile,omitempty"` +} diff --git a/pkg/apis/config/webhook/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/config/webhook/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 000000000..3faedb8d1 --- /dev/null +++ b/pkg/apis/config/webhook/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,122 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DynamicServingConfig) DeepCopyInto(out *DynamicServingConfig) { + *out = *in + if in.DNSNames != nil { + in, out := &in.DNSNames, &out.DNSNames + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicServingConfig. +func (in *DynamicServingConfig) DeepCopy() *DynamicServingConfig { + if in == nil { + return nil + } + out := new(DynamicServingConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FilesystemServingConfig) DeepCopyInto(out *FilesystemServingConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FilesystemServingConfig. +func (in *FilesystemServingConfig) DeepCopy() *FilesystemServingConfig { + if in == nil { + return nil + } + out := new(FilesystemServingConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSConfig) DeepCopyInto(out *TLSConfig) { + *out = *in + if in.CipherSuites != nil { + in, out := &in.CipherSuites, &out.CipherSuites + *out = make([]string, len(*in)) + copy(*out, *in) + } + out.Filesystem = in.Filesystem + in.Dynamic.DeepCopyInto(&out.Dynamic) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSConfig. +func (in *TLSConfig) DeepCopy() *TLSConfig { + if in == nil { + return nil + } + out := new(TLSConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WebhookConfiguration) DeepCopyInto(out *WebhookConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.SecurePort != nil { + in, out := &in.SecurePort, &out.SecurePort + *out = new(int) + **out = **in + } + if in.HealthzPort != nil { + in, out := &in.HealthzPort, &out.HealthzPort + *out = new(int) + **out = **in + } + in.TLSConfig.DeepCopyInto(&out.TLSConfig) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookConfiguration. +func (in *WebhookConfiguration) DeepCopy() *WebhookConfiguration { + if in == nil { + return nil + } + out := new(WebhookConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *WebhookConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/pkg/webhook/BUILD.bazel b/pkg/webhook/BUILD.bazel index 894cc84b5..033dd304d 100644 --- a/pkg/webhook/BUILD.bazel +++ b/pkg/webhook/BUILD.bazel @@ -27,6 +27,7 @@ filegroup( srcs = [ ":package-srcs", "//pkg/webhook/authority:all-srcs", + "//pkg/webhook/configfile:all-srcs", "//pkg/webhook/handlers:all-srcs", "//pkg/webhook/server:all-srcs", ], diff --git a/pkg/webhook/configfile/BUILD.bazel b/pkg/webhook/configfile/BUILD.bazel new file mode 100644 index 000000000..643e89c2e --- /dev/null +++ b/pkg/webhook/configfile/BUILD.bazel @@ -0,0 +1,33 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["configfile.go"], + importpath = "github.com/jetstack/cert-manager/pkg/webhook/configfile", + visibility = ["//visibility:public"], + deps = [ + "//internal/apis/config/webhook:go_default_library", + "//internal/apis/config/webhook/scheme:go_default_library", + "@io_k8s_apimachinery//pkg/runtime/serializer:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_test", + srcs = ["configfile_test.go"], + embed = [":go_default_library"], +) diff --git a/pkg/webhook/configfile/configfile.go b/pkg/webhook/configfile/configfile.go new file mode 100644 index 000000000..65b874fcf --- /dev/null +++ b/pkg/webhook/configfile/configfile.go @@ -0,0 +1,124 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package configfile + +import ( + "fmt" + "io/ioutil" + "path/filepath" + + "k8s.io/apimachinery/pkg/runtime/serializer" + + config "github.com/jetstack/cert-manager/internal/apis/config/webhook" + "github.com/jetstack/cert-manager/internal/apis/config/webhook/scheme" +) + +// Filesystem is an interface used to mock out calls to ReadFile +type Filesystem interface { + ReadFile(filename string) ([]byte, error) +} + +type realFS struct{} + +func (fs realFS) ReadFile(filename string) ([]byte, error) { + return ioutil.ReadFile(filename) +} + +// NewRealFS builds a Filesystem that wraps around `ioutil.ReadFile`. +func NewRealFS() Filesystem { + return realFS{} +} + +type Loader interface { + Load() (*config.WebhookConfiguration, error) +} + +type fsLoader struct { + fs Filesystem + filename string + codec *serializer.CodecFactory +} + +var _ Loader = &fsLoader{} + +func (f *fsLoader) Load() (*config.WebhookConfiguration, error) { + data, err := f.fs.ReadFile(f.filename) + if err != nil { + return nil, fmt.Errorf("failed to read webhook config file %q, error: %v", f.filename, err) + } + + if len(data) == 0 { + return nil, fmt.Errorf("webhook config file %q was empty", f.filename) + } + + cfg, err := decodeWebhookConfiguration(f.codec, data) + if err != nil { + return nil, err + } + + // make all paths absolute + resolveRelativePaths(webhookConfigurationPathRefs(cfg), filepath.Dir(f.filename)) + return cfg, nil +} + +func NewFSLoader(fs Filesystem, name string) (Loader, error) { + _, webhookCodec, err := scheme.NewSchemeAndCodecs(serializer.EnableStrict) + if err != nil { + return nil, err + } + + return &fsLoader{ + fs: fs, + filename: name, + codec: webhookCodec, + }, nil +} + +func resolveRelativePaths(paths []*string, root string) { + for _, path := range paths { + // leave empty paths alone, "no path" is a valid input + // do not attempt to resolve paths that are already absolute + if len(*path) > 0 && !filepath.IsAbs(*path) { + *path = filepath.Join(root, *path) + } + } +} + +func decodeWebhookConfiguration(codec *serializer.CodecFactory, data []byte) (*config.WebhookConfiguration, error) { + obj, gvk, err := codec.UniversalDecoder().Decode(data, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to decode: %w", err) + } + + internalObj, ok := obj.(*config.WebhookConfiguration) + if !ok { + return nil, fmt.Errorf("failed to cast object to WebhookConfiguration, unexpected type: %v", gvk) + } + + return internalObj, nil +} + +// webhookConfigurationPathRefs returns pointers to all the WebhookConfiguration fields that contain filepaths. +// You might use this, for example, to resolve all relative paths against some common root before +// passing the configuration to the application. This method must be kept up to date as new fields are added. +func webhookConfigurationPathRefs(cfg *config.WebhookConfiguration) []*string { + return []*string{ + &cfg.TLSConfig.Filesystem.KeyFile, + &cfg.TLSConfig.Filesystem.CertFile, + &cfg.KubeConfig, + } +} diff --git a/pkg/webhook/configfile/configfile_test.go b/pkg/webhook/configfile/configfile_test.go new file mode 100644 index 000000000..475960b30 --- /dev/null +++ b/pkg/webhook/configfile/configfile_test.go @@ -0,0 +1,63 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package configfile + +import ( + "fmt" + "testing" +) + +func TestFSLoader_Load(t *testing.T) { + const expectedFilename = "/path/to/config/file" + const kubeConfigPath = "path/to/kubeconfig/file" + + loader, err := NewFSLoader(newFakeFS(func(filename string) ([]byte, error) { + if filename != expectedFilename { + t.Fatalf("unexpected filename %q passed to ReadFile", filename) + return nil, fmt.Errorf("unexpected filename %q", filename) + } + return []byte(fmt.Sprintf(`apiVersion: webhook.config.cert-manager.io/v1alpha1 +kind: WebhookConfiguration +kubeConfig: %s`, kubeConfigPath)), nil + }), expectedFilename) + if err != nil { + t.Fatal(err) + } + + cfg, err := loader.Load() + if err != nil { + t.Fatal(err) + } + + // the config loader will force paths to be 'absolute' if they are provided as relative. + absKubeConfigPath := "/path/to/config/path/to/kubeconfig/file" + if cfg.KubeConfig != absKubeConfigPath { + t.Errorf("expected kubeConfig to be set to %q but got %q", absKubeConfigPath, cfg.KubeConfig) + } +} + +func newFakeFS(readFileFunc func(string) ([]byte, error)) Filesystem { + return fakeFS{readFileFunc: readFileFunc} +} + +type fakeFS struct { + readFileFunc func(string) ([]byte, error) +} + +func (f fakeFS) ReadFile(filename string) ([]byte, error) { + return f.readFileFunc(filename) +}