Skip to content
Snippets Groups Projects
Commit 4f9c61b5 authored by Alex Ellis (OpenFaaS Ltd)'s avatar Alex Ellis (OpenFaaS Ltd)
Browse files

Deprecate external auth plugins


There is no need for OpenFaaS CE to have external auth plugins
since this added extra overhead and was never used.

OpenFaaS Pro retains the option so it can use the OIDC
auth plugin.

It's still possible, as it ever was to put a proxy in front
of any HTTP server like the gateway.

Tested with a local KinD cluster, auth still worked for the
API and UI.

Signed-off-by: default avatarAlex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
parent a7d486ee
No related branches found
No related tags found
No related merge requests found
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)
}
}
}
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)
// }
// }
......@@ -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
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment