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 }