mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-18 02:48:40 +00:00
WIP: grafana auth proxy
This commit is contained in:
parent
8fc11d582d
commit
f2dc643360
146
cmd/authproxy/authproxy.go
Normal file
146
cmd/authproxy/authproxy.go
Normal 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
1
go.mod
@ -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
5
go.sum
@ -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=
|
||||
|
Loading…
x
Reference in New Issue
Block a user