diff --git a/gateway/handlers/external_auth.go b/gateway/handlers/external_auth.go deleted file mode 100644 index 47dcf3c8..00000000 --- a/gateway/handlers/external_auth.go +++ /dev/null @@ -1,47 +0,0 @@ -package handlers - -import ( - "context" - "io" - "log" - "net/http" - "time" -) - -// MakeExternalAuthHandler make an authentication proxy handler -func MakeExternalAuthHandler(next http.HandlerFunc, upstreamTimeout time.Duration, upstreamURL string, passBody bool) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - req, _ := http.NewRequest(http.MethodGet, upstreamURL, nil) - - copyHeaders(req.Header, &r.Header) - - deadlineContext, cancel := context.WithTimeout( - context.Background(), - upstreamTimeout) - - defer cancel() - - res, err := http.DefaultClient.Do(req.WithContext(deadlineContext)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - log.Printf("ExternalAuthHandler: %s", err.Error()) - return - } - - if res.Body != nil { - defer res.Body.Close() - } - - if res.StatusCode == http.StatusOK { - next.ServeHTTP(w, r) - return - } - - copyHeaders(w.Header(), &res.Header) - w.WriteHeader(res.StatusCode) - - if res.Body != nil { - io.Copy(w, res.Body) - } - } -} diff --git a/gateway/handlers/external_auth_test.go b/gateway/handlers/external_auth_test.go deleted file mode 100644 index f507e7b3..00000000 --- a/gateway/handlers/external_auth_test.go +++ /dev/null @@ -1,239 +0,0 @@ -package handlers - -import ( - "bytes" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" -) - -func Test_External_Auth_Wrapper_FailsInvalidAuth(t *testing.T) { - - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusForbidden) - })) - defer s.Close() - - next := func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) - } - - passBody := false - handler := MakeExternalAuthHandler(next, time.Second*5, s.URL, passBody) - - req := httptest.NewRequest(http.MethodGet, s.URL, nil) - rr := httptest.NewRecorder() - handler(rr, req) - - if rr.Code == http.StatusOK { - t.Errorf("Status incorrect, did not want: %d, but got %d", http.StatusOK, rr.Code) - } -} - -func Test_External_Auth_Wrapper_FailsInvalidAuth_WritesBody(t *testing.T) { - - wantBody := []byte(`invalid credentials`) - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusForbidden) - w.Write(wantBody) - })) - - defer s.Close() - - next := func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) - } - - passBody := false - handler := MakeExternalAuthHandler(next, time.Second*5, s.URL, passBody) - - req := httptest.NewRequest(http.MethodGet, s.URL, nil) - rr := httptest.NewRecorder() - handler(rr, req) - - if rr.Code == http.StatusOK { - t.Errorf("Status incorrect, did not want: %d, but got %d", http.StatusOK, rr.Code) - } - - if bytes.Compare(rr.Body.Bytes(), wantBody) != 0 { - t.Errorf("Body incorrect, want: %s, but got %s", []byte(wantBody), rr.Body) - } -} - -func Test_External_Auth_Wrapper_PassesValidAuth(t *testing.T) { - - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - })) - defer s.Close() - - next := func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) - } - - passBody := false - handler := MakeExternalAuthHandler(next, time.Second*5, s.URL, passBody) - - req := httptest.NewRequest(http.MethodGet, s.URL, nil) - rr := httptest.NewRecorder() - handler(rr, req) - want := http.StatusNotImplemented - if rr.Code != want { - t.Errorf("Status incorrect, want: %d, but got %d", want, rr.Code) - } -} - -func Test_External_Auth_Wrapper_WithoutRequiredHeaderFailsAuth(t *testing.T) { - wantToken := "secret-key" - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("X-Token") == wantToken { - w.WriteHeader(http.StatusOK) - return - } - w.WriteHeader(http.StatusUnauthorized) - })) - defer s.Close() - - next := func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) - } - - passBody := false - handler := MakeExternalAuthHandler(next, time.Second*5, s.URL, passBody) - - req := httptest.NewRequest(http.MethodGet, s.URL, nil) - - // use an invalid token - req.Header.Set("X-Token", "invalid-key") - - rr := httptest.NewRecorder() - handler(rr, req) - want := http.StatusUnauthorized - if rr.Code != want { - t.Errorf("Status incorrect, want: %d, but got %d", want, rr.Code) - } -} - -func Test_External_Auth_Wrapper_WithoutRequiredHeaderFailsAuth_ProxiesServerHeaders(t *testing.T) { - wantToken := "secret-key" - wantRealm := `Basic realm="Restricted"` - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("X-Token") == wantToken { - w.WriteHeader(http.StatusOK) - return - } - - w.Header().Set("Www-Authenticate", wantRealm) - w.WriteHeader(http.StatusUnauthorized) - })) - defer s.Close() - - next := func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) - } - - passBody := false - handler := MakeExternalAuthHandler(next, time.Second*5, s.URL, passBody) - - req := httptest.NewRequest(http.MethodGet, s.URL, nil) - - // use an invalid token - req.Header.Set("X-Token", "invalid-key") - - rr := httptest.NewRecorder() - handler(rr, req) - want := http.StatusUnauthorized - if rr.Code != want { - t.Errorf("Status incorrect, want: %d, but got %d", want, rr.Code) - } - - got := rr.Header().Get("Www-Authenticate") - if got != wantRealm { - t.Errorf("Www-Authenticate header, want: %s, but got %s, %q", wantRealm, got, rr.Header()) - } -} - -func Test_External_Auth_Wrapper_WithRequiredHeaderPassesValidAuth(t *testing.T) { - wantToken := "secret-key" - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("X-Token") == wantToken { - w.WriteHeader(http.StatusOK) - return - } - w.WriteHeader(http.StatusUnauthorized) - })) - defer s.Close() - - next := func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) - } - - passBody := false - handler := MakeExternalAuthHandler(next, time.Second*5, s.URL, passBody) - - req := httptest.NewRequest(http.MethodGet, s.URL, nil) - req.Header.Set("X-Token", wantToken) - - rr := httptest.NewRecorder() - handler(rr, req) - want := http.StatusNotImplemented - if rr.Code != want { - t.Errorf("Status incorrect, want: %d, but got %d", want, rr.Code) - } -} - -func Test_External_Auth_Wrapper_TimeoutGivesInternalServerError(t *testing.T) { - - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - time.Sleep(50 * time.Millisecond) - w.WriteHeader(http.StatusOK) - })) - defer s.Close() - - next := func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) - } - - passBody := false - handler := MakeExternalAuthHandler(next, time.Millisecond*10, s.URL, passBody) - - req := httptest.NewRequest(http.MethodGet, s.URL, nil) - rr := httptest.NewRecorder() - handler(rr, req) - - want := http.StatusInternalServerError - if rr.Code != want { - t.Errorf("Status incorrect, want: %d, but got %d", want, rr.Code) - } - wantSubstring := "context deadline exceeded\n" - if !strings.HasSuffix(string(rr.Body.Bytes()), wantSubstring) { - t.Errorf("Body incorrect, want to have suffix: %q, but got %q", []byte(wantSubstring), rr.Body) - } -} - -// // Test_External_Auth_Wrapper_PassesValidAuthButOnly200IsValid this test exists -// // to document the TODO action to consider all "2xx" statuses as valid. -// func Test_External_Auth_Wrapper_PassesValidAuthButOnly200IsValid(t *testing.T) { - -// s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { -// w.WriteHeader(http.StatusAccepted) -// })) -// defer s.Close() - -// next := func(w http.ResponseWriter, r *http.Request) { -// w.WriteHeader(http.StatusNotImplemented) -// } - -// passBody := false -// handler := MakeExternalAuthHandler(next, time.Second*5, s.URL, passBody) - -// req := httptest.NewRequest(http.MethodGet, s.URL, nil) -// rr := httptest.NewRecorder() -// handler(rr, req) -// want := http.StatusUnauthorized -// if rr.Code != want { -// t.Errorf("Status incorrect, want: %d, but got %d", want, rr.Code) -// } -// } diff --git a/gateway/main.go b/gateway/main.go index 7d7ad756..9e0ac104 100644 --- a/gateway/main.go +++ b/gateway/main.go @@ -105,8 +105,6 @@ func main() { serviceAuthInjector = &middleware.BasicAuthInjector{Credentials: credentials} } - decorateExternalAuth := handlers.MakeExternalAuthHandler - // externalServiceQuery is used to query metadata from the provider about a function externalServiceQuery := plugin.NewExternalServiceQuery(*config.FunctionsProviderURL, serviceAuthInjector) @@ -178,27 +176,27 @@ func main() { if credentials != nil { faasHandlers.Alert = - decorateExternalAuth(faasHandlers.Alert, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) + auth.DecorateWithBasicAuth(faasHandlers.Alert, credentials) faasHandlers.UpdateFunction = - decorateExternalAuth(faasHandlers.UpdateFunction, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) + auth.DecorateWithBasicAuth(faasHandlers.UpdateFunction, credentials) faasHandlers.DeleteFunction = - decorateExternalAuth(faasHandlers.DeleteFunction, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) + auth.DecorateWithBasicAuth(faasHandlers.DeleteFunction, credentials) faasHandlers.DeployFunction = - decorateExternalAuth(faasHandlers.DeployFunction, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) + auth.DecorateWithBasicAuth(faasHandlers.DeployFunction, credentials) faasHandlers.ListFunctions = - decorateExternalAuth(faasHandlers.ListFunctions, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) + auth.DecorateWithBasicAuth(faasHandlers.ListFunctions, credentials) faasHandlers.ScaleFunction = - decorateExternalAuth(faasHandlers.ScaleFunction, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) + auth.DecorateWithBasicAuth(faasHandlers.ScaleFunction, credentials) faasHandlers.FunctionStatus = - decorateExternalAuth(faasHandlers.FunctionStatus, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) + auth.DecorateWithBasicAuth(faasHandlers.FunctionStatus, credentials) faasHandlers.InfoHandler = - decorateExternalAuth(faasHandlers.InfoHandler, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) + auth.DecorateWithBasicAuth(faasHandlers.InfoHandler, credentials) faasHandlers.SecretHandler = - decorateExternalAuth(faasHandlers.SecretHandler, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) + auth.DecorateWithBasicAuth(faasHandlers.SecretHandler, credentials) faasHandlers.LogProxyHandler = - decorateExternalAuth(faasHandlers.LogProxyHandler, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) + auth.DecorateWithBasicAuth(faasHandlers.LogProxyHandler, credentials) faasHandlers.NamespaceListerHandler = - decorateExternalAuth(faasHandlers.NamespaceListerHandler, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) + auth.DecorateWithBasicAuth(faasHandlers.NamespaceListerHandler, credentials) } r := mux.NewRouter() @@ -238,9 +236,11 @@ func main() { uiHandler := http.StripPrefix("/ui", fsCORS) if credentials != nil { r.PathPrefix("/ui/").Handler( - decorateExternalAuth(uiHandler.ServeHTTP, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)).Methods(http.MethodGet) + auth.DecorateWithBasicAuth(uiHandler.ServeHTTP, credentials)). + Methods(http.MethodGet) } else { - r.PathPrefix("/ui/").Handler(uiHandler).Methods(http.MethodGet) + r.PathPrefix("/ui/").Handler(uiHandler). + Methods(http.MethodGet) } //Start metrics server in a goroutine