Refactor auth-proxy app to make it easier to test.
The command line application is now //kube/cmd/auth-proxy and just
a simple stub that calls into //kube/go/authproxy.
Added a cleanup.AtExit which allows for an orderly shutdown of
auth-proxy when receiving SIGTERM, which is important to let requests
drain from auth-proxy before termination:
https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-terminating-with-grace
Bug: b/249507110
Change-Id: Ie831590a02d8da9e53df3f49feae065b7f23f613
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/585897
Reviewed-by: Ravi Mistry <rmistry@google.com>
Commit-Queue: Joe Gregorio <jcgregorio@google.com>
diff --git a/go_repositories.bzl b/go_repositories.bzl
index fc652c7..824532f 100644
--- a/go_repositories.bzl
+++ b/go_repositories.bzl
@@ -2673,8 +2673,8 @@
go_repository(
name = "com_github_oklog_run",
importpath = "github.com/oklog/run",
- sum = "h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=",
- version = "v1.0.0",
+ sum = "h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=",
+ version = "v1.1.0",
)
go_repository(
diff --git a/kube/Makefile b/kube/Makefile
index 253fda5..726899c 100644
--- a/kube/Makefile
+++ b/kube/Makefile
@@ -12,7 +12,7 @@
$(BAZEL) run //kube:configmap_reload_container
release_auth_proxy:
- CGO_ENABLED=0 GOOS=linux go install -a ./go/auth-proxy
+ CGO_ENABLED=0 GOOS=linux go install -a ./cmd/auth-proxy
./build_auth_proxy_release
release_basealpine:
diff --git a/kube/cmd/auth-proxy/BUILD.bazel b/kube/cmd/auth-proxy/BUILD.bazel
new file mode 100644
index 0000000..adb94b1
--- /dev/null
+++ b/kube/cmd/auth-proxy/BUILD.bazel
@@ -0,0 +1,18 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_library(
+ name = "auth-proxy_lib",
+ srcs = ["main.go"],
+ importpath = "go.skia.org/infra/kube/cmd/auth-proxy",
+ visibility = ["//visibility:private"],
+ deps = [
+ "//go/sklog",
+ "//kube/go/authproxy",
+ ],
+)
+
+go_binary(
+ name = "auth-proxy",
+ embed = [":auth-proxy_lib"],
+ visibility = ["//visibility:public"],
+)
diff --git a/kube/cmd/auth-proxy/main.go b/kube/cmd/auth-proxy/main.go
new file mode 100644
index 0000000..a42858d
--- /dev/null
+++ b/kube/cmd/auth-proxy/main.go
@@ -0,0 +1,10 @@
+package main
+
+import (
+ "go.skia.org/infra/go/sklog"
+ "go.skia.org/infra/kube/go/authproxy"
+)
+
+func main() {
+ sklog.Fatal(authproxy.Main())
+}
diff --git a/kube/go/auth-proxy/BUILD.bazel b/kube/go/auth-proxy/BUILD.bazel
deleted file mode 100644
index bed10f0..0000000
--- a/kube/go/auth-proxy/BUILD.bazel
+++ /dev/null
@@ -1,33 +0,0 @@
-load("//bazel/go:go_test.bzl", "go_test")
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-
-go_library(
- name = "auth-proxy_lib",
- srcs = ["main.go"],
- importpath = "go.skia.org/infra/kube/go/auth-proxy",
- visibility = ["//visibility:private"],
- deps = [
- "//go/allowed",
- "//go/common",
- "//go/httputils",
- "//go/sklog",
- "//kube/go/auth-proxy/auth",
- "@org_golang_x_oauth2//google",
- ],
-)
-
-go_binary(
- name = "auth-proxy",
- embed = [":auth-proxy_lib"],
- visibility = ["//visibility:public"],
-)
-
-go_test(
- name = "auth-proxy_test",
- srcs = ["main_test.go"],
- embed = [":auth-proxy_lib"],
- deps = [
- "//kube/go/auth-proxy/auth/mocks",
- "@com_github_stretchr_testify//require",
- ],
-)
diff --git a/kube/go/auth-proxy/auth/mocks/generate.go b/kube/go/auth-proxy/auth/mocks/generate.go
deleted file mode 100644
index 6f9d347..0000000
--- a/kube/go/auth-proxy/auth/mocks/generate.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package mocks
-
-//go:generate bazelisk run //:mockery -- --name Auth --srcpkg=go.skia.org/infra/kube/go/auth-proxy/auth --output ${PWD}
diff --git a/kube/go/auth-proxy/main.go b/kube/go/auth-proxy/main.go
deleted file mode 100644
index 8858a4d..0000000
--- a/kube/go/auth-proxy/main.go
+++ /dev/null
@@ -1,143 +0,0 @@
-// 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) {
- 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))
-}
diff --git a/kube/go/authproxy/BUILD.bazel b/kube/go/authproxy/BUILD.bazel
new file mode 100644
index 0000000..56edb57
--- /dev/null
+++ b/kube/go/authproxy/BUILD.bazel
@@ -0,0 +1,31 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+load("//bazel/go:go_test.bzl", "go_test")
+
+go_library(
+ name = "authproxy",
+ srcs = ["authproxy.go"],
+ importpath = "go.skia.org/infra/kube/go/authproxy",
+ visibility = ["//visibility:public"],
+ deps = [
+ "//go/allowed",
+ "//go/cleanup",
+ "//go/common",
+ "//go/httputils",
+ "//go/skerr",
+ "//go/sklog",
+ "//kube/go/authproxy/auth",
+ "@org_golang_x_oauth2//google",
+ ],
+)
+
+go_test(
+ name = "authproxy_test",
+ srcs = ["authproxy_test.go"],
+ embed = [":authproxy"],
+ deps = [
+ "//go/cleanup",
+ "//kube/go/authproxy/auth/mocks",
+ "@com_github_stretchr_testify//assert",
+ "@com_github_stretchr_testify//require",
+ ],
+)
diff --git a/kube/go/auth-proxy/auth/BUILD.bazel b/kube/go/authproxy/auth/BUILD.bazel
similarity index 80%
rename from kube/go/auth-proxy/auth/BUILD.bazel
rename to kube/go/authproxy/auth/BUILD.bazel
index 6464262..21ddcc7 100644
--- a/kube/go/auth-proxy/auth/BUILD.bazel
+++ b/kube/go/authproxy/auth/BUILD.bazel
@@ -6,7 +6,7 @@
"auth.go",
"impl.go",
],
- importpath = "go.skia.org/infra/kube/go/auth-proxy/auth",
+ importpath = "go.skia.org/infra/kube/go/authproxy/auth",
visibility = ["//visibility:public"],
deps = [
"//go/allowed",
diff --git a/kube/go/auth-proxy/auth/auth.go b/kube/go/authproxy/auth/auth.go
similarity index 100%
rename from kube/go/auth-proxy/auth/auth.go
rename to kube/go/authproxy/auth/auth.go
diff --git a/kube/go/auth-proxy/auth/impl.go b/kube/go/authproxy/auth/impl.go
similarity index 100%
rename from kube/go/auth-proxy/auth/impl.go
rename to kube/go/authproxy/auth/impl.go
diff --git a/kube/go/auth-proxy/auth/mocks/Auth.go b/kube/go/authproxy/auth/mocks/Auth.go
similarity index 100%
rename from kube/go/auth-proxy/auth/mocks/Auth.go
rename to kube/go/authproxy/auth/mocks/Auth.go
diff --git a/kube/go/auth-proxy/auth/mocks/BUILD.bazel b/kube/go/authproxy/auth/mocks/BUILD.bazel
similarity index 80%
rename from kube/go/auth-proxy/auth/mocks/BUILD.bazel
rename to kube/go/authproxy/auth/mocks/BUILD.bazel
index d554267..926abd0 100644
--- a/kube/go/auth-proxy/auth/mocks/BUILD.bazel
+++ b/kube/go/authproxy/auth/mocks/BUILD.bazel
@@ -6,7 +6,7 @@
"Auth.go",
"generate.go",
],
- importpath = "go.skia.org/infra/kube/go/auth-proxy/auth/mocks",
+ importpath = "go.skia.org/infra/kube/go/authproxy/auth/mocks",
visibility = ["//visibility:public"],
deps = [
"//go/allowed",
diff --git a/kube/go/authproxy/auth/mocks/generate.go b/kube/go/authproxy/auth/mocks/generate.go
new file mode 100644
index 0000000..62b9bdb
--- /dev/null
+++ b/kube/go/authproxy/auth/mocks/generate.go
@@ -0,0 +1,3 @@
+package mocks
+
+//go:generate bazelisk run //:mockery -- --name Auth --srcpkg=go.skia.org/infra/kube/go/authproxy/auth --output ${PWD}
diff --git a/kube/go/authproxy/authproxy.go b/kube/go/authproxy/authproxy.go
new file mode 100644
index 0000000..49d8029
--- /dev/null
+++ b/kube/go/authproxy/authproxy.go
@@ -0,0 +1,234 @@
+// 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 authproxy
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "net/http"
+ "net/http/httputil"
+ "net/url"
+ "strings"
+ "time"
+
+ "go.skia.org/infra/go/allowed"
+ "go.skia.org/infra/go/cleanup"
+ "go.skia.org/infra/go/common"
+ "go.skia.org/infra/go/httputils"
+ "go.skia.org/infra/go/skerr"
+ "go.skia.org/infra/go/sklog"
+ "go.skia.org/infra/kube/go/authproxy/auth"
+ "golang.org/x/oauth2/google"
+)
+
+const (
+ appName = "auth-proxy"
+ serverReadTimeout = time.Hour
+ serverWriteTimeout = time.Hour
+ drainTime = time.Minute
+)
+
+// 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 {
+ allowPost bool
+ passive bool
+ reverseProxy http.Handler
+ authProvider auth.Auth
+}
+
+func newProxy(target *url.URL, authProvider auth.Auth, allowPost bool, passive bool) *proxy {
+ return &proxy{
+ reverseProxy: httputil.NewSingleHostReverseProxy(target),
+ authProvider: authProvider,
+ allowPost: allowPost,
+ passive: passive,
+ }
+}
+
+func (p proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ email := p.authProvider.LoggedInAs(r)
+ r.Header.Del(webAuthHeaderName)
+ r.Header.Add(webAuthHeaderName, email)
+ if r.Method == "POST" && p.allowPost {
+ p.reverseProxy.ServeHTTP(w, r)
+ return
+ }
+ if !p.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)
+}
+
+// App is the auth-proxy application.
+type App struct {
+ port string
+ promPort string
+ criaGroup string
+ local bool
+ targetPort string
+ allowPost bool
+ allowedFrom string
+ passive bool
+
+ target *url.URL
+ authProvider auth.Auth
+ server *http.Server
+}
+
+// Flagset constructs a flag.FlagSet for the App.
+func (a *App) Flagset() *flag.FlagSet {
+ fs := flag.NewFlagSet(appName, flag.ExitOnError)
+ fs.StringVar(&a.port, "port", ":8000", "HTTP service address (e.g., ':8000')")
+ fs.StringVar(&a.promPort, "prom-port", ":20000", "Metrics service address (e.g., ':10110')")
+ fs.StringVar(&a.criaGroup, "cria_group", "", "The chrome infra auth group to use for restricting access. Example: 'google/skia-staff@google.com'")
+ fs.BoolVar(&a.local, "local", false, "Running locally if true. As opposed to in production.")
+ fs.StringVar(&a.targetPort, "target_port", ":9000", "The port we are proxying to.")
+ fs.BoolVar(&a.allowPost, "allow_post", false, "Allow POST requests to bypass auth.")
+ fs.StringVar(&a.allowedFrom, "allowed_from", "", "A comma separated list of of domains and email addresses that are allowed to access the site. Example: 'google.com'")
+ fs.BoolVar(&a.passive, "passive", false, "If true then allow unauthenticated requests to go through, while still adding logged in users emails in via the webAuthHeaderName.")
+
+ return fs
+}
+
+// New returns a new *App.
+func New(ctx context.Context) (*App, error) {
+ var ret App
+
+ err := common.InitWith(
+ appName,
+ common.PrometheusOpt(&ret.promPort),
+ common.MetricsLoggingOpt(),
+ common.FlagSetOpt(ret.Flagset()),
+ )
+ if err != nil {
+ return nil, skerr.Wrap(err)
+ }
+
+ err = ret.validateFlags()
+ if err != nil {
+ return nil, skerr.Wrap(err)
+ }
+
+ var allow allowed.Allow
+ if ret.criaGroup != "" {
+ ctx := context.Background()
+ ts, err := google.DefaultTokenSource(ctx, "email")
+ if err != nil {
+ return nil, skerr.Wrap(err)
+ }
+ criaClient := httputils.DefaultClientConfig().WithTokenSource(ts).With2xxOnly().Client()
+ allow, err = allowed.NewAllowedFromChromeInfraAuth(criaClient, ret.criaGroup)
+ if err != nil {
+ return nil, skerr.Wrap(err)
+ }
+ } else {
+ allow = allowed.NewAllowedFromList(strings.Split(ret.allowedFrom, ","))
+ }
+
+ authInstance := auth.New()
+ authInstance.SimpleInitWithAllow(ret.port, ret.local, nil, nil, allow)
+ targetURL := fmt.Sprintf("http://localhost%s", ret.targetPort)
+ target, err := url.Parse(targetURL)
+ if err != nil {
+ return nil, skerr.Wrap(err)
+ }
+ ret.authProvider = authInstance
+ ret.target = target
+ ret.registerCleanup()
+
+ return &ret, nil
+}
+
+func (a *App) registerCleanup() {
+ cleanup.AtExit(func() {
+ if a.server != nil {
+ sklog.Info("Shutdown server gracefully.")
+ ctx, cancel := context.WithTimeout(context.Background(), drainTime)
+ err := a.server.Shutdown(ctx)
+ if err != nil {
+ sklog.Error(err)
+ }
+ cancel()
+ }
+ })
+
+}
+
+// Run starts the application serving, it does not return unless there is an
+// error or the passed in context is cancelled.
+func (a *App) Run(ctx context.Context) error {
+ var h http.Handler = newProxy(a.target, a.authProvider, a.allowPost, a.passive)
+ h = httputils.HealthzAndHTTPS(h)
+ server := &http.Server{
+ Addr: a.port,
+ Handler: h,
+ ReadTimeout: serverReadTimeout,
+ WriteTimeout: serverWriteTimeout,
+ MaxHeaderBytes: 1 << 20,
+ }
+ a.server = server
+
+ sklog.Infof("Ready to serve on port %s", a.port)
+ err := server.ListenAndServe()
+ if err == http.ErrServerClosed {
+ // This is an orderly shutdown.
+ return nil
+ }
+ return skerr.Wrap(err)
+}
+
+func (a *App) validateFlags() error {
+ if a.criaGroup != "" && a.allowedFrom != "" {
+ return fmt.Errorf("Only one of the flags in [--auth_group, --allowed_from] can be specified.")
+ }
+ if a.criaGroup == "" && a.allowedFrom == "" {
+ return fmt.Errorf("At least one of the flags in [--auth_group, --allowed_from] must be specified.")
+ }
+
+ return nil
+}
+
+// Main constructs and runs the application. This function will only return on failure.
+func Main() error {
+ ctx := context.Background()
+ app, err := New(ctx)
+ if err != nil {
+ return skerr.Wrap(err)
+ }
+
+ return app.Run(ctx)
+}
diff --git a/kube/go/auth-proxy/main_test.go b/kube/go/authproxy/authproxy_test.go
similarity index 73%
rename from kube/go/auth-proxy/main_test.go
rename to kube/go/authproxy/authproxy_test.go
index a1a0085..9110724 100644
--- a/kube/go/auth-proxy/main_test.go
+++ b/kube/go/authproxy/authproxy_test.go
@@ -1,20 +1,23 @@
-package main
+package authproxy
import (
+ "context"
"net/http"
"net/http/httptest"
"net/url"
+ "sync"
"testing"
+ "time"
+ "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "go.skia.org/infra/kube/go/auth-proxy/auth/mocks"
+ "go.skia.org/infra/go/cleanup"
+ "go.skia.org/infra/kube/go/authproxy/auth/mocks"
)
const email = "nobody@example.org"
func setupForTest(t *testing.T, cb http.HandlerFunc) (*url.URL, *bool, *httptest.ResponseRecorder, *http.Request) {
- *allowPost = false
- *passive = false
called := false
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cb(w, r)
@@ -38,11 +41,10 @@
require.Equal(t, []string{""}, r.Header.Values(webAuthHeaderName))
require.Equal(t, []string(nil), r.Header.Values("X-SOME-UNSET-HEADER"))
})
- *allowPost = true
authMock := &mocks.Auth{}
authMock.On("LoggedInAs", r).Return("")
- proxy := newProxy(u, authMock)
+ proxy := newProxy(u, authMock, true, false)
proxy.ServeHTTP(w, r)
require.True(t, *called)
@@ -59,7 +61,7 @@
authMock.On("LoggedInAs", r).Return(email)
authMock.On("IsViewer", r).Return(true)
- proxy := newProxy(u, authMock)
+ proxy := newProxy(u, authMock, false, false)
proxy.ServeHTTP(w, r)
require.True(t, *called)
@@ -74,7 +76,7 @@
authMock.On("LoggedInAs", r).Return("")
authMock.On("LoginURL", w, r).Return("http://example.org/login")
- proxy := newProxy(u, authMock)
+ proxy := newProxy(u, authMock, false, false)
proxy.ServeHTTP(w, r)
require.False(t, *called)
@@ -88,7 +90,7 @@
authMock.On("LoggedInAs", r).Return(email)
authMock.On("IsViewer", r).Return(false)
- proxy := newProxy(u, authMock)
+ proxy := newProxy(u, authMock, false, false)
proxy.ServeHTTP(w, r)
require.False(t, *called)
@@ -106,7 +108,7 @@
authMock.On("LoggedInAs", r).Return(email)
authMock.On("IsViewer", r).Return(true)
- proxy := newProxy(u, authMock)
+ proxy := newProxy(u, authMock, false, false)
proxy.ServeHTTP(w, r)
require.True(t, *called)
@@ -119,12 +121,11 @@
require.Equal(t, []string{""}, r.Header.Values(webAuthHeaderName))
})
- *passive = true
r.Header.Add(webAuthHeaderName, "haxor@example.org") // Try to spoof the header.
authMock := &mocks.Auth{}
authMock.On("LoggedInAs", r).Return("")
- proxy := newProxy(u, authMock)
+ proxy := newProxy(u, authMock, false, true)
proxy.ServeHTTP(w, r)
require.True(t, *called)
@@ -137,12 +138,11 @@
require.Equal(t, []string{email}, r.Header.Values(webAuthHeaderName))
})
- *passive = true
r.Header.Add(webAuthHeaderName, "haxor@example.org") // Try to spoof the header.
authMock := &mocks.Auth{}
authMock.On("LoggedInAs", r).Return(email)
- proxy := newProxy(u, authMock)
+ proxy := newProxy(u, authMock, false, true)
proxy.ServeHTTP(w, r)
require.True(t, *called)
@@ -150,27 +150,67 @@
}
func TestValidateFlags_BothFlagsSpecified_ReturnsError(t *testing.T) {
- *criaGroup = "project-angle-committers"
- *allowedFrom = "google.com"
+ app := &App{
+ criaGroup: "project-angle-committers",
+ allowedFrom: "google.com",
+ }
- require.Error(t, validateFlags())
+ require.Error(t, app.validateFlags())
}
func TestValidateFlags_NeitherFlagIsSpecified_ReturnsError(t *testing.T) {
- *criaGroup = ""
- *allowedFrom = ""
+ app := &App{
+ criaGroup: "",
+ allowedFrom: "",
+ }
- require.Error(t, validateFlags())
+ require.Error(t, app.validateFlags())
}
func TestValidateFlags_OnlyOneFlagIsSpecified_ReturnsNoError(t *testing.T) {
- *criaGroup = "project-angle-committers"
- *allowedFrom = ""
- require.NoError(t, validateFlags())
+ app := &App{
+ criaGroup: "project-angle-committers",
+ allowedFrom: "",
+ }
- *criaGroup = ""
- *allowedFrom = "google.com"
+ require.NoError(t, app.validateFlags())
- require.NoError(t, validateFlags())
+ app = &App{
+ criaGroup: "",
+ allowedFrom: "google.com",
+ }
+
+ require.NoError(t, app.validateFlags())
+}
+
+func TestAppRun_ContextIsCancelled_ReturnsNil(t *testing.T) {
+ // Construct minimal App.
+ target, err := url.Parse("http://my-service")
+ require.NoError(t, err)
+ app := &App{
+ target: target,
+ port: ":0",
+ promPort: ":0",
+ }
+ app.registerCleanup()
+
+ var w sync.WaitGroup
+ w.Add(1)
+ go func() {
+ err := app.Run(context.Background())
+ assert.NoError(t, err)
+ w.Done()
+ }()
+
+ // Ensure the server has been started.
+ for app.server == nil {
+ time.Sleep(time.Millisecond)
+ }
+
+ // Force a cleanup.
+ cleanup.Cleanup()
+ w.Wait()
+
+ // Test will fail by timeout if the app.Run() didn't return.
}