Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server/http): allow Basic Auth to be configurable for web UI #6018

Closed
wants to merge 9 commits into from
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ Main (unreleased)
- [GO-2023-2412](https://github.com/advisories/GHSA-7ww5-4wqc-m92c)
- [CVE-2023-49568](https://github.com/advisories/GHSA-mw99-9chc-xw7r)

### Enhancements

- Add support for Basic Auth for agent's web UI, including the APIs. (@hainenber)

### Bugfixes

- Fix performance issue where perf lib where clause was not being set, leading to timeouts in collecting metrics for windows_exporter. (@mattdurham)
Expand Down
8 changes: 6 additions & 2 deletions cmd/internal/flowmode/cmd_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,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 @@ -131,6 +132,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 @@ -239,8 +241,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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,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
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2194,8 +2194,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
39 changes: 37 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,33 @@ 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()
ok := htpassFile.Match(username, password)
if !authOk || !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
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
2 changes: 1 addition & 1 deletion web/ui/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="manifest" crossorigin="use-credentials" href="%PUBLIC_URL%/manifest.json" />
<title>Grafana Agent</title>
</head>
<body>
Expand Down