istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/analyzers/gateway/certificate.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 gateway
    16  
    17  import (
    18  	"istio.io/api/networking/v1alpha3"
    19  	"istio.io/istio/pilot/pkg/features"
    20  	"istio.io/istio/pkg/config"
    21  	"istio.io/istio/pkg/config/analysis"
    22  	"istio.io/istio/pkg/config/analysis/analyzers/util"
    23  	"istio.io/istio/pkg/config/analysis/msg"
    24  	"istio.io/istio/pkg/config/resource"
    25  	"istio.io/istio/pkg/config/schema/gvk"
    26  )
    27  
    28  type CertificateAnalyzer struct{}
    29  
    30  var _ analysis.Analyzer = &CertificateAnalyzer{}
    31  
    32  func (*CertificateAnalyzer) Metadata() analysis.Metadata {
    33  	return analysis.Metadata{
    34  		Name:        "gateway.CertificateAnalyzer",
    35  		Description: "Checks a gateway certificate",
    36  		Inputs: []config.GroupVersionKind{
    37  			gvk.Gateway,
    38  		},
    39  	}
    40  }
    41  
    42  // Analyze implements analysis.Analyzer
    43  func (gateway *CertificateAnalyzer) Analyze(context analysis.Context) {
    44  	context.ForEach(gvk.Gateway, func(resource *resource.Instance) bool {
    45  		gateway.analyzeDuplicateCertificate(resource, context, features.ScopeGatewayToNamespace)
    46  		return true
    47  	})
    48  }
    49  
    50  func (gateway *CertificateAnalyzer) analyzeDuplicateCertificate(currentResource *resource.Instance, context analysis.Context, scopeGatewayToNamespace bool) {
    51  	currentGateway := currentResource.Message.(*v1alpha3.Gateway)
    52  	currentGatewayFullName := currentResource.Metadata.FullName
    53  	gateways := getGatewaysWithSelector(context, scopeGatewayToNamespace, currentGatewayFullName, currentGateway.Selector)
    54  
    55  	for _, gatewayFullName := range gateways {
    56  		// ignore matching the same exact gateway
    57  		if currentGatewayFullName == gatewayFullName {
    58  			continue
    59  		}
    60  
    61  		gatewayInstance := context.Find(gvk.Gateway, gatewayFullName)
    62  		gateway := gatewayInstance.Message.(*v1alpha3.Gateway)
    63  		for _, currentServer := range currentGateway.Servers {
    64  			for _, server := range gateway.Servers {
    65  				// make sure have TLS configuration
    66  				if currentServer.Tls == nil || server.Tls == nil {
    67  					continue
    68  				}
    69  
    70  				if haveSameCertificate(currentServer.Tls, server.Tls) {
    71  					gatewayNames := []string{currentGatewayFullName.String(), gatewayFullName.String()}
    72  					message := msg.NewGatewayDuplicateCertificate(currentResource, gatewayNames)
    73  
    74  					if line, ok := util.ErrorLine(currentResource, util.MetadataName); ok {
    75  						message.Line = line
    76  					}
    77  
    78  					context.Report(gvk.Gateway, message)
    79  				}
    80  			}
    81  		}
    82  	}
    83  }
    84  
    85  func haveSameCertificate(currentGatewayTLS, gatewayTLS *v1alpha3.ServerTLSSettings) bool {
    86  	if currentGatewayTLS.CredentialName != "" && gatewayTLS.CredentialName != "" {
    87  		return currentGatewayTLS.CredentialName == gatewayTLS.CredentialName
    88  	}
    89  
    90  	if currentGatewayTLS.CredentialName == "" && gatewayTLS.CredentialName == "" {
    91  		if currentGatewayTLS.ServerCertificate != "" && gatewayTLS.ServerCertificate != "" {
    92  			if currentGatewayTLS.ServerCertificate == gatewayTLS.ServerCertificate {
    93  				if currentGatewayTLS.PrivateKey != "" && gatewayTLS.PrivateKey != "" {
    94  					return currentGatewayTLS.PrivateKey == gatewayTLS.PrivateKey
    95  				}
    96  				return false
    97  			}
    98  		}
    99  	}
   100  
   101  	return false
   102  }
   103  
   104  // get all gateways that is superset of the selector
   105  func getGatewaysWithSelector(c analysis.Context, gwScope bool, currentGWName resource.FullName, currentGWSelector map[string]string) []resource.FullName {
   106  	var gateways []resource.FullName
   107  
   108  	c.ForEach(gvk.Gateway, func(resource *resource.Instance) bool {
   109  		// if scopeToNamespace true, ignore adding gateways from other namespace
   110  		if gwScope {
   111  			if currentGWName.Namespace != resource.Metadata.FullName.Namespace {
   112  				return true
   113  			}
   114  		}
   115  
   116  		// if current gateway selector is empty, match all gateway
   117  		if len(currentGWSelector) == 0 {
   118  			gateways = append(gateways, resource.Metadata.FullName)
   119  			return true
   120  		}
   121  
   122  		gateway := resource.Message.(*v1alpha3.Gateway)
   123  		// if current gateway selector is subset of other gateway selector
   124  		// add other gateway
   125  		if selectorSubset(currentGWSelector, gateway.Selector) {
   126  			gateways = append(gateways, resource.Metadata.FullName)
   127  		}
   128  
   129  		return true
   130  	})
   131  
   132  	return gateways
   133  }
   134  
   135  func selectorSubset(selectorX, selectorY map[string]string) bool {
   136  	var count int
   137  
   138  	for keyX, valueX := range selectorX {
   139  		for keyY, valueY := range selectorY {
   140  			if keyX == keyY {
   141  				// if have same key but different value
   142  				// mean selectorX is not subset of selectorY
   143  				if valueX != valueY {
   144  					return false
   145  				}
   146  				// if key and value is same
   147  				// increase the counting
   148  				count++
   149  			}
   150  		}
   151  	}
   152  
   153  	// if total counting is not same with the length
   154  	// of selectorX, selectorX is not subset of selectorY
   155  	return count == len(selectorX)
   156  }