Skip to content

Commit

Permalink
feat(server/http): allow Basic Auth to be configurable for web UI
Browse files Browse the repository at this point in the history
Signed-off-by: hainenber <[email protected]>
  • Loading branch information
hainenber committed Dec 22, 2023
1 parent 5e9df54 commit e339791
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 16 deletions.
8 changes: 6 additions & 2 deletions cmd/internal/flowmode/cmd_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ depending on the nature of the reload error.
cmd.Flags().
StringVar(&r.httpListenAddr, "server.http.listen-addr", r.httpListenAddr, "Address to listen for HTTP traffic on")
cmd.Flags().StringVar(&r.inMemoryAddr, "server.http.memory-addr", r.inMemoryAddr, "Address to listen for in-memory HTTP traffic on. Change if it collides with a real address")
cmd.Flags().StringVar(&r.basicAuthUserFilepath, "server.http.basic-auth-filepath", r.basicAuthUserFilepath, "Absolute path to file containing user and hashed password for Basic Auth")
cmd.Flags().StringVar(&r.storagePath, "storage.path", r.storagePath, "Base directory where components can store data")
cmd.Flags().StringVar(&r.uiPrefix, "server.http.ui-path-prefix", r.uiPrefix, "Prefix to serve the HTTP UI at")
cmd.Flags().
Expand Down Expand Up @@ -130,6 +131,7 @@ depending on the nature of the reload error.
type flowRun struct {
inMemoryAddr string
httpListenAddr string
basicAuthUserFilepath string
storagePath string
uiPrefix string
enablePprof bool
Expand Down Expand Up @@ -238,8 +240,10 @@ func (fr *flowRun) Run(configPath string) error {
})

uiService := uiservice.New(uiservice.Options{
UIPrefix: fr.uiPrefix,
Cluster: clusterService.Data().(cluster.Cluster),
BasicAuthUserFilepath: fr.basicAuthUserFilepath,
Cluster: clusterService.Data().(cluster.Cluster),
Logger: log.With(l, "service", "http"),
UIPrefix: fr.uiPrefix,
})

otelService := otel_service.New(l)
Expand Down
1 change: 1 addition & 0 deletions docs/sources/flow/reference/cli/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ The following flags are supported:
(default `agent.internal:12345`).
* `--server.http.listen-addr`: Address to listen for HTTP traffic on (default `127.0.0.1:12345`).
* `--server.http.ui-path-prefix`: Base path where the UI is exposed (default `/`).
* `server.http.basic-auth-filepath`: Absolute path to file containing user and hashed password for Basic Auth (default `""`, signifies disabled).
* `--storage.path`: Base directory where components can store data (default `data-agent/`).
* `--disable-reporting`: Disable [data collection][] (default `false`).
* `--cluster.enabled`: Start {{< param "PRODUCT_NAME" >}} in clustered mode (default `false`).
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,11 @@ require (
go.uber.org/goleak v1.2.1
go.uber.org/multierr v1.11.0
go.uber.org/zap v1.26.0
golang.org/x/crypto v0.15.0
golang.org/x/crypto v0.17.0
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
golang.org/x/net v0.18.0
golang.org/x/oauth2 v0.13.0
golang.org/x/sys v0.14.1-0.20231108175955-e4099bfacb8c
golang.org/x/sys v0.15.0
golang.org/x/text v0.14.0
golang.org/x/time v0.3.0
google.golang.org/api v0.146.0
Expand Down Expand Up @@ -545,7 +545,7 @@ require (
github.com/subosito/gotenv v1.4.2 // indirect
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
github.com/tencentcloud/tencentcloud-sdk-go v1.0.162 // indirect
github.com/tg123/go-htpasswd v1.2.1 // indirect
github.com/tg123/go-htpasswd v1.2.2
github.com/tilinna/clock v1.1.0
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
Expand Down Expand Up @@ -583,7 +583,7 @@ require (
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/term v0.14.0 // indirect
golang.org/x/term v0.15.0 // indirect
golang.org/x/tools v0.15.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
Expand Down
16 changes: 8 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2189,8 +2189,8 @@ github.com/testcontainers/testcontainers-go v0.25.0 h1:erH6cQjsaJrH+rJDU9qIf89KF
github.com/testcontainers/testcontainers-go v0.25.0/go.mod h1:4sC9SiJyzD1XFi59q8umTQYWxnkweEc5OjVtTUlJzqQ=
github.com/testcontainers/testcontainers-go/modules/k3s v0.0.0-20230615142642-c175df34bd1d h1:KyYCHo9iBoQYw5AzcozD/77uNbFlRjTmMTA7QjSxHOQ=
github.com/testcontainers/testcontainers-go/modules/k3s v0.0.0-20230615142642-c175df34bd1d/go.mod h1:Pa91ahCbzRB6d9FBi6UAjurTEm7WmyBVeuklLkwAKKs=
github.com/tg123/go-htpasswd v1.2.1 h1:i4wfsX1KvvkyoMiHZzjS0VzbAPWfxzI8INcZAKtutoU=
github.com/tg123/go-htpasswd v1.2.1/go.mod h1:erHp1B86KXdwQf1X5ZrLb7erXZnWueEQezb2dql4q58=
github.com/tg123/go-htpasswd v1.2.2 h1:tmNccDsQ+wYsoRfiONzIhDm5OkVHQzN3w4FOBAlN6BY=
github.com/tg123/go-htpasswd v1.2.2/go.mod h1:FcIrK0J+6zptgVwK1JDlqyajW/1B4PtuJ/FLWl7nx8A=
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/gjson v1.10.2 h1:APbLGOM0rrEkd8WBw9C24nllro4ajFuJu0Sc9hRz8Bo=
github.com/tidwall/gjson v1.10.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
Expand Down Expand Up @@ -2508,8 +2508,8 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
Expand Down Expand Up @@ -2781,8 +2781,8 @@ golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.1-0.20231108175955-e4099bfacb8c h1:3kC/TjQ+xzIblQv39bCOyRk8fbEeJcDHwbyxPUU2BpA=
golang.org/x/sys v0.14.1-0.20231108175955-e4099bfacb8c/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
Expand All @@ -2792,8 +2792,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
40 changes: 38 additions & 2 deletions service/ui/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@ package ui

import (
"context"
"errors"
"fmt"
"io/fs"
"net/http"
"os"
"path"

"github.com/go-kit/log"
"github.com/gorilla/mux"
"github.com/grafana/agent/pkg/flow/logging/level"
"github.com/grafana/agent/service"
"github.com/grafana/agent/service/cluster"
http_service "github.com/grafana/agent/service/http"
"github.com/grafana/agent/web/api"
"github.com/grafana/agent/web/ui"
"github.com/tg123/go-htpasswd"
)

// ServiceName defines the name used for the UI service.
Expand All @@ -21,8 +27,10 @@ const ServiceName = "ui"
// Options are used to configure the UI service. Options are constant for the
// lifetime of the UI service.
type Options struct {
Cluster cluster.Cluster
UIPrefix string // Path prefix to host the UI at.
BasicAuthUserFilepath string // Path of Basic Auth user and hashed password.
Cluster cluster.Cluster // Cluster
Logger log.Logger // Logger
UIPrefix string // Path prefix to host the UI at.
}

// Service implements the UI service.
Expand Down Expand Up @@ -75,6 +83,34 @@ func (s *Service) Data() any {
func (s *Service) ServiceHandler(host service.Host) (base string, handler http.Handler) {
r := mux.NewRouter()

// If toggled, setup Basic Auth for all UI and API routes
if s.opts.BasicAuthUserFilepath != "" {
_, err := os.Stat(s.opts.BasicAuthUserFilepath)
if errors.Is(err, fs.ErrNotExist) {
level.Info(s.opts.Logger).Log("msg", "Basic Auth user file doesn't exist")
} else {
r.Use(func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)

htpassFile, err := htpasswd.New(s.opts.BasicAuthUserFilepath, htpasswd.DefaultSystems, nil)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
username, password, authOk := r.BasicAuth()
if !authOk {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if ok := htpassFile.Match(username, password); ok {
h.ServeHTTP(w, r)
}
})
})
}
}

// TODO(rfratto): allow service.Host to return services so we don't have to
// pass the clustering service in Options.
fa := api.NewFlowAPI(host, s.opts.Cluster)
Expand Down

0 comments on commit e339791

Please sign in to comment.