github.com/cilium/cilium@v1.16.2/operator/pkg/model/helpers.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package model
     5  
     6  import (
     7  	"crypto/sha256"
     8  	"fmt"
     9  	"sort"
    10  	"strings"
    11  
    12  	"github.com/google/go-cmp/cmp"
    13  )
    14  
    15  const (
    16  	allHosts = "*"
    17  )
    18  
    19  func AddSource(sourceList []FullyQualifiedResource, source FullyQualifiedResource) []FullyQualifiedResource {
    20  	for _, s := range sourceList {
    21  		if cmp.Equal(s, source) {
    22  			return sourceList
    23  		}
    24  	}
    25  	return append(sourceList, source)
    26  }
    27  
    28  // ComputeHosts returns a list of the intersecting hostnames between the route and the listener.
    29  // The below function is inspired from https://github.com/envoyproxy/gateway/blob/main/internal/gatewayapi/helpers.go.
    30  // Special thanks to Envoy team.
    31  // The function takes a list of route hostnames, a listener hostname, and a list of other listener hostnames.
    32  // Note that the listenerHostname value will be skipped if it is present in the otherListenerHosts list.
    33  func ComputeHosts(routeHostnames []string, listenerHostname *string, otherListenerHosts []string) []string {
    34  	var listenerHostnameVal string
    35  	if listenerHostname != nil {
    36  		listenerHostnameVal = *listenerHostname
    37  	}
    38  
    39  	// No route hostnames specified: use the listener hostname if specified,
    40  	// or else match all hostnames.
    41  	if len(routeHostnames) == 0 {
    42  		if len(listenerHostnameVal) > 0 {
    43  			return []string{listenerHostnameVal}
    44  		}
    45  
    46  		return []string{allHosts}
    47  	}
    48  
    49  	var hostnames []string
    50  
    51  	for i := range routeHostnames {
    52  		routeHostname := routeHostnames[i]
    53  
    54  		switch {
    55  		// No listener hostname: use the route hostname if there is no overlapping with other listener hostnames.
    56  		case len(listenerHostnameVal) == 0:
    57  			if !checkHostNameIsolation(routeHostname, listenerHostnameVal, otherListenerHosts) {
    58  				hostnames = append(hostnames, routeHostname)
    59  			}
    60  
    61  		// Listener hostname matches the route hostname: use it.
    62  		case listenerHostnameVal == routeHostname:
    63  			hostnames = append(hostnames, routeHostname)
    64  
    65  		// Listener has a wildcard hostname: check if the route hostname matches.
    66  		case strings.HasPrefix(listenerHostnameVal, allHosts):
    67  			if hostnameMatchesWildcardHostname(routeHostname, listenerHostnameVal) &&
    68  				!checkHostNameIsolation(routeHostname, listenerHostnameVal, otherListenerHosts) {
    69  				hostnames = append(hostnames, routeHostname)
    70  			}
    71  
    72  		// Route has a wildcard hostname: check if the listener hostname matches.
    73  		case strings.HasPrefix(routeHostname, allHosts):
    74  			if hostnameMatchesWildcardHostname(listenerHostnameVal, routeHostname) {
    75  				hostnames = append(hostnames, listenerHostnameVal)
    76  			}
    77  		}
    78  	}
    79  
    80  	sort.Strings(hostnames)
    81  	return hostnames
    82  }
    83  
    84  func checkHostNameIsolation(routeHostname string, listenerHostName string, excludedListenerHostnames []string) bool {
    85  	for _, exHost := range excludedListenerHostnames {
    86  		if exHost == listenerHostName {
    87  			continue
    88  		}
    89  		if routeHostname == exHost {
    90  			return true
    91  		}
    92  		if strings.HasPrefix(exHost, allHosts) &&
    93  			hostnameMatchesWildcardHostname(routeHostname, exHost) &&
    94  			len(exHost) > len(listenerHostName) {
    95  			return true
    96  		}
    97  	}
    98  
    99  	return false
   100  }
   101  
   102  // hostnameMatchesWildcardHostname returns true if hostname has the non-wildcard
   103  // portion of wildcardHostname as a suffix, plus at least one DNS label matching the
   104  // wildcard.
   105  func hostnameMatchesWildcardHostname(hostname, wildcardHostname string) bool {
   106  	if !strings.HasSuffix(hostname, strings.TrimPrefix(wildcardHostname, allHosts)) {
   107  		return false
   108  	}
   109  
   110  	wildcardMatch := strings.TrimSuffix(hostname, strings.TrimPrefix(wildcardHostname, allHosts))
   111  	return len(wildcardMatch) > 0
   112  }
   113  
   114  func AddressOf[T any](v T) *T {
   115  	return &v
   116  }
   117  
   118  // Shorten shortens the string to 63 characters.
   119  // this is the implicit required for all the resource naming in k8s.
   120  func Shorten(s string) string {
   121  	if len(s) > 63 {
   122  		return s[:52] + "-" + encodeHash(hash(s))
   123  	}
   124  	return s
   125  }
   126  
   127  // encodeHash encodes the first 10 characters of the hex string.
   128  // https://github.com/kubernetes/kubernetes/blob/f0dcf0614036d8c3cd1c9f3b3cf8df4bb1d8e44e/staging/src/k8s.io/kubectl/pkg/util/hash/hash.go#L105
   129  func encodeHash(hex string) string {
   130  	enc := []rune(hex[:10])
   131  	for i := range enc {
   132  		switch enc[i] {
   133  		case '0':
   134  			enc[i] = 'g'
   135  		case '1':
   136  			enc[i] = 'h'
   137  		case '3':
   138  			enc[i] = 'k'
   139  		case 'a':
   140  			enc[i] = 'm'
   141  		case 'e':
   142  			enc[i] = 't'
   143  		}
   144  	}
   145  	return string(enc)
   146  }
   147  
   148  // hash hashes `data` with sha256 and returns the hex string
   149  func hash(data string) string {
   150  	return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
   151  }