mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-23 20:37:12 +01:00
app/vmauth: add support for authorization via Authorization: Bearer <token>
This commit is contained in:
parent
b88feb631e
commit
b1d0028e79
@ -36,11 +36,15 @@ Auth config is represented in the following simple `yml` format:
|
|||||||
# Usernames must be unique.
|
# Usernames must be unique.
|
||||||
|
|
||||||
users:
|
users:
|
||||||
|
# Requests with the 'Authorization: Bearer XXXX' header are proxied to http://localhost:8428 .
|
||||||
|
# For example, http://vmauth:8427/api/v1/query is proxied to http://localhost:8428/api/v1/query
|
||||||
|
- bearer_token: "XXXX"
|
||||||
|
url_prefix: "http://localhost:8428"
|
||||||
|
|
||||||
# The user for querying local single-node VictoriaMetrics.
|
# The user for querying local single-node VictoriaMetrics.
|
||||||
# All the requests to http://vmauth:8427 with the given Basic Auth (username:password)
|
# All the requests to http://vmauth:8427 with the given Basic Auth (username:password)
|
||||||
# will be routed to http://localhost:8428 .
|
# will be proxied to http://localhost:8428 .
|
||||||
# For example, http://vmauth:8427/api/v1/query is routed to http://localhost:8428/api/v1/query
|
# For example, http://vmauth:8427/api/v1/query is proxied to http://localhost:8428/api/v1/query
|
||||||
- username: "local-single-node"
|
- username: "local-single-node"
|
||||||
password: "***"
|
password: "***"
|
||||||
url_prefix: "http://localhost:8428"
|
url_prefix: "http://localhost:8428"
|
||||||
@ -48,8 +52,8 @@ users:
|
|||||||
# The user for querying account 123 in VictoriaMetrics cluster
|
# The user for querying account 123 in VictoriaMetrics cluster
|
||||||
# See https://victoriametrics.github.io/Cluster-VictoriaMetrics.html#url-format
|
# See https://victoriametrics.github.io/Cluster-VictoriaMetrics.html#url-format
|
||||||
# All the requests to http://vmauth:8427 with the given Basic Auth (username:password)
|
# All the requests to http://vmauth:8427 with the given Basic Auth (username:password)
|
||||||
# will be routed to http://vmselect:8481/select/123/prometheus .
|
# will be proxied to http://vmselect:8481/select/123/prometheus .
|
||||||
# For example, http://vmauth:8427/api/v1/query is routed to http://vmselect:8481/select/123/prometheus/api/v1/select
|
# For example, http://vmauth:8427/api/v1/query is proxied to http://vmselect:8481/select/123/prometheus/api/v1/select
|
||||||
- username: "cluster-select-account-123"
|
- username: "cluster-select-account-123"
|
||||||
password: "***"
|
password: "***"
|
||||||
url_prefix: "http://vmselect:8481/select/123/prometheus"
|
url_prefix: "http://vmselect:8481/select/123/prometheus"
|
||||||
@ -57,8 +61,8 @@ users:
|
|||||||
# The user for inserting Prometheus data into VictoriaMetrics cluster under account 42
|
# The user for inserting Prometheus data into VictoriaMetrics cluster under account 42
|
||||||
# See https://victoriametrics.github.io/Cluster-VictoriaMetrics.html#url-format
|
# See https://victoriametrics.github.io/Cluster-VictoriaMetrics.html#url-format
|
||||||
# All the requests to http://vmauth:8427 with the given Basic Auth (username:password)
|
# All the requests to http://vmauth:8427 with the given Basic Auth (username:password)
|
||||||
# will be routed to http://vminsert:8480/insert/42/prometheus .
|
# will be proxied to http://vminsert:8480/insert/42/prometheus .
|
||||||
# For example, http://vmauth:8427/api/v1/write is routed to http://vminsert:8480/insert/42/prometheus/api/v1/write
|
# For example, http://vmauth:8427/api/v1/write is proxied to http://vminsert:8480/insert/42/prometheus/api/v1/write
|
||||||
- username: "cluster-insert-account-42"
|
- username: "cluster-insert-account-42"
|
||||||
password: "***"
|
password: "***"
|
||||||
url_prefix: "http://vminsert:8480/insert/42/prometheus"
|
url_prefix: "http://vminsert:8480/insert/42/prometheus"
|
||||||
@ -66,9 +70,9 @@ users:
|
|||||||
|
|
||||||
# A single user for querying and inserting data:
|
# A single user for querying and inserting data:
|
||||||
# - Requests to http://vmauth:8427/api/v1/query, http://vmauth:8427/api/v1/query_range
|
# - Requests to http://vmauth:8427/api/v1/query, http://vmauth:8427/api/v1/query_range
|
||||||
# and http://vmauth:8427/api/v1/label/<label_name>/values are routed to http://vmselect:8481/select/42/prometheus.
|
# and http://vmauth:8427/api/v1/label/<label_name>/values are proxied to http://vmselect:8481/select/42/prometheus.
|
||||||
# For example, http://vmauth:8427/api/v1/query is routed to http://vmselect:8480/select/42/prometheus/api/v1/query
|
# For example, http://vmauth:8427/api/v1/query is proxied to http://vmselect:8480/select/42/prometheus/api/v1/query
|
||||||
# - Requests to http://vmauth:8427/api/v1/write are routed to http://vminsert:8480/insert/42/prometheus/api/v1/write
|
# - Requests to http://vmauth:8427/api/v1/write are proxied to http://vminsert:8480/insert/42/prometheus/api/v1/write
|
||||||
- username: "foobar"
|
- username: "foobar"
|
||||||
url_map:
|
url_map:
|
||||||
- src_paths: ["/api/v1/query", "/api/v1/query_range", "/api/v1/label/[^/]+/values"]
|
- src_paths: ["/api/v1/query", "/api/v1/query_range", "/api/v1/label/[^/]+/values"]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -29,10 +30,11 @@ type AuthConfig struct {
|
|||||||
|
|
||||||
// UserInfo is user information read from authConfigPath
|
// UserInfo is user information read from authConfigPath
|
||||||
type UserInfo struct {
|
type UserInfo struct {
|
||||||
Username string `yaml:"username"`
|
BearerToken string `yaml:"bearer_token"`
|
||||||
Password string `yaml:"password"`
|
Username string `yaml:"username"`
|
||||||
URLPrefix string `yaml:"url_prefix"`
|
Password string `yaml:"password"`
|
||||||
URLMap []URLMap `yaml:"url_map"`
|
URLPrefix string `yaml:"url_prefix"`
|
||||||
|
URLMap []URLMap `yaml:"url_map"`
|
||||||
|
|
||||||
requests *metrics.Counter
|
requests *metrics.Counter
|
||||||
}
|
}
|
||||||
@ -150,12 +152,27 @@ func parseAuthConfig(data []byte) (map[string]*UserInfo, error) {
|
|||||||
if len(uis) == 0 {
|
if len(uis) == 0 {
|
||||||
return nil, fmt.Errorf("`users` section cannot be empty in AuthConfig")
|
return nil, fmt.Errorf("`users` section cannot be empty in AuthConfig")
|
||||||
}
|
}
|
||||||
m := make(map[string]*UserInfo, len(uis))
|
byAuthToken := make(map[string]*UserInfo, len(uis))
|
||||||
|
byUsername := make(map[string]bool, len(uis))
|
||||||
|
byBearerToken := make(map[string]bool, len(uis))
|
||||||
for i := range uis {
|
for i := range uis {
|
||||||
ui := &uis[i]
|
ui := &uis[i]
|
||||||
if m[ui.Username] != nil {
|
if ui.BearerToken == "" && ui.Username == "" {
|
||||||
|
return nil, fmt.Errorf("either bearer_token or username must be set")
|
||||||
|
}
|
||||||
|
if ui.BearerToken != "" && ui.Username != "" {
|
||||||
|
return nil, fmt.Errorf("bearer_token=%q and username=%q cannot be set simultaneously", ui.BearerToken, ui.Username)
|
||||||
|
}
|
||||||
|
if byBearerToken[ui.BearerToken] {
|
||||||
|
return nil, fmt.Errorf("duplicate bearer_token found; bearer_token: %q", ui.BearerToken)
|
||||||
|
}
|
||||||
|
if byUsername[ui.Username] {
|
||||||
return nil, fmt.Errorf("duplicate username found; username: %q", ui.Username)
|
return nil, fmt.Errorf("duplicate username found; username: %q", ui.Username)
|
||||||
}
|
}
|
||||||
|
authToken := getAuthToken(ui.BearerToken, ui.Username, ui.Password)
|
||||||
|
if byAuthToken[authToken] != nil {
|
||||||
|
return nil, fmt.Errorf("duplicate auth token found for bearer_token=%q, username=%q: %q", authToken, ui.BearerToken, ui.Username)
|
||||||
|
}
|
||||||
if len(ui.URLPrefix) > 0 {
|
if len(ui.URLPrefix) > 0 {
|
||||||
urlPrefix, err := sanitizeURLPrefix(ui.URLPrefix)
|
urlPrefix, err := sanitizeURLPrefix(ui.URLPrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -176,10 +193,29 @@ func parseAuthConfig(data []byte) (map[string]*UserInfo, error) {
|
|||||||
if len(ui.URLMap) == 0 && len(ui.URLPrefix) == 0 {
|
if len(ui.URLMap) == 0 && len(ui.URLPrefix) == 0 {
|
||||||
return nil, fmt.Errorf("missing `url_prefix`")
|
return nil, fmt.Errorf("missing `url_prefix`")
|
||||||
}
|
}
|
||||||
ui.requests = metrics.GetOrCreateCounter(fmt.Sprintf(`vmauth_user_requests_total{username=%q}`, ui.Username))
|
if ui.BearerToken != "" {
|
||||||
m[ui.Username] = ui
|
if ui.Password != "" {
|
||||||
|
return nil, fmt.Errorf("password shouldn't be set for bearer_token %q", ui.BearerToken)
|
||||||
|
}
|
||||||
|
ui.requests = metrics.GetOrCreateCounter(`vmauth_user_requests_total{username="bearer_token"}`)
|
||||||
|
byBearerToken[ui.BearerToken] = true
|
||||||
|
}
|
||||||
|
if ui.Username != "" {
|
||||||
|
ui.requests = metrics.GetOrCreateCounter(fmt.Sprintf(`vmauth_user_requests_total{username=%q}`, ui.Username))
|
||||||
|
byUsername[ui.Username] = true
|
||||||
|
}
|
||||||
|
byAuthToken[authToken] = ui
|
||||||
}
|
}
|
||||||
return m, nil
|
return byAuthToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAuthToken(bearerToken, username, password string) string {
|
||||||
|
if bearerToken != "" {
|
||||||
|
return "Bearer " + bearerToken
|
||||||
|
}
|
||||||
|
token := username + ":" + password
|
||||||
|
token64 := base64.StdEncoding.EncodeToString([]byte(token))
|
||||||
|
return "Basic " + token64
|
||||||
}
|
}
|
||||||
|
|
||||||
func sanitizeURLPrefix(urlPrefix string) (string, error) {
|
func sanitizeURLPrefix(urlPrefix string) (string, error) {
|
||||||
|
@ -56,6 +56,22 @@ users:
|
|||||||
url_prefix: http:///bar
|
url_prefix: http:///bar
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
// Username and bearer_token in a single config
|
||||||
|
f(`
|
||||||
|
users:
|
||||||
|
- username: foo
|
||||||
|
bearer_token: bbb
|
||||||
|
url_prefix: http://foo.bar
|
||||||
|
`)
|
||||||
|
|
||||||
|
// Bearer_token and password in a single config
|
||||||
|
f(`
|
||||||
|
users:
|
||||||
|
- password: foo
|
||||||
|
bearer_token: bbb
|
||||||
|
url_prefix: http://foo.bar
|
||||||
|
`)
|
||||||
|
|
||||||
// Duplicate users
|
// Duplicate users
|
||||||
f(`
|
f(`
|
||||||
users:
|
users:
|
||||||
@ -67,6 +83,17 @@ users:
|
|||||||
url_prefix: https://sss.sss
|
url_prefix: https://sss.sss
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
// Duplicate bearer_tokens
|
||||||
|
f(`
|
||||||
|
users:
|
||||||
|
- bearer_token: foo
|
||||||
|
url_prefix: http://foo.bar
|
||||||
|
- username: bar
|
||||||
|
url_prefix: http://xxx.yyy
|
||||||
|
- bearer_token: foo
|
||||||
|
url_prefix: https://sss.sss
|
||||||
|
`)
|
||||||
|
|
||||||
// Missing url_prefix in url_map
|
// Missing url_prefix in url_map
|
||||||
f(`
|
f(`
|
||||||
users:
|
users:
|
||||||
@ -113,7 +140,7 @@ users:
|
|||||||
password: bar
|
password: bar
|
||||||
url_prefix: http://aaa:343/bbb
|
url_prefix: http://aaa:343/bbb
|
||||||
`, map[string]*UserInfo{
|
`, map[string]*UserInfo{
|
||||||
"foo": {
|
getAuthToken("", "foo", "bar"): {
|
||||||
Username: "foo",
|
Username: "foo",
|
||||||
Password: "bar",
|
Password: "bar",
|
||||||
URLPrefix: "http://aaa:343/bbb",
|
URLPrefix: "http://aaa:343/bbb",
|
||||||
@ -128,11 +155,11 @@ users:
|
|||||||
- username: bar
|
- username: bar
|
||||||
url_prefix: https://bar/x///
|
url_prefix: https://bar/x///
|
||||||
`, map[string]*UserInfo{
|
`, map[string]*UserInfo{
|
||||||
"foo": {
|
getAuthToken("", "foo", ""): {
|
||||||
Username: "foo",
|
Username: "foo",
|
||||||
URLPrefix: "http://foo",
|
URLPrefix: "http://foo",
|
||||||
},
|
},
|
||||||
"bar": {
|
getAuthToken("", "bar", ""): {
|
||||||
Username: "bar",
|
Username: "bar",
|
||||||
URLPrefix: "https://bar/x",
|
URLPrefix: "https://bar/x",
|
||||||
},
|
},
|
||||||
@ -141,15 +168,15 @@ users:
|
|||||||
// non-empty URLMap
|
// non-empty URLMap
|
||||||
f(`
|
f(`
|
||||||
users:
|
users:
|
||||||
- username: foo
|
- bearer_token: foo
|
||||||
url_map:
|
url_map:
|
||||||
- src_paths: ["/api/v1/query","/api/v1/query_range","/api/v1/label/[^./]+/.+"]
|
- src_paths: ["/api/v1/query","/api/v1/query_range","/api/v1/label/[^./]+/.+"]
|
||||||
url_prefix: http://vmselect/select/0/prometheus
|
url_prefix: http://vmselect/select/0/prometheus
|
||||||
- src_paths: ["/api/v1/write"]
|
- src_paths: ["/api/v1/write"]
|
||||||
url_prefix: http://vminsert/insert/0/prometheus
|
url_prefix: http://vminsert/insert/0/prometheus
|
||||||
`, map[string]*UserInfo{
|
`, map[string]*UserInfo{
|
||||||
"foo": {
|
getAuthToken("foo", "", ""): {
|
||||||
Username: "foo",
|
BearerToken: "foo",
|
||||||
URLMap: []URLMap{
|
URLMap: []URLMap{
|
||||||
{
|
{
|
||||||
SrcPaths: getSrcPaths([]string{"/api/v1/query", "/api/v1/query_range", "/api/v1/label/[^./]+/.+"}),
|
SrcPaths: getSrcPaths([]string{"/api/v1/query", "/api/v1/query_range", "/api/v1/label/[^./]+/.+"}),
|
||||||
|
@ -47,16 +47,16 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func requestHandler(w http.ResponseWriter, r *http.Request) bool {
|
func requestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||||
username, password, ok := r.BasicAuth()
|
authToken := r.Header.Get("Authorization")
|
||||||
if !ok {
|
if authToken == "" {
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||||
http.Error(w, "missing `Authorization: Basic *` header", http.StatusUnauthorized)
|
http.Error(w, "missing `Authorization` request header", http.StatusUnauthorized)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
ac := authConfig.Load().(map[string]*UserInfo)
|
ac := authConfig.Load().(map[string]*UserInfo)
|
||||||
ui := ac[username]
|
ui := ac[authToken]
|
||||||
if ui == nil || ui.Password != password {
|
if ui == nil {
|
||||||
httpserver.Errorf(w, r, "cannot find the provided username %q or password in config", username)
|
httpserver.Errorf(w, r, "cannot find the provided auth token %q in config", authToken)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
ui.requests.Inc()
|
ui.requests.Inc()
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
* FEATURE: vmagent: add support for `authorization` section in `-promscrape.config` in the same way as [Prometheus 2.26 does](https://github.com/prometheus/prometheus/pull/8512).
|
* FEATURE: vmagent: add support for `authorization` section in `-promscrape.config` in the same way as [Prometheus 2.26 does](https://github.com/prometheus/prometheus/pull/8512).
|
||||||
* FEATURE: vmagent: reduce memory usage when `-remoteWrite.queues` is set to a big value. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1167).
|
* FEATURE: vmagent: reduce memory usage when `-remoteWrite.queues` is set to a big value. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1167).
|
||||||
* FEATURE: vmagent: add AWS IAM roles for tasks support for EC2 service discovery according to [these docs](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html).
|
* FEATURE: vmagent: add AWS IAM roles for tasks support for EC2 service discovery according to [these docs](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html).
|
||||||
|
FEATURE: vmauth: add support for authorization via [bearer token](https://swagger.io/docs/specification/authentication/bearer-authentication/). See [the docs](https://victoriametrics.github.io/vmauth.html#auth-config) for details.
|
||||||
|
|
||||||
* BUGFIX: vmagent: properly discovery targets if multiple namespace selectors are put inside `kubernetes_sd_config`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1170).
|
* BUGFIX: vmagent: properly discovery targets if multiple namespace selectors are put inside `kubernetes_sd_config`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1170).
|
||||||
* BUGFIX: properly generate filename for `*.tar.gz` archive inside `_checksums.txt` file posted at [releases page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1171).
|
* BUGFIX: properly generate filename for `*.tar.gz` archive inside `_checksums.txt` file posted at [releases page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1171).
|
||||||
|
Loading…
Reference in New Issue
Block a user