From 9bc1bb84985016bbf48e9998bcbc900556eecce1 Mon Sep 17 00:00:00 2001 From: John W Higgins Date: Sun, 24 Nov 2024 21:48:16 -0800 Subject: [PATCH] Add support for https endpoint Add config.yaml stanza to specify the TLS certificate and private key. Move all "servers" (including single port variant) to a standard struct and have that struct setup the TLS listeners if applicable. --- cmd/smoothmq/server/server.go | 49 ++++++++++++++++-------------- config.yaml | 6 ++++ config/config.go | 6 ++++ dashboard/dashboard.go | 18 ++++++----- metrics/metrics.go | 53 +++++++++++++++++++++++++++++++++ protocols/sqs/sqs.go | 17 +++++++---- web/web.go | 56 +++++++++++++++++++++++++++++++++++ 7 files changed, 170 insertions(+), 35 deletions(-) create mode 100644 metrics/metrics.go create mode 100644 web/web.go diff --git a/cmd/smoothmq/server/server.go b/cmd/smoothmq/server/server.go index 926c595..bc241ae 100644 --- a/cmd/smoothmq/server/server.go +++ b/cmd/smoothmq/server/server.go @@ -10,14 +10,14 @@ import ( "time" "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/adaptor" "github.com/poundifdef/smoothmq/config" "github.com/poundifdef/smoothmq/dashboard" + "github.com/poundifdef/smoothmq/metrics" "github.com/poundifdef/smoothmq/models" "github.com/poundifdef/smoothmq/protocols/sqs" "github.com/poundifdef/smoothmq/queue/sqlite" "github.com/poundifdef/smoothmq/tenants/defaultmanager" - "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/poundifdef/smoothmq/web" ) func recordTelemetry(message string, disabled bool) { @@ -58,8 +58,9 @@ func Run(tm models.TenantManager, queue models.Queue, cfg config.ServerCommand) queue = sqlite.NewSQLiteQueue(cfg.SQLite) } - dashboardServer := dashboard.NewDashboard(queue, tm, cfg.Dashboard) - sqsServer := sqs.NewSQS(queue, tm, cfg.SQS) + dashboardServer := dashboard.NewDashboard(queue, tm, cfg.Dashboard, cfg.TLS) + sqsServer := sqs.NewSQS(queue, tm, cfg.SQS, cfg.TLS) + metricsServer := metrics.NewMetrics(cfg.Metrics, cfg.TLS) c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) @@ -73,47 +74,51 @@ func Run(tm models.TenantManager, queue models.Queue, cfg config.ServerCommand) sqsServer.Start() }() - if cfg.Metrics.PrometheusEnabled { - fmt.Printf("Prometheus metrics: http://localhost:%d%s\n", cfg.Metrics.PrometheusPort, cfg.Metrics.PrometheusPath) - go func() { - http.Handle(cfg.Metrics.PrometheusPath, promhttp.Handler()) - http.ListenAndServe(fmt.Sprintf(":%d", cfg.Metrics.PrometheusPort), nil) - }() - } + go func() { + metricsServer.Start() + }() <-c // This blocks the main thread until an interrupt is received fmt.Println("Gracefully shutting down...") dashboardServer.Stop() sqsServer.Stop() + metricsServer.Stop() } else { app := fiber.New(fiber.Config{ DisableStartupMessage: true, }) - if cfg.Dashboard.Enabled { - app.Mount("/", dashboardServer.App) - fmt.Printf("Dashboard http://localhost:%d\n", cfg.Port) - } - if cfg.SQS.Enabled { - app.Mount("/sqs", sqsServer.App) - fmt.Printf("SQS Endpoint http://localhost:%d/sqs\n", cfg.Port) + sqsServer.App.Port = cfg.Port + sqsServer.App.Path = "/sqs" + app.Mount("/sqs", sqsServer.App.FiberApp) + sqsServer.App.OutputPort() } if cfg.Metrics.PrometheusEnabled { - app.Group("/metrics", adaptor.HTTPHandler(promhttp.Handler())) - fmt.Printf("Prometheus http://localhost:%d/metrics\n", cfg.Port) + // "/metrics" is the standard path for prometheus - no need to add it here + app.Mount("", metricsServer.App.FiberApp) + metricsServer.App.Port = cfg.Port + metricsServer.App.OutputPort() + } + + // This needs to go last to avoid confliciting with prometheus + if cfg.Dashboard.Enabled { + dashboardServer.App.Port = cfg.Port + app.Mount("/", dashboardServer.App.FiberApp) + dashboardServer.App.OutputPort() } + web_app := web.Web{FiberApp: app, TLS: cfg.TLS, Port: cfg.Port} go func() { - app.Listen(fmt.Sprintf(":%d", cfg.Port)) + web_app.Start() }() <-c // This blocks the main thread until an interrupt is received fmt.Println("Gracefully shutting down...") - app.Shutdown() + web_app.Stop() } queue.Shutdown() diff --git a/config.yaml b/config.yaml index 07d1588..fae97fb 100644 --- a/config.yaml +++ b/config.yaml @@ -7,6 +7,12 @@ server: - accesskey: DEV_ACCESS_KEY_ID secretkey: DEV_SECRET_ACCESS_KEY +# Allow for tls based servers +# tls: +# cert: /shared/ssl-certs/bundle.pem +# private-key: /shared/ssl-certs/pk.pem + + dashboard: enabled: true port: 3000 diff --git a/config/config.go b/config/config.go index 0238d72..a09d5fb 100644 --- a/config/config.go +++ b/config/config.go @@ -29,6 +29,7 @@ type TesterCommand struct { type ServerCommand struct { SQS SQSConfig `embed:"" prefix:"sqs-" envprefix:"Q_SQS_"` + TLS TLSConfig `embed:"" prefix:"tls-" envprefix:"Q_TLS_"` Dashboard DashboardConfig `embed:"" prefix:"dashboard-" envprefix:"Q_DASHBOARD_"` SQLite SQLiteConfig `embed:"" prefix:"sqlite-" envprefix:"Q_SQLITE_"` Metrics MetricsConfig `embed:"" prefix:"metrics-" name:"metrics" envprefix:"Q_METRICS_"` @@ -64,6 +65,11 @@ type SQSConfig struct { EndpointOverride string `name:"endpoint-override" default:"" env:"ENDPOINT_OVERRIDE" help:"Endpoint to advertise in queue URLs. Defaults to HTTP hostname."` } +type TLSConfig struct { + Cert string `name:"cert" default:"" env:"CERT" help:"TLS Certificate"` + PrivateKey string `name:"private-key" default:"" env:"PRIVATE_KEY" help:"TLS Private Key"` +} + type AWSKey struct { AccessKey string `name:"accesskey"` SecretKey string `name:"secretkey"` diff --git a/dashboard/dashboard.go b/dashboard/dashboard.go index eaa1695..f361046 100644 --- a/dashboard/dashboard.go +++ b/dashboard/dashboard.go @@ -5,7 +5,6 @@ import ( "encoding/base64" "encoding/json" "errors" - "fmt" "io/fs" "net/http" "strconv" @@ -13,6 +12,7 @@ import ( "github.com/poundifdef/smoothmq/config" "github.com/poundifdef/smoothmq/models" + "github.com/poundifdef/smoothmq/web" "github.com/rs/zerolog/log" @@ -26,7 +26,7 @@ import ( var viewsfs embed.FS type Dashboard struct { - App *fiber.App + App *web.Web queue models.Queue tenantManager models.TenantManager @@ -34,7 +34,7 @@ type Dashboard struct { cfg config.DashboardConfig } -func NewDashboard(queue models.Queue, tenantManager models.TenantManager, cfg config.DashboardConfig) *Dashboard { +func NewDashboard(queue models.Queue, tenantManager models.TenantManager, cfg config.DashboardConfig, tls config.TLSConfig) *Dashboard { var engine *html.Engine if cfg.Dev { @@ -75,7 +75,6 @@ func NewDashboard(queue models.Queue, tenantManager models.TenantManager, cfg co } d := &Dashboard{ - App: app, queue: queue, tenantManager: tenantManager, cfg: cfg, @@ -90,6 +89,12 @@ func NewDashboard(queue models.Queue, tenantManager models.TenantManager, cfg co app.Post("/queues/:queue/delete", d.DeleteQueue) app.Get("/queues/:queue/messages/:message", d.Message) + d.App = &web.Web{ + FiberApp: app, + Port: cfg.Port, + TLS: tls, + Type: "Dashboard"} + return d } @@ -98,13 +103,12 @@ func (d *Dashboard) Start() error { return nil } - fmt.Printf("Dashboard: http://localhost:%d\n", d.cfg.Port) - return d.App.Listen(fmt.Sprintf(":%d", d.cfg.Port)) + return d.App.Start() } func (d *Dashboard) Stop() error { if d.cfg.Enabled { - return d.App.Shutdown() + return d.App.Stop() } return nil diff --git a/metrics/metrics.go b/metrics/metrics.go new file mode 100644 index 0000000..f7d4a04 --- /dev/null +++ b/metrics/metrics.go @@ -0,0 +1,53 @@ +package metrics + +import ( + "github.com/poundifdef/smoothmq/config" + "github.com/poundifdef/smoothmq/web" + "github.com/prometheus/client_golang/prometheus/promhttp" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/adaptor" +) + +type Metrics struct { + App *web.Web + + cfg config.MetricsConfig +} + +func NewMetrics(cfg config.MetricsConfig, tls config.TLSConfig) *Metrics { + app := fiber.New(fiber.Config{ + DisableStartupMessage: true, + }) + + m := &Metrics{ + cfg: cfg, + } + + m.App = &web.Web{ + FiberApp: app, + Path: "/metrics", + Port: cfg.PrometheusPort, + TLS: tls, + Type: "Prometheus Metrics"} + + m.App.FiberApp.Group(m.App.Path, adaptor.HTTPHandler(promhttp.Handler())) + + return m +} + +func (m *Metrics) Start() error { + if !m.cfg.PrometheusEnabled { + return nil + } + + return m.App.Start() +} + +func (m *Metrics) Stop() error { + if m.cfg.PrometheusEnabled { + return m.App.Stop() + } + + return nil +} diff --git a/protocols/sqs/sqs.go b/protocols/sqs/sqs.go index eb31958..c93f734 100644 --- a/protocols/sqs/sqs.go +++ b/protocols/sqs/sqs.go @@ -32,6 +32,7 @@ import ( "github.com/poundifdef/smoothmq/config" "github.com/poundifdef/smoothmq/models" + "github.com/poundifdef/smoothmq/web" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/tidwall/gjson" @@ -46,7 +47,7 @@ import ( ) type SQS struct { - App *fiber.App + App *web.Web queue models.Queue tenantManager models.TenantManager @@ -70,7 +71,7 @@ var requestStatus = promauto.NewCounterVec( []string{"tenant_id", "aws_method", "status"}, ) -func NewSQS(queue models.Queue, tenantManager models.TenantManager, cfg config.SQSConfig) *SQS { +func NewSQS(queue models.Queue, tenantManager models.TenantManager, cfg config.SQSConfig, tls config.TLSConfig) *SQS { s := &SQS{ queue: queue, tenantManager: tenantManager, @@ -91,7 +92,11 @@ func NewSQS(queue models.Queue, tenantManager models.TenantManager, cfg config.S app.Use(s.authMiddleware) app.Post("/*", s.Action) - s.App = app + s.App = &web.Web{ + FiberApp: app, + Port: cfg.Port, + TLS: tls, + Type: "SQS Endpoint"} return s } @@ -135,14 +140,14 @@ func (s *SQS) Start() error { return nil } - fmt.Printf("SQS Endpoint: http://localhost:%d\n", s.cfg.Port) - return s.App.Listen(fmt.Sprintf(":%d", s.cfg.Port)) + return s.App.Start() } func (s *SQS) Stop() error { if s.cfg.Enabled { - return s.App.Shutdown() + return s.App.Stop() } + return nil } diff --git a/web/web.go b/web/web.go new file mode 100644 index 0000000..12df653 --- /dev/null +++ b/web/web.go @@ -0,0 +1,56 @@ +package web + +import ( + "crypto/tls" + "fmt" + + "github.com/gofiber/fiber/v2" + "github.com/poundifdef/smoothmq/config" +) + +type Web struct { + FiberApp *fiber.App + Path string + Port int + TLS config.TLSConfig + Type string +} + +func (w *Web) Start() error { + port := fmt.Sprintf(":%d", w.Port) + + if w.TLS.Cert != "" { + cer, err := tls.LoadX509KeyPair(w.TLS.Cert, w.TLS.PrivateKey) + if err != nil { + panic(err) + } + + tlsCfg := &tls.Config{Certificates: []tls.Certificate{cer}} + + listener, err := tls.Listen("tcp", port, tlsCfg) + if err != nil { + panic(err) + } + + w.OutputPort() + return w.FiberApp.Listener(listener) + } + + w.OutputPort() + return w.FiberApp.Listen(port) +} + +func (w *Web) Stop() error { + return w.FiberApp.Shutdown() +} + +func (w *Web) OutputPort() { + if w.Type != "" { + scheme := "http" + if w.TLS.Cert != "" { + scheme = "https" + } + + fmt.Printf("%18s: %s://localhost:%d%s\n", w.Type, scheme, w.Port, w.Path) + } +}