istio.io/istio@v0.0.0-20240520182934-d79c90f27776/security/pkg/k8s/tokenreview/k8sauthn.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 tokenreview 16 17 import ( 18 "context" 19 "fmt" 20 "strings" 21 22 k8sauth "k8s.io/api/authentication/v1" 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 "k8s.io/client-go/kubernetes" 25 26 "istio.io/istio/pkg/security" 27 ) 28 29 // nolint: lll 30 // From https://github.com/kubernetes/kubernetes/blob/4f2faa2f1ce8f49983173ef29214156afdf405f9/staging/src/k8s.io/apiserver/pkg/authentication/serviceaccount/util.go#L41 31 const ( 32 // PodNameKey is the key used in a user's "extra" to specify the pod name of 33 // the authenticating request. 34 PodNameKey = "authentication.kubernetes.io/pod-name" 35 // PodUIDKey is the key used in a user's "extra" to specify the pod UID of 36 // the authenticating request. 37 PodUIDKey = "authentication.kubernetes.io/pod-uid" 38 ) 39 40 // ValidateK8sJwt validates a k8s JWT at API server. 41 // Return {<namespace>, <serviceaccountname>} in the targetToken when the validation passes. 42 // Otherwise, return the error. 43 // targetToken: the JWT of the K8s service account to be reviewed 44 // aud: list of audiences to check. If empty 1st party tokens will be checked. 45 func ValidateK8sJwt(kubeClient kubernetes.Interface, targetToken string, aud []string) (security.KubernetesInfo, error) { 46 tokenReview := &k8sauth.TokenReview{ 47 Spec: k8sauth.TokenReviewSpec{ 48 Token: targetToken, 49 }, 50 } 51 if aud != nil { 52 tokenReview.Spec.Audiences = aud 53 } 54 reviewRes, err := kubeClient.AuthenticationV1().TokenReviews().Create(context.TODO(), tokenReview, metav1.CreateOptions{}) 55 if err != nil { 56 return security.KubernetesInfo{}, err 57 } 58 59 return getTokenReviewResult(reviewRes) 60 } 61 62 func getTokenReviewResult(tokenReview *k8sauth.TokenReview) (security.KubernetesInfo, error) { 63 if tokenReview.Status.Error != "" { 64 return security.KubernetesInfo{}, fmt.Errorf("the service account authentication returns an error: %v", 65 tokenReview.Status.Error) 66 } 67 // An example SA token: 68 // {"alg":"RS256","typ":"JWT"} 69 // {"iss":"kubernetes/serviceaccount", 70 // "kubernetes.io/serviceaccount/namespace":"default", 71 // "kubernetes.io/serviceaccount/secret.name":"example-pod-sa-token-h4jqx", 72 // "kubernetes.io/serviceaccount/service-account.name":"example-pod-sa", 73 // "kubernetes.io/serviceaccount/service-account.uid":"ff578a9e-65d3-11e8-aad2-42010a8a001d", 74 // "sub":"system:serviceaccount:default:example-pod-sa" 75 // } 76 77 // An example token review status 78 // "status":{ 79 // "authenticated":true, 80 // "user":{ 81 // "username":"system:serviceaccount:default:example-pod-sa", 82 // "uid":"ff578a9e-65d3-11e8-aad2-42010a8a001d", 83 // "groups":["system:serviceaccounts","system:serviceaccounts:default","system:authenticated"] 84 // } 85 // } 86 87 if !tokenReview.Status.Authenticated { 88 return security.KubernetesInfo{}, fmt.Errorf("the token is not authenticated") 89 } 90 inServiceAccountGroup := false 91 for _, group := range tokenReview.Status.User.Groups { 92 if group == "system:serviceaccounts" { 93 inServiceAccountGroup = true 94 break 95 } 96 } 97 if !inServiceAccountGroup { 98 return security.KubernetesInfo{}, fmt.Errorf("the token is not a service account") 99 } 100 // "username" is in the form of system:serviceaccount:{namespace}:{service account name}", 101 // e.g., "username":"system:serviceaccount:default:example-pod-sa" 102 subStrings := strings.Split(tokenReview.Status.User.Username, ":") 103 if len(subStrings) != 4 { 104 return security.KubernetesInfo{}, fmt.Errorf("invalid username field in the token review result") 105 } 106 107 return security.KubernetesInfo{ 108 PodName: extractExtra(tokenReview, PodNameKey), 109 PodNamespace: subStrings[2], 110 PodUID: extractExtra(tokenReview, PodUIDKey), 111 PodServiceAccount: subStrings[3], 112 }, nil 113 } 114 115 func extractExtra(review *k8sauth.TokenReview, s string) string { 116 values, ok := review.Status.User.Extra[s] 117 if !ok || len(values) == 0 { 118 return "" 119 } 120 return values[0] 121 }