blob: 0ee4c044ce2329e416afa886731af7d48216b613 [file] [log] [blame]
// auth-proxy is a reverse proxy that runs in front of applications and takes
// care of authentication.
//
// This is useful for applications like Promentheus that doesn't handle
// authentication itself, so we can run it behind auth-proxy to restrict access.
//
// The auth-proxy application also adds the X-WEBAUTH-USER header to each
// authenticated request and gives it the value of the logged in users email
// address, which can be used for audit logging. The application running behind
// auth-proxy should then use:
//
// https://pkg.go.dev/go.skia.org/infra/go/alogin/proxylogin
//
// When using --cria_group this application should be run using work-load
// identity with a service account that as read access to CRIA, such as:
//
// skia-auth-proxy-cria-reader@skia-public.iam.gserviceaccount.com
//
// See also:
//
// https://chrome-infra-auth.appspot.com/auth/groups/project-skia-auth-service-access
//
// https://grafana.com/blog/2015/12/07/grafana-authproxy-have-it-your-way/
package main
import (
"context"
"flag"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"go.skia.org/infra/go/allowed"
"go.skia.org/infra/go/common"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/kube/go/auth-proxy/auth"
"golang.org/x/oauth2/google"
)
var (
criaGroup = flag.String("cria_group", "", "The chrome infra auth group to use for restricting access. Example: 'google/skia-staff@google.com'")
local = flag.Bool("local", false, "Running locally if true. As opposed to in production.")
port = flag.String("port", ":8000", "HTTP service address (e.g., ':8000')")
promPort = flag.String("prom_port", ":20000", "Metrics service address (e.g., ':10110')")
targetPort = flag.String("target_port", ":9000", "The port we are proxying to.")
allowPost = flag.Bool("allow_post", false, "Allow POST requests to bypass auth.")
allowedFrom = flag.String("allowed_from", "", "A comma separated list of of domains and email addresses that are allowed to access the site. Example: 'google.com'")
passive = flag.Bool("passive", false, "If true then allow unauthenticated requests to go through, while still adding logged in users emails in via the webAuthHeaderName.")
)
// Send the logged in user email in the following header. This allows decoupling
// of authentication from the core of the app. See
// https://grafana.com/blog/2015/12/07/grafana-authproxy-have-it-your-way/ for
// how Grafana uses this to support almost any authentication handler.
const webAuthHeaderName = "X-WEBAUTH-USER"
type proxy struct {
reverseProxy http.Handler
authProvider auth.Auth
}
func newProxy(target *url.URL, authProvider auth.Auth) *proxy {
return &proxy{
reverseProxy: httputil.NewSingleHostReverseProxy(target),
authProvider: authProvider,
}
}
func (p proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
sklog.Infof("Requesting: %s", r.RequestURI)
email := p.authProvider.LoggedInAs(r)
r.Header.Del(webAuthHeaderName)
r.Header.Add(webAuthHeaderName, email)
if r.Method == "POST" && *allowPost {
p.reverseProxy.ServeHTTP(w, r)
return
}
if !*passive {
if email == "" {
http.Redirect(w, r, p.authProvider.LoginURL(w, r), http.StatusSeeOther)
return
}
if !p.authProvider.IsViewer(r) {
http.Error(w, "403 Forbidden", http.StatusForbidden)
return
}
}
p.reverseProxy.ServeHTTP(w, r)
}
func validateFlags() error {
if *criaGroup != "" && *allowedFrom != "" {
return fmt.Errorf("Only one of the flags in [--auth_group, --allowed_from] can be specified.")
}
if *criaGroup == "" && *allowedFrom == "" {
return fmt.Errorf("At least one of the flags in [--auth_group, --allowed_from] must be specified.")
}
return nil
}
func main() {
common.InitWithMust(
"auth-proxy",
common.PrometheusOpt(promPort),
common.MetricsLoggingOpt(),
)
if err := validateFlags(); err != nil {
sklog.Fatal(err)
}
var allow allowed.Allow
if *criaGroup != "" {
ctx := context.Background()
ts, err := google.DefaultTokenSource(ctx, "email")
if err != nil {
sklog.Fatal(err)
}
criaClient := httputils.DefaultClientConfig().WithTokenSource(ts).With2xxOnly().Client()
allow, err = allowed.NewAllowedFromChromeInfraAuth(criaClient, *criaGroup)
if err != nil {
sklog.Fatal(err)
}
} else {
allow = allowed.NewAllowedFromList(strings.Split(*allowedFrom, ","))
}
authInstance := auth.New()
authInstance.SimpleInitWithAllow(*port, *local, nil, nil, allow)
targetURL := fmt.Sprintf("http://localhost%s", *targetPort)
target, err := url.Parse(targetURL)
if err != nil {
sklog.Fatalf("Unable to parse target URL %s: %s", targetURL, err)
}
var h http.Handler = newProxy(target, authInstance)
h = httputils.HealthzAndHTTPS(h)
http.Handle("/", h)
sklog.Fatal(http.ListenAndServe(*port, nil))
}