istio.io/istio@v0.0.0-20240520182934-d79c90f27776/security/pkg/server/ca/authenticate/kubeauth/kube_jwt.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kubeauth
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net/http"
    21  	"strings"
    22  
    23  	"google.golang.org/grpc/metadata"
    24  	"k8s.io/client-go/kubernetes"
    25  
    26  	"istio.io/istio/pkg/cluster"
    27  	"istio.io/istio/pkg/config/mesh"
    28  	"istio.io/istio/pkg/security"
    29  	"istio.io/istio/pkg/spiffe"
    30  	"istio.io/istio/security/pkg/k8s/tokenreview"
    31  )
    32  
    33  const (
    34  	KubeJWTAuthenticatorType = "KubeJWTAuthenticator"
    35  
    36  	clusterIDMeta = "clusterid"
    37  )
    38  
    39  type RemoteKubeClientGetter func(clusterID cluster.ID) kubernetes.Interface
    40  
    41  // KubeJWTAuthenticator authenticates K8s JWTs.
    42  type KubeJWTAuthenticator struct {
    43  	// holder of a mesh configuration for dynamically updating trust domain
    44  	meshHolder mesh.Holder
    45  
    46  	// Primary cluster kube client
    47  	kubeClient kubernetes.Interface
    48  	// Primary cluster ID
    49  	clusterID cluster.ID
    50  
    51  	// remote cluster kubeClient getter
    52  	remoteKubeClientGetter RemoteKubeClientGetter
    53  }
    54  
    55  var _ security.Authenticator = &KubeJWTAuthenticator{}
    56  
    57  // NewKubeJWTAuthenticator creates a new kubeJWTAuthenticator.
    58  func NewKubeJWTAuthenticator(
    59  	meshHolder mesh.Holder,
    60  	client kubernetes.Interface,
    61  	clusterID cluster.ID,
    62  	remoteKubeClientGetter RemoteKubeClientGetter,
    63  ) *KubeJWTAuthenticator {
    64  	return &KubeJWTAuthenticator{
    65  		meshHolder:             meshHolder,
    66  		kubeClient:             client,
    67  		clusterID:              clusterID,
    68  		remoteKubeClientGetter: remoteKubeClientGetter,
    69  	}
    70  }
    71  
    72  func (a *KubeJWTAuthenticator) AuthenticatorType() string {
    73  	return KubeJWTAuthenticatorType
    74  }
    75  
    76  func isAllowedKubernetesAudience(a string) bool {
    77  	// We do not use url.Parse() as it *requires* the protocol.
    78  	a = strings.TrimPrefix(a, "https://")
    79  	a = strings.TrimPrefix(a, "http://")
    80  	return strings.HasPrefix(a, "kubernetes.default.svc")
    81  }
    82  
    83  // Authenticate authenticates the call using the K8s JWT from the context.
    84  // The returned Caller.Identities is in SPIFFE format.
    85  func (a *KubeJWTAuthenticator) Authenticate(authRequest security.AuthContext) (*security.Caller, error) {
    86  	if authRequest.GrpcContext != nil {
    87  		return a.authenticateGrpc(authRequest.GrpcContext)
    88  	}
    89  	if authRequest.Request != nil {
    90  		return a.authenticateHTTP(authRequest.Request)
    91  	}
    92  	return nil, nil
    93  }
    94  
    95  func (a *KubeJWTAuthenticator) authenticateHTTP(req *http.Request) (*security.Caller, error) {
    96  	targetJWT, err := security.ExtractRequestToken(req)
    97  	if err != nil {
    98  		return nil, fmt.Errorf("target JWT extraction error: %v", err)
    99  	}
   100  	clusterID := cluster.ID(req.Header.Get(clusterIDMeta))
   101  	return a.authenticate(targetJWT, clusterID)
   102  }
   103  
   104  func (a *KubeJWTAuthenticator) authenticateGrpc(ctx context.Context) (*security.Caller, error) {
   105  	targetJWT, err := security.ExtractBearerToken(ctx)
   106  	if err != nil {
   107  		return nil, fmt.Errorf("target JWT extraction error: %v", err)
   108  	}
   109  	clusterID := ExtractClusterID(ctx)
   110  
   111  	return a.authenticate(targetJWT, clusterID)
   112  }
   113  
   114  func (a *KubeJWTAuthenticator) authenticate(targetJWT string, clusterID cluster.ID) (*security.Caller, error) {
   115  	kubeClient := a.getKubeClient(clusterID)
   116  	if kubeClient == nil {
   117  		return nil, fmt.Errorf("could not get cluster %s's kube client", clusterID)
   118  	}
   119  
   120  	id, err := tokenreview.ValidateK8sJwt(kubeClient, targetJWT, security.TokenAudiences)
   121  	if err != nil {
   122  		return nil, fmt.Errorf("failed to validate the JWT from cluster %q: %v", clusterID, err)
   123  	}
   124  	if id.PodServiceAccount == "" {
   125  		return nil, fmt.Errorf("failed to parse the JWT; service account required")
   126  	}
   127  	if id.PodNamespace == "" {
   128  		return nil, fmt.Errorf("failed to parse the JWT; namespace required")
   129  	}
   130  	return &security.Caller{
   131  		AuthSource:     security.AuthSourceIDToken,
   132  		Identities:     []string{spiffe.MustGenSpiffeURI(id.PodNamespace, id.PodServiceAccount)},
   133  		KubernetesInfo: id,
   134  	}, nil
   135  }
   136  
   137  func (a *KubeJWTAuthenticator) getKubeClient(clusterID cluster.ID) kubernetes.Interface {
   138  	// first match local/primary cluster
   139  	// or if clusterID is not sent (we assume that its a single cluster)
   140  	if a.clusterID == clusterID || clusterID == "" {
   141  		return a.kubeClient
   142  	}
   143  
   144  	// secondly try other remote clusters
   145  	if a.remoteKubeClientGetter != nil {
   146  		if res := a.remoteKubeClientGetter(clusterID); res != nil {
   147  			return res
   148  		}
   149  	}
   150  
   151  	// we did not find the kube client for this cluster.
   152  	// return nil so that logs will show that this cluster is not available in istiod
   153  	return nil
   154  }
   155  
   156  func ExtractClusterID(ctx context.Context) cluster.ID {
   157  	md, ok := metadata.FromIncomingContext(ctx)
   158  	if !ok {
   159  		return ""
   160  	}
   161  
   162  	clusterIDHeader, exists := md[clusterIDMeta]
   163  	if !exists {
   164  		return ""
   165  	}
   166  
   167  	if len(clusterIDHeader) == 1 {
   168  		return cluster.ID(clusterIDHeader[0])
   169  	}
   170  	return ""
   171  }