forked from linkerd/linkerd2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
validator.go
114 lines (100 loc) · 3.52 KB
/
validator.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
package identity
import (
"context"
"fmt"
"strings"
"github.com/linkerd/linkerd2/pkg/identity"
log "github.com/sirupsen/logrus"
kauthnApi "k8s.io/api/authentication/v1"
kauthzApi "k8s.io/api/authorization/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation"
k8s "k8s.io/client-go/kubernetes"
kauthn "k8s.io/client-go/kubernetes/typed/authentication/v1"
kauthz "k8s.io/client-go/kubernetes/typed/authorization/v1"
)
const (
// LinkerdAudienceKey is the audience key used for the Linkerd token creation
// and review requests.
LinkerdAudienceKey = "identity.l5d.io"
)
// K8sTokenValidator implements Validator for Kubernetes bearer tokens.
type K8sTokenValidator struct {
authn kauthn.AuthenticationV1Interface
domain *TrustDomain
}
// NewK8sTokenValidator takes a kubernetes client and trust domain to create a
// K8sTokenValidator.
//
// The kubernetes client is used immediately to validate that the client has
// sufficient privileges to perform token reviews. An error is returned if this
// access check fails.
func NewK8sTokenValidator(
ctx context.Context,
k8s k8s.Interface,
domain *TrustDomain,
) (identity.Validator, error) {
if err := checkAccess(ctx, k8s.AuthorizationV1()); err != nil {
return nil, err
}
authn := k8s.AuthenticationV1()
return &K8sTokenValidator{authn, domain}, nil
}
// Validate accepts kubernetes bearer tokens and returns a DNS-form linkerd ID.
func (k *K8sTokenValidator) Validate(ctx context.Context, tok []byte) (string, error) {
tr := kauthnApi.TokenReview{Spec: kauthnApi.TokenReviewSpec{Token: string(tok), Audiences: []string{LinkerdAudienceKey}}}
rvw, err := k.authn.TokenReviews().Create(ctx, &tr, metav1.CreateOptions{})
if err != nil {
return "", err
}
if rvw.Status.Error != "" {
if strings.Contains(rvw.Status.Error, "token audiences") {
// Fallback to the default service account token validation if the error is realted to audiences
log.Debugf("TokenReview with audiences Failed. Falling back to the default")
tr = kauthnApi.TokenReview{Spec: kauthnApi.TokenReviewSpec{Token: string(tok), Audiences: []string{}}}
rvw, err = k.authn.TokenReviews().Create(ctx, &tr, metav1.CreateOptions{})
if err != nil {
return "", err
}
}
if rvw.Status.Error != "" {
return "", identity.InvalidToken{Reason: rvw.Status.Error}
}
}
if !rvw.Status.Authenticated {
return "", identity.NotAuthenticated{}
}
// Determine the identity associated with the token's userinfo.
uns := strings.Split(rvw.Status.User.Username, ":")
if len(uns) != 4 || uns[0] != "system" {
msg := fmt.Sprintf("Username must be in form system:TYPE:NS:SA: %s", rvw.Status.User.Username)
return "", identity.InvalidToken{Reason: msg}
}
uns = uns[1:]
for _, l := range uns {
if errs := validation.IsDNS1123Label(l); len(errs) > 0 {
return "", identity.InvalidToken{Reason: fmt.Sprintf("Not a label: %s", l)}
}
}
return k.domain.Identity(uns[0], uns[2], uns[1])
}
func checkAccess(ctx context.Context, authz kauthz.AuthorizationV1Interface) error {
r := &kauthzApi.SelfSubjectAccessReview{
Spec: kauthzApi.SelfSubjectAccessReviewSpec{
ResourceAttributes: &kauthzApi.ResourceAttributes{
Group: "authentication.k8s.io",
Version: "v1",
Resource: "tokenreviews",
Verb: "create",
},
},
}
rvw, err := authz.SelfSubjectAccessReviews().Create(ctx, r, metav1.CreateOptions{})
if err != nil {
return err
}
if !rvw.Status.Allowed {
return fmt.Errorf("Unable to create kubernetes token reviews: %s", rvw.Status.Reason)
}
return nil
}