From e574c6a8d477ebbc2a2a10155ed82a11d147e0b5 Mon Sep 17 00:00:00 2001 From: Sergey Lanzman Date: Fri, 20 Dec 2024 08:59:27 +0200 Subject: [PATCH] feat: add http basic auth for receive Signed-off-by: Sergey Lanzman --- CHANGELOG.md | 2 ++ cmd/thanos/receive.go | 8 ++++++++ pkg/receive/handler.go | 16 +++++++++++----- pkg/server/http/middleware/basic_auth.go | 20 ++++++++++++++++++++ 4 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 pkg/server/http/middleware/basic_auth.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a26715c0c..33215f55bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ We use *breaking :warning:* to mark changes that are not backward compatible (re - [#7907](https://github.com/thanos-io/thanos/pull/7907) Receive: Add `--receive.grpc-service-config` flag to configure gRPC service config for the receivers. - [#7961](https://github.com/thanos-io/thanos/pull/7961) Store Gateway: Add `--store.posting-group-max-keys` flag to mark posting group as lazy if it exceeds number of keys limit. Added `thanos_bucket_store_lazy_expanded_posting_groups_total` for total number of lazy posting groups and corresponding reasons. - [#8000](https://github.com/thanos-io/thanos/pull/8000) Query: Bump promql-engine, pass partial response through options +- [#8012](https://github.com/thanos-io/thanos/pull/8012) Receive: Add optional HTTP Basic Auth checks for incoming write requests via `--receive.basic-auth-username` and `--receive.basic-auth-password` flags. + ### Changed diff --git a/cmd/thanos/receive.go b/cmd/thanos/receive.go index 0372e83690..f3160a5dc4 100644 --- a/cmd/thanos/receive.go +++ b/cmd/thanos/receive.go @@ -264,6 +264,8 @@ func runReceive( Registry: reg, Endpoint: conf.endpoint, TenantHeader: conf.tenantHeader, + BasicAuthUsername: conf.basicAuthUsername, + BasicAuthPassword: conf.basicAuthPassword, TenantField: conf.tenantField, DefaultTenantID: conf.defaultTenantID, ReplicaHeader: conf.replicaHeader, @@ -851,6 +853,8 @@ type receiveConfig struct { refreshInterval *model.Duration endpoint string tenantHeader string + basicAuthUsername string + basicAuthPassword string tenantField string tenantLabelName string defaultTenantID string @@ -952,6 +956,10 @@ func (rc *receiveConfig) registerFlag(cmd extkingpin.FlagClause) { cmd.Flag("receive.tenant-header", "HTTP header to determine tenant for write requests.").Default(tenancy.DefaultTenantHeader).StringVar(&rc.tenantHeader) + cmd.Flag("receive.basic-auth-username", "HTTP Basic Auth username for write requests.").Default("").StringVar(&rc.basicAuthUsername) + + cmd.Flag("receive.basic-auth-password", "HTTP Basic Auth password for write requests.").Default("").StringVar(&rc.basicAuthPassword) + cmd.Flag("receive.tenant-certificate-field", "Use TLS client's certificate field to determine tenant for write requests. Must be one of "+tenancy.CertificateFieldOrganization+", "+tenancy.CertificateFieldOrganizationalUnit+" or "+tenancy.CertificateFieldCommonName+". This setting will cause the receive.tenant-header flag value to be ignored.").Default("").EnumVar(&rc.tenantField, "", tenancy.CertificateFieldOrganization, tenancy.CertificateFieldOrganizationalUnit, tenancy.CertificateFieldCommonName) cmd.Flag("receive.default-tenant-id", "Default tenant ID to use when none is provided via a header.").Default(tenancy.DefaultTenant).StringVar(&rc.defaultTenantID) diff --git a/pkg/receive/handler.go b/pkg/receive/handler.go index 86c90c30fa..adbafccb48 100644 --- a/pkg/receive/handler.go +++ b/pkg/receive/handler.go @@ -99,6 +99,8 @@ type Options struct { ListenAddress string Registry *prometheus.Registry TenantHeader string + BasicAuthUsername string + BasicAuthPassword string TenantField string DefaultTenantID string ReplicaHeader string @@ -262,16 +264,20 @@ func NewHandler(logger log.Logger, o *Options) *Handler { } return next } + handler := http.HandlerFunc(h.receiveHTTP) + + // Conditionally apply BasicAuth + if o.BasicAuthUsername != "" && o.BasicAuthPassword != "" { + level.Info(logger).Log("msg", "Basic Auth enabled for incoming requests") + handler = middleware.BasicAuth(handler, o.BasicAuthUsername, o.BasicAuthPassword) + } + handler = middleware.RequestID(handler) h.router.Post( "/api/v1/receive", instrf( "receive", - readyf( - middleware.RequestID( - http.HandlerFunc(h.receiveHTTP), - ), - ), + readyf(handler), ), ) diff --git a/pkg/server/http/middleware/basic_auth.go b/pkg/server/http/middleware/basic_auth.go new file mode 100644 index 0000000000..7f63f75fee --- /dev/null +++ b/pkg/server/http/middleware/basic_auth.go @@ -0,0 +1,20 @@ +package middleware + +import ( + "net/http" +) + +// BasicAuth returns a middleware that checks HTTP Basic Authentication. +// If the username or password do not match the expected credentials, +// a 401 Unauthorized response is returned. Otherwise, the wrapped handler +// is invoked. +func BasicAuth(next http.HandlerFunc, expectedUser, expectedPassword string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + user, pass, ok := r.BasicAuth() + if !ok || user != expectedUser || pass != expectedPassword { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + next(w, r) + } +}