From 02cf70c6f51764bb057a2f6db88986937268bf28 Mon Sep 17 00:00:00 2001 From: Alex Ellis Date: Wed, 5 Jun 2019 12:03:03 +0100 Subject: [PATCH] Add basic auth plugin This plugin can be tested as a stand-alone Golang app, or deployed as a Docker image with the following image: openfaas/basic-auth-plugin:0.1.0 Signed-off-by: Alex Ellis --- auth/README.md | 13 ++++ auth/basic-auth/.gitignore | 1 + auth/basic-auth/Dockerfile | 26 +++++++ auth/basic-auth/Gopkg.lock | 17 +++++ auth/basic-auth/Gopkg.toml | 34 ++++++++++ auth/basic-auth/README.md | 6 ++ auth/basic-auth/main.go | 68 +++++++++++++++++++ .../github.com/openfaas/faas-provider/LICENSE | 21 ++++++ .../openfaas/faas-provider/auth/basic_auth.go | 26 +++++++ .../faas-provider/auth/credentials.go | 52 ++++++++++++++ 10 files changed, 264 insertions(+) create mode 100644 auth/README.md create mode 100644 auth/basic-auth/.gitignore create mode 100644 auth/basic-auth/Dockerfile create mode 100644 auth/basic-auth/Gopkg.lock create mode 100644 auth/basic-auth/Gopkg.toml create mode 100644 auth/basic-auth/README.md create mode 100644 auth/basic-auth/main.go create mode 100644 auth/basic-auth/vendor/github.com/openfaas/faas-provider/LICENSE create mode 100644 auth/basic-auth/vendor/github.com/openfaas/faas-provider/auth/basic_auth.go create mode 100644 auth/basic-auth/vendor/github.com/openfaas/faas-provider/auth/credentials.go diff --git a/auth/README.md b/auth/README.md new file mode 100644 index 00000000..ffb0ac21 --- /dev/null +++ b/auth/README.md @@ -0,0 +1,13 @@ +auth plugins +============ + +Auth plugins must implement request checking on a HTTP port and path such as `:8080/validate`. + +* Valid requests: return 2xx +* Invalid requests: return non 2xx + +It is up to the developer to pick whether a request body is required for validation. For strategies such as [Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication), headers are sufficient. + +Plugins available: + +* [basic-auth](./basic-auth/) diff --git a/auth/basic-auth/.gitignore b/auth/basic-auth/.gitignore new file mode 100644 index 00000000..094f2968 --- /dev/null +++ b/auth/basic-auth/.gitignore @@ -0,0 +1 @@ +basic-auth* diff --git a/auth/basic-auth/Dockerfile b/auth/basic-auth/Dockerfile new file mode 100644 index 00000000..1039ee6a --- /dev/null +++ b/auth/basic-auth/Dockerfile @@ -0,0 +1,26 @@ +FROM golang:1.10.4-alpine3.8 as build + +RUN mkdir -p /go/src/handler +WORKDIR /go/src/handler +COPY . . + +RUN CGO_ENABLED=0 GOOS=linux \ + go build --ldflags "-s -w" -a -installsuffix cgo -o handler . && \ + go test $(go list ./... | grep -v /vendor/) -cover + +FROM alpine:3.9 +# Add non root user and certs +RUN apk --no-cache add ca-certificates \ + && addgroup -S app && adduser -S -g app app \ + && mkdir -p /home/app \ + && chown app /home/app + +WORKDIR /home/app + +COPY --from=build /go/src/handler/handler . + +RUN chown -R app /home/app + +USER app + +CMD ["/go/src/handler/handler"] diff --git a/auth/basic-auth/Gopkg.lock b/auth/basic-auth/Gopkg.lock new file mode 100644 index 00000000..16e49e41 --- /dev/null +++ b/auth/basic-auth/Gopkg.lock @@ -0,0 +1,17 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + digest = "1:deb76da5396c9f641ddea9ca79e31a14bdb09c787cdfda90488768b7539b1fd6" + name = "github.com/openfaas/faas-provider" + packages = ["auth"] + pruneopts = "UT" + revision = "6a76a052deb12fd94b373c082963d8a8ad44d4d1" + version = "0.9.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = ["github.com/openfaas/faas-provider/auth"] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/auth/basic-auth/Gopkg.toml b/auth/basic-auth/Gopkg.toml new file mode 100644 index 00000000..e5462e5c --- /dev/null +++ b/auth/basic-auth/Gopkg.toml @@ -0,0 +1,34 @@ +# Gopkg.toml example +# +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + name = "github.com/openfaas/faas-provider" + version = "0.9.0" + +[prune] + go-tests = true + unused-packages = true diff --git a/auth/basic-auth/README.md b/auth/basic-auth/README.md new file mode 100644 index 00000000..d0b50bc8 --- /dev/null +++ b/auth/basic-auth/README.md @@ -0,0 +1,6 @@ +basic-auth +============ + +This component provides plug-in authentication for the OpenFaaS gateway using basic authentication secrets. + +Wikipedia: [Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) diff --git a/auth/basic-auth/main.go b/auth/basic-auth/main.go new file mode 100644 index 00000000..380aae8b --- /dev/null +++ b/auth/basic-auth/main.go @@ -0,0 +1,68 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "net/http/httptest" + "os" + "strconv" + "time" + + "github.com/openfaas/faas-provider/auth" + "github.com/pkg/errors" +) + +func main() { + port := 8080 + + if val, ok := os.LookupEnv("port"); ok { + intOut, err := strconv.Atoi(val) + if err != nil { + panic(errors.Wrap(err, fmt.Sprintf("value of `port`: %s, not a valid port", val))) + } + port = intOut + } + + s := &http.Server{ + Addr: fmt.Sprintf(":%d", port), + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + MaxHeaderBytes: 1 << 20, // Max header of 1MB + } + + credentialsReader := auth.ReadBasicAuthFromDisk{ + SecretMountPath: os.Getenv("secret_mount_path"), + } + + credentials, err := credentialsReader.Read() + if err != nil { + panic(errors.Wrap(err, "unable to read basic auth credentials, check `secret_mount_path`")) + } + + authHandler := auth.DecorateWithBasicAuth(func(w http.ResponseWriter, r *http.Request) { + }, credentials) + http.HandleFunc("/validate", makeLogger(authHandler)) + + http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + log.Printf("Listening on: %d\n", port) + log.Fatal(s.ListenAndServe()) +} + +func makeLogger(next http.Handler) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + + rr := httptest.NewRecorder() + + next.ServeHTTP(rr, r) + log.Printf("Validated request %d.\n", rr.Code) + + w.WriteHeader(rr.Code) + if rr.Body != nil { + w.Write(rr.Body.Bytes()) + } + } +} diff --git a/auth/basic-auth/vendor/github.com/openfaas/faas-provider/LICENSE b/auth/basic-auth/vendor/github.com/openfaas/faas-provider/LICENSE new file mode 100644 index 00000000..e547e747 --- /dev/null +++ b/auth/basic-auth/vendor/github.com/openfaas/faas-provider/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Alex Ellis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/auth/basic-auth/vendor/github.com/openfaas/faas-provider/auth/basic_auth.go b/auth/basic-auth/vendor/github.com/openfaas/faas-provider/auth/basic_auth.go new file mode 100644 index 00000000..3fd7d759 --- /dev/null +++ b/auth/basic-auth/vendor/github.com/openfaas/faas-provider/auth/basic_auth.go @@ -0,0 +1,26 @@ +// Copyright (c) OpenFaaS Author(s). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +package auth + +import ( + "net/http" +) + +// DecorateWithBasicAuth enforces basic auth as a middleware with given credentials +func DecorateWithBasicAuth(next http.HandlerFunc, credentials *BasicAuthCredentials) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + user, password, ok := r.BasicAuth() + w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) + + if !ok || !(credentials.Password == password && user == credentials.User) { + + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("invalid credentials")) + return + } + + next.ServeHTTP(w, r) + } +} diff --git a/auth/basic-auth/vendor/github.com/openfaas/faas-provider/auth/credentials.go b/auth/basic-auth/vendor/github.com/openfaas/faas-provider/auth/credentials.go new file mode 100644 index 00000000..4f2ca34a --- /dev/null +++ b/auth/basic-auth/vendor/github.com/openfaas/faas-provider/auth/credentials.go @@ -0,0 +1,52 @@ +// Copyright (c) OpenFaaS Author(s). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +package auth + +import ( + "fmt" + "io/ioutil" + "path" + "strings" +) + +// BasicAuthCredentials for credentials +type BasicAuthCredentials struct { + User string + Password string +} + +type ReadBasicAuth interface { + Read() (error, *BasicAuthCredentials) +} + +type ReadBasicAuthFromDisk struct { + SecretMountPath string +} + +func (r *ReadBasicAuthFromDisk) Read() (*BasicAuthCredentials, error) { + var credentials *BasicAuthCredentials + + if len(r.SecretMountPath) == 0 { + return nil, fmt.Errorf("invalid SecretMountPath specified for reading secrets") + } + + userPath := path.Join(r.SecretMountPath, "basic-auth-user") + user, userErr := ioutil.ReadFile(userPath) + if userErr != nil { + return nil, fmt.Errorf("unable to load %s", userPath) + } + + userPassword := path.Join(r.SecretMountPath, "basic-auth-password") + password, passErr := ioutil.ReadFile(userPassword) + if passErr != nil { + return nil, fmt.Errorf("Unable to load %s", userPassword) + } + + credentials = &BasicAuthCredentials{ + User: strings.TrimSpace(string(user)), + Password: strings.TrimSpace(string(password)), + } + + return credentials, nil +}