-
Notifications
You must be signed in to change notification settings - Fork 6
/
jwt.go
121 lines (100 loc) · 3.32 KB
/
jwt.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// Copyright (c) 2022 Cisco and/or its affiliates. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"context"
"fmt"
"strings"
"emperror.dev/errors"
"github.com/golang-jwt/jwt/v4"
authv1 "k8s.io/api/authentication/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
const (
naspTokenAudiencePrefix = "nasp-heimdall:workloadgroup:" //nolint:gosec
naspTokenAudienceFormat = naspTokenAudiencePrefix + "%s:%s"
)
type NASPTokenAudience string
func (a NASPTokenAudience) Generate(ref client.ObjectKey) string {
return fmt.Sprintf(naspTokenAudienceFormat, ref.Namespace, ref.Name)
}
func (a NASPTokenAudience) Valid() bool {
return strings.HasPrefix(string(a), naspTokenAudiencePrefix)
}
func (a NASPTokenAudience) String() string {
return string(a)
}
func (a NASPTokenAudience) Parse() client.ObjectKey {
ref := client.ObjectKey{}
if p := strings.Split(string(a), ":"); len(p) == 4 {
ref.Name = p[3]
ref.Namespace = p[2]
}
return ref
}
type AuthenticatedUser struct {
UID string
Name string
Groups []string
ServiceAccountRef client.ObjectKey
WorkloadGroupRef client.ObjectKey
}
func AuthenticateToken(ctx context.Context, c client.Client, token string) (*AuthenticatedUser, error) {
var naspAudience NASPTokenAudience
// parse JWT token to extract NASP audience
claims := jwt.RegisteredClaims{}
_, _, err := jwt.NewParser().ParseUnverified(token, &claims)
if err != nil {
return nil, errors.WrapIf(err, "could not parse JWT token")
}
for _, aud := range claims.Audience {
if naspAud := NASPTokenAudience(aud); naspAud.Valid() {
naspAudience = naspAud
break
}
}
// create token review
tokenReview := &authv1.TokenReview{
Spec: authv1.TokenReviewSpec{Token: token, Audiences: []string{naspAudience.String()}},
}
err = c.Create(ctx, tokenReview)
if err != nil {
return nil, errors.WrapIf(err, "could not create token review")
}
if tokenReview.Status.Error != "" {
return nil, errors.New(tokenReview.Status.Error)
}
if !tokenReview.Status.Authenticated {
return nil, errors.New("token authentication failed")
}
user := &AuthenticatedUser{
UID: tokenReview.Status.User.UID,
Name: tokenReview.Status.User.Username,
Groups: tokenReview.Status.User.Groups,
WorkloadGroupRef: naspAudience.Parse(),
}
// extract service account info from username
if strings.HasPrefix(tokenReview.Status.User.Username, "system:serviceaccount:") {
// The username is of format: system:serviceaccount:(NAMESPACE):(SERVICEACCOUNT)
parts := strings.Split(tokenReview.Status.User.Username, ":")
if len(parts) != 4 {
return nil, errors.New("username format is invalid")
}
user.ServiceAccountRef = client.ObjectKey{
Name: parts[3],
Namespace: parts[2],
}
}
return user, nil
}