Add CRIA group support to auth-proxy.

Bug: skia:13319
Change-Id: I031aa759fd112d62e842e27e56d250a605890101
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/540087
Reviewed-by: Leandro Lovisolo <lovisolo@google.com>
Commit-Queue: Joe Gregorio <jcgregorio@google.com>
diff --git a/kube/go/auth-proxy/BUILD.bazel b/kube/go/auth-proxy/BUILD.bazel
index fdcced4..2862a51 100644
--- a/kube/go/auth-proxy/BUILD.bazel
+++ b/kube/go/auth-proxy/BUILD.bazel
@@ -12,6 +12,7 @@
         "//go/httputils",
         "//go/sklog",
         "//kube/go/auth-proxy/auth",
+        "@org_golang_x_oauth2//google",
     ],
 )
 
diff --git a/kube/go/auth-proxy/main.go b/kube/go/auth-proxy/main.go
index e50050f..0ee4c04 100644
--- a/kube/go/auth-proxy/main.go
+++ b/kube/go/auth-proxy/main.go
@@ -1,8 +1,30 @@
-// Prometheus doesn't handle authentication, so use a reverse
-// proxy that requires login to protect it.
+// 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"
@@ -15,15 +37,17 @@
 	"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", "google.com", "A comma separated list of of domains and email addresses that are allowed to access the site.")
+	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.")
 )
 
@@ -67,6 +91,17 @@
 	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",
@@ -74,9 +109,28 @@
 		common.MetricsLoggingOpt(),
 	)
 
-	allowedList := strings.Split(*allowedFrom, ",")
+	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, allowed.NewAllowedFromList(allowedList))
+	authInstance.SimpleInitWithAllow(*port, *local, nil, nil, allow)
 	targetURL := fmt.Sprintf("http://localhost%s", *targetPort)
 	target, err := url.Parse(targetURL)
 	if err != nil {
diff --git a/kube/go/auth-proxy/main_test.go b/kube/go/auth-proxy/main_test.go
index 05521a0..1ea54c2 100644
--- a/kube/go/auth-proxy/main_test.go
+++ b/kube/go/auth-proxy/main_test.go
@@ -156,3 +156,32 @@
 	require.True(t, *called)
 	authMock.AssertExpectations(t)
 }
+
+func TestValidateFlags_BothFlagsSpecified_ReturnsError(t *testing.T) {
+	unittest.SmallTest(t)
+	*criaGroup = "project-angle-committers"
+	*allowedFrom = "google.com"
+
+	require.Error(t, validateFlags())
+}
+
+func TestValidateFlags_NeitherFlagIsSpecified_ReturnsError(t *testing.T) {
+	unittest.SmallTest(t)
+	*criaGroup = ""
+	*allowedFrom = ""
+
+	require.Error(t, validateFlags())
+}
+
+func TestValidateFlags_OnlyOneFlagIsSpecified_ReturnsNoError(t *testing.T) {
+	unittest.SmallTest(t)
+	*criaGroup = "project-angle-committers"
+	*allowedFrom = ""
+
+	require.NoError(t, validateFlags())
+
+	*criaGroup = ""
+	*allowedFrom = "google.com"
+
+	require.NoError(t, validateFlags())
+}