Skip to content

Commit

Permalink
Validate permissions
Browse files Browse the repository at this point in the history
Signed-off-by: Han Verstraete (OpenFaaS Ltd) <[email protected]>
  • Loading branch information
welteki committed May 7, 2024
1 parent 6197024 commit 5ac0dbc
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ LDFLAGS := "-s -w -X main.Version=$(GIT_VERSION) -X main.GitCommit=$(GIT_COMMIT)
SERVER?=ghcr.io
OWNER?=openfaas
IMG_NAME?=of-watchdog
TAG?=latest
TAG?=$(GIT_VERSION)

export GOFLAGS=-mod=vendor

Expand Down
79 changes: 78 additions & 1 deletion executor/jwt_authenticator.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ func NewJWTAuthMiddleware(next http.Handler) (http.Handler, error) {

issuer := config.Issuer

namespace, err := getFnNamespace()
if err != nil {
return nil, fmt.Errorf("failed to get function namespace: %s", err)
}
name, err := getFnName()
if err != nil {
return nil, fmt.Errorf("failed to get function name: %s", err)
}

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
st := time.Now()
for _, key := range keyset.Keys {
Expand All @@ -68,7 +77,6 @@ func NewJWTAuthMiddleware(next http.Handler) (http.Handler, error) {
}

mapClaims := jwt.MapClaims{}

token, err := jwt.ParseWithClaims(bearer, &mapClaims, func(token *jwt.Token) (interface{}, error) {
if jwtAuthDebug {
log.Printf("[JWT Auth] Token: audience: %v\tissuer: %v", mapClaims["aud"], mapClaims["iss"])
Expand Down Expand Up @@ -109,6 +117,14 @@ func NewJWTAuthMiddleware(next http.Handler) (http.Handler, error) {
return
}

permissions := getPermissions(mapClaims)
if !isAuthorized(permissions, namespace, name) {
http.Error(w, "insufficient permissions", http.StatusForbidden)

log.Printf("%s %s - %d ACCESS DENIED - (%s)", r.Method, r.URL.Path, http.StatusForbidden, time.Since(st).Round(time.Millisecond))
return
}

next.ServeHTTP(w, r)
}), nil
}
Expand Down Expand Up @@ -179,3 +195,64 @@ type OpenIDConfiguration struct {
Issuer string `json:"issuer"`
JWKSURI string `json:"jwks_uri"`
}

func getFnName() (string, error) {
name, ok := os.LookupEnv("OPENFAAS_NAME")
if !ok || len(name) == 0 {
return "", fmt.Errorf("env variable 'OPENFAAS_NAME' not set")
}

return name, nil
}

func getFnNamespace() (string, error) {
nsVal, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
if err != nil {
return "", err
}
return string(nsVal), nil
}

func isAuthorized(permissions []string, namespace, fn string) bool {
for _, permission := range permissions {
if permission == "*" {
return true
}

parts := strings.Split(permission, ":")
allowedNamespace := parts[0]
allowedFunction := parts[1]

if namespace != allowedNamespace {
continue
}

if allowedFunction != "*" && fn != allowedFunction {
continue
}

return true
}

return false
}

func getPermissions(mapClaims jwt.MapClaims) []string {
values := []string{}
if v, ok := mapClaims["permissions"]; ok {
if vv, ok := v.([]interface{}); ok {
for _, vvv := range vv {
if vvvv, ok := vvv.(string); ok {
values = append(values, vvvv)
}
}
return values
}

if vv, ok := v.(string); ok {
values = append(values, vv)
return values
}
}
return values
}
76 changes: 76 additions & 0 deletions executor/jwt_authenticator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package executor

import (
"testing"
)

func Test_isAuthorized(t *testing.T) {
tests := []struct {
name string
want bool
permissions []string
namespace string
function string
}{
{
name: "allow cluster wildcard",
want: true,
permissions: []string{"*"},
namespace: "staging",
function: "figlet",
},
{
name: "allow namespace wildcard",
want: true,
permissions: []string{"dev:*"},
namespace: "dev",
function: "figlet",
},
{
name: "allow function",
want: true,
permissions: []string{"openfaas-fn:env"},
namespace: "openfaas-fn",
function: "env",
},
{
name: "deny function",
want: false,
permissions: []string{"openfaas-fn:env"},
namespace: "openfaas-fn",
function: "figlet",
},
{
name: "deny namespace",
want: false,
permissions: []string{"openfaas-fn:*"},
namespace: "staging",
function: "env",
},
{
name: "multiple permissions allow function",
want: true,
permissions: []string{"openfaas-fn:*", "staging:env"},
namespace: "staging",
function: "env",
},
{
name: "deny empty permission list",
want: false,
permissions: []string{},
namespace: "staging",
function: "env",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
want := test.want
got := isAuthorized(test.permissions, test.namespace, test.function)

if want != got {
t.Errorf("want: %t, got: %t", want, got)
}
})
}
}

0 comments on commit 5ac0dbc

Please sign in to comment.