Skip to content

Commit 5ac0dbc

Browse files
committed
Validate permissions
Signed-off-by: Han Verstraete (OpenFaaS Ltd) <[email protected]>
1 parent 6197024 commit 5ac0dbc

File tree

3 files changed

+155
-2
lines changed

3 files changed

+155
-2
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ LDFLAGS := "-s -w -X main.Version=$(GIT_VERSION) -X main.GitCommit=$(GIT_COMMIT)
1010
SERVER?=ghcr.io
1111
OWNER?=openfaas
1212
IMG_NAME?=of-watchdog
13-
TAG?=latest
13+
TAG?=$(GIT_VERSION)
1414

1515
export GOFLAGS=-mod=vendor
1616

executor/jwt_authenticator.go

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ func NewJWTAuthMiddleware(next http.Handler) (http.Handler, error) {
5050

5151
issuer := config.Issuer
5252

53+
namespace, err := getFnNamespace()
54+
if err != nil {
55+
return nil, fmt.Errorf("failed to get function namespace: %s", err)
56+
}
57+
name, err := getFnName()
58+
if err != nil {
59+
return nil, fmt.Errorf("failed to get function name: %s", err)
60+
}
61+
5362
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
5463
st := time.Now()
5564
for _, key := range keyset.Keys {
@@ -68,7 +77,6 @@ func NewJWTAuthMiddleware(next http.Handler) (http.Handler, error) {
6877
}
6978

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

120+
permissions := getPermissions(mapClaims)
121+
if !isAuthorized(permissions, namespace, name) {
122+
http.Error(w, "insufficient permissions", http.StatusForbidden)
123+
124+
log.Printf("%s %s - %d ACCESS DENIED - (%s)", r.Method, r.URL.Path, http.StatusForbidden, time.Since(st).Round(time.Millisecond))
125+
return
126+
}
127+
112128
next.ServeHTTP(w, r)
113129
}), nil
114130
}
@@ -179,3 +195,64 @@ type OpenIDConfiguration struct {
179195
Issuer string `json:"issuer"`
180196
JWKSURI string `json:"jwks_uri"`
181197
}
198+
199+
func getFnName() (string, error) {
200+
name, ok := os.LookupEnv("OPENFAAS_NAME")
201+
if !ok || len(name) == 0 {
202+
return "", fmt.Errorf("env variable 'OPENFAAS_NAME' not set")
203+
}
204+
205+
return name, nil
206+
}
207+
208+
func getFnNamespace() (string, error) {
209+
nsVal, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
210+
if err != nil {
211+
return "", err
212+
}
213+
return string(nsVal), nil
214+
}
215+
216+
func isAuthorized(permissions []string, namespace, fn string) bool {
217+
for _, permission := range permissions {
218+
if permission == "*" {
219+
return true
220+
}
221+
222+
parts := strings.Split(permission, ":")
223+
allowedNamespace := parts[0]
224+
allowedFunction := parts[1]
225+
226+
if namespace != allowedNamespace {
227+
continue
228+
}
229+
230+
if allowedFunction != "*" && fn != allowedFunction {
231+
continue
232+
}
233+
234+
return true
235+
}
236+
237+
return false
238+
}
239+
240+
func getPermissions(mapClaims jwt.MapClaims) []string {
241+
values := []string{}
242+
if v, ok := mapClaims["permissions"]; ok {
243+
if vv, ok := v.([]interface{}); ok {
244+
for _, vvv := range vv {
245+
if vvvv, ok := vvv.(string); ok {
246+
values = append(values, vvvv)
247+
}
248+
}
249+
return values
250+
}
251+
252+
if vv, ok := v.(string); ok {
253+
values = append(values, vv)
254+
return values
255+
}
256+
}
257+
return values
258+
}

executor/jwt_authenticator_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package executor
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func Test_isAuthorized(t *testing.T) {
8+
tests := []struct {
9+
name string
10+
want bool
11+
permissions []string
12+
namespace string
13+
function string
14+
}{
15+
{
16+
name: "allow cluster wildcard",
17+
want: true,
18+
permissions: []string{"*"},
19+
namespace: "staging",
20+
function: "figlet",
21+
},
22+
{
23+
name: "allow namespace wildcard",
24+
want: true,
25+
permissions: []string{"dev:*"},
26+
namespace: "dev",
27+
function: "figlet",
28+
},
29+
{
30+
name: "allow function",
31+
want: true,
32+
permissions: []string{"openfaas-fn:env"},
33+
namespace: "openfaas-fn",
34+
function: "env",
35+
},
36+
{
37+
name: "deny function",
38+
want: false,
39+
permissions: []string{"openfaas-fn:env"},
40+
namespace: "openfaas-fn",
41+
function: "figlet",
42+
},
43+
{
44+
name: "deny namespace",
45+
want: false,
46+
permissions: []string{"openfaas-fn:*"},
47+
namespace: "staging",
48+
function: "env",
49+
},
50+
{
51+
name: "multiple permissions allow function",
52+
want: true,
53+
permissions: []string{"openfaas-fn:*", "staging:env"},
54+
namespace: "staging",
55+
function: "env",
56+
},
57+
{
58+
name: "deny empty permission list",
59+
want: false,
60+
permissions: []string{},
61+
namespace: "staging",
62+
function: "env",
63+
},
64+
}
65+
66+
for _, test := range tests {
67+
t.Run(test.name, func(t *testing.T) {
68+
want := test.want
69+
got := isAuthorized(test.permissions, test.namespace, test.function)
70+
71+
if want != got {
72+
t.Errorf("want: %t, got: %t", want, got)
73+
}
74+
})
75+
}
76+
}

0 commit comments

Comments
 (0)