From 8376ca4238adc8da4d1dd6fec0ea8d16910d95e6 Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Wed, 8 May 2024 15:26:15 -0500 Subject: [PATCH] Add certwatcher for TLS cert and key from controller-runtime - Add error for missing either tls-key or tls-cert arguments. - Move server creation and configuration to serverutil Signed-off-by: Tayler Geiger --- cmd/manager/main.go | 75 +++++++++++-------------------- internal/serverutil/serverutil.go | 70 +++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 49 deletions(-) create mode 100644 internal/serverutil/serverutil.go diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 512147f..49890a4 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -17,11 +17,8 @@ limitations under the License. package main import ( - "crypto/tls" "flag" "fmt" - "net" - "net/http" "net/url" "os" "path/filepath" @@ -41,8 +38,8 @@ import ( "github.com/operator-framework/catalogd/api/core/v1alpha1" "github.com/operator-framework/catalogd/internal/garbagecollection" + "github.com/operator-framework/catalogd/internal/serverutil" "github.com/operator-framework/catalogd/internal/source" - "github.com/operator-framework/catalogd/internal/third_party/server" "github.com/operator-framework/catalogd/internal/version" corecontrollers "github.com/operator-framework/catalogd/pkg/controllers/core" "github.com/operator-framework/catalogd/pkg/features" @@ -78,7 +75,6 @@ func main() { gcInterval time.Duration certFile string keyFile string - listener net.Listener ) flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") @@ -92,8 +88,8 @@ func main() { flag.StringVar(&cacheDir, "cache-dir", "/var/cache/", "The directory in the filesystem that catalogd will use for file based caching") flag.BoolVar(&catalogdVersion, "version", false, "print the catalogd version and exit") flag.DurationVar(&gcInterval, "gc-interval", 12*time.Hour, "interval in which garbage collection should be run against the catalog content cache") - flag.StringVar(&certFile, "tls-cert", "", "The certificate file used for serving catalog contents over HTTPS") - flag.StringVar(&keyFile, "tls-key", "", "The key file used for serving catalog contents over HTTPS") + flag.StringVar(&certFile, "tls-cert", "", "The certificate file used for serving catalog contents over HTTPS. Requires tls-key.") + flag.StringVar(&keyFile, "tls-key", "", "The key file used for serving catalog contents over HTTPS. Requires tls-cert.") opts := zap.Options{ Development: true, } @@ -110,6 +106,18 @@ func main() { } ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + if (certFile != "" && keyFile == "") || (certFile == "" && keyFile != "") { + setupLog.Error(nil, "unable to configure TLS certificates: tls-cert and tls-key flags must be used together") + os.Exit(1) + } + + protocol := "http://" + if certFile != "" && keyFile != "" { + protocol = "https://" + } + externalAddr = protocol + externalAddr + cfg := ctrl.GetConfigOrDie() mgr, err := ctrl.NewManager(cfg, ctrl.Options{ Scheme: scheme, @@ -150,31 +158,6 @@ func main() { os.Exit(1) } - if certFile != "" && keyFile != "" { - cert, err := tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - setupLog.Error(err, "unable to load certificate key pair") - os.Exit(1) - } - config := &tls.Config{ - Certificates: []tls.Certificate{cert}, - MinVersion: tls.VersionTLS13, - } - listener, err = tls.Listen("tcp", catalogServerAddr, config) - if err != nil { - setupLog.Error(err, "unable to create HTTPS server listener") - os.Exit(1) - } - externalAddr = "https://" + externalAddr - } else { - listener, err = net.Listen("tcp", catalogServerAddr) - if err != nil { - setupLog.Error(err, "unable to create HTTP server listener") - os.Exit(1) - } - externalAddr = "http://" + externalAddr - } - baseStorageURL, err := url.Parse(fmt.Sprintf("%s/catalogs/", externalAddr)) if err != nil { setupLog.Error(err, "unable to create base storage URL") @@ -182,24 +165,18 @@ func main() { } localStorage = storage.LocalDir{RootDir: storeDir, BaseURL: baseStorageURL} - shutdownTimeout := 30 * time.Second - - catalogServer := server.Server{ - Kind: "catalogs", - Server: &http.Server{ - Addr: catalogServerAddr, - Handler: catalogdmetrics.AddMetricsToHandler(localStorage.StorageServerHandler()), - ReadTimeout: 5 * time.Second, - // TODO: Revert this to 10 seconds if/when the API - // evolves to have significantly smaller responses - WriteTimeout: 5 * time.Minute, - }, - ShutdownTimeout: &shutdownTimeout, - Listener: listener, + + catalogServerConfig := serverutil.CatalogServerConfig{ + ExternalAddr: externalAddr, + CatalogAddr: catalogServerAddr, + CertFile: certFile, + KeyFile: keyFile, + LocalStorage: localStorage, } - if err := mgr.Add(&catalogServer); err != nil { - setupLog.Error(err, "unable to start catalog server") + err = serverutil.AddCatalogServerToManager(mgr, catalogServerConfig) + if err != nil { + setupLog.Error(err, "unable to configure catalog server") os.Exit(1) } @@ -236,7 +213,7 @@ func main() { Interval: gcInterval, } if err := mgr.Add(gc); err != nil { - setupLog.Error(err, "problem adding garbage collector to manager") + setupLog.Error(err, "unable to add garbage collector to manager") os.Exit(1) } diff --git a/internal/serverutil/serverutil.go b/internal/serverutil/serverutil.go new file mode 100644 index 0000000..83f8da6 --- /dev/null +++ b/internal/serverutil/serverutil.go @@ -0,0 +1,70 @@ +package serverutil + +import ( + "crypto/tls" + "fmt" + "net" + "net/http" + "time" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/certwatcher" + + "github.com/operator-framework/catalogd/internal/third_party/server" + catalogdmetrics "github.com/operator-framework/catalogd/pkg/metrics" + "github.com/operator-framework/catalogd/pkg/storage" +) + +type CatalogServerConfig struct { + ExternalAddr string + CatalogAddr string + CertFile string + KeyFile string + LocalStorage storage.Instance +} + +func AddCatalogServerToManager(mgr ctrl.Manager, cfg CatalogServerConfig) error { + listener, err := net.Listen("tcp", cfg.CatalogAddr) + if err != nil { + return fmt.Errorf("error creating catalog server listener: %w", err) + } + + if cfg.CertFile != "" && cfg.KeyFile != "" { + tlsFileWatcher, err := certwatcher.New(cfg.CertFile, cfg.KeyFile) + if err != nil { + return fmt.Errorf("error creating TLS certificate watcher: %w", err) + } + config := &tls.Config{ + GetCertificate: tlsFileWatcher.GetCertificate, + MinVersion: tls.VersionTLS12, + } + err = mgr.Add(tlsFileWatcher) + if err != nil { + return fmt.Errorf("error adding TLS certificate watcher to manager: %w", err) + } + listener = tls.NewListener(listener, config) + } + + shutdownTimeout := 30 * time.Second + + catalogServer := server.Server{ + Kind: "catalogs", + Server: &http.Server{ + Addr: cfg.CatalogAddr, + Handler: catalogdmetrics.AddMetricsToHandler(cfg.LocalStorage.StorageServerHandler()), + ReadTimeout: 5 * time.Second, + // TODO: Revert this to 10 seconds if/when the API + // evolves to have significantly smaller responses + WriteTimeout: 5 * time.Minute, + }, + ShutdownTimeout: &shutdownTimeout, + Listener: listener, + } + + err = mgr.Add(&catalogServer) + if err != nil { + return fmt.Errorf("error adding catalog server to manager: %w", err) + } + + return nil +}