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: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
This commit is contained in:
Alex Ellis (OpenFaaS Ltd) 2023-01-11 12:22:12 +00:00
parent a7d486eee6
commit 4f9c61b5d2
3 changed files with 15 additions and 301 deletions

View File

@ -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)
}
}
}

View File

@ -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)
// }
// }

View File

@ -105,8 +105,6 @@ func main() {
serviceAuthInjector = &middleware.BasicAuthInjector{Credentials: credentials} serviceAuthInjector = &middleware.BasicAuthInjector{Credentials: credentials}
} }
decorateExternalAuth := handlers.MakeExternalAuthHandler
// externalServiceQuery is used to query metadata from the provider about a function // externalServiceQuery is used to query metadata from the provider about a function
externalServiceQuery := plugin.NewExternalServiceQuery(*config.FunctionsProviderURL, serviceAuthInjector) externalServiceQuery := plugin.NewExternalServiceQuery(*config.FunctionsProviderURL, serviceAuthInjector)
@ -178,27 +176,27 @@ func main() {
if credentials != nil { if credentials != nil {
faasHandlers.Alert = faasHandlers.Alert =
decorateExternalAuth(faasHandlers.Alert, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) auth.DecorateWithBasicAuth(faasHandlers.Alert, credentials)
faasHandlers.UpdateFunction = faasHandlers.UpdateFunction =
decorateExternalAuth(faasHandlers.UpdateFunction, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) auth.DecorateWithBasicAuth(faasHandlers.UpdateFunction, credentials)
faasHandlers.DeleteFunction = faasHandlers.DeleteFunction =
decorateExternalAuth(faasHandlers.DeleteFunction, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) auth.DecorateWithBasicAuth(faasHandlers.DeleteFunction, credentials)
faasHandlers.DeployFunction = faasHandlers.DeployFunction =
decorateExternalAuth(faasHandlers.DeployFunction, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) auth.DecorateWithBasicAuth(faasHandlers.DeployFunction, credentials)
faasHandlers.ListFunctions = faasHandlers.ListFunctions =
decorateExternalAuth(faasHandlers.ListFunctions, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) auth.DecorateWithBasicAuth(faasHandlers.ListFunctions, credentials)
faasHandlers.ScaleFunction = faasHandlers.ScaleFunction =
decorateExternalAuth(faasHandlers.ScaleFunction, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) auth.DecorateWithBasicAuth(faasHandlers.ScaleFunction, credentials)
faasHandlers.FunctionStatus = faasHandlers.FunctionStatus =
decorateExternalAuth(faasHandlers.FunctionStatus, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) auth.DecorateWithBasicAuth(faasHandlers.FunctionStatus, credentials)
faasHandlers.InfoHandler = faasHandlers.InfoHandler =
decorateExternalAuth(faasHandlers.InfoHandler, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) auth.DecorateWithBasicAuth(faasHandlers.InfoHandler, credentials)
faasHandlers.SecretHandler = faasHandlers.SecretHandler =
decorateExternalAuth(faasHandlers.SecretHandler, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) auth.DecorateWithBasicAuth(faasHandlers.SecretHandler, credentials)
faasHandlers.LogProxyHandler = faasHandlers.LogProxyHandler =
decorateExternalAuth(faasHandlers.LogProxyHandler, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) auth.DecorateWithBasicAuth(faasHandlers.LogProxyHandler, credentials)
faasHandlers.NamespaceListerHandler = faasHandlers.NamespaceListerHandler =
decorateExternalAuth(faasHandlers.NamespaceListerHandler, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) auth.DecorateWithBasicAuth(faasHandlers.NamespaceListerHandler, credentials)
} }
r := mux.NewRouter() r := mux.NewRouter()
@ -238,9 +236,11 @@ func main() {
uiHandler := http.StripPrefix("/ui", fsCORS) uiHandler := http.StripPrefix("/ui", fsCORS)
if credentials != nil { if credentials != nil {
r.PathPrefix("/ui/").Handler( 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 { } else {
r.PathPrefix("/ui/").Handler(uiHandler).Methods(http.MethodGet) r.PathPrefix("/ui/").Handler(uiHandler).
Methods(http.MethodGet)
} }
//Start metrics server in a goroutine //Start metrics server in a goroutine