mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #14077 from bobmshannon/bs/metrics_endpoint_auth
Add basic authentication support to metrics endpoint
This commit is contained in:
commit
db8bd8298a
@ -490,6 +490,10 @@ enabled = false
|
||||
enabled = true
|
||||
interval_seconds = 10
|
||||
|
||||
#If both are set, basic auth will be required for the metrics endpoint.
|
||||
basic_auth_username =
|
||||
basic_auth_password =
|
||||
|
||||
# Send internal Grafana metrics to graphite
|
||||
[metrics.graphite]
|
||||
# Enable by setting the address setting (ex localhost:2003)
|
||||
|
@ -454,6 +454,12 @@ Ex `filters = sqlstore:debug`
|
||||
### enabled
|
||||
Enable metrics reporting. defaults true. Available via HTTP API `/metrics`.
|
||||
|
||||
### basic_auth_username
|
||||
If set configures the username to use for basic authentication on the metrics endpoint.
|
||||
|
||||
### basic_auth_password
|
||||
If set configures the password to use for basic authentication on the metrics endpoint.
|
||||
|
||||
### interval_seconds
|
||||
|
||||
Flush/Write interval when sending metrics to external TSDB. Defaults to 10s.
|
||||
|
19
pkg/api/basic_auth.go
Normal file
19
pkg/api/basic_auth.go
Normal file
@ -0,0 +1,19 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
macaron "gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
// BasicAuthenticatedRequest parses the provided HTTP request for basic authentication credentials
|
||||
// and returns true if the provided credentials match the expected username and password.
|
||||
// Returns false if the request is unauthenticated.
|
||||
// Uses constant-time comparison in order to mitigate timing attacks.
|
||||
func BasicAuthenticatedRequest(req macaron.Request, expectedUser, expectedPass string) bool {
|
||||
user, pass, ok := req.BasicAuth()
|
||||
if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(expectedUser)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(expectedPass)) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
45
pkg/api/basic_auth_test.go
Normal file
45
pkg/api/basic_auth_test.go
Normal file
@ -0,0 +1,45 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
func TestBasicAuthenticatedRequest(t *testing.T) {
|
||||
expectedUser := "prometheus"
|
||||
expectedPass := "password"
|
||||
|
||||
Convey("Given a valid set of basic auth credentials", t, func() {
|
||||
httpReq, err := http.NewRequest("GET", "http://localhost:3000/metrics", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req := macaron.Request{
|
||||
Request: httpReq,
|
||||
}
|
||||
encodedCreds := encodeBasicAuthCredentials(expectedUser, expectedPass)
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Basic %s", encodedCreds))
|
||||
authenticated := BasicAuthenticatedRequest(req, expectedUser, expectedPass)
|
||||
So(authenticated, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Given an invalid set of basic auth credentials", t, func() {
|
||||
httpReq, err := http.NewRequest("GET", "http://localhost:3000/metrics", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req := macaron.Request{
|
||||
Request: httpReq,
|
||||
}
|
||||
encodedCreds := encodeBasicAuthCredentials("invaliduser", "invalidpass")
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Basic %s", encodedCreds))
|
||||
authenticated := BasicAuthenticatedRequest(req, expectedUser, expectedPass)
|
||||
So(authenticated, ShouldBeFalse)
|
||||
})
|
||||
}
|
||||
|
||||
func encodeBasicAuthCredentials(user, pass string) string {
|
||||
creds := fmt.Sprintf("%s:%s", user, pass)
|
||||
return base64.StdEncoding.EncodeToString([]byte(creds))
|
||||
}
|
@ -245,6 +245,11 @@ func (hs *HTTPServer) metricsEndpoint(ctx *macaron.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if hs.metricsEndpointBasicAuthEnabled() && !BasicAuthenticatedRequest(ctx.Req, hs.Cfg.MetricsEndpointBasicAuthUsername, hs.Cfg.MetricsEndpointBasicAuthPassword) {
|
||||
ctx.Resp.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{}).
|
||||
ServeHTTP(ctx.Resp, ctx.Req.Request)
|
||||
}
|
||||
@ -299,3 +304,7 @@ func (hs *HTTPServer) mapStatic(m *macaron.Macaron, rootDir string, dir string,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) metricsEndpointBasicAuthEnabled() bool {
|
||||
return hs.Cfg.MetricsEndpointBasicAuthUsername != "" && hs.Cfg.MetricsEndpointBasicAuthPassword != ""
|
||||
}
|
||||
|
30
pkg/api/http_server_test.go
Normal file
30
pkg/api/http_server_test.go
Normal file
@ -0,0 +1,30 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestHTTPServer(t *testing.T) {
|
||||
Convey("Given a HTTPServer", t, func() {
|
||||
ts := &HTTPServer{
|
||||
Cfg: setting.NewCfg(),
|
||||
}
|
||||
|
||||
Convey("Given that basic auth on the metrics endpoint is enabled", func() {
|
||||
ts.Cfg.MetricsEndpointBasicAuthUsername = "foo"
|
||||
ts.Cfg.MetricsEndpointBasicAuthPassword = "bar"
|
||||
|
||||
So(ts.metricsEndpointBasicAuthEnabled(), ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Given that basic auth on the metrics endpoint is disabled", func() {
|
||||
ts.Cfg.MetricsEndpointBasicAuthUsername = ""
|
||||
ts.Cfg.MetricsEndpointBasicAuthPassword = ""
|
||||
|
||||
So(ts.metricsEndpointBasicAuthEnabled(), ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
}
|
@ -219,6 +219,8 @@ type Cfg struct {
|
||||
DisableBruteForceLoginProtection bool
|
||||
TempDataLifetime time.Duration
|
||||
MetricsEndpointEnabled bool
|
||||
MetricsEndpointBasicAuthUsername string
|
||||
MetricsEndpointBasicAuthPassword string
|
||||
EnableAlphaPanels bool
|
||||
EnterpriseLicensePath string
|
||||
}
|
||||
@ -681,6 +683,8 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
|
||||
cfg.PhantomDir = filepath.Join(HomePath, "tools/phantomjs")
|
||||
cfg.TempDataLifetime = iniFile.Section("paths").Key("temp_data_lifetime").MustDuration(time.Second * 3600 * 24)
|
||||
cfg.MetricsEndpointEnabled = iniFile.Section("metrics").Key("enabled").MustBool(true)
|
||||
cfg.MetricsEndpointBasicAuthUsername = iniFile.Section("metrics").Key("basic_auth_username").String()
|
||||
cfg.MetricsEndpointBasicAuthPassword = iniFile.Section("metrics").Key("basic_auth_password").String()
|
||||
|
||||
analytics := iniFile.Section("analytics")
|
||||
ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
|
||||
|
Loading…
Reference in New Issue
Block a user