Skip to content

Commit

Permalink
Add hot reload configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
angelbarrera92 committed Jul 23, 2022
1 parent a2e5644 commit e5976c4
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 58 deletions.
91 changes: 51 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Prometheus — Multi-tenant proxy

![Build Status](https://action-badges.now.sh/k8spin/k8spin-operator)
![Build Status](https://action-badges.now.sh/k8spin/prometheus-multi-tenant-proxy)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)

[![Twitter](https://img.shields.io/twitter/url/https/twitter.com/k8spin.svg?style=social&label=Follow%20%40k8spin)](https://twitter.com/k8spin)
Expand Down Expand Up @@ -31,14 +31,15 @@ instance, configure the auth proxy configuration and run it.
### Run it

```bash
$ prometheus-multi-tenant-proxy run --prometheus-endpoint http://localhost:9090 --port 9091 --auth-config ./my-auth-config.yaml
$ prometheus-multi-tenant-proxy run --prometheus-endpoint http://localhost:9090 --port 9091 --auth-config ./my-auth-config.yaml --reload-interval=5
```

Where:

- `--port`: Port used to expose this proxy.
- `--prometheus-endpoint`: URL of your Prometheus instance.
- `--auth-config`: Authentication configuration file path.
- `--reload-interval`: Interval in minutes to reload the auth config file.

#### Configure the proxy

Expand Down Expand Up @@ -77,54 +78,64 @@ A tenant can contain multiple users. But a user is tied to a simple tenant.
If you want to build it from this repository, follow the instructions bellow:
```bash
$ docker run -it --entrypoint /bin/bash --rm golang:1.15.8-buster
$ docker run -it --entrypoint /bin/bash --rm golang:1.17-buster
root@6985c5523ed0:/go# git clone https://github.com/k8spin/prometheus-multi-tenant-proxy.git
Cloning into 'prometheus-multi-tenant-proxy'...
remote: Enumerating objects: 96, done.
remote: Counting objects: 100% (96/96), done.
remote: Compressing objects: 100% (54/54), done.
remote: Total 96 (delta 31), reused 87 (delta 22), pack-reused 0
Unpacking objects: 100% (96/96), done.
remote: Enumerating objects: 297, done.
remote: Counting objects: 100% (85/85), done.
remote: Compressing objects: 100% (42/42), done.
remote: Total 297 (delta 42), reused 57 (delta 37), pack-reused 212
Receiving objects: 100% (297/297), 209.10 KiB | 376.00 KiB/s, done.
Resolving deltas: 100% (120/120), done.
root@6985c5523ed0:/go# cd prometheus-multi-tenant-proxy/cmd/prometheus-multi-tenant-proxy/
root@6985c5523ed0:/go# go build
go: downloading github.com/urfave/cli/v2 v2.3.0
go: downloading github.com/prometheus-community/prom-label-proxy v0.3.0
go: downloading github.com/prometheus/prometheus v1.8.2-0.20210811141203-dcb07e8eac34
go: downloading github.com/urfave/cli/v2 v2.11.1
go: downloading github.com/prometheus/prometheus v0.35.0
go: downloading github.com/prometheus-community/prom-label-proxy v0.5.0
go: downloading gopkg.in/yaml.v2 v2.4.0
go: downloading github.com/efficientgo/tools/core v0.0.0-20210201224146-3d78f4d30648
go: downloading github.com/go-openapi/runtime v0.19.28
go: downloading github.com/go-openapi/strfmt v0.20.1
go: downloading github.com/pkg/errors v0.9.1
go: downloading github.com/prometheus/alertmanager v0.22.2
go: downloading github.com/cpuguy83/go-md2man/v2 v2.0.0
go: downloading github.com/cespare/xxhash/v2 v2.1.1
go: downloading github.com/prometheus/common v0.30.0
go: downloading github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
go: downloading github.com/go-openapi/errors v0.20.0
go: downloading github.com/mitchellh/mapstructure v1.4.1
go: downloading github.com/efficientgo/tools/core v0.0.0-20220225185207-fe763185946b
go: downloading github.com/go-openapi/runtime v0.24.1
go: downloading github.com/go-openapi/strfmt v0.21.3
go: downloading github.com/prometheus/alertmanager v0.24.0
go: downloading github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
go: downloading github.com/go-openapi/errors v0.20.2
go: downloading github.com/mitchellh/mapstructure v1.5.0
go: downloading github.com/oklog/ulid v1.3.1
go: downloading go.mongodb.org/mongo-driver v1.5.1
go: downloading go.mongodb.org/mongo-driver v1.10.0
go: downloading github.com/opentracing/opentracing-go v1.2.0
go: downloading github.com/go-openapi/swag v0.19.15
go: downloading github.com/go-openapi/validate v0.20.2
go: downloading github.com/russross/blackfriday/v2 v2.0.1
go: downloading github.com/go-kit/log v0.1.0
go: downloading github.com/go-openapi/analysis v0.20.0
go: downloading github.com/go-openapi/loads v0.20.2
go: downloading github.com/go-openapi/spec v0.20.3
go: downloading github.com/mailru/easyjson v0.7.6
go: downloading github.com/shurcooL/sanitized_anchor_name v1.0.0
go: downloading github.com/go-logfmt/logfmt v0.5.0
go: downloading go.uber.org/atomic v1.9.0
go: downloading github.com/cpuguy83/go-md2man/v2 v2.0.2
go: downloading github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673
go: downloading github.com/prometheus/common v0.37.0
go: downloading github.com/go-openapi/swag v0.21.1
go: downloading github.com/go-openapi/validate v0.22.0
go: downloading github.com/go-openapi/analysis v0.21.4
go: downloading github.com/go-openapi/loads v0.21.1
go: downloading github.com/go-openapi/spec v0.20.6
go: downloading github.com/cespare/xxhash/v2 v2.1.2
go: downloading github.com/grafana/regexp v0.0.0-20220304095617-2e8d9baf4ac2
go: downloading github.com/russross/blackfriday/v2 v2.1.0
go: downloading github.com/mailru/easyjson v0.7.7
go: downloading github.com/go-openapi/jsonpointer v0.19.5
go: downloading github.com/go-stack/stack v1.8.0
go: downloading github.com/go-kit/log v0.2.1
go: downloading github.com/josharian/intern v1.0.0
go: downloading golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
go: downloading github.com/go-openapi/jsonreference v0.19.5
go: downloading github.com/PuerkitoBio/purell v1.1.1
go: downloading github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578
go: downloading golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985
go: downloading golang.org/x/text v0.3.6
go: downloading github.com/go-openapi/jsonreference v0.20.0
go: downloading github.com/dennwc/varint v1.0.0
go: downloading github.com/prometheus/client_golang v1.12.2
go: downloading go.uber.org/atomic v1.9.0
go: downloading github.com/stretchr/testify v1.8.0
go: downloading github.com/go-logfmt/logfmt v0.5.1
go: downloading golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
go: downloading go.uber.org/goleak v1.1.12
go: downloading github.com/davecgh/go-spew v1.1.1
go: downloading github.com/pmezard/go-difflib v1.0.0
go: downloading gopkg.in/yaml.v3 v3.0.1
go: downloading github.com/prometheus/client_model v0.2.0
go: downloading github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369
go: downloading github.com/golang/protobuf v1.5.2
go: downloading github.com/beorn7/perks v1.0.1
go: downloading github.com/prometheus/procfs v0.7.3
go: downloading google.golang.org/protobuf v1.28.0
root@6985c5523ed0:/go# ./prometheus-multi-tenant-proxy
NAME:
Prometheus multi-tenant proxy - Makes your Prometheus server multi tenant
Expand Down
4 changes: 4 additions & 0 deletions cmd/prometheus-multi-tenant-proxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ func main() {
Name: "auth-config",
Usage: "AuthN yaml configuration file path",
Value: "authn.yaml",
}, &cli.IntFlag{
Name: "reload-interval",
Usage: "Interval time to reload the authn configuration file (minutes)",
Value: 5,
},
},
},
Expand Down
13 changes: 10 additions & 3 deletions deployments/kubernetes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@ metadata:
application: prometheus-multi-tenant-proxy
name: prometheus-auth-config
namespace: default
data:
authn.yaml: dXNlcnM6CiAgLSB1c2VybmFtZTogSGFwcHkKICAgIHBhc3N3b3JkOiBQcm9tZXRoZXVzCiAgICBuYW1lc3BhY2U6IGRlZmF1bHQKICAtIHVzZXJuYW1lOiBTYWQKICAgIHBhc3N3b3JkOiBQcm9tZXRoZXVzCiAgICBuYW1lc3BhY2U6IGt1YmUtc3lzdGVtCg==
stringData:
authn.yaml: |
users:
- username: Happy
password: Prometheus
namespace: default
- username: Sad
password: Prometheus
namespace: kube-system
---
apiVersion: apps/v1
kind: Deployment
Expand Down Expand Up @@ -43,7 +50,7 @@ spec:
image: ghcr.io/k8spin/prometheus-multi-tenant-proxy:latest
imagePullPolicy: Always
command: ["/bin/bash"]
args: ["-c", "/prometheus-multi-tenant-proxy run --port=9092 --prometheus-endpoint=${PROMETHEUS_MULTI_TENANT_PROXY_PROMETHEUS_ENDPOINT} --auth-config=/etc/prometheus-auth-config/authn.yaml"]
args: ["-c", "/prometheus-multi-tenant-proxy run --port=9092 --prometheus-endpoint=${PROMETHEUS_MULTI_TENANT_PROXY_PROMETHEUS_ENDPOINT} --auth-config=/etc/prometheus-auth-config/authn.yaml --reload-interval=5"]
ports:
- name: http
containerPort: 9092
Expand Down
9 changes: 4 additions & 5 deletions internal/app/prometheus-multi-tenant-proxy/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import (
"context"
"crypto/subtle"
"net/http"

"github.com/k8spin/prometheus-multi-tenant-proxy/internal/pkg"
)

type key int
Expand All @@ -17,10 +15,10 @@ const (
)

// BasicAuth can be used as a middleware chain to authenticate users before proxying a request
func BasicAuth(handler http.HandlerFunc, authConfig *pkg.Authn) http.HandlerFunc {
func BasicAuth(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
authorized, namespace := isAuthorized(user, pass, authConfig)
authorized, namespace := isAuthorized(user, pass)
if !ok || !authorized {
writeUnauthorisedResponse(w)
return
Expand All @@ -30,7 +28,8 @@ func BasicAuth(handler http.HandlerFunc, authConfig *pkg.Authn) http.HandlerFunc
}
}

func isAuthorized(user string, pass string, authConfig *pkg.Authn) (bool, string) {
func isAuthorized(user string, pass string) (bool, string) {
authConfig := GetConfig()
for _, v := range authConfig.Users {
if subtle.ConstantTimeCompare([]byte(user), []byte(v.Username)) == 1 && subtle.ConstantTimeCompare([]byte(pass), []byte(v.Password)) == 1 {
return true, v.Namespace
Expand Down
18 changes: 10 additions & 8 deletions internal/app/prometheus-multi-tenant-proxy/auth_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package proxy

import (
"sync"
"testing"

"github.com/k8spin/prometheus-multi-tenant-proxy/internal/pkg"
)

func Test_isAuthorized(t *testing.T) {
authConfig := pkg.Authn{
func init() {
config = &pkg.Authn{
Users: []pkg.User{
{
Username: "User-a",
Expand All @@ -21,10 +22,13 @@ func Test_isAuthorized(t *testing.T) {
},
},
}
configLock = new(sync.RWMutex)
}

func Test_isAuthorized(t *testing.T) {
type args struct {
user string
pass string
authConfig *pkg.Authn
user string
pass string
}
tests := []struct {
name string
Expand All @@ -37,7 +41,6 @@ func Test_isAuthorized(t *testing.T) {
args{
"User-a",
"pass-a",
&authConfig,
},
true,
"tenant-a",
Expand All @@ -46,15 +49,14 @@ func Test_isAuthorized(t *testing.T) {
args{
"invalid",
"pass-a",
&authConfig,
},
false,
"",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1 := isAuthorized(tt.args.user, tt.args.pass, tt.args.authConfig)
got, got1 := isAuthorized(tt.args.user, tt.args.pass)
if got != tt.want {
t.Errorf("isAuthorized() got = %v, want %v", got, tt.want)
}
Expand Down
45 changes: 43 additions & 2 deletions internal/app/prometheus-multi-tenant-proxy/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,41 @@ import (
"net/http"
"net/http/httputil"
"net/url"
"os"
"sync"
"time"

"github.com/k8spin/prometheus-multi-tenant-proxy/internal/pkg"
"github.com/urfave/cli/v2"
)

var (
config *pkg.Authn
configLock = new(sync.RWMutex)
)

// Serve serves
func Serve(c *cli.Context) error {
prometheusServerURL, _ := url.Parse(c.String("prometheus-endpoint"))
serveAt := fmt.Sprintf(":%d", c.Int("port"))
authConfigLocation := c.String("auth-config")
authConfig, _ := pkg.ParseConfig(&authConfigLocation)
reloadInterval := c.Int("reload-interval")

loadConfig(authConfigLocation)
ticker := time.NewTicker(time.Duration(reloadInterval) * time.Minute)
quit := make(chan struct{})
go func() {
for {
select {
case <-ticker.C:
loadConfig(authConfigLocation)
log.Printf("Reloaded config file %s", authConfigLocation)
case <-quit:
ticker.Stop()
return
}
}
}()

rprt := ReversePrometheusRoundTripper{
prometheusServerURL: prometheusServerURL,
Expand All @@ -29,10 +53,27 @@ func Serve(c *cli.Context) error {

http.HandleFunc("/-/healthy", LogRequest(reverseProxy.ServeHTTP))
http.HandleFunc("/-/ready", LogRequest(reverseProxy.ServeHTTP))
http.HandleFunc("/", LogRequest(BasicAuth(reverseProxy.ServeHTTP, authConfig)))
http.HandleFunc("/", LogRequest(BasicAuth(reverseProxy.ServeHTTP)))
if err := http.ListenAndServe(serveAt, nil); err != nil {
log.Fatalf("Prometheus multi tenant proxy can not start %v", err)
return err
}
return nil
}

func loadConfig(location string) {
temp, err := pkg.ParseConfig(&location)
if err != nil {
log.Fatalf("Could not parse config file %s: %v", location, err)
os.Exit(1)
}
configLock.Lock()
config = temp
configLock.Unlock()
}

func GetConfig() *pkg.Authn {
configLock.RLock()
defer configLock.RUnlock()
return config
}

0 comments on commit e5976c4

Please sign in to comment.