istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/analyzers/virtualservice/jwtclaimroute.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 virtualservice
    16  
    17  import (
    18  	klabels "k8s.io/apimachinery/pkg/labels"
    19  
    20  	"istio.io/api/networking/v1alpha3"
    21  	"istio.io/api/security/v1beta1"
    22  	"istio.io/istio/pkg/config"
    23  	"istio.io/istio/pkg/config/analysis"
    24  	"istio.io/istio/pkg/config/analysis/analyzers/util"
    25  	"istio.io/istio/pkg/config/analysis/msg"
    26  	"istio.io/istio/pkg/config/constants"
    27  	"istio.io/istio/pkg/config/resource"
    28  	"istio.io/istio/pkg/config/schema/gvk"
    29  	"istio.io/istio/pkg/jwt"
    30  )
    31  
    32  type JWTClaimRouteAnalyzer struct{}
    33  
    34  var _ analysis.Analyzer = &JWTClaimRouteAnalyzer{}
    35  
    36  // Metadata implements Analyzer
    37  func (s *JWTClaimRouteAnalyzer) Metadata() analysis.Metadata {
    38  	return analysis.Metadata{
    39  		Name:        "virtualservice.JWTClaimRouteAnalyzer",
    40  		Description: "Checks the VirtualService using JWT claim based routing has corresponding RequestAuthentication",
    41  		Inputs: []config.GroupVersionKind{
    42  			gvk.VirtualService,
    43  			gvk.RequestAuthentication,
    44  			gvk.Gateway,
    45  			gvk.Pod,
    46  		},
    47  	}
    48  }
    49  
    50  // Analyze implements Analyzer
    51  func (s *JWTClaimRouteAnalyzer) Analyze(c analysis.Context) {
    52  	requestAuthNByNamespace := map[string][]klabels.Selector{}
    53  	c.ForEach(gvk.RequestAuthentication, func(r *resource.Instance) bool {
    54  		ns := r.Metadata.FullName.Namespace.String()
    55  		if _, found := requestAuthNByNamespace[ns]; !found {
    56  			requestAuthNByNamespace[ns] = []klabels.Selector{}
    57  		}
    58  		ra := r.Message.(*v1beta1.RequestAuthentication)
    59  		raSelector := klabels.SelectorFromSet(ra.GetSelector().GetMatchLabels())
    60  		requestAuthNByNamespace[ns] = append(requestAuthNByNamespace[ns], raSelector)
    61  		return true
    62  	})
    63  
    64  	c.ForEach(gvk.VirtualService, func(r *resource.Instance) bool {
    65  		s.analyze(r, c, requestAuthNByNamespace)
    66  		return true
    67  	})
    68  }
    69  
    70  func (s *JWTClaimRouteAnalyzer) analyze(r *resource.Instance, c analysis.Context, requestAuthNByNamespace map[string][]klabels.Selector) {
    71  	// Check if the virtual service is using JWT claim based routing.
    72  	vs := r.Message.(*v1alpha3.VirtualService)
    73  	var vsRouteKey string
    74  	if vsRouteKey = routeBasedOnJWTClaimKey(vs); vsRouteKey == "" {
    75  		return
    76  	}
    77  	vsNs := r.Metadata.FullName.Namespace
    78  
    79  	// Check if the virtual service is applied to gateway.
    80  	for _, gwName := range vs.Gateways {
    81  		if gwName == util.MeshGateway {
    82  			continue
    83  		}
    84  
    85  		gwFullName := resource.NewShortOrFullName(vsNs, gwName)
    86  		gwRes := c.Find(gvk.Gateway, gwFullName)
    87  		if gwRes == nil {
    88  			// The gateway does not exist, this should already be covered by the gateway analyzer.
    89  			continue
    90  		}
    91  
    92  		gw := gwRes.Message.(*v1alpha3.Gateway)
    93  		gwSelector := klabels.SelectorFromSet(gw.Selector)
    94  
    95  		// Check each pod selected by the gateway.
    96  		c.ForEach(gvk.Pod, func(rPod *resource.Instance) bool {
    97  			podLabels := klabels.Set(rPod.Metadata.Labels)
    98  			if !gwSelector.Matches(podLabels) {
    99  				return true
   100  			}
   101  
   102  			// Check if there is request authentication applied to the pod.
   103  			var hasRequestAuthNForPod bool
   104  
   105  			raSelectors := requestAuthNByNamespace[constants.IstioSystemNamespace]
   106  			raSelectors = append(raSelectors, requestAuthNByNamespace[rPod.Metadata.FullName.Namespace.String()]...)
   107  			for _, raSelector := range raSelectors {
   108  				if raSelector.Matches(podLabels) {
   109  					hasRequestAuthNForPod = true
   110  					break
   111  				}
   112  			}
   113  			if !hasRequestAuthNForPod {
   114  				m := msg.NewJwtClaimBasedRoutingWithoutRequestAuthN(r, vsRouteKey, gwFullName.String(), rPod.Metadata.FullName.Name.String())
   115  				c.Report(gvk.VirtualService, m)
   116  			}
   117  			return true
   118  		})
   119  	}
   120  }
   121  
   122  func routeBasedOnJWTClaimKey(vs *v1alpha3.VirtualService) string {
   123  	for _, httpRoute := range vs.GetHttp() {
   124  		for _, match := range httpRoute.GetMatch() {
   125  			for key := range match.GetHeaders() {
   126  				if jwt.ToRoutingClaim(key).Match {
   127  					return key
   128  				}
   129  			}
   130  			for key := range match.GetWithoutHeaders() {
   131  				if jwt.ToRoutingClaim(key).Match {
   132  					return key
   133  				}
   134  			}
   135  		}
   136  	}
   137  	return ""
   138  }