...
1 package identity
2
3 import (
4 "context"
5 "fmt"
6 "strings"
7
8 "github.com/linkerd/linkerd2/pkg/identity"
9 log "github.com/sirupsen/logrus"
10 kauthnApi "k8s.io/api/authentication/v1"
11 kauthzApi "k8s.io/api/authorization/v1"
12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13 "k8s.io/apimachinery/pkg/util/validation"
14 k8s "k8s.io/client-go/kubernetes"
15 kauthn "k8s.io/client-go/kubernetes/typed/authentication/v1"
16 kauthz "k8s.io/client-go/kubernetes/typed/authorization/v1"
17 )
18
19 const (
20
21
22 LinkerdAudienceKey = "identity.l5d.io"
23 )
24
25
26 type K8sTokenValidator struct {
27 authn kauthn.AuthenticationV1Interface
28 domain *TrustDomain
29 }
30
31
32
33
34
35
36
37 func NewK8sTokenValidator(
38 ctx context.Context,
39 k8s k8s.Interface,
40 domain *TrustDomain,
41 ) (identity.Validator, error) {
42 if err := checkAccess(ctx, k8s.AuthorizationV1()); err != nil {
43 return nil, err
44 }
45
46 authn := k8s.AuthenticationV1()
47 return &K8sTokenValidator{authn, domain}, nil
48 }
49
50
51 func (k *K8sTokenValidator) Validate(ctx context.Context, tok []byte) (string, error) {
52 tr := kauthnApi.TokenReview{Spec: kauthnApi.TokenReviewSpec{Token: string(tok), Audiences: []string{LinkerdAudienceKey}}}
53 rvw, err := k.authn.TokenReviews().Create(ctx, &tr, metav1.CreateOptions{})
54 if err != nil {
55 return "", err
56 }
57
58 if rvw.Status.Error != "" {
59 if strings.Contains(rvw.Status.Error, "token audiences") {
60
61 log.Debugf("TokenReview with audiences Failed. Falling back to the default")
62 tr = kauthnApi.TokenReview{Spec: kauthnApi.TokenReviewSpec{Token: string(tok), Audiences: []string{}}}
63 rvw, err = k.authn.TokenReviews().Create(ctx, &tr, metav1.CreateOptions{})
64 if err != nil {
65 return "", err
66 }
67 }
68
69 if rvw.Status.Error != "" {
70 return "", identity.InvalidToken{Reason: rvw.Status.Error}
71 }
72 }
73
74 if !rvw.Status.Authenticated {
75 return "", identity.NotAuthenticated{}
76 }
77
78
79 uns := strings.Split(rvw.Status.User.Username, ":")
80 if len(uns) != 4 || uns[0] != "system" {
81 msg := fmt.Sprintf("Username must be in form system:TYPE:NS:SA: %s", rvw.Status.User.Username)
82 return "", identity.InvalidToken{Reason: msg}
83 }
84 uns = uns[1:]
85 for _, l := range uns {
86 if errs := validation.IsDNS1123Label(l); len(errs) > 0 {
87 return "", identity.InvalidToken{Reason: fmt.Sprintf("Not a label: %s", l)}
88 }
89 }
90
91 return k.domain.Identity(uns[0], uns[2], uns[1])
92 }
93
94 func checkAccess(ctx context.Context, authz kauthz.AuthorizationV1Interface) error {
95 r := &kauthzApi.SelfSubjectAccessReview{
96 Spec: kauthzApi.SelfSubjectAccessReviewSpec{
97 ResourceAttributes: &kauthzApi.ResourceAttributes{
98 Group: "authentication.k8s.io",
99 Version: "v1",
100 Resource: "tokenreviews",
101 Verb: "create",
102 },
103 },
104 }
105 rvw, err := authz.SelfSubjectAccessReviews().Create(ctx, r, metav1.CreateOptions{})
106 if err != nil {
107 return err
108 }
109 if !rvw.Status.Allowed {
110 return fmt.Errorf("Unable to create kubernetes token reviews: %s", rvw.Status.Reason)
111 }
112
113 return nil
114 }
115
View as plain text