Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
angelbarrera92 committed Feb 7, 2020
0 parents commit c887d6d
Show file tree
Hide file tree
Showing 17 changed files with 496 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .gitignore
@@ -0,0 +1,15 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, build with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Binary
cmd/prometheus-multi-tenant-proxy/prometheus-multi-tenant-proxy
Empty file added README.md
Empty file.
24 changes: 24 additions & 0 deletions build/package/Dockerfile
@@ -0,0 +1,24 @@
FROM golang:1.12-alpine as builder

ENV GO111MODULE=on
ENV CGO_ENABLED=0

RUN apk add -U --no-cache git ca-certificates && \
mkdir -p src/github.com/angelbarrera92/prometheus-multi-tenant-proxy

WORKDIR /go/src/github.com/angelbarrera92/prometheus-multi-tenant-proxy

COPY go.mod go.mod
COPY go.sum go.sum
COPY cmd cmd
COPY internal internal

RUN cd cmd/prometheus-multi-tenant-proxy && \
go build .

FROM scratch

COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /go/src/github.com/angelbarrera92/prometheus-multi-tenant-proxy/cmd/prometheus-multi-tenant-proxy/prometheus-multi-tenant-proxy /prometheus-multi-tenant-proxy

ENTRYPOINT [ "/prometheus-multi-tenant-proxy" ]
45 changes: 45 additions & 0 deletions cmd/prometheus-multi-tenant-proxy/main.go
@@ -0,0 +1,45 @@
package main

import (
"os"

"github.com/angelbarrera92/prometheus-multi-tenant-proxy/internal/app/prometheus-multi-tenant-proxy"
"github.com/urfave/cli"
)

var (
version = "dev"
commit = "none"
date = "unknown"
)

func main() {
app := cli.NewApp()
app.Name = "Prometheus Multitenant Proxy"
app.Usage = "Makes your Prometheus server multi tenant"
app.Version = version
app.Author = "Ángel Barrera - @angelbarrera92"
app.Commands = []cli.Command{
{
Name: "run",
Usage: "Runs the Prometheus multi tenant proxy",
Action: proxy.Serve,
Flags: []cli.Flag{
cli.IntFlag{
Name: "port",
Usage: "Port to expose this prometheus proxy",
Value: 9092,
}, cli.StringFlag{
Name: "prometheus-label-proxy-endpoint",
Usage: "Prometheus Label Proxy",
Value: "http://localhost:9091",
}, cli.StringFlag{
Name: "auth-config",
Usage: "AuthN yaml configuration file path",
Value: "authn.yaml",
},
},
},
}
app.Run(os.Args)
}
4 changes: 4 additions & 0 deletions configs/bad.yaml
@@ -0,0 +1,4 @@
users
- username: User
password: Prom
namespace: tenant-1
7 changes: 7 additions & 0 deletions configs/multiple.user.yaml
@@ -0,0 +1,7 @@
users:
- username: User-a
password: pass-a
namespace: tenant-a
- username: User-b
password: pass-b
namespace: tenant-b
7 changes: 7 additions & 0 deletions configs/sample.yaml
@@ -0,0 +1,7 @@
users:
- username: Happy
password: Prometheus
namespace: default
- username: Sad
password: Prometheus
namespace: kube-system
74 changes: 74 additions & 0 deletions deployment.yaml
@@ -0,0 +1,74 @@
apiVersion: v1
data:
authn.yaml: dXNlcnM6CiAgLSB1c2VybmFtZTogSGFwcHkKICAgIHBhc3N3b3JkOiBQcm9tZXRoZXVzCiAgICBuYW1lc3BhY2U6IGRlZmF1bHQKICAtIHVzZXJuYW1lOiBTYWQKICAgIHBhc3N3b3JkOiBQcm9tZXRoZXVzCiAgICBuYW1lc3BhY2U6IGt1YmUtc3lzdGVtCg==
kind: Secret
metadata:
labels:
application: prometheus-multitenant-proxy
name: prometheus-auth-config
namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
application: prometheus-multitenant-proxy
name: prometheus-multitenant-proxy
namespace: default
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
application: prometheus-multitenant-proxy
template:
metadata:
labels:
application: prometheus-multitenant-proxy
spec:
volumes:
- name: prometheus-auth-config
secret:
secretName: prometheus-auth-config
containers:
- name: prometheus-multitenant-proxy
image: angelbarrera92/prometheus-multi-tenant-proxy:dev
imagePullPolicy: Always
args:
- "run"
- "--port=9092"
- "--prometheus-label-proxy-endpoint=http://127.0.0.1:9091"
- "--auth-config=/etc/prometheus-auth-config/authn.yaml"
ports:
- name: http
containerPort: 9092
protocol: TCP
volumeMounts:
- name: prometheus-auth-config
mountPath: /etc/prometheus-auth-config
- name: prometheus-label-proxy
image: angelbarrera92/prom-label-proxy:dev
imagePullPolicy: Always
args:
- "--insecure-listen-address=127.0.0.1:9091"
- "--label=namespace"
- "--upstream=http://prometheus-operated.default.svc.cluster.local:9090"
---
apiVersion: v1
kind: Service
metadata:
name: prometheus-multitenant-proxy
namespace: default
labels:
application: prometheus-multitenant-proxy
spec:
type: ClusterIP
ports:
- name: http
port: 9092
protocol: TCP
targetPort: http
selector:
application: prometheus-multitenant-proxy

8 changes: 8 additions & 0 deletions go.mod
@@ -0,0 +1,8 @@
module github.com/angelbarrera92/prometheus-multi-tenant-proxy

go 1.12

require (
github.com/urfave/cli v1.21.0
gopkg.in/yaml.v2 v2.2.2
)
6 changes: 6 additions & 0 deletions go.sum
@@ -0,0 +1,6 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/urfave/cli v1.21.0 h1:wYSSj06510qPIzGSua9ZqsncMmWE3Zr55KBERygyrxE=
github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
46 changes: 46 additions & 0 deletions internal/app/prometheus-multi-tenant-proxy/auth.go
@@ -0,0 +1,46 @@
package proxy

import (
"context"
"crypto/subtle"
"net/http"

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

type key int

const (
//Namespace Key used to pass prometheus tenant id though the middleware context
Namespace key = iota
realm = "Prometheus multi-tenant proxy"
)

// 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 {
return func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
authorized, namespace := isAuthorized(user, pass, authConfig)
if !ok || !authorized {
writeUnauthorisedResponse(w)
return
}
ctx := context.WithValue(r.Context(), Namespace, namespace)
handler(w, r.WithContext(ctx))
}
}

func isAuthorized(user string, pass string, authConfig *pkg.Authn) (bool, string) {
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
}
}
return false, ""
}

func writeUnauthorisedResponse(w http.ResponseWriter) {
w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
w.WriteHeader(401)
w.Write([]byte("Unauthorised\n"))
}
66 changes: 66 additions & 0 deletions internal/app/prometheus-multi-tenant-proxy/auth_test.go
@@ -0,0 +1,66 @@
package proxy

import (
"testing"

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

func Test_isAuthorized(t *testing.T) {
authConfig := pkg.Authn{
[]pkg.User{
pkg.User{
"User-a",
"pass-a",
"tenant-a",
},
pkg.User{
"User-b",
"pass-b",
"tenant-b",
},
},
}
type args struct {
user string
pass string
authConfig *pkg.Authn
}
tests := []struct {
name string
args args
want bool
want1 string
}{
{
"Valid User",
args{
"User-a",
"pass-a",
&authConfig,
},
true,
"tenant-a",
}, {
"Invalid User",
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)
if got != tt.want {
t.Errorf("isAuthorized() got = %v, want %v", got, tt.want)
}
if got1 != tt.want1 {
t.Errorf("isAuthorized() got1 = %v, want %v", got1, tt.want1)
}
})
}
}
14 changes: 14 additions & 0 deletions internal/app/prometheus-multi-tenant-proxy/logging.go
@@ -0,0 +1,14 @@
package proxy

import (
"log"
"net/http"
)

// LogRequest can be used as a middleware chain to log every request before proxying the request
func LogRequest(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
handler(w, r)
}
}
28 changes: 28 additions & 0 deletions internal/app/prometheus-multi-tenant-proxy/reverse.go
@@ -0,0 +1,28 @@
package proxy

import (
"net/http"
"net/http/httputil"
"net/url"
"log"
)

// ReversePrometheus a
func ReversePrometheus(reverseProxy *httputil.ReverseProxy, prometheusLabelProxyServerURL *url.URL) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
modifyRequest(r, prometheusLabelProxyServerURL)
reverseProxy.ServeHTTP(w, r)
log.Printf("[PROXY]\t%+v\n", r.URL)
}
}

func modifyRequest(r *http.Request, prometheusLabelProxyServerURL *url.URL) {
r.URL.Scheme = prometheusLabelProxyServerURL.Scheme
r.URL.Host = prometheusLabelProxyServerURL.Host
r.Host = prometheusLabelProxyServerURL.Host
namespace := r.Context().Value(Namespace)
r.Header.Set("X-Forwarded-Host", r.Host)
q := r.URL.Query()
q.Add("namespace", namespace.(string))
r.URL.RawQuery = q.Encode()
}
32 changes: 32 additions & 0 deletions internal/app/prometheus-multi-tenant-proxy/server.go
@@ -0,0 +1,32 @@
package proxy

import (
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"

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

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

http.HandleFunc("/", createHandler(prometheusLabelProxyServerURL, authConfig))
if err := http.ListenAndServe(serveAt, nil); err != nil {
log.Fatalf("Prometheus multi tenant proxy can not start %v", err)
return err
}
return nil
}

func createHandler(prometheusLabelProxyServerURL *url.URL, authConfig *pkg.Authn) http.HandlerFunc {
reverseProxy := httputil.NewSingleHostReverseProxy(prometheusLabelProxyServerURL)
return LogRequest(BasicAuth(ReversePrometheus(reverseProxy, prometheusLabelProxyServerURL), authConfig))
}

0 comments on commit c887d6d

Please sign in to comment.