mirror of
https://github.com/alibaba/higress.git
synced 2026-06-26 10:45:25 +08:00
feat: Enhance SSL passthrough support (#3943)
Signed-off-by: zijiren233 <pyh1670605849@gmail.com>
This commit is contained in:
@@ -337,6 +337,13 @@ func extractTLSSecretName(host string, tls []ingress.IngressTLS) string {
|
||||
}
|
||||
|
||||
func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapper *common.WrapperConfig, httpsCredentialConfig *cert.Config) error {
|
||||
if convertOptions == nil {
|
||||
return fmt.Errorf("convertOptions is nil")
|
||||
}
|
||||
if wrapper == nil {
|
||||
return fmt.Errorf("wrapperConfig is nil")
|
||||
}
|
||||
|
||||
// Ignore canary config.
|
||||
if wrapper.AnnotationsConfig.IsCanary() {
|
||||
return nil
|
||||
@@ -382,7 +389,7 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
||||
Protocol: string(protocol.HTTP),
|
||||
Name: common.CreateConvertedName("http-"+strconv.FormatUint(uint64(c.options.GatewayHttpPort), 10)+"-ingress", string(c.options.ClusterId)),
|
||||
},
|
||||
Hosts: []string{rule.Host},
|
||||
Hosts: []string{common.WildcardHost(rule.Host)},
|
||||
})
|
||||
|
||||
// Add new gateway, builder
|
||||
@@ -395,6 +402,45 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
||||
}
|
||||
}
|
||||
|
||||
passthroughOwner := common.PassthroughTLSHostOwner(convertOptions, rule.Host)
|
||||
standaloneSSLPassthrough := convertOptions.PassthroughTLSHostOwners == nil && wrapper.AnnotationsConfig.IsSSLPassthrough()
|
||||
if common.SameConfig(passthroughOwner, cfg) || standaloneSSLPassthrough {
|
||||
if rule.HTTP == nil || len(rule.HTTP.Paths) == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := rootHTTPIngressPath(rule.HTTP.Paths); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
domainBuilder.Protocol = common.HTTPS
|
||||
if wrapperGateway.IsHTTPS() {
|
||||
if common.SameConfig(preDomainBuilder.Ingress, cfg) {
|
||||
continue
|
||||
}
|
||||
domainBuilder.Event = common.DuplicatedTls
|
||||
domainBuilder.PreIngress = preDomainBuilder.Ingress
|
||||
convertOptions.IngressDomainCache.Invalid = append(convertOptions.IngressDomainCache.Invalid,
|
||||
domainBuilder.Build())
|
||||
continue
|
||||
}
|
||||
wrapperGateway.Gateway.Servers = append(wrapperGateway.Gateway.Servers,
|
||||
common.CreateSSLPassthroughServer(rule.Host, c.options.GatewayHttpsPort, c.options.ClusterId))
|
||||
convertOptions.IngressDomainCache.Valid[rule.Host] = domainBuilder
|
||||
continue
|
||||
}
|
||||
if wrapper.AnnotationsConfig.IsSSLPassthrough() {
|
||||
if rule.HTTP != nil {
|
||||
if _, ok := rootHTTPIngressPath(rule.HTTP.Paths); ok && passthroughOwner != nil {
|
||||
domainBuilder.Protocol = common.HTTPS
|
||||
domainBuilder.Event = common.DuplicatedTls
|
||||
domainBuilder.PreIngress = passthroughOwner
|
||||
convertOptions.IngressDomainCache.Invalid = append(convertOptions.IngressDomainCache.Invalid,
|
||||
domainBuilder.Build())
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// There are no tls settings, so just skip.
|
||||
if len(ingressV1.TLS) == 0 {
|
||||
continue
|
||||
@@ -443,6 +489,14 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
||||
domainBuilder.Protocol = common.HTTPS
|
||||
domainBuilder.SecretName = path.Join(c.options.ClusterId.String(), cfg.Namespace, secretName)
|
||||
|
||||
if passthroughOwner != nil {
|
||||
domainBuilder.Event = common.DuplicatedTls
|
||||
domainBuilder.PreIngress = passthroughOwner
|
||||
convertOptions.IngressDomainCache.Invalid = append(convertOptions.IngressDomainCache.Invalid,
|
||||
domainBuilder.Build())
|
||||
continue
|
||||
}
|
||||
|
||||
// There is a matching secret and the gateway has already a tls secret.
|
||||
// We should report the duplicated tls secret event.
|
||||
if wrapperGateway.IsHTTPS() {
|
||||
@@ -460,7 +514,7 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
||||
Protocol: string(protocol.HTTPS),
|
||||
Name: common.CreateConvertedName("https-"+strconv.FormatUint(uint64(c.options.GatewayHttpsPort), 10)+"-ingress", string(c.options.ClusterId)),
|
||||
},
|
||||
Hosts: []string{rule.Host},
|
||||
Hosts: []string{common.WildcardHost(rule.Host)},
|
||||
Tls: &networking.ServerTLSSettings{
|
||||
Mode: networking.ServerTLSSettings_SIMPLE,
|
||||
CredentialName: credentials.ToKubernetesIngressResource(c.options.RawClusterId, secretNamespace, secretName),
|
||||
@@ -475,12 +529,32 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
||||
}
|
||||
|
||||
func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wrapper *common.WrapperConfig) error {
|
||||
if convertOptions == nil {
|
||||
return fmt.Errorf("convertOptions is nil")
|
||||
}
|
||||
if wrapper == nil {
|
||||
return fmt.Errorf("wrapperConfig is nil")
|
||||
}
|
||||
|
||||
// Canary ingress will be processed in the end.
|
||||
if wrapper.AnnotationsConfig.IsCanary() {
|
||||
convertOptions.CanaryIngresses = append(convertOptions.CanaryIngresses, wrapper)
|
||||
return nil
|
||||
}
|
||||
|
||||
if convertOptions.Route2Ingress == nil {
|
||||
convertOptions.Route2Ingress = map[string]*common.WrapperConfigWithRuleKey{}
|
||||
}
|
||||
if convertOptions.IngressRouteCache == nil {
|
||||
convertOptions.IngressRouteCache = common.NewIngressRouteCache()
|
||||
}
|
||||
if convertOptions.VirtualServices == nil {
|
||||
convertOptions.VirtualServices = map[string]*common.WrapperVirtualService{}
|
||||
}
|
||||
if convertOptions.HTTPRoutes == nil {
|
||||
convertOptions.HTTPRoutes = map[string][]*common.WrapperHTTPRoute{}
|
||||
}
|
||||
|
||||
cfg := wrapper.Config
|
||||
ingressV1, ok := cfg.Spec.(ingress.IngressSpec)
|
||||
if !ok {
|
||||
@@ -515,12 +589,7 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
|
||||
|
||||
wrapperVS, exist := convertOptions.VirtualServices[rule.Host]
|
||||
if !exist {
|
||||
wrapperVS = &common.WrapperVirtualService{
|
||||
VirtualService: &networking.VirtualService{
|
||||
Hosts: []string{rule.Host},
|
||||
},
|
||||
WrapperConfig: wrapper,
|
||||
}
|
||||
wrapperVS = common.NewWrapperVirtualService(rule.Host, wrapper)
|
||||
convertOptions.VirtualServices[rule.Host] = wrapperVS
|
||||
}
|
||||
|
||||
@@ -549,7 +618,11 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
|
||||
pathType = common.PrefixRegex
|
||||
}
|
||||
} else {
|
||||
switch *httpPath.PathType {
|
||||
ingressPathType := defaultPathType
|
||||
if httpPath.PathType != nil {
|
||||
ingressPathType = *httpPath.PathType
|
||||
}
|
||||
switch ingressPathType {
|
||||
case ingress.PathTypeExact:
|
||||
pathType = common.Exact
|
||||
case ingress.PathTypePrefix:
|
||||
@@ -626,9 +699,84 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
|
||||
}
|
||||
}
|
||||
|
||||
if common.HasPassthroughTLSHostOwner(convertOptions, cfg) ||
|
||||
(convertOptions.PassthroughTLSHostOwners == nil && wrapper.AnnotationsConfig.IsSSLPassthrough()) {
|
||||
return c.ConvertTLSRoute(convertOptions, wrapper)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *controller) ConvertTLSRoute(convertOptions *common.ConvertOptions, wrapper *common.WrapperConfig) error {
|
||||
if convertOptions == nil {
|
||||
return fmt.Errorf("convertOptions is nil")
|
||||
}
|
||||
if wrapper == nil {
|
||||
return fmt.Errorf("wrapperConfig is nil")
|
||||
}
|
||||
|
||||
if convertOptions.VirtualServices == nil {
|
||||
convertOptions.VirtualServices = map[string]*common.WrapperVirtualService{}
|
||||
}
|
||||
|
||||
cfg := wrapper.Config
|
||||
ingressV1, ok := cfg.Spec.(ingress.IngressSpec)
|
||||
if !ok {
|
||||
common.IncrementInvalidIngress(c.options.ClusterId, common.Unknown)
|
||||
return fmt.Errorf("convert type is invalid in cluster %s", c.options.ClusterId)
|
||||
}
|
||||
if len(ingressV1.Rules) == 0 {
|
||||
common.IncrementInvalidIngress(c.options.ClusterId, common.EmptyRule)
|
||||
return fmt.Errorf("invalid ingress rule %s:%s in cluster %s, `rules` must be specified", cfg.Namespace, cfg.Name, c.options.ClusterId)
|
||||
}
|
||||
|
||||
for _, rule := range ingressV1.Rules {
|
||||
if !common.IsPassthroughTLSHostOwner(convertOptions, cfg, rule.Host) {
|
||||
IngressLog.Warnf("ignore duplicated ssl passthrough ingress rule %s:%s for host %q in cluster %s", cfg.Namespace, cfg.Name, rule.Host, c.options.ClusterId)
|
||||
continue
|
||||
}
|
||||
|
||||
if rule.HTTP == nil || len(rule.HTTP.Paths) == 0 {
|
||||
IngressLog.Warnf("invalid ssl passthrough ingress rule %s:%s for host %q in cluster %s, no paths defined", cfg.Namespace, cfg.Name, rule.Host, c.options.ClusterId)
|
||||
continue
|
||||
}
|
||||
|
||||
httpPath, ok := rootHTTPIngressPath(rule.HTTP.Paths)
|
||||
if !ok {
|
||||
IngressLog.Warnf("ignore ssl passthrough ingress rule %s:%s for host %q in cluster %s, root path is not defined", cfg.Namespace, cfg.Name, rule.Host, c.options.ClusterId)
|
||||
continue
|
||||
}
|
||||
|
||||
wrapperVS, exist := convertOptions.VirtualServices[rule.Host]
|
||||
if !exist {
|
||||
wrapperVS = common.NewWrapperVirtualService(rule.Host, wrapper)
|
||||
convertOptions.VirtualServices[rule.Host] = wrapperVS
|
||||
} else if wrapperVS.HasTLSRouteForHost(rule.Host) {
|
||||
continue
|
||||
}
|
||||
|
||||
routeDestination, event := c.backendToTLSRouteDestination(&httpPath.Backend, cfg.Namespace, wrapper.AnnotationsConfig.Destination)
|
||||
if event != common.Normal {
|
||||
common.IncrementInvalidIngress(c.options.ClusterId, event)
|
||||
continue
|
||||
}
|
||||
|
||||
wrapperVS.VirtualService.Tls = append(wrapperVS.VirtualService.Tls,
|
||||
common.CreateTLSRoute(rule.Host, routeDestination))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func rootHTTPIngressPath(paths []ingress.HTTPIngressPath) (*ingress.HTTPIngressPath, bool) {
|
||||
for idx := range paths {
|
||||
if paths[idx].Path == "" || paths[idx].Path == "/" {
|
||||
return &paths[idx], true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (c *controller) generateHttpMatches(pathType common.PathType, path string, wrapperVS *common.WrapperVirtualService) []*networking.HTTPMatchRequest {
|
||||
var httpMatches []*networking.HTTPMatchRequest
|
||||
|
||||
@@ -689,12 +837,7 @@ func (c *controller) ApplyDefaultBackend(convertOptions *common.ConvertOptions,
|
||||
wirecardVS, exist := convertOptions.VirtualServices[host]
|
||||
if !exist || !wirecardVS.ConfiguredDefaultBackend {
|
||||
if !exist {
|
||||
wirecardVS = &common.WrapperVirtualService{
|
||||
VirtualService: &networking.VirtualService{
|
||||
Hosts: []string{host},
|
||||
},
|
||||
WrapperConfig: wrapper,
|
||||
}
|
||||
wirecardVS = common.NewWrapperVirtualService(host, wrapper)
|
||||
convertOptions.VirtualServices[host] = wirecardVS
|
||||
}
|
||||
|
||||
@@ -782,7 +925,11 @@ func (c *controller) ApplyCanaryIngress(convertOptions *common.ConvertOptions, w
|
||||
pathType = common.PrefixRegex
|
||||
}
|
||||
} else {
|
||||
switch *httpPath.PathType {
|
||||
ingressPathType := defaultPathType
|
||||
if httpPath.PathType != nil {
|
||||
ingressPathType = *httpPath.PathType
|
||||
}
|
||||
switch ingressPathType {
|
||||
case ingress.PathTypeExact:
|
||||
pathType = common.Exact
|
||||
case ingress.PathTypePrefix:
|
||||
@@ -1074,6 +1221,54 @@ func (c *controller) backendToRouteDestination(backend *ingress.IngressBackend,
|
||||
}, common.Normal
|
||||
}
|
||||
|
||||
func (c *controller) backendToTLSRouteDestination(backend *ingress.IngressBackend, namespace string,
|
||||
config *annotations.DestinationConfig,
|
||||
) ([]*networking.RouteDestination, common.Event) {
|
||||
if backend == nil {
|
||||
return nil, common.InvalidBackendService
|
||||
}
|
||||
|
||||
if backend.Service == nil {
|
||||
if config != nil && len(config.McpDestination) > 0 {
|
||||
return httpRouteDestinationToRouteDestination(config.McpDestination), common.Normal
|
||||
}
|
||||
return nil, common.InvalidBackendService
|
||||
}
|
||||
|
||||
service := backend.Service
|
||||
port := &networking.PortSelector{}
|
||||
if service.Port.Number > 0 {
|
||||
port.Number = uint32(service.Port.Number)
|
||||
} else {
|
||||
resolvedPort, err := resolveNamedPort(service, namespace, c.serviceLister)
|
||||
if err != nil {
|
||||
return nil, common.PortNameResolveError
|
||||
}
|
||||
port.Number = uint32(resolvedPort)
|
||||
}
|
||||
|
||||
return []*networking.RouteDestination{
|
||||
{
|
||||
Destination: &networking.Destination{
|
||||
Host: util.CreateServiceFQDN(namespace, service.Name),
|
||||
Port: port,
|
||||
},
|
||||
Weight: 100,
|
||||
},
|
||||
}, common.Normal
|
||||
}
|
||||
|
||||
func httpRouteDestinationToRouteDestination(destinations []*networking.HTTPRouteDestination) []*networking.RouteDestination {
|
||||
out := make([]*networking.RouteDestination, 0, len(destinations))
|
||||
for _, destination := range destinations {
|
||||
out = append(out, &networking.RouteDestination{
|
||||
Destination: destination.Destination,
|
||||
Weight: destination.Weight,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func resolveNamedPort(service *ingress.IngressServiceBackend, namespace string, serviceLister listerv1.ServiceLister) (int32, error) {
|
||||
svc, err := serviceLister.Services(namespace).Get(service.Name)
|
||||
if err != nil {
|
||||
|
||||
@@ -15,12 +15,17 @@
|
||||
package ingressv1
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/alibaba/higress/v2/pkg/cert"
|
||||
"github.com/alibaba/higress/v2/pkg/ingress/kube/annotations"
|
||||
"github.com/alibaba/higress/v2/pkg/ingress/kube/common"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
networking "istio.io/api/networking/v1alpha3"
|
||||
"istio.io/istio/pkg/config"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
@@ -118,3 +123,904 @@ func TestGenerateHttpMatches(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLPassthroughConvertGatewayAndTLSRoute(t *testing.T) {
|
||||
c := controller{
|
||||
options: common.Options{
|
||||
GatewayHttpPort: 80,
|
||||
GatewayHttpsPort: 443,
|
||||
},
|
||||
}
|
||||
wrapper := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-passthrough",
|
||||
},
|
||||
Spec: ingressSpecWithSSLPassthroughBackend("example.com", "app", 443),
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
gatewayOptions := &common.ConvertOptions{
|
||||
Gateways: map[string]*common.WrapperGateway{},
|
||||
IngressDomainCache: common.NewIngressDomainCache(),
|
||||
}
|
||||
if err := c.ConvertGateway(gatewayOptions, wrapper, nil); err != nil {
|
||||
t.Fatalf("ConvertGateway() error = %v", err)
|
||||
}
|
||||
gateway := gatewayOptions.Gateways["example.com"].Gateway
|
||||
if len(gateway.Servers) != 2 {
|
||||
t.Fatalf("server count mismatch, want 2, got %d", len(gateway.Servers))
|
||||
}
|
||||
tlsServer := gateway.Servers[1]
|
||||
if tlsServer.Port.Protocol != "TLS" {
|
||||
t.Fatalf("protocol mismatch, want TLS, got %s", tlsServer.Port.Protocol)
|
||||
}
|
||||
if tlsServer.Port.Number != 443 {
|
||||
t.Fatalf("port mismatch, want 443, got %d", tlsServer.Port.Number)
|
||||
}
|
||||
if tlsServer.Tls.GetMode() != networking.ServerTLSSettings_PASSTHROUGH {
|
||||
t.Fatalf("tls mode mismatch, want PASSTHROUGH, got %s", tlsServer.Tls.GetMode())
|
||||
}
|
||||
|
||||
routeOptions := &common.ConvertOptions{}
|
||||
if err := c.ConvertHTTPRoute(routeOptions, wrapper); err != nil {
|
||||
t.Fatalf("ConvertHTTPRoute() error = %v", err)
|
||||
}
|
||||
httpRoutes := routeOptions.HTTPRoutes["example.com"]
|
||||
if len(httpRoutes) != 1 {
|
||||
t.Fatalf("http route count mismatch, want 1, got %d", len(httpRoutes))
|
||||
}
|
||||
if got := httpRoutes[0].HTTPRoute.Route[0].Destination.Host; got != "app.default.svc.cluster.local" {
|
||||
t.Fatalf("http destination host mismatch, got %s", got)
|
||||
}
|
||||
routes := routeOptions.VirtualServices["example.com"].VirtualService.Tls
|
||||
if len(routes) != 1 {
|
||||
t.Fatalf("tls route count mismatch, want 1, got %d", len(routes))
|
||||
}
|
||||
route := routes[0]
|
||||
if got := route.Match[0].SniHosts[0]; got != "example.com" {
|
||||
t.Fatalf("sni host mismatch, want example.com, got %s", got)
|
||||
}
|
||||
if got := route.Route[0].Destination.Host; got != "app.default.svc.cluster.local" {
|
||||
t.Fatalf("destination host mismatch, got %s", got)
|
||||
}
|
||||
if got := route.Route[0].Destination.Port.Number; got != 443 {
|
||||
t.Fatalf("destination port mismatch, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLPassthroughConvertGatewayRejectsNilInputs(t *testing.T) {
|
||||
c := controller{}
|
||||
wrapper := &common.WrapperConfig{
|
||||
Config: &config.Config{},
|
||||
AnnotationsConfig: &annotations.Ingress{},
|
||||
}
|
||||
|
||||
if err := c.ConvertGateway(nil, wrapper, nil); err == nil {
|
||||
t.Fatal("ConvertGateway() with nil convertOptions returned nil error")
|
||||
}
|
||||
if err := c.ConvertGateway(&common.ConvertOptions{}, nil, nil); err == nil {
|
||||
t.Fatal("ConvertGateway() with nil wrapper returned nil error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLPassthroughConvertTLSRouteRejectsNilInputs(t *testing.T) {
|
||||
c := controller{}
|
||||
wrapper := &common.WrapperConfig{
|
||||
Config: &config.Config{},
|
||||
AnnotationsConfig: &annotations.Ingress{},
|
||||
}
|
||||
|
||||
if err := c.ConvertTLSRoute(nil, wrapper); err == nil {
|
||||
t.Fatal("ConvertTLSRoute() with nil convertOptions returned nil error")
|
||||
}
|
||||
if err := c.ConvertTLSRoute(&common.ConvertOptions{}, nil); err == nil {
|
||||
t.Fatal("ConvertTLSRoute() with nil wrapper returned nil error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLPassthroughUsesConfiguredHTTPSPort(t *testing.T) {
|
||||
c := controller{
|
||||
options: common.Options{
|
||||
GatewayHttpPort: 80,
|
||||
GatewayHttpsPort: 8443,
|
||||
},
|
||||
}
|
||||
wrapper := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-passthrough",
|
||||
},
|
||||
Spec: ingressSpecWithSSLPassthroughBackend("example.com", "app", 443),
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
gatewayOptions := &common.ConvertOptions{
|
||||
Gateways: map[string]*common.WrapperGateway{},
|
||||
IngressDomainCache: common.NewIngressDomainCache(),
|
||||
}
|
||||
if err := c.ConvertGateway(gatewayOptions, wrapper, nil); err != nil {
|
||||
t.Fatalf("ConvertGateway() error = %v", err)
|
||||
}
|
||||
tlsServer := gatewayOptions.Gateways["example.com"].Gateway.Servers[1]
|
||||
if tlsServer.Port.Number != 8443 {
|
||||
t.Fatalf("port mismatch, want 8443, got %d", tlsServer.Port.Number)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLPassthroughCanaryIngressKeepsCanaryHandling(t *testing.T) {
|
||||
c := controller{
|
||||
options: common.Options{
|
||||
GatewayHttpPort: 80,
|
||||
GatewayHttpsPort: 443,
|
||||
},
|
||||
}
|
||||
wrapper := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-passthrough-canary",
|
||||
},
|
||||
Spec: ingressSpecWithSSLPassthroughBackend("example.com", "app-canary", 443),
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
Canary: &annotations.CanaryConfig{Enabled: true},
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
routeOptions := &common.ConvertOptions{}
|
||||
if err := c.ConvertHTTPRoute(routeOptions, wrapper); err != nil {
|
||||
t.Fatalf("ConvertHTTPRoute() error = %v", err)
|
||||
}
|
||||
if len(routeOptions.CanaryIngresses) != 1 {
|
||||
t.Fatalf("canary ingress count mismatch, want 1, got %d", len(routeOptions.CanaryIngresses))
|
||||
}
|
||||
if len(routeOptions.VirtualServices) != 0 {
|
||||
t.Fatalf("unexpected virtual services: %+v", routeOptions.VirtualServices)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLPassthroughSkipsDuplicatedTLSHost(t *testing.T) {
|
||||
c := controller{
|
||||
options: common.Options{
|
||||
GatewayHttpPort: 80,
|
||||
GatewayHttpsPort: 443,
|
||||
},
|
||||
}
|
||||
primary := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-passthrough-primary",
|
||||
},
|
||||
Spec: ingressSpecWithSSLPassthroughBackend("example.com", "primary", 443),
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
duplicate := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-passthrough-duplicate",
|
||||
},
|
||||
Spec: ingressSpecWithSSLPassthroughBackend("example.com", "duplicate", 443),
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
options := &common.ConvertOptions{
|
||||
Gateways: map[string]*common.WrapperGateway{},
|
||||
IngressDomainCache: common.NewIngressDomainCache(),
|
||||
PassthroughTLSHostOwners: map[string]*config.Config{"example.com": primary.Config},
|
||||
}
|
||||
if err := c.ConvertGateway(options, primary, nil); err != nil {
|
||||
t.Fatalf("ConvertGateway(primary) error = %v", err)
|
||||
}
|
||||
if err := c.ConvertGateway(options, duplicate, nil); err != nil {
|
||||
t.Fatalf("ConvertGateway(duplicate) error = %v", err)
|
||||
}
|
||||
options.VirtualServices = map[string]*common.WrapperVirtualService{}
|
||||
if err := c.ConvertTLSRoute(options, duplicate); err != nil {
|
||||
t.Fatalf("ConvertTLSRoute() error = %v", err)
|
||||
}
|
||||
if len(options.VirtualServices) != 0 {
|
||||
t.Fatalf("unexpected virtual services: %+v", options.VirtualServices)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLPassthroughDuplicateTLSHostRecordsInvalidDomain(t *testing.T) {
|
||||
c := controller{
|
||||
options: common.Options{
|
||||
GatewayHttpPort: 80,
|
||||
GatewayHttpsPort: 443,
|
||||
},
|
||||
}
|
||||
primary := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-passthrough-primary",
|
||||
},
|
||||
Spec: ingressSpecWithSSLPassthroughBackend("example.com", "primary", 443),
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
duplicate := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-passthrough-duplicate",
|
||||
},
|
||||
Spec: ingressSpecWithSSLPassthroughBackend("example.com", "duplicate", 443),
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
options := &common.ConvertOptions{
|
||||
Gateways: map[string]*common.WrapperGateway{},
|
||||
IngressDomainCache: common.NewIngressDomainCache(),
|
||||
PassthroughTLSHostOwners: map[string]*config.Config{"example.com": primary.Config},
|
||||
}
|
||||
if err := c.ConvertGateway(options, primary, nil); err != nil {
|
||||
t.Fatalf("ConvertGateway(primary) error = %v", err)
|
||||
}
|
||||
if err := c.ConvertGateway(options, duplicate, nil); err != nil {
|
||||
t.Fatalf("ConvertGateway(duplicate) error = %v", err)
|
||||
}
|
||||
|
||||
if len(options.IngressDomainCache.Invalid) != 1 {
|
||||
t.Fatalf("invalid domain count mismatch, want 1, got %d", len(options.IngressDomainCache.Invalid))
|
||||
}
|
||||
invalid := options.IngressDomainCache.Invalid[0]
|
||||
if invalid.Error == "" {
|
||||
t.Fatal("duplicated tls invalid domain error is empty")
|
||||
}
|
||||
if !strings.Contains(invalid.Error, "tls-passthrough-primary") {
|
||||
t.Fatalf("invalid domain error does not reference previous ingress: %s", invalid.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLPassthroughDuplicateTLSHostUsesExistingGatewayOwner(t *testing.T) {
|
||||
c := controller{
|
||||
options: common.Options{
|
||||
GatewayHttpPort: 80,
|
||||
GatewayHttpsPort: 443,
|
||||
},
|
||||
}
|
||||
primary := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-primary",
|
||||
},
|
||||
Spec: v1.IngressSpec{
|
||||
TLS: []v1.IngressTLS{
|
||||
{Hosts: []string{"example.com"}},
|
||||
},
|
||||
Rules: []v1.IngressRule{
|
||||
ingressRule("example.com", ingressPath("/", "primary", 443)),
|
||||
},
|
||||
},
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{},
|
||||
}
|
||||
duplicate := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-passthrough-duplicate",
|
||||
},
|
||||
Spec: ingressSpecWithSSLPassthroughBackend("example.com", "duplicate", 443),
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
httpsCredentialConfig := &cert.Config{
|
||||
CredentialConfig: []cert.CredentialEntry{
|
||||
{
|
||||
Domains: []string{"example.com"},
|
||||
TLSSecret: "default/example-tls",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
options := &common.ConvertOptions{
|
||||
Gateways: map[string]*common.WrapperGateway{},
|
||||
IngressDomainCache: common.NewIngressDomainCache(),
|
||||
}
|
||||
if err := c.ConvertGateway(options, primary, httpsCredentialConfig); err != nil {
|
||||
t.Fatalf("ConvertGateway(primary) error = %v", err)
|
||||
}
|
||||
if err := c.ConvertGateway(options, duplicate, httpsCredentialConfig); err != nil {
|
||||
t.Fatalf("ConvertGateway(duplicate) error = %v", err)
|
||||
}
|
||||
|
||||
if len(options.IngressDomainCache.Invalid) != 1 {
|
||||
t.Fatalf("invalid domain count mismatch, want 1, got %d", len(options.IngressDomainCache.Invalid))
|
||||
}
|
||||
invalid := options.IngressDomainCache.Invalid[0]
|
||||
if !strings.Contains(invalid.Error, "tls-primary") {
|
||||
t.Fatalf("invalid domain error does not reference existing gateway owner: %s", invalid.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLPassthroughUsesFirstRootOwnerWhenLaterIngressEnablesPassthrough(t *testing.T) {
|
||||
c := controller{
|
||||
options: common.Options{
|
||||
GatewayHttpPort: 80,
|
||||
GatewayHttpsPort: 443,
|
||||
},
|
||||
}
|
||||
root := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "root",
|
||||
},
|
||||
Spec: v1.IngressSpec{
|
||||
Rules: []v1.IngressRule{
|
||||
ingressRule("example.com", ingressPath("/", "root", 443)),
|
||||
},
|
||||
},
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{},
|
||||
}
|
||||
passthrough := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "passthrough",
|
||||
},
|
||||
Spec: ingressSpecWithSSLPassthroughPaths("example.com", []v1.HTTPIngressPath{
|
||||
ingressPath("/passthrough", "passthrough", 443),
|
||||
}),
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
options := &common.ConvertOptions{
|
||||
Gateways: map[string]*common.WrapperGateway{},
|
||||
IngressDomainCache: common.NewIngressDomainCache(),
|
||||
PassthroughTLSHostOwners: map[string]*config.Config{"example.com": root.Config},
|
||||
}
|
||||
if err := c.ConvertGateway(options, root, nil); err != nil {
|
||||
t.Fatalf("ConvertGateway(root) error = %v", err)
|
||||
}
|
||||
if err := c.ConvertGateway(options, passthrough, nil); err != nil {
|
||||
t.Fatalf("ConvertGateway(passthrough) error = %v", err)
|
||||
}
|
||||
gateway := options.Gateways["example.com"].Gateway
|
||||
if len(gateway.Servers) != 2 {
|
||||
t.Fatalf("server count mismatch, want 2, got %d", len(gateway.Servers))
|
||||
}
|
||||
if gateway.Servers[1].Tls.GetMode() != networking.ServerTLSSettings_PASSTHROUGH {
|
||||
t.Fatalf("tls mode mismatch, want PASSTHROUGH, got %s", gateway.Servers[1].Tls.GetMode())
|
||||
}
|
||||
|
||||
routeOptions := &common.ConvertOptions{
|
||||
PassthroughTLSHostOwners: map[string]*config.Config{"example.com": root.Config},
|
||||
}
|
||||
if err := c.ConvertHTTPRoute(routeOptions, root); err != nil {
|
||||
t.Fatalf("ConvertHTTPRoute(root) error = %v", err)
|
||||
}
|
||||
if err := c.ConvertHTTPRoute(routeOptions, passthrough); err != nil {
|
||||
t.Fatalf("ConvertHTTPRoute(passthrough) error = %v", err)
|
||||
}
|
||||
routes := routeOptions.VirtualServices["example.com"].VirtualService.Tls
|
||||
if len(routes) != 1 {
|
||||
t.Fatalf("tls route count mismatch, want 1, got %d", len(routes))
|
||||
}
|
||||
if got := routes[0].Route[0].Destination.Host; got != "root.default.svc.cluster.local" {
|
||||
t.Fatalf("destination host mismatch, want root.default.svc.cluster.local, got %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLPassthroughNonRootIngressDoesNotBlockLaterRootIngress(t *testing.T) {
|
||||
c := controller{
|
||||
options: common.Options{
|
||||
GatewayHttpPort: 80,
|
||||
GatewayHttpsPort: 443,
|
||||
},
|
||||
}
|
||||
nonRoot := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-passthrough-non-root",
|
||||
},
|
||||
Spec: ingressSpecWithSSLPassthroughPaths("example.com", []v1.HTTPIngressPath{
|
||||
ingressPath("/api", "api", 8443),
|
||||
}),
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
root := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-passthrough-root",
|
||||
},
|
||||
Spec: ingressSpecWithSSLPassthroughBackend("example.com", "root", 443),
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
options := &common.ConvertOptions{
|
||||
Gateways: map[string]*common.WrapperGateway{},
|
||||
IngressDomainCache: common.NewIngressDomainCache(),
|
||||
}
|
||||
if err := c.ConvertGateway(options, nonRoot, nil); err != nil {
|
||||
t.Fatalf("ConvertGateway(nonRoot) error = %v", err)
|
||||
}
|
||||
if len(options.Gateways["example.com"].Gateway.Servers) != 1 {
|
||||
t.Fatalf("non-root ingress server count mismatch, want 1, got %d", len(options.Gateways["example.com"].Gateway.Servers))
|
||||
}
|
||||
if err := c.ConvertGateway(options, root, nil); err != nil {
|
||||
t.Fatalf("ConvertGateway(root) error = %v", err)
|
||||
}
|
||||
if options.Gateways["example.com"].Gateway.Servers[1].Tls.GetMode() != networking.ServerTLSSettings_PASSTHROUGH {
|
||||
t.Fatal("root ingress did not create a TLS passthrough server")
|
||||
}
|
||||
|
||||
options.VirtualServices = map[string]*common.WrapperVirtualService{}
|
||||
if err := c.ConvertTLSRoute(options, root); err != nil {
|
||||
t.Fatalf("ConvertTLSRoute(root) error = %v", err)
|
||||
}
|
||||
routes := options.VirtualServices["example.com"].VirtualService.Tls
|
||||
if len(routes) != 1 {
|
||||
t.Fatalf("tls route count mismatch, want 1, got %d", len(routes))
|
||||
}
|
||||
if got := routes[0].Route[0].Destination.Host; got != "root.default.svc.cluster.local" {
|
||||
t.Fatalf("destination host mismatch, got %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLPassthroughPreservesRepeatedHostInSameIngress(t *testing.T) {
|
||||
c := controller{
|
||||
options: common.Options{
|
||||
GatewayHttpPort: 80,
|
||||
GatewayHttpsPort: 443,
|
||||
},
|
||||
}
|
||||
wrapper := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-passthrough-repeated-host",
|
||||
},
|
||||
Spec: v1.IngressSpec{
|
||||
Rules: []v1.IngressRule{
|
||||
{
|
||||
Host: "example.com",
|
||||
IngressRuleValue: v1.IngressRuleValue{
|
||||
HTTP: &v1.HTTPIngressRuleValue{
|
||||
Paths: []v1.HTTPIngressPath{
|
||||
ingressPath("/health", "health", 8443),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Host: "example.com",
|
||||
IngressRuleValue: v1.IngressRuleValue{
|
||||
HTTP: &v1.HTTPIngressRuleValue{
|
||||
Paths: []v1.HTTPIngressPath{
|
||||
ingressPath("/", "root", 443),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
options := &common.ConvertOptions{
|
||||
Gateways: map[string]*common.WrapperGateway{},
|
||||
IngressDomainCache: common.NewIngressDomainCache(),
|
||||
}
|
||||
if err := c.ConvertGateway(options, wrapper, nil); err != nil {
|
||||
t.Fatalf("ConvertGateway() error = %v", err)
|
||||
}
|
||||
options.VirtualServices = map[string]*common.WrapperVirtualService{}
|
||||
if err := c.ConvertTLSRoute(options, wrapper); err != nil {
|
||||
t.Fatalf("ConvertTLSRoute() error = %v", err)
|
||||
}
|
||||
routes := options.VirtualServices["example.com"].VirtualService.Tls
|
||||
if len(routes) != 1 {
|
||||
t.Fatalf("tls route count mismatch, want 1, got %d", len(routes))
|
||||
}
|
||||
if got := routes[0].Route[0].Destination.Host; got != "root.default.svc.cluster.local" {
|
||||
t.Fatalf("destination host mismatch, got %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLPassthroughUsesFirstRootBackendForRepeatedHost(t *testing.T) {
|
||||
c := controller{}
|
||||
wrapper := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-passthrough-repeated-root",
|
||||
},
|
||||
Spec: v1.IngressSpec{
|
||||
Rules: []v1.IngressRule{
|
||||
{
|
||||
Host: "example.com",
|
||||
IngressRuleValue: v1.IngressRuleValue{
|
||||
HTTP: &v1.HTTPIngressRuleValue{
|
||||
Paths: []v1.HTTPIngressPath{
|
||||
ingressPath("/", "first", 443),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Host: "example.com",
|
||||
IngressRuleValue: v1.IngressRuleValue{
|
||||
HTTP: &v1.HTTPIngressRuleValue{
|
||||
Paths: []v1.HTTPIngressPath{
|
||||
ingressPath("/", "second", 443),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
routeOptions := &common.ConvertOptions{}
|
||||
if err := c.ConvertHTTPRoute(routeOptions, wrapper); err != nil {
|
||||
t.Fatalf("ConvertHTTPRoute() error = %v", err)
|
||||
}
|
||||
routes := routeOptions.VirtualServices["example.com"].VirtualService.Tls
|
||||
if len(routes) != 1 {
|
||||
t.Fatalf("tls route count mismatch, want 1, got %d", len(routes))
|
||||
}
|
||||
if got := routes[0].Route[0].Destination.Host; got != "first.default.svc.cluster.local" {
|
||||
t.Fatalf("destination host mismatch, got %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLPassthroughHandlesMultipleHosts(t *testing.T) {
|
||||
c := controller{}
|
||||
testcases := []struct {
|
||||
name string
|
||||
rules []v1.IngressRule
|
||||
wantHosts []string
|
||||
wantRoutes map[string]string
|
||||
}{
|
||||
{
|
||||
name: "root path first",
|
||||
rules: []v1.IngressRule{
|
||||
ingressRule("first.example.com", ingressPath("/", "first", 443)),
|
||||
ingressRule("middle.example.com", ingressPath("/health", "middle", 8443)),
|
||||
ingressRule("last.example.com", ingressPath("/health", "last", 8443)),
|
||||
},
|
||||
wantHosts: []string{"first.example.com"},
|
||||
wantRoutes: map[string]string{
|
||||
"first.example.com": "first.default.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "root path middle",
|
||||
rules: []v1.IngressRule{
|
||||
ingressRule("first.example.com", ingressPath("/health", "first", 8443)),
|
||||
ingressRule("middle.example.com", ingressPath("/", "middle", 443)),
|
||||
ingressRule("last.example.com", ingressPath("/health", "last", 8443)),
|
||||
},
|
||||
wantHosts: []string{"middle.example.com"},
|
||||
wantRoutes: map[string]string{
|
||||
"middle.example.com": "middle.default.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "root path last",
|
||||
rules: []v1.IngressRule{
|
||||
ingressRule("first.example.com", ingressPath("/health", "first", 8443)),
|
||||
ingressRule("middle.example.com", ingressPath("/health", "middle", 8443)),
|
||||
ingressRule("last.example.com", ingressPath("/", "last", 443)),
|
||||
},
|
||||
wantHosts: []string{"last.example.com"},
|
||||
wantRoutes: map[string]string{
|
||||
"last.example.com": "last.default.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple root hosts",
|
||||
rules: []v1.IngressRule{
|
||||
ingressRule("first.example.com", ingressPath("/", "first", 443)),
|
||||
ingressRule("middle.example.com", ingressPath("/", "middle", 443)),
|
||||
ingressRule("last.example.com", ingressPath("/", "last", 443)),
|
||||
},
|
||||
wantHosts: []string{"first.example.com", "middle.example.com", "last.example.com"},
|
||||
wantRoutes: map[string]string{
|
||||
"first.example.com": "first.default.svc.cluster.local",
|
||||
"middle.example.com": "middle.default.svc.cluster.local",
|
||||
"last.example.com": "last.default.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
wrapper := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-passthrough-multi-host",
|
||||
},
|
||||
Spec: v1.IngressSpec{
|
||||
Rules: tc.rules,
|
||||
},
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
routeOptions := &common.ConvertOptions{}
|
||||
if err := c.ConvertHTTPRoute(routeOptions, wrapper); err != nil {
|
||||
t.Fatalf("ConvertHTTPRoute() error = %v", err)
|
||||
}
|
||||
for _, host := range tc.wantHosts {
|
||||
routes := routeOptions.VirtualServices[host].VirtualService.Tls
|
||||
if len(routes) != 1 {
|
||||
t.Fatalf("tls route count mismatch for host %s, want 1, got %d", host, len(routes))
|
||||
}
|
||||
if got := routes[0].Route[0].Destination.Host; got != tc.wantRoutes[host] {
|
||||
t.Fatalf("destination host mismatch for host %s, want %s, got %s", host, tc.wantRoutes[host], got)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLPassthroughUsesRootPathBackend(t *testing.T) {
|
||||
c := controller{}
|
||||
wrapper := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-passthrough-root",
|
||||
},
|
||||
Spec: ingressSpecWithSSLPassthroughPaths("example.com", []v1.HTTPIngressPath{
|
||||
ingressPath("/api", "api", 8443),
|
||||
ingressPath("/", "root", 443),
|
||||
}),
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
routeOptions := &common.ConvertOptions{}
|
||||
if err := c.ConvertHTTPRoute(routeOptions, wrapper); err != nil {
|
||||
t.Fatalf("ConvertHTTPRoute() error = %v", err)
|
||||
}
|
||||
routes := routeOptions.VirtualServices["example.com"].VirtualService.Tls
|
||||
if len(routes) != 1 {
|
||||
t.Fatalf("tls route count mismatch, want 1, got %d", len(routes))
|
||||
}
|
||||
if got := routes[0].Route[0].Destination.Host; got != "root.default.svc.cluster.local" {
|
||||
t.Fatalf("destination host mismatch, got %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLPassthroughWildcardHostKeepsVirtualServiceConsistent(t *testing.T) {
|
||||
c := controller{}
|
||||
wrapper := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-passthrough-wildcard",
|
||||
},
|
||||
Spec: ingressSpecWithSSLPassthroughBackend("", "root", 443),
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
routeOptions := &common.ConvertOptions{}
|
||||
if err := c.ConvertHTTPRoute(routeOptions, wrapper); err != nil {
|
||||
t.Fatalf("ConvertHTTPRoute() error = %v", err)
|
||||
}
|
||||
if err := c.ConvertTLSRoute(routeOptions, wrapper); err != nil {
|
||||
t.Fatalf("ConvertTLSRoute() error = %v", err)
|
||||
}
|
||||
|
||||
vs := routeOptions.VirtualServices[""].VirtualService
|
||||
if got := vs.Hosts; len(got) != 1 || got[0] != "*" {
|
||||
t.Fatalf("virtual service hosts mismatch, got %+v", got)
|
||||
}
|
||||
if len(vs.Tls) != 1 {
|
||||
t.Fatalf("tls route count mismatch, want 1, got %d", len(vs.Tls))
|
||||
}
|
||||
if got := vs.Tls[0].Match[0].SniHosts; len(got) != 1 || got[0] != "*" {
|
||||
t.Fatalf("sni hosts mismatch, got %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLPassthroughIgnoresNonRootPath(t *testing.T) {
|
||||
c := controller{}
|
||||
wrapper := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-passthrough-non-root",
|
||||
},
|
||||
Spec: ingressSpecWithSSLPassthroughPaths("example.com", []v1.HTTPIngressPath{
|
||||
ingressPath("/api", "api", 8443),
|
||||
}),
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
routeOptions := &common.ConvertOptions{}
|
||||
if err := c.ConvertHTTPRoute(routeOptions, wrapper); err != nil {
|
||||
t.Fatalf("ConvertHTTPRoute() error = %v", err)
|
||||
}
|
||||
if len(routeOptions.HTTPRoutes["example.com"]) != 1 {
|
||||
t.Fatalf("http route count mismatch, want 1, got %d", len(routeOptions.HTTPRoutes["example.com"]))
|
||||
}
|
||||
if routes := routeOptions.VirtualServices["example.com"].VirtualService.Tls; len(routes) != 0 {
|
||||
t.Fatalf("unexpected tls routes: %+v", routes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLPassthroughKeepsMCPResourceBackend(t *testing.T) {
|
||||
c := controller{}
|
||||
apiGroup := "networking.higress.io"
|
||||
wrapper := &common.WrapperConfig{
|
||||
Config: &config.Config{
|
||||
Meta: config.Meta{
|
||||
Namespace: "default",
|
||||
Name: "tls-passthrough-mcp",
|
||||
},
|
||||
Spec: ingressSpecWithSSLPassthroughPaths("example.com", []v1.HTTPIngressPath{
|
||||
{
|
||||
Path: "/",
|
||||
PathType: pathTypePtr(v1.PathTypePrefix),
|
||||
Backend: v1.IngressBackend{
|
||||
Resource: &corev1.TypedLocalObjectReference{
|
||||
APIGroup: &apiGroup,
|
||||
Kind: "McpBridge",
|
||||
Name: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
AnnotationsConfig: &annotations.Ingress{
|
||||
Destination: &annotations.DestinationConfig{
|
||||
McpDestination: []*networking.HTTPRouteDestination{
|
||||
{
|
||||
Destination: &networking.Destination{
|
||||
Host: "mcp.example.internal",
|
||||
Port: &networking.PortSelector{Number: 443},
|
||||
},
|
||||
Weight: 100,
|
||||
},
|
||||
},
|
||||
WeightSum: 100,
|
||||
},
|
||||
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
routeOptions := &common.ConvertOptions{}
|
||||
if err := c.ConvertHTTPRoute(routeOptions, wrapper); err != nil {
|
||||
t.Fatalf("ConvertHTTPRoute() error = %v", err)
|
||||
}
|
||||
routes := routeOptions.VirtualServices["example.com"].VirtualService.Tls
|
||||
if len(routes) != 1 {
|
||||
t.Fatalf("tls route count mismatch, want 1, got %d", len(routes))
|
||||
}
|
||||
if got := routes[0].Route[0].Destination.Host; got != "mcp.example.internal" {
|
||||
t.Fatalf("destination host mismatch, got %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackendToTLSRouteDestinationRejectsEmptyMCPDestination(t *testing.T) {
|
||||
c := controller{}
|
||||
apiGroup := "networking.higress.io"
|
||||
backend := &v1.IngressBackend{
|
||||
Resource: &corev1.TypedLocalObjectReference{
|
||||
APIGroup: &apiGroup,
|
||||
Kind: "McpBridge",
|
||||
Name: "default",
|
||||
},
|
||||
}
|
||||
config := &annotations.DestinationConfig{}
|
||||
|
||||
destinations, event := c.backendToTLSRouteDestination(backend, "default", config)
|
||||
if event != common.InvalidBackendService {
|
||||
t.Fatalf("event mismatch, want InvalidBackendService, got %s", event)
|
||||
}
|
||||
if len(destinations) != 0 {
|
||||
t.Fatalf("destination count mismatch, want 0, got %d", len(destinations))
|
||||
}
|
||||
}
|
||||
|
||||
func ingressSpecWithSSLPassthroughBackend(host, service string, port int32) v1.IngressSpec {
|
||||
return ingressSpecWithSSLPassthroughPaths(host, []v1.HTTPIngressPath{
|
||||
ingressPath("/", service, port),
|
||||
})
|
||||
}
|
||||
|
||||
func ingressSpecWithSSLPassthroughPaths(host string, paths []v1.HTTPIngressPath) v1.IngressSpec {
|
||||
return v1.IngressSpec{
|
||||
Rules: []v1.IngressRule{
|
||||
{
|
||||
Host: host,
|
||||
IngressRuleValue: v1.IngressRuleValue{
|
||||
HTTP: &v1.HTTPIngressRuleValue{
|
||||
Paths: paths,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func ingressPath(path, service string, port int32) v1.HTTPIngressPath {
|
||||
return v1.HTTPIngressPath{
|
||||
Path: path,
|
||||
PathType: pathTypePtr(v1.PathTypePrefix),
|
||||
Backend: v1.IngressBackend{
|
||||
Service: &v1.IngressServiceBackend{
|
||||
Name: service,
|
||||
Port: v1.ServiceBackendPort{Number: port},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func pathTypePtr(pathType v1.PathType) *v1.PathType {
|
||||
return &pathType
|
||||
}
|
||||
|
||||
func ingressRule(host string, paths ...v1.HTTPIngressPath) v1.IngressRule {
|
||||
return v1.IngressRule{
|
||||
Host: host,
|
||||
IngressRuleValue: v1.IngressRuleValue{
|
||||
HTTP: &v1.HTTPIngressRuleValue{
|
||||
Paths: paths,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user