From 4f3de3e14dc84f4b28af58e98c34e4715433be38 Mon Sep 17 00:00:00 2001 From: Jorge Luis Betancourt Gonzalez Date: Tue, 16 Jun 2020 01:36:10 +0200 Subject: [PATCH] Initial prototype --- .gitignore | 22 ++++++++++++ go.mod | 13 +++++++ go.sum | 41 +++++++++++++++++++++ grafana.ini | 11 ++++++ main.go | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 189 insertions(+) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 go.sum create mode 100644 grafana.ini create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e4adbc --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +* +!/**/ +!*.* + +.envrc +.env + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4971b2b --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/jorgelbg/cloudflare-access-grafana + +go 1.14 + +require ( + github.com/coreos/go-oidc v2.2.1+incompatible + github.com/google/go-cmp v0.4.1 // indirect + github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect + github.com/stretchr/testify v1.6.1 // indirect + golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect + gopkg.in/square/go-jose.v2 v2.5.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9af92b4 --- /dev/null +++ b/go.sum @@ -0,0 +1,41 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= +github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU= +github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/grafana.ini b/grafana.ini new file mode 100644 index 0000000..8aea3ee --- /dev/null +++ b/grafana.ini @@ -0,0 +1,11 @@ +[users] +allow_sign_up = false +auto_assign_org = true +auto_assign_org_role = Editor + +[auth.proxy] +enabled = true +header_name = X-WEBAUTH-USER +header_property = email +auto_sign_up = true + diff --git a/main.go b/main.go new file mode 100644 index 0000000..0b0e70a --- /dev/null +++ b/main.go @@ -0,0 +1,102 @@ +package main + +import ( + "context" + "fmt" + "log" + "net/http" + "net/http/httputil" + "os" + "strings" + + "github.com/coreos/go-oidc" +) + +var ( + ctx = context.TODO() + authDomain = os.Getenv("AUTHDOMAIN") + certsURL = fmt.Sprintf("%s/cdn-cgi/access/certs", authDomain) + + // policyAUD is your application AUD value + policyAUD = os.Getenv("POLICYAUD") + + // forwardHeader is the header to be set from the email claim embedded in the JWT token + forwardHeader = os.Getenv("FORWARDHEADER") + + // forwardHost is the host to bet used to forward the request. If set it will override the Host + // header of the original request + forwardHost = os.Getenv("FORWARDHOST") + + // listenAddr is the port where this proxy will be listening + listenAddr = os.Getenv("ADDR") + + config = &oidc.Config{ + ClientID: policyAUD, + } + keySet = oidc.NewRemoteKeySet(ctx, certsURL) + verifier = oidc.NewVerifier(authDomain, keySet, config) +) + +// VerifyToken is a middleware to verify a CF Access token +func VerifyToken(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + headers := r.Header + + // Make sure that the incoming request has our token header + // Could also look in the cookies for CF_AUTHORIZATION + accessJWT := headers.Get("Cf-Access-Jwt-Assertion") + if accessJWT == "" { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("No token on the request")) + return + } + + // Verify the access token + ctx := r.Context() + token, err := verifier.Verify(ctx, accessJWT) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(fmt.Sprintf("Invalid token: %s", err.Error()))) + return + } + + // Extract custom claims + var claims struct { + Email string `json:"email"` + Type string `json:"type"` + } + + if err := token.Claims(&claims); err != nil { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(fmt.Sprintf("Invalid claims in token: %s", err.Error()))) + } + + // set the authentication forward header before proxying the request + r.Header.Add(forwardHeader, claims.Email) + log.Printf("Authenticated as: %s", claims.Email) + + next.ServeHTTP(w, r) + } + + return http.HandlerFunc(fn) +} + +func main() { + director := func(req *http.Request) { + req.Header.Add("X-Forwarded-Host", req.Host) + req.Header.Add("X-Origin-Host", "cloudflare-access-proxy") + req.URL.Scheme = "http" + + if len(strings.TrimSpace(forwardHost)) > 0 { + req.URL.Host = forwardHost + } + } + + proxy := &httputil.ReverseProxy{Director: director} + http.Handle("/", VerifyToken(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + proxy.ServeHTTP(w, r) + }))) + + log.Printf("Listening on http://%s/", listenAddr) + http.ListenAndServe(listenAddr, nil) +}