WIP: grafana auth proxy

This commit is contained in:
Brad Fitzpatrick 2021-01-04 08:41:10 -08:00
parent 8fc11d582d
commit f2dc643360
3 changed files with 152 additions and 0 deletions

146
cmd/authproxy/authproxy.go Normal file
View File

@ -0,0 +1,146 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os/exec"
"strings"
"sync"
"time"
grafanaclient "github.com/nytm/go-grafana-api"
"inet.af/netaddr"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
)
var spoofAdmin = flag.Bool("spoof-admin", false, "make everybody be an admin")
func main() {
flag.Parse()
log.Printf("starting")
ln, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
log.Printf("listening on %v", ln.Addr())
target, _ := url.Parse("http://localhost:80")
rp := httputil.NewSingleHostReverseProxy(target)
creds, err := ioutil.ReadFile("/etc/grafana/admin-creds.authproxy")
if err != nil {
log.Fatal(err)
}
userColonPass := strings.TrimSpace(string(creds))
log.Printf("user pass: %q", userColonPass)
gc, err := grafanaclient.New(userColonPass, "http://localhost")
if err != nil {
log.Fatal(err)
}
var (
addMu sync.Mutex
added = map[string]bool{}
)
addUser := func(email, role string) {
addMu.Lock()
defer addMu.Unlock()
if added[email] {
return
}
added[email] = true
err := gc.AddOrgUser(1, "email", role)
log.Printf("adding org user %s as %v: %v", email, role, err)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
ipp, err := netaddr.ParseIPPort(r.RemoteAddr)
if err != nil {
http.Error(w, "bad RemoteAddr", 400)
return
}
if !tsaddr.IsTailscaleIP(ipp.IP) {
http.Error(w, "not a Tailscale IP", 403)
return
}
tstat, err := getTailscaleStatus()
if err != nil {
log.Printf("getting Tailscale status: %v", err)
http.Error(w, "failed to get Tailscale status", 500)
return
}
ro := r.Clone(r.Context())
if u, ok := tstat.userOfIP(ipp.IP); ok && !strings.HasPrefix(r.RequestURI, "/invite") {
role := "viewer"
if strings.HasSuffix(u.LoginName, "@tailscale.com") {
role = "editor"
}
email := strings.Replace(u.LoginName, "@", "-auto@", 1)
addUser(email, role)
log.Printf("serving %v, %v, %v", email, r.RemoteAddr, r.RequestURI)
ro.Header.Add("X-Webauth-User", email)
ro.Header.Add("X-User-Name", u.DisplayName)
ro.Header.Add("X-User-Email", email)
} else {
log.Printf("serving ??, %v, %v", r.RemoteAddr, r.RequestURI)
}
if *spoofAdmin {
ro.Header.Add("X-Webauth-User", "apenwarr@tailscale.com")
}
rp.ServeHTTP(w, ro)
})
var hs http.Server
log.Fatal(hs.Serve(ln))
}
// /etc/grafana/admin-creds.authproxy
// curl -v -X PATCH -u 'apenwarr@tailscale.com:XXXXX' --data '{"role":"Editor"}' -H "Content-Type:application/json" http://localhost:80/api/org/users/
var (
mu sync.Mutex
tsCache *tailscaleStatus
)
func getTailscaleStatus() (*tailscaleStatus, error) {
mu.Lock()
defer mu.Unlock()
if s := tsCache; s != nil && time.Since(s.at) < 10*time.Second {
return s, nil
}
out, err := exec.Command("tailscale", "status", "--json").Output()
if err != nil {
return nil, err
}
tss := &tailscaleStatus{at: time.Now()}
if err := json.Unmarshal(out, &tss.s); err != nil {
return nil, err
}
if tss.s.BackendState != "Running" {
return nil, fmt.Errorf("tailscale not running; in state %q", tss.s.BackendState)
}
return tss, nil
}
type tailscaleStatus struct {
at time.Time
s ipnstate.Status
}
func (tss *tailscaleStatus) userOfIP(ip netaddr.IP) (u tailcfg.UserProfile, ok bool) {
for _, ps := range tss.s.Peer {
if peerIP, err := netaddr.ParseIP(ps.TailAddr); err == nil && ip == peerIP {
u, ok = tss.s.User[ps.UserID]
return
}
}
return u, false
}

1
go.mod
View File

@ -21,6 +21,7 @@ require (
github.com/mdlayher/netlink v1.2.0
github.com/mdlayher/sdnotify v0.0.0-20200625151349-e4a4f32afc4a
github.com/miekg/dns v1.1.30
github.com/nytm/go-grafana-api v0.5.0
github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3
github.com/peterbourgon/ff/v2 v2.0.0
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027

5
go.sum
View File

@ -32,6 +32,7 @@ github.com/go-multierror/multierror v1.0.2 h1:AwsKbEXkmf49ajdFJgcFXqSG0aLo0HEyAE
github.com/go-multierror/multierror v1.0.2/go.mod h1:U7SZR/D9jHgt2nkSj8XcbCWdmVM2igraCHQ3HC1HiKY=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
@ -52,6 +53,8 @@ github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0 h1:BW6OvS3kpT5UEPbC
github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg=
github.com/goreleaser/nfpm v1.1.10 h1:0nwzKUJTcygNxTzVKq2Dh9wpVP1W2biUH6SNKmoxR3w=
github.com/goreleaser/nfpm v1.1.10/go.mod h1:oOcoGRVwvKIODz57NUfiRwFWGfn00NXdgnn6MrYtO5k=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
@ -86,6 +89,8 @@ github.com/mdlayher/sdnotify v0.0.0-20200625151349-e4a4f32afc4a h1:wMv2mvcHRH4jq
github.com/mdlayher/sdnotify v0.0.0-20200625151349-e4a4f32afc4a/go.mod h1:HtjVsQfsrBm1GDcDTUFn4ZXhftxTwO/hxrvEiRc61U4=
github.com/miekg/dns v1.1.30 h1:Qww6FseFn8PRfw07jueqIXqodm0JKiiKuK0DeXSqfyo=
github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/nytm/go-grafana-api v0.5.0 h1:8pIbNPNDguBa4aUNxcYl0GN247W6PXMvsOwiRBmk1sE=
github.com/nytm/go-grafana-api v0.5.0/go.mod h1:YOJL2MOLAmCeqz0cbHU9tZIDj0OxpOiIFKSJXRbAorY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3 h1:YtFkrqsMEj7YqpIhRteVxJxCeC3jJBieuLr0d4C4rSA=