mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
app/vmauth: add initial version of vmauth. See https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmauth/README.md for details
This commit is contained in:
parent
b5a780930d
commit
e767aedd17
7 changed files with 511 additions and 3 deletions
11
Makefile
11
Makefile
|
@ -13,7 +13,8 @@ GO_BUILDINFO = -X '$(PKG_PREFIX)/lib/buildinfo.Version=$(APP_NAME)-$(shell date
|
||||||
all: \
|
all: \
|
||||||
victoria-metrics-prod \
|
victoria-metrics-prod \
|
||||||
vmagent-prod \
|
vmagent-prod \
|
||||||
vmalert-prot \
|
vmalert-prod \
|
||||||
|
vmauth-prod \
|
||||||
vmbackup-prod \
|
vmbackup-prod \
|
||||||
vmrestore-prod
|
vmrestore-prod
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ publish: \
|
||||||
publish-victoria-metrics \
|
publish-victoria-metrics \
|
||||||
publish-vmagent \
|
publish-vmagent \
|
||||||
publish-vmalert \
|
publish-vmalert \
|
||||||
|
publish-vmauth \
|
||||||
publish-vmbackup \
|
publish-vmbackup \
|
||||||
publish-vmrestore
|
publish-vmrestore
|
||||||
|
|
||||||
|
@ -34,12 +36,14 @@ package: \
|
||||||
package-victoria-metrics \
|
package-victoria-metrics \
|
||||||
package-vmagent \
|
package-vmagent \
|
||||||
package-vmalert \
|
package-vmalert \
|
||||||
|
package-vmauth \
|
||||||
package-vmbackup \
|
package-vmbackup \
|
||||||
package-vmrestore
|
package-vmrestore
|
||||||
|
|
||||||
vmutils: \
|
vmutils: \
|
||||||
vmagent \
|
vmagent \
|
||||||
vmalert \
|
vmalert \
|
||||||
|
vmauth \
|
||||||
vmbackup \
|
vmbackup \
|
||||||
vmrestore
|
vmrestore
|
||||||
|
|
||||||
|
@ -54,9 +58,10 @@ release-victoria-metrics: victoria-metrics-prod
|
||||||
release-vmutils: \
|
release-vmutils: \
|
||||||
vmagent-prod \
|
vmagent-prod \
|
||||||
vmalert-prod \
|
vmalert-prod \
|
||||||
|
vmauth-prod \
|
||||||
vmbackup-prod \
|
vmbackup-prod \
|
||||||
vmrestore-prod
|
vmrestore-prod
|
||||||
cd bin && tar czf vmutils-$(PKG_TAG).tar.gz vmagent-prod vmalert-prod vmbackup-prod vmrestore-prod && \
|
cd bin && tar czf vmutils-$(PKG_TAG).tar.gz vmagent-prod vmalert-prod vmauth-prod vmbackup-prod vmrestore-prod && \
|
||||||
sha256sum vmutils-$(PKG_TAG).tar.gz > vmutils-$(PKG_TAG)_checksums.txt
|
sha256sum vmutils-$(PKG_TAG).tar.gz > vmutils-$(PKG_TAG)_checksums.txt
|
||||||
|
|
||||||
pprof-cpu:
|
pprof-cpu:
|
||||||
|
@ -84,9 +89,9 @@ errcheck: install-errcheck
|
||||||
errcheck -exclude=errcheck_excludes.txt ./app/vmstorage/...
|
errcheck -exclude=errcheck_excludes.txt ./app/vmstorage/...
|
||||||
errcheck -exclude=errcheck_excludes.txt ./app/vmagent/...
|
errcheck -exclude=errcheck_excludes.txt ./app/vmagent/...
|
||||||
errcheck -exclude=errcheck_excludes.txt ./app/vmalert/...
|
errcheck -exclude=errcheck_excludes.txt ./app/vmalert/...
|
||||||
|
errcheck -exclude=errcheck_excludes.txt ./app/vmauth/...
|
||||||
errcheck -exclude=errcheck_excludes.txt ./app/vmbackup/...
|
errcheck -exclude=errcheck_excludes.txt ./app/vmbackup/...
|
||||||
errcheck -exclude=errcheck_excludes.txt ./app/vmrestore/...
|
errcheck -exclude=errcheck_excludes.txt ./app/vmrestore/...
|
||||||
errcheck -exclude=errcheck_excludes.txt ./app/vmalert/...
|
|
||||||
|
|
||||||
install-errcheck:
|
install-errcheck:
|
||||||
which errcheck || GO111MODULE=off go get -u github.com/kisielk/errcheck
|
which errcheck || GO111MODULE=off go get -u github.com/kisielk/errcheck
|
||||||
|
|
74
app/vmauth/Makefile
Normal file
74
app/vmauth/Makefile
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
# All these commands must run from repository root.
|
||||||
|
|
||||||
|
vmauth:
|
||||||
|
APP_NAME=vmauth $(MAKE) app-local
|
||||||
|
|
||||||
|
vmauth-race:
|
||||||
|
APP_NAME=vmauth RACE=-race $(MAKE) app-local
|
||||||
|
|
||||||
|
vmauth-prod:
|
||||||
|
APP_NAME=vmauth $(MAKE) app-via-docker
|
||||||
|
|
||||||
|
vmauth-pure-prod:
|
||||||
|
APP_NAME=vmauth $(MAKE) app-via-docker-pure
|
||||||
|
|
||||||
|
vmauth-amd64-prod:
|
||||||
|
APP_NAME=vmauth $(MAKE) app-via-docker-amd64
|
||||||
|
|
||||||
|
vmauth-arm-prod:
|
||||||
|
APP_NAME=vmauth $(MAKE) app-via-docker-arm
|
||||||
|
|
||||||
|
vmauth-arm64-prod:
|
||||||
|
APP_NAME=vmauth $(MAKE) app-via-docker-arm64
|
||||||
|
|
||||||
|
vmauth-ppc64le-prod:
|
||||||
|
APP_NAME=vmauth $(MAKE) app-via-docker-ppc64le
|
||||||
|
|
||||||
|
vmauth-386-prod:
|
||||||
|
APP_NAME=vmauth $(MAKE) app-via-docker-386
|
||||||
|
|
||||||
|
package-vmauth:
|
||||||
|
APP_NAME=vmauth $(MAKE) package-via-docker
|
||||||
|
|
||||||
|
package-vmauth-pure:
|
||||||
|
APP_NAME=vmauth $(MAKE) package-via-docker-pure
|
||||||
|
|
||||||
|
package-vmauth-amd64:
|
||||||
|
APP_NAME=vmauth $(MAKE) package-via-docker-amd64
|
||||||
|
|
||||||
|
package-vmauth-arm:
|
||||||
|
APP_NAME=vmauth $(MAKE) package-via-docker-arm
|
||||||
|
|
||||||
|
package-vmauth-arm64:
|
||||||
|
APP_NAME=vmauth $(MAKE) package-via-docker-arm64
|
||||||
|
|
||||||
|
package-vmauth-ppc64le:
|
||||||
|
APP_NAME=vmauth $(MAKE) package-via-docker-ppc64le
|
||||||
|
|
||||||
|
package-vmauth-386:
|
||||||
|
APP_NAME=vmauth $(MAKE) package-via-docker-386
|
||||||
|
|
||||||
|
publish-vmauth:
|
||||||
|
APP_NAME=vmauth $(MAKE) publish-via-docker
|
||||||
|
|
||||||
|
run-vmauth:
|
||||||
|
APP_NAME=vmauth \
|
||||||
|
$(MAKE) run-via-docker
|
||||||
|
|
||||||
|
vmauth-amd64:
|
||||||
|
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmauth-amd64 ./app/vmauth
|
||||||
|
|
||||||
|
vmauth-arm:
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmauth-arm ./app/vmauth
|
||||||
|
|
||||||
|
vmauth-arm64:
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmauth-arm64 ./app/vmauth
|
||||||
|
|
||||||
|
vmauth-ppc64le:
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=ppc64le GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmauth-ppc64le ./app/vmauth
|
||||||
|
|
||||||
|
vmauth-386:
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=386 GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmauth-386 ./app/vmauth
|
||||||
|
|
||||||
|
vmauth-pure:
|
||||||
|
APP_NAME=vmauth $(MAKE) app-local-pure
|
103
app/vmauth/README.md
Normal file
103
app/vmauth/README.md
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
## vmauth
|
||||||
|
|
||||||
|
`vmauth` is a simple auth proxy and router. It reads username and password from [Basic Auth headers](https://en.wikipedia.org/wiki/Basic_access_authentication)
|
||||||
|
and matches them against configs pointed by `-auth.config` command-line flag and proxies incoming HTTP requests to the configured per-user `url_prefix` on successful match.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Just pass path to [auth config](#auth-config) file to `vmauth` binary:
|
||||||
|
|
||||||
|
```
|
||||||
|
/path/to/vmauth -auth.config=/path/to/auth/config.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
After that `vmauth` starts accepting HTTP requests on port `8427` and routing them according to the provided `-auth.config`.
|
||||||
|
The port can be modified via `-httpListenAddr` command-line flag.
|
||||||
|
|
||||||
|
The auth config can be reloaded by passing `SIGHUP` signal to `vmauth`.
|
||||||
|
|
||||||
|
Pass `-help` to `vmauth` in order to see all the supported command-line flags with their descriptions.
|
||||||
|
|
||||||
|
|
||||||
|
### Auth config
|
||||||
|
|
||||||
|
Auth config is represented in the following simple `yml` format:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
|
||||||
|
# Arbitrary number of usernames may be put here.
|
||||||
|
# Usernames must be unique.
|
||||||
|
|
||||||
|
users:
|
||||||
|
|
||||||
|
# The user for querying local single-node VictoriaMetrics
|
||||||
|
- username: "local-single-node"
|
||||||
|
password: "***"
|
||||||
|
url_prefix: "http://localhost:8428"
|
||||||
|
|
||||||
|
# The user for querying account 123 in VictoriaMetrics cluster
|
||||||
|
# See https://github.com/VictoriaMetrics/VictoriaMetrics/blob/cluster/README.md#url-format
|
||||||
|
- username: "cluster-select-account-123"
|
||||||
|
password: "***"
|
||||||
|
url_prefix: "http://vmselect:8481/select/123/prometheus"
|
||||||
|
|
||||||
|
# The user for inserting Prometheus data into VictoriaMetrics cluster under account 42
|
||||||
|
# See https://github.com/VictoriaMetrics/VictoriaMetrics/blob/cluster/README.md#url-format
|
||||||
|
- username: "cluster-insert-account-42"
|
||||||
|
password: "***"
|
||||||
|
url_prefix: "http://localhost:8480/insert/42/prometheus"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
Do not transfer Basic Auth headers in plaintext over untrusted networks. Enable https. This can be done by passing the following `-tls*` command-line flags to `vmauth`:
|
||||||
|
|
||||||
|
```
|
||||||
|
-tls
|
||||||
|
Whether to enable TLS (aka HTTPS) for incoming requests. -tlsCertFile and -tlsKeyFile must be set if -tls is set
|
||||||
|
-tlsCertFile string
|
||||||
|
Path to file with TLS certificate. Used only if -tls is set. Prefer ECDSA certs instead of RSA certs, since RSA certs are slow
|
||||||
|
-tlsKeyFile string
|
||||||
|
Path to file with TLS key. Used only if -tls is set
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, [https termination proxy](https://en.wikipedia.org/wiki/TLS_termination_proxy) may be put in front of `vmauth`.
|
||||||
|
|
||||||
|
|
||||||
|
### Monitoring
|
||||||
|
|
||||||
|
`vmauth` exports various metrics in Prometheus exposition format at `http://vmauth-host:8427/metrics` page. It is recommended setting up regular scraping of this page
|
||||||
|
either via [vmagent](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmagent/README.md) or via Prometheus, so the exported metrics could be analyzed later.
|
||||||
|
|
||||||
|
|
||||||
|
### How to build from sources
|
||||||
|
|
||||||
|
It is recommended using [binary releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases) - `vmauth` is located in `vmutils-*` archives there.
|
||||||
|
|
||||||
|
|
||||||
|
#### Development build
|
||||||
|
|
||||||
|
1. [Install Go](https://golang.org/doc/install). The minimum supported version is Go 1.13.
|
||||||
|
2. Run `make vmauth` from the root folder of the repository.
|
||||||
|
It builds `vmauth` binary and puts it into the `bin` folder.
|
||||||
|
|
||||||
|
#### Production build
|
||||||
|
|
||||||
|
1. [Install docker](https://docs.docker.com/install/).
|
||||||
|
2. Run `make vmauth-prod` from the root folder of the repository.
|
||||||
|
It builds `vmauth-prod` binary and puts it into the `bin` folder.
|
||||||
|
|
||||||
|
#### Building docker images
|
||||||
|
|
||||||
|
Run `make package-vmauth`. It builds `victoriametrics/vmauth:<PKG_TAG>` docker image locally.
|
||||||
|
`<PKG_TAG>` is auto-generated image tag, which depends on source code in the repository.
|
||||||
|
The `<PKG_TAG>` may be manually set via `PKG_TAG=foobar make package-vmauth`.
|
||||||
|
|
||||||
|
By default the image is built on top of `scratch` image. It is possible to build the package on top of any other base image
|
||||||
|
by setting it via `<ROOT_IMAGE>` environment variable. For example, the following command builds the image on top of `alpine:3.11` image:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ROOT_IMAGE=alpine:3.11 make package-vmauth
|
||||||
|
```
|
127
app/vmauth/auth_config.go
Normal file
127
app/vmauth/auth_config.go
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/procutil"
|
||||||
|
"github.com/VictoriaMetrics/metrics"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
authConfigPath = flag.String("auth.config", "", "Path to auth config. See https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmauth/README.md "+
|
||||||
|
"for details on the format of this auth config")
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthConfig represents auth config.
|
||||||
|
type AuthConfig struct {
|
||||||
|
Users []UserInfo `yaml:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserInfo is user information read from authConfigPath
|
||||||
|
type UserInfo struct {
|
||||||
|
Username string `yaml:"username"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
URLPrefix string `yaml:"url_prefix"`
|
||||||
|
|
||||||
|
requests *metrics.Counter
|
||||||
|
}
|
||||||
|
|
||||||
|
func initAuthConfig() {
|
||||||
|
if len(*authConfigPath) == 0 {
|
||||||
|
logger.Panicf("FATAL: missing required `-auth.config` command-line flag")
|
||||||
|
}
|
||||||
|
m, err := readAuthConfig(*authConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
logger.Panicf("FATAL: cannot load auth config from `-auth.config=%s`: %s", *authConfigPath, err)
|
||||||
|
}
|
||||||
|
authConfig.Store(m)
|
||||||
|
stopCh = make(chan struct{})
|
||||||
|
authConfigWG.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer authConfigWG.Done()
|
||||||
|
authConfigReloader()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopAuthConfig() {
|
||||||
|
close(stopCh)
|
||||||
|
authConfigWG.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func authConfigReloader() {
|
||||||
|
sighupCh := procutil.NewSighupChan()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stopCh:
|
||||||
|
return
|
||||||
|
case <-sighupCh:
|
||||||
|
m, err := readAuthConfig(*authConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("failed to load auth config; using the last successfully loaded config; error: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
authConfig.Store(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var authConfig atomic.Value
|
||||||
|
var authConfigWG sync.WaitGroup
|
||||||
|
var stopCh chan struct{}
|
||||||
|
|
||||||
|
func readAuthConfig(path string) (map[string]*UserInfo, error) {
|
||||||
|
data, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot read %q: %s", path, err)
|
||||||
|
}
|
||||||
|
m, err := parseAuthConfig(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse %q: %s", path, err)
|
||||||
|
}
|
||||||
|
logger.Infof("Loaded information about %d users from %q", len(m), path)
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAuthConfig(data []byte) (map[string]*UserInfo, error) {
|
||||||
|
var ac AuthConfig
|
||||||
|
if err := yaml.UnmarshalStrict(data, &ac); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot unmarshal AuthConfig data: %s", err)
|
||||||
|
}
|
||||||
|
uis := ac.Users
|
||||||
|
if len(uis) == 0 {
|
||||||
|
return nil, fmt.Errorf("`users` section cannot be empty in AuthConfig")
|
||||||
|
}
|
||||||
|
m := make(map[string]*UserInfo, len(uis))
|
||||||
|
for i := range uis {
|
||||||
|
ui := &uis[i]
|
||||||
|
if m[ui.Username] != nil {
|
||||||
|
return nil, fmt.Errorf("duplicate username found; username: %q", ui.Username)
|
||||||
|
}
|
||||||
|
urlPrefix := ui.URLPrefix
|
||||||
|
// Remove trailing '/' from urlPrefix
|
||||||
|
for strings.HasSuffix(urlPrefix, "/") {
|
||||||
|
urlPrefix = urlPrefix[:len(urlPrefix)-1]
|
||||||
|
}
|
||||||
|
// Validate urlPrefix
|
||||||
|
target, err := url.Parse(urlPrefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid `url_prefix: %q`: %s", urlPrefix, err)
|
||||||
|
}
|
||||||
|
if target.Scheme != "http" && target.Scheme != "https" {
|
||||||
|
return nil, fmt.Errorf("unsupported scheme for `url_prefix: %q`: %q; must be `http` or `https`", urlPrefix, target.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.URLPrefix = urlPrefix
|
||||||
|
ui.requests = metrics.GetOrCreateCounter(fmt.Sprintf(`vmauth_user_requests_total{username=%q}`, ui.Username))
|
||||||
|
m[ui.Username] = ui
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
112
app/vmauth/auth_config_test.go
Normal file
112
app/vmauth/auth_config_test.go
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseAuthConfigFailure(t *testing.T) {
|
||||||
|
f := func(s string) {
|
||||||
|
t.Helper()
|
||||||
|
_, err := parseAuthConfig([]byte(s))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expecting non-nil error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty config
|
||||||
|
f(``)
|
||||||
|
|
||||||
|
// Invalid entry
|
||||||
|
f(`foobar`)
|
||||||
|
f(`foobar: baz`)
|
||||||
|
|
||||||
|
// Empty users
|
||||||
|
f(`users: []`)
|
||||||
|
|
||||||
|
// Missing url_prefix
|
||||||
|
f(`
|
||||||
|
users:
|
||||||
|
- username: foo
|
||||||
|
`)
|
||||||
|
|
||||||
|
// Invalid url_prefix
|
||||||
|
f(`
|
||||||
|
users:
|
||||||
|
- username: foo
|
||||||
|
url_prefix: bar
|
||||||
|
`)
|
||||||
|
f(`
|
||||||
|
users:
|
||||||
|
- username: foo
|
||||||
|
url_prefix: ftp://bar
|
||||||
|
`)
|
||||||
|
f(`
|
||||||
|
users:
|
||||||
|
- username: foo
|
||||||
|
url_prefix: //bar
|
||||||
|
`)
|
||||||
|
|
||||||
|
// Duplicate users
|
||||||
|
f(`
|
||||||
|
users:
|
||||||
|
- username: foo
|
||||||
|
url_prefix: http://foo.bar
|
||||||
|
- username: bar
|
||||||
|
url_prefix: http://xxx.yyy
|
||||||
|
- username: foo
|
||||||
|
url_prefix: https://sss.sss
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseAuthConfigSuccess(t *testing.T) {
|
||||||
|
f := func(s string, expectedAuthConfig map[string]*UserInfo) {
|
||||||
|
t.Helper()
|
||||||
|
m, err := parseAuthConfig([]byte(s))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
removeMetrics(m)
|
||||||
|
if !reflect.DeepEqual(m, expectedAuthConfig) {
|
||||||
|
t.Fatalf("unexpected auth config\ngot\n%v\nwant\n%v", m, expectedAuthConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single user
|
||||||
|
f(`
|
||||||
|
users:
|
||||||
|
- username: foo
|
||||||
|
password: bar
|
||||||
|
url_prefix: http://aaa:343/bbb
|
||||||
|
`, map[string]*UserInfo{
|
||||||
|
"foo": {
|
||||||
|
Username: "foo",
|
||||||
|
Password: "bar",
|
||||||
|
URLPrefix: "http://aaa:343/bbb",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Multiple users
|
||||||
|
f(`
|
||||||
|
users:
|
||||||
|
- username: foo
|
||||||
|
url_prefix: http://foo
|
||||||
|
- username: bar
|
||||||
|
url_prefix: https://bar/x///
|
||||||
|
`, map[string]*UserInfo{
|
||||||
|
"foo": {
|
||||||
|
Username: "foo",
|
||||||
|
URLPrefix: "http://foo",
|
||||||
|
},
|
||||||
|
"bar": {
|
||||||
|
Username: "bar",
|
||||||
|
URLPrefix: "https://bar/x",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeMetrics(m map[string]*UserInfo) {
|
||||||
|
for _, info := range m {
|
||||||
|
info.requests = nil
|
||||||
|
}
|
||||||
|
}
|
8
app/vmauth/deployment/Dockerfile
Normal file
8
app/vmauth/deployment/Dockerfile
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
ARG base_image
|
||||||
|
FROM $base_image
|
||||||
|
|
||||||
|
EXPOSE 8427
|
||||||
|
|
||||||
|
ENTRYPOINT ["/vmauth-prod"]
|
||||||
|
ARG src_binary
|
||||||
|
COPY $src_binary ./vmauth-prod
|
79
app/vmauth/main.go
Normal file
79
app/vmauth/main.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envflag"
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/procutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
httpListenAddr = flag.String("httpListenAddr", ":8427", "TCP address to listen for http connections")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
envflag.Parse()
|
||||||
|
buildinfo.Init()
|
||||||
|
logger.Init()
|
||||||
|
logger.Infof("starting vmauth at %q...", *httpListenAddr)
|
||||||
|
startTime := time.Now()
|
||||||
|
initAuthConfig()
|
||||||
|
go httpserver.Serve(*httpListenAddr, requestHandler)
|
||||||
|
logger.Infof("started vmauth in %.3f seconds", time.Since(startTime).Seconds())
|
||||||
|
|
||||||
|
sig := procutil.WaitForSigterm()
|
||||||
|
logger.Infof("received signal %s", sig)
|
||||||
|
|
||||||
|
startTime = time.Now()
|
||||||
|
logger.Infof("gracefully shutting down webservice at %q", *httpListenAddr)
|
||||||
|
if err := httpserver.Stop(*httpListenAddr); err != nil {
|
||||||
|
logger.Fatalf("cannot stop the webservice: %s", err)
|
||||||
|
}
|
||||||
|
logger.Infof("successfully shut down the webservice in %.3f seconds", time.Since(startTime).Seconds())
|
||||||
|
stopAuthConfig()
|
||||||
|
logger.Infof("successfully stopped vmauth in %.3f seconds", time.Since(startTime).Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||||
|
username, password, ok := r.BasicAuth()
|
||||||
|
if !ok {
|
||||||
|
httpserver.Errorf(w, "Missing `Authorization: Basic *` header")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
ac := authConfig.Load().(map[string]*UserInfo)
|
||||||
|
info := ac[username]
|
||||||
|
if info == nil || info.Password != password {
|
||||||
|
httpserver.Errorf(w, "Cannot find the provided username %q or password in config", username)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
info.requests.Inc()
|
||||||
|
|
||||||
|
targetURL := info.URLPrefix + r.RequestURI
|
||||||
|
if _, err := url.Parse(targetURL); err != nil {
|
||||||
|
httpserver.Errorf(w, "Invalid targetURL=%q: %s", targetURL, err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
r.Header.Set("vm-target-url", targetURL)
|
||||||
|
reverseProxy.ServeHTTP(w, r)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var reverseProxy = &httputil.ReverseProxy{
|
||||||
|
Director: func(r *http.Request) {
|
||||||
|
targetURL := r.Header.Get("vm-target-url")
|
||||||
|
target, err := url.Parse(targetURL)
|
||||||
|
if err != nil {
|
||||||
|
logger.Panicf("BUG: unexpected error when parsing targetURL=%q: %s", targetURL, err)
|
||||||
|
}
|
||||||
|
r.URL = target
|
||||||
|
},
|
||||||
|
FlushInterval: time.Second,
|
||||||
|
ErrorLog: logger.StdErrorLogger(),
|
||||||
|
}
|
Loading…
Reference in a new issue