...

Source file src/github.com/linkerd/linkerd2/controller/identity/validator.go

Documentation: github.com/linkerd/linkerd2/controller/identity

     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  	// LinkerdAudienceKey is the audience key used for the Linkerd token creation
    21  	// and  review requests.
    22  	LinkerdAudienceKey = "identity.l5d.io"
    23  )
    24  
    25  // K8sTokenValidator implements Validator for Kubernetes bearer tokens.
    26  type K8sTokenValidator struct {
    27  	authn  kauthn.AuthenticationV1Interface
    28  	domain *TrustDomain
    29  }
    30  
    31  // NewK8sTokenValidator takes a kubernetes client and trust domain to create a
    32  // K8sTokenValidator.
    33  //
    34  // The kubernetes client is used immediately to validate that the client has
    35  // sufficient privileges to perform token reviews. An error is returned if this
    36  // access check fails.
    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  // Validate accepts kubernetes bearer tokens and returns a DNS-form linkerd ID.
    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  			// Fallback to the default service account token validation if the error is realted to audiences
    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  	// Determine the identity associated with the token's userinfo.
    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