mirror of
https://github.com/alibaba/higress.git
synced 2026-06-26 02:35:02 +08:00
feat: Enhance SSL passthrough support (#3943)
Signed-off-by: zijiren233 <pyh1670605849@gmail.com>
This commit is contained in:
@@ -46,6 +46,8 @@ import (
|
|||||||
"istio.io/istio/pkg/log"
|
"istio.io/istio/pkg/log"
|
||||||
"istio.io/istio/pkg/util/sets"
|
"istio.io/istio/pkg/util/sets"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
networkingv1 "k8s.io/api/networking/v1"
|
||||||
|
networkingv1beta1 "k8s.io/api/networking/v1beta1"
|
||||||
listersv1 "k8s.io/client-go/listers/core/v1"
|
listersv1 "k8s.io/client-go/listers/core/v1"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
|
|
||||||
@@ -438,6 +440,7 @@ func (m *IngressConfig) convertGateways(configs []common.WrapperConfig) []config
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
IngressLog.Errorf("Get higress https configmap err %v", err)
|
IngressLog.Errorf("Get higress https configmap err %v", err)
|
||||||
}
|
}
|
||||||
|
m.preparePassthroughTLSHostOwners(&convertOptions, configs)
|
||||||
for idx := range configs {
|
for idx := range configs {
|
||||||
cfg := configs[idx]
|
cfg := configs[idx]
|
||||||
clusterId := common.GetClusterId(cfg.Config.Annotations)
|
clusterId := common.GetClusterId(cfg.Config.Annotations)
|
||||||
@@ -504,6 +507,8 @@ func (m *IngressConfig) convertVirtualService(configs []common.WrapperConfig) []
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.preparePassthroughTLSHostOwners(&convertOptions, configs)
|
||||||
|
|
||||||
// convert http route
|
// convert http route
|
||||||
for idx := range configs {
|
for idx := range configs {
|
||||||
cfg := configs[idx]
|
cfg := configs[idx]
|
||||||
@@ -570,13 +575,8 @@ func (m *IngressConfig) convertVirtualService(configs []common.WrapperConfig) []
|
|||||||
m.ingressRouteCache = convertOptions.IngressRouteCache.Extract()
|
m.ingressRouteCache = convertOptions.IngressRouteCache.Extract()
|
||||||
m.mutex.Unlock()
|
m.mutex.Unlock()
|
||||||
|
|
||||||
// Convert http route to virtual service
|
out := make([]config.Config, 0, len(convertOptions.VirtualServices))
|
||||||
out := make([]config.Config, 0, len(convertOptions.HTTPRoutes))
|
for host, wrapperVS := range convertOptions.VirtualServices {
|
||||||
for host, routes := range convertOptions.HTTPRoutes {
|
|
||||||
if len(routes) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanHost := common.CleanHost(host)
|
cleanHost := common.CleanHost(host)
|
||||||
// namespace/name, name format: (istio cluster id)-host
|
// namespace/name, name format: (istio cluster id)-host
|
||||||
gateways := []string{
|
gateways := []string{
|
||||||
@@ -585,13 +585,10 @@ func (m *IngressConfig) convertVirtualService(configs []common.WrapperConfig) []
|
|||||||
common.CreateConvertedName(constants.IstioIngressGatewayName, cleanHost),
|
common.CreateConvertedName(constants.IstioIngressGatewayName, cleanHost),
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapperVS, exist := convertOptions.VirtualServices[host]
|
|
||||||
if !exist {
|
|
||||||
IngressLog.Warnf("virtual service for host %s does not exist.", host)
|
|
||||||
}
|
|
||||||
vs := wrapperVS.VirtualService
|
vs := wrapperVS.VirtualService
|
||||||
vs.Gateways = gateways
|
vs.Gateways = gateways
|
||||||
|
|
||||||
|
routes := convertOptions.HTTPRoutes[host]
|
||||||
// Sort, exact -> prefix -> regex
|
// Sort, exact -> prefix -> regex
|
||||||
common.SortHTTPRoutes(routes)
|
common.SortHTTPRoutes(routes)
|
||||||
|
|
||||||
@@ -599,14 +596,18 @@ func (m *IngressConfig) convertVirtualService(configs []common.WrapperConfig) []
|
|||||||
vs.Http = append(vs.Http, route.HTTPRoute)
|
vs.Http = append(vs.Http, route.HTTPRoute)
|
||||||
}
|
}
|
||||||
|
|
||||||
firstRoute := routes[0]
|
if len(vs.Http) == 0 && len(vs.Tls) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
vsName, clusterId := virtualServiceNameAndClusterID(cleanHost, wrapperVS, routes)
|
||||||
out = append(out, config.Config{
|
out = append(out, config.Config{
|
||||||
Meta: config.Meta{
|
Meta: config.Meta{
|
||||||
GroupVersionKind: gvk.VirtualService,
|
GroupVersionKind: gvk.VirtualService,
|
||||||
Name: common.CreateConvertedName(constants.IstioIngressGatewayName, firstRoute.WrapperConfig.Config.Namespace, firstRoute.WrapperConfig.Config.Name, cleanHost),
|
Name: vsName,
|
||||||
Namespace: m.namespace,
|
Namespace: m.namespace,
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
common.ClusterIdAnnotation: firstRoute.ClusterId.String(),
|
common.ClusterIdAnnotation: clusterId.String(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Spec: vs,
|
Spec: vs,
|
||||||
@@ -625,6 +626,129 @@ func (m *IngressConfig) convertVirtualService(configs []common.WrapperConfig) []
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func virtualServiceNameAndClusterID(cleanHost string, wrapperVS *common.WrapperVirtualService, routes []*common.WrapperHTTPRoute) (string, cluster.ID) {
|
||||||
|
if len(routes) > 0 {
|
||||||
|
firstRoute := routes[0]
|
||||||
|
return common.CreateConvertedName(constants.IstioIngressGatewayName, firstRoute.WrapperConfig.Config.Namespace, firstRoute.WrapperConfig.Config.Name, cleanHost), firstRoute.ClusterId
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := wrapperVS.WrapperConfig.Config
|
||||||
|
return common.CreateConvertedName(constants.IstioIngressGatewayName, cfg.Namespace, cfg.Name, cleanHost), common.GetClusterId(cfg.Annotations)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *IngressConfig) preparePassthroughTLSHostOwners(convertOptions *common.ConvertOptions, configs []common.WrapperConfig) {
|
||||||
|
if convertOptions.PassthroughTLSHostOwners == nil {
|
||||||
|
convertOptions.PassthroughTLSHostOwners = map[string]*config.Config{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ingress-nginx enables SSL passthrough at host level when any ingress for the host has the
|
||||||
|
// annotation, then uses the first root path as the passthrough backend.
|
||||||
|
passthroughHosts := map[string]struct{}{}
|
||||||
|
firstRootPathHostOwners := map[string]*config.Config{}
|
||||||
|
for idx := range configs {
|
||||||
|
cfg := configs[idx]
|
||||||
|
if cfg.AnnotationsConfig.IsCanary() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.AnnotationsConfig.IsSSLPassthrough() {
|
||||||
|
for _, host := range ingressRuleHosts(cfg.Config.Spec) {
|
||||||
|
passthroughHosts[host] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, host := range ingressRootPathHosts(cfg.Config.Spec) {
|
||||||
|
if _, exist := firstRootPathHostOwners[host]; exist {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
firstRootPathHostOwners[host] = cfg.Config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for host := range passthroughHosts {
|
||||||
|
if owner := firstRootPathHostOwners[host]; owner != nil {
|
||||||
|
convertOptions.PassthroughTLSHostOwners[host] = owner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ingressRuleHosts(spec config.Spec) []string {
|
||||||
|
switch ingressSpec := spec.(type) {
|
||||||
|
case networkingv1.IngressSpec:
|
||||||
|
return ingressV1RuleHosts(ingressSpec.Rules)
|
||||||
|
case networkingv1beta1.IngressSpec:
|
||||||
|
return ingressV1Beta1RuleHosts(ingressSpec.Rules)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ingressRootPathHosts(spec config.Spec) []string {
|
||||||
|
switch ingressSpec := spec.(type) {
|
||||||
|
case networkingv1.IngressSpec:
|
||||||
|
return ingressV1RootPathHosts(ingressSpec.Rules)
|
||||||
|
case networkingv1beta1.IngressSpec:
|
||||||
|
return ingressV1Beta1RootPathHosts(ingressSpec.Rules)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ingressV1RuleHosts(rules []networkingv1.IngressRule) []string {
|
||||||
|
out := make([]string, 0, len(rules))
|
||||||
|
for _, rule := range rules {
|
||||||
|
out = append(out, rule.Host)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func ingressV1Beta1RuleHosts(rules []networkingv1beta1.IngressRule) []string {
|
||||||
|
out := make([]string, 0, len(rules))
|
||||||
|
for _, rule := range rules {
|
||||||
|
out = append(out, rule.Host)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func ingressV1RootPathHosts(rules []networkingv1.IngressRule) []string {
|
||||||
|
out := make([]string, 0, len(rules))
|
||||||
|
for _, rule := range rules {
|
||||||
|
if rule.HTTP == nil || !hasV1RootHTTPIngressPath(rule.HTTP.Paths) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, rule.Host)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func ingressV1Beta1RootPathHosts(rules []networkingv1beta1.IngressRule) []string {
|
||||||
|
out := make([]string, 0, len(rules))
|
||||||
|
for _, rule := range rules {
|
||||||
|
if rule.HTTP == nil || !hasV1Beta1RootHTTPIngressPath(rule.HTTP.Paths) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, rule.Host)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasV1RootHTTPIngressPath(paths []networkingv1.HTTPIngressPath) bool {
|
||||||
|
for _, path := range paths {
|
||||||
|
if path.Path == "" || path.Path == "/" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasV1Beta1RootHTTPIngressPath(paths []networkingv1beta1.HTTPIngressPath) bool {
|
||||||
|
for _, path := range paths {
|
||||||
|
if path.Path == "" || path.Path == "/" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (m *IngressConfig) convertEnvoyFilter(convertOptions *common.ConvertOptions) {
|
func (m *IngressConfig) convertEnvoyFilter(convertOptions *common.ConvertOptions) {
|
||||||
var envoyFilters []config.Config
|
var envoyFilters []config.Config
|
||||||
mappings := map[string]*common.Rule{}
|
mappings := map[string]*common.Rule{}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
networking "istio.io/api/networking/v1alpha3"
|
networking "istio.io/api/networking/v1alpha3"
|
||||||
"istio.io/istio/pkg/cluster"
|
"istio.io/istio/pkg/cluster"
|
||||||
"istio.io/istio/pkg/config"
|
"istio.io/istio/pkg/config"
|
||||||
|
"istio.io/istio/pkg/config/constants"
|
||||||
"istio.io/istio/pkg/config/schema/gvk"
|
"istio.io/istio/pkg/config/schema/gvk"
|
||||||
"istio.io/istio/pkg/config/xds"
|
"istio.io/istio/pkg/config/xds"
|
||||||
ingress "k8s.io/api/networking/v1"
|
ingress "k8s.io/api/networking/v1"
|
||||||
@@ -109,6 +110,405 @@ func TestNormalizeWeightedCluster(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVirtualServiceNameAndClusterID(t *testing.T) {
|
||||||
|
cleanHost := common.CleanHost("example.com")
|
||||||
|
wrapperVS := &common.WrapperVirtualService{
|
||||||
|
WrapperConfig: &common.WrapperConfig{
|
||||||
|
Config: &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
Namespace: "tls-ns",
|
||||||
|
Name: "tls-ingress",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
common.ClusterIdAnnotation: "tls-cluster",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
routes := []*common.WrapperHTTPRoute{
|
||||||
|
{
|
||||||
|
WrapperConfig: &common.WrapperConfig{
|
||||||
|
Config: &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
Namespace: "http-ns",
|
||||||
|
Name: "http-ingress",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClusterId: "http-cluster",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
name, clusterID := virtualServiceNameAndClusterID(cleanHost, wrapperVS, routes)
|
||||||
|
if name != common.CreateConvertedName(constants.IstioIngressGatewayName, "http-ns", "http-ingress", cleanHost) {
|
||||||
|
t.Fatalf("http-backed virtual service name mismatch: %s", name)
|
||||||
|
}
|
||||||
|
if clusterID != "http-cluster" {
|
||||||
|
t.Fatalf("http-backed cluster id mismatch: %s", clusterID)
|
||||||
|
}
|
||||||
|
|
||||||
|
name, clusterID = virtualServiceNameAndClusterID(cleanHost, wrapperVS, nil)
|
||||||
|
if name != common.CreateConvertedName(constants.IstioIngressGatewayName, "tls-ns", "tls-ingress", cleanHost) {
|
||||||
|
t.Fatalf("tls-only virtual service name mismatch: %s", name)
|
||||||
|
}
|
||||||
|
if clusterID != "tls-cluster" {
|
||||||
|
t.Fatalf("tls-only cluster id mismatch: %s", clusterID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPreparePassthroughTLSHostOwnersRequiresPassthroughHost(t *testing.T) {
|
||||||
|
m := &IngressConfig{}
|
||||||
|
configs := []common.WrapperConfig{
|
||||||
|
{
|
||||||
|
Config: &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "plain-root",
|
||||||
|
},
|
||||||
|
Spec: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{Path: "/"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AnnotationsConfig: &annotations.Ingress{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "plain-root-duplicate",
|
||||||
|
},
|
||||||
|
Spec: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{Path: "/"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AnnotationsConfig: &annotations.Ingress{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
options := &common.ConvertOptions{}
|
||||||
|
m.preparePassthroughTLSHostOwners(options, configs)
|
||||||
|
|
||||||
|
if len(options.PassthroughTLSHostOwners) != 0 {
|
||||||
|
t.Fatalf("unexpected ssl passthrough owners: %+v", options.PassthroughTLSHostOwners)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPreparePassthroughTLSHostOwnersUsesFirstRootPathOwner(t *testing.T) {
|
||||||
|
m := &IngressConfig{}
|
||||||
|
configs := []common.WrapperConfig{
|
||||||
|
{
|
||||||
|
Config: &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "plain-root",
|
||||||
|
},
|
||||||
|
Spec: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{Path: "/"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AnnotationsConfig: &annotations.Ingress{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "passthrough-non-root",
|
||||||
|
},
|
||||||
|
Spec: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{Path: "/api"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AnnotationsConfig: &annotations.Ingress{
|
||||||
|
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
options := &common.ConvertOptions{}
|
||||||
|
m.preparePassthroughTLSHostOwners(options, configs)
|
||||||
|
|
||||||
|
if !common.IsPassthroughTLSHostOwner(options, configs[0].Config, "example.com") {
|
||||||
|
t.Fatal("first root ingress was not recorded as passthrough owner")
|
||||||
|
}
|
||||||
|
if !common.HasPassthroughTLSHostOwner(options, configs[0].Config) {
|
||||||
|
t.Fatal("first root ingress was not found as passthrough owner")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPreparePassthroughTLSHostOwnersIgnoresHTTPOnlyIngressForHTTPSFallback(t *testing.T) {
|
||||||
|
m := &IngressConfig{}
|
||||||
|
configs := []common.WrapperConfig{
|
||||||
|
{
|
||||||
|
Config: &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "http-only",
|
||||||
|
},
|
||||||
|
Spec: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{Path: "/api"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AnnotationsConfig: &annotations.Ingress{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "tls-ingress",
|
||||||
|
},
|
||||||
|
Spec: ingress.IngressSpec{
|
||||||
|
TLS: []ingress.IngressTLS{
|
||||||
|
{
|
||||||
|
Hosts: []string{"example.com"},
|
||||||
|
SecretName: "example-com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{Path: "/app"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AnnotationsConfig: &annotations.Ingress{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
options := &common.ConvertOptions{}
|
||||||
|
m.preparePassthroughTLSHostOwners(options, configs)
|
||||||
|
|
||||||
|
if len(options.PassthroughTLSHostOwners) != 0 {
|
||||||
|
t.Fatalf("unexpected ssl passthrough owners: %+v", options.PassthroughTLSHostOwners)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertGatewaysHonorsFirstRootPathSSLPassthroughOwner(t *testing.T) {
|
||||||
|
fake := kube.NewFakeClient()
|
||||||
|
options := common.Options{
|
||||||
|
Enable: true,
|
||||||
|
ClusterId: "ingress-v1",
|
||||||
|
RawClusterId: "ingress-v1__",
|
||||||
|
GatewayHttpPort: 80,
|
||||||
|
GatewayHttpsPort: 443,
|
||||||
|
}
|
||||||
|
ingressController := controllerv1.NewController(fake, fake, options, nil)
|
||||||
|
m := NewIngressConfig(fake, nil, "wakanda", options)
|
||||||
|
m.remoteIngressControllers = map[cluster.ID]common.IngressController{
|
||||||
|
"ingress-v1": ingressController,
|
||||||
|
}
|
||||||
|
|
||||||
|
configs := []common.WrapperConfig{
|
||||||
|
{
|
||||||
|
Config: &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "tls-non-root",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
common.ClusterIdAnnotation: "ingress-v1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: ingress.IngressSpec{
|
||||||
|
TLS: []ingress.IngressTLS{
|
||||||
|
{
|
||||||
|
Hosts: []string{"example.com"},
|
||||||
|
SecretName: "example-com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{Path: "/api"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AnnotationsConfig: &annotations.Ingress{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "passthrough-root",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
common.ClusterIdAnnotation: "ingress-v1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{Path: "/"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AnnotationsConfig: &annotations.Ingress{
|
||||||
|
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := m.convertGateways(configs)
|
||||||
|
if len(result) != 1 {
|
||||||
|
t.Fatalf("gateway count mismatch, want 1, got %d", len(result))
|
||||||
|
}
|
||||||
|
gateway := result[0].Spec.(*networking.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("tls server protocol mismatch, want TLS, got %s", tlsServer.Port.Protocol)
|
||||||
|
}
|
||||||
|
if tlsServer.Tls.GetMode() != networking.ServerTLSSettings_PASSTHROUGH {
|
||||||
|
t.Fatalf("tls mode mismatch, want PASSTHROUGH, got %s", tlsServer.Tls.GetMode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertGatewaysUsesFirstRootOwnerWhenLaterIngressEnablesSSLPassthrough(t *testing.T) {
|
||||||
|
fake := kube.NewFakeClient()
|
||||||
|
options := common.Options{
|
||||||
|
Enable: true,
|
||||||
|
ClusterId: "ingress-v1",
|
||||||
|
RawClusterId: "ingress-v1__",
|
||||||
|
GatewayHttpPort: 80,
|
||||||
|
GatewayHttpsPort: 443,
|
||||||
|
}
|
||||||
|
ingressController := controllerv1.NewController(fake, fake, options, nil)
|
||||||
|
m := NewIngressConfig(fake, nil, "wakanda", options)
|
||||||
|
m.remoteIngressControllers = map[cluster.ID]common.IngressController{
|
||||||
|
"ingress-v1": ingressController,
|
||||||
|
}
|
||||||
|
|
||||||
|
configs := []common.WrapperConfig{
|
||||||
|
ingressV1Wrapper("root", "example.com", "/", false),
|
||||||
|
ingressV1Wrapper("passthrough", "example.com", "/passthrough", true),
|
||||||
|
}
|
||||||
|
|
||||||
|
result := m.convertGateways(configs)
|
||||||
|
if len(result) != 1 {
|
||||||
|
t.Fatalf("gateway count mismatch, want 1, got %d", len(result))
|
||||||
|
}
|
||||||
|
gateway := result[0].Spec.(*networking.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("tls server protocol mismatch, want TLS, got %s", tlsServer.Port.Protocol)
|
||||||
|
}
|
||||||
|
if tlsServer.Tls.GetMode() != networking.ServerTLSSettings_PASSTHROUGH {
|
||||||
|
t.Fatalf("tls mode mismatch, want PASSTHROUGH, got %s", tlsServer.Tls.GetMode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertVirtualServiceUsesFirstRootOwnerWhenLaterIngressEnablesSSLPassthrough(t *testing.T) {
|
||||||
|
fake := kube.NewFakeClient()
|
||||||
|
options := common.Options{
|
||||||
|
Enable: true,
|
||||||
|
ClusterId: "ingress-v1",
|
||||||
|
RawClusterId: "ingress-v1__",
|
||||||
|
GatewayHttpPort: 80,
|
||||||
|
GatewayHttpsPort: 443,
|
||||||
|
}
|
||||||
|
ingressController := controllerv1.NewController(fake, fake, options, nil)
|
||||||
|
m := NewIngressConfig(fake, nil, "wakanda", options)
|
||||||
|
m.remoteIngressControllers = map[cluster.ID]common.IngressController{
|
||||||
|
"ingress-v1": ingressController,
|
||||||
|
}
|
||||||
|
|
||||||
|
configs := []common.WrapperConfig{
|
||||||
|
ingressV1Wrapper("root", "example.com", "/", false),
|
||||||
|
ingressV1Wrapper("passthrough", "example.com", "/passthrough", true),
|
||||||
|
}
|
||||||
|
|
||||||
|
result := m.convertVirtualService(configs)
|
||||||
|
if len(result) != 1 {
|
||||||
|
t.Fatalf("virtual service count mismatch, want 1, got %d", len(result))
|
||||||
|
}
|
||||||
|
vs := result[0].Spec.(*networking.VirtualService)
|
||||||
|
if len(vs.Tls) != 1 {
|
||||||
|
t.Fatalf("tls route count mismatch, want 1, got %d", len(vs.Tls))
|
||||||
|
}
|
||||||
|
if got := vs.Tls[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 TestConvertGatewaysForIngress(t *testing.T) {
|
func TestConvertGatewaysForIngress(t *testing.T) {
|
||||||
fake := kube.NewFakeClient()
|
fake := kube.NewFakeClient()
|
||||||
v1Beta1Options := common.Options{
|
v1Beta1Options := common.Options{
|
||||||
@@ -616,3 +1016,46 @@ func TestConstructBasicAuthEnvoyFilter(t *testing.T) {
|
|||||||
target := proto.Clone(pb).(*httppb.HttpFilter)
|
target := proto.Clone(pb).(*httppb.HttpFilter)
|
||||||
t.Log(target)
|
t.Log(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ingressV1Wrapper(name, host, path string, sslPassthrough bool) common.WrapperConfig {
|
||||||
|
wrapper := common.WrapperConfig{
|
||||||
|
Config: &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: name,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
common.ClusterIdAnnotation: "ingress-v1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: host,
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: path,
|
||||||
|
Backend: ingress.IngressBackend{
|
||||||
|
Service: &ingress.IngressServiceBackend{
|
||||||
|
Name: name,
|
||||||
|
Port: ingress.ServiceBackendPort{Number: 443},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AnnotationsConfig: &annotations.Ingress{
|
||||||
|
Match: &annotations.MatchConfig{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if sslPassthrough {
|
||||||
|
wrapper.AnnotationsConfig.SSLPassthrough = &annotations.SSLPassthroughConfig{Enabled: true}
|
||||||
|
}
|
||||||
|
return wrapper
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ type Ingress struct {
|
|||||||
|
|
||||||
DownstreamTLS *DownstreamTLSConfig
|
DownstreamTLS *DownstreamTLSConfig
|
||||||
|
|
||||||
|
SSLPassthrough *SSLPassthroughConfig
|
||||||
|
|
||||||
Canary *CanaryConfig
|
Canary *CanaryConfig
|
||||||
|
|
||||||
IPAccessControl *IPAccessControlConfig
|
IPAccessControl *IPAccessControlConfig
|
||||||
@@ -115,6 +117,10 @@ func (i *Ingress) IsCanary() bool {
|
|||||||
return i.Canary.Enabled
|
return i.Canary.Enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Ingress) IsSSLPassthrough() bool {
|
||||||
|
return i.SSLPassthrough != nil && i.SSLPassthrough.Enabled
|
||||||
|
}
|
||||||
|
|
||||||
// CanaryKind return byHeader, byWeight
|
// CanaryKind return byHeader, byWeight
|
||||||
func (i *Ingress) CanaryKind() (bool, bool) {
|
func (i *Ingress) CanaryKind() (bool, bool) {
|
||||||
if !i.IsCanary() {
|
if !i.IsCanary() {
|
||||||
@@ -157,6 +163,7 @@ func NewAnnotationHandlerManager() AnnotationHandler {
|
|||||||
canary{},
|
canary{},
|
||||||
cors{},
|
cors{},
|
||||||
downstreamTLS{},
|
downstreamTLS{},
|
||||||
|
sslPassthrough{},
|
||||||
redirect{},
|
redirect{},
|
||||||
rewrite{},
|
rewrite{},
|
||||||
upstreamTLS{},
|
upstreamTLS{},
|
||||||
|
|||||||
@@ -106,6 +106,9 @@ func (d downstreamTLS) ApplyGateway(gateway *networking.Gateway, config *Ingress
|
|||||||
downstreamTLSConfig := config.DownstreamTLS
|
downstreamTLSConfig := config.DownstreamTLS
|
||||||
for _, server := range gateway.Servers {
|
for _, server := range gateway.Servers {
|
||||||
if gatewaytool.IsTLSServer(server) {
|
if gatewaytool.IsTLSServer(server) {
|
||||||
|
if server.Tls != nil && server.Tls.Mode == networking.ServerTLSSettings_PASSTHROUGH {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if downstreamTLSConfig.CASecretName.Name != "" {
|
if downstreamTLSConfig.CASecretName.Name != "" {
|
||||||
serverCert := extraSecret(server.Tls.CredentialName)
|
serverCert := extraSecret(server.Tls.CredentialName)
|
||||||
if downstreamTLSConfig.CASecretName.Namespace != serverCert.Namespace ||
|
if downstreamTLSConfig.CASecretName.Namespace != serverCert.Namespace ||
|
||||||
|
|||||||
@@ -269,6 +269,40 @@ func TestApplyGateway(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "skip passthrough server",
|
||||||
|
input: &networking.Gateway{
|
||||||
|
Servers: []*networking.Server{
|
||||||
|
{
|
||||||
|
Port: &networking.Port{
|
||||||
|
Protocol: "TLS",
|
||||||
|
},
|
||||||
|
Tls: &networking.ServerTLSSettings{
|
||||||
|
Mode: networking.ServerTLSSettings_PASSTHROUGH,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
config: &Ingress{
|
||||||
|
DownstreamTLS: &DownstreamTLSConfig{
|
||||||
|
CipherSuites: []string{"ECDHE-RSA-AES256-GCM-SHA384"},
|
||||||
|
MinVersion: "TLSv1.2",
|
||||||
|
MaxVersion: "TLSv1.3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: &networking.Gateway{
|
||||||
|
Servers: []*networking.Server{
|
||||||
|
{
|
||||||
|
Port: &networking.Port{
|
||||||
|
Protocol: "TLS",
|
||||||
|
},
|
||||||
|
Tls: &networking.ServerTLSSettings{
|
||||||
|
Mode: networking.ServerTLSSettings_PASSTHROUGH,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
|||||||
34
pkg/ingress/kube/annotations/ssl_passthrough.go
Normal file
34
pkg/ingress/kube/annotations/ssl_passthrough.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package annotations
|
||||||
|
|
||||||
|
const sslPassthroughAnnotation = "ssl-passthrough"
|
||||||
|
|
||||||
|
var _ Parser = &sslPassthrough{}
|
||||||
|
|
||||||
|
type SSLPassthroughConfig struct {
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type sslPassthrough struct{}
|
||||||
|
|
||||||
|
func (s sslPassthrough) Parse(annotations Annotations, config *Ingress, _ *GlobalContext) error {
|
||||||
|
enabled, err := annotations.ParseBoolASAP(sslPassthroughAnnotation)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
config.SSLPassthrough = &SSLPassthroughConfig{Enabled: enabled}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
112
pkg/ingress/kube/annotations/ssl_passthrough_test.go
Normal file
112
pkg/ingress/kube/annotations/ssl_passthrough_test.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package annotations
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestSSLPassthroughParse(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
input Annotations
|
||||||
|
enabled bool
|
||||||
|
exists bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing",
|
||||||
|
input: Annotations{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "enabled by nginx annotation",
|
||||||
|
input: Annotations{
|
||||||
|
buildNginxAnnotationKey(sslPassthroughAnnotation): "true",
|
||||||
|
},
|
||||||
|
enabled: true,
|
||||||
|
exists: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "enabled by higress annotation",
|
||||||
|
input: Annotations{
|
||||||
|
buildHigressAnnotationKey(sslPassthroughAnnotation): "true",
|
||||||
|
},
|
||||||
|
enabled: true,
|
||||||
|
exists: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "disabled by nginx annotation",
|
||||||
|
input: Annotations{
|
||||||
|
buildNginxAnnotationKey(sslPassthroughAnnotation): "false",
|
||||||
|
},
|
||||||
|
exists: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "disabled by higress annotation",
|
||||||
|
input: Annotations{
|
||||||
|
buildHigressAnnotationKey(sslPassthroughAnnotation): "false",
|
||||||
|
},
|
||||||
|
exists: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := sslPassthrough{}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
config := &Ingress{}
|
||||||
|
if err := parser.Parse(tc.input, config, nil); err != nil {
|
||||||
|
t.Fatalf("Parse() error = %v", err)
|
||||||
|
}
|
||||||
|
if tc.exists && config.SSLPassthrough == nil {
|
||||||
|
t.Fatal("expected ssl passthrough config")
|
||||||
|
}
|
||||||
|
if !tc.exists && config.SSLPassthrough != nil {
|
||||||
|
t.Fatal("unexpected ssl passthrough config")
|
||||||
|
}
|
||||||
|
if tc.exists && config.SSLPassthrough.Enabled != tc.enabled {
|
||||||
|
t.Fatalf("enabled mismatch, want %v, got %v", tc.enabled, config.SSLPassthrough.Enabled)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSLPassthroughDoesNotSetUpstreamTLS(t *testing.T) {
|
||||||
|
parser := sslPassthrough{}
|
||||||
|
config := &Ingress{}
|
||||||
|
err := parser.Parse(Annotations{
|
||||||
|
buildNginxAnnotationKey(sslPassthroughAnnotation): "true",
|
||||||
|
}, config, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse() error = %v", err)
|
||||||
|
}
|
||||||
|
if config.UpstreamTLS != nil {
|
||||||
|
t.Fatal("unexpected upstream tls config")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSLPassthroughKeepsExplicitBackendProtocol(t *testing.T) {
|
||||||
|
manager := NewAnnotationHandlerManager()
|
||||||
|
config := &Ingress{}
|
||||||
|
err := manager.Parse(Annotations{
|
||||||
|
buildNginxAnnotationKey(sslPassthroughAnnotation): "true",
|
||||||
|
buildNginxAnnotationKey(backendProtocol): "HTTPS",
|
||||||
|
}, config, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse() error = %v", err)
|
||||||
|
}
|
||||||
|
if config.UpstreamTLS == nil {
|
||||||
|
t.Fatal("expected upstream tls config")
|
||||||
|
}
|
||||||
|
if config.UpstreamTLS.BackendProtocol != "HTTPS" {
|
||||||
|
t.Fatalf("backend protocol mismatch, want HTTPS, got %s", config.UpstreamTLS.BackendProtocol)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ import (
|
|||||||
"istio.io/istio/pkg/cluster"
|
"istio.io/istio/pkg/cluster"
|
||||||
"istio.io/istio/pkg/config"
|
"istio.io/istio/pkg/config"
|
||||||
gatewaytool "istio.io/istio/pkg/config/gateway"
|
gatewaytool "istio.io/istio/pkg/config/gateway"
|
||||||
|
"istio.io/istio/pkg/config/protocol"
|
||||||
listerv1 "k8s.io/client-go/listers/core/v1"
|
listerv1 "k8s.io/client-go/listers/core/v1"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
|
|
||||||
@@ -78,6 +80,20 @@ func (w *WrapperGateway) IsHTTPS() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CreateSSLPassthroughServer(host string, port uint32, clusterId cluster.ID) *networking.Server {
|
||||||
|
return &networking.Server{
|
||||||
|
Port: &networking.Port{
|
||||||
|
Number: port,
|
||||||
|
Protocol: string(protocol.TLS),
|
||||||
|
Name: CreateConvertedName("tls-"+strconv.FormatUint(uint64(port), 10)+"-ingress", clusterId.String()),
|
||||||
|
},
|
||||||
|
Hosts: []string{WildcardHost(host)},
|
||||||
|
Tls: &networking.ServerTLSSettings{
|
||||||
|
Mode: networking.ServerTLSSettings_PASSTHROUGH,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type WrapperHTTPRoute struct {
|
type WrapperHTTPRoute struct {
|
||||||
HTTPRoute *networking.HTTPRoute
|
HTTPRoute *networking.HTTPRoute
|
||||||
WrapperConfig *WrapperConfig
|
WrapperConfig *WrapperConfig
|
||||||
@@ -111,6 +127,50 @@ type WrapperVirtualService struct {
|
|||||||
AppRoot string
|
AppRoot string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *WrapperVirtualService) HasTLSRouteForHost(host string) bool {
|
||||||
|
if w == nil || w.VirtualService == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
host = WildcardHost(host)
|
||||||
|
for _, route := range w.VirtualService.Tls {
|
||||||
|
for _, match := range route.Match {
|
||||||
|
for _, sniHost := range match.SniHosts {
|
||||||
|
if WildcardHost(sniHost) == host {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWrapperVirtualService(host string, wrapper *WrapperConfig) *WrapperVirtualService {
|
||||||
|
return &WrapperVirtualService{
|
||||||
|
VirtualService: &networking.VirtualService{
|
||||||
|
Hosts: []string{WildcardHost(host)},
|
||||||
|
},
|
||||||
|
WrapperConfig: wrapper,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTLSRoute(host string, routeDestination []*networking.RouteDestination) *networking.TLSRoute {
|
||||||
|
return &networking.TLSRoute{
|
||||||
|
Match: []*networking.TLSMatchAttributes{
|
||||||
|
{
|
||||||
|
SniHosts: []string{WildcardHost(host)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Route: routeDestination,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WildcardHost(host string) string {
|
||||||
|
if host == "" {
|
||||||
|
return "*"
|
||||||
|
}
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
|
||||||
type WrapperTrafficPolicy struct {
|
type WrapperTrafficPolicy struct {
|
||||||
TrafficPolicy *networking.TrafficPolicy
|
TrafficPolicy *networking.TrafficPolicy
|
||||||
PortTrafficPolicy *networking.TrafficPolicy_PortTrafficPolicy
|
PortTrafficPolicy *networking.TrafficPolicy_PortTrafficPolicy
|
||||||
|
|||||||
@@ -145,6 +145,41 @@ func (i *IngressDomainCache) Extract() model.IngressDomainCollection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SameConfig(left *config.Config, right *config.Config) bool {
|
||||||
|
if left == nil || right == nil {
|
||||||
|
return left == right
|
||||||
|
}
|
||||||
|
return GetClusterId(left.Annotations) == GetClusterId(right.Annotations) &&
|
||||||
|
left.Namespace == right.Namespace &&
|
||||||
|
left.Name == right.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsPassthroughTLSHostOwner(convertOptions *ConvertOptions, cfg *config.Config, host string) bool {
|
||||||
|
if convertOptions == nil || convertOptions.PassthroughTLSHostOwners == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return SameConfig(convertOptions.PassthroughTLSHostOwners[host], cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PassthroughTLSHostOwner(convertOptions *ConvertOptions, host string) *config.Config {
|
||||||
|
if convertOptions == nil || len(convertOptions.PassthroughTLSHostOwners) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return convertOptions.PassthroughTLSHostOwners[host]
|
||||||
|
}
|
||||||
|
|
||||||
|
func HasPassthroughTLSHostOwner(convertOptions *ConvertOptions, cfg *config.Config) bool {
|
||||||
|
if convertOptions == nil || len(convertOptions.PassthroughTLSHostOwners) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, owner := range convertOptions.PassthroughTLSHostOwners {
|
||||||
|
if SameConfig(owner, cfg) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type ConvertOptions struct {
|
type ConvertOptions struct {
|
||||||
HostWithRule2Ingress map[string]*config.Config
|
HostWithRule2Ingress map[string]*config.Config
|
||||||
|
|
||||||
@@ -167,6 +202,9 @@ type ConvertOptions struct {
|
|||||||
|
|
||||||
CanaryIngresses []*WrapperConfig
|
CanaryIngresses []*WrapperConfig
|
||||||
|
|
||||||
|
// Host to the first root-path ingress owner for hosts that have TLS passthrough enabled.
|
||||||
|
PassthroughTLSHostOwners map[string]*config.Config
|
||||||
|
|
||||||
Service2TrafficPolicy map[ServiceKey]*WrapperTrafficPolicy
|
Service2TrafficPolicy map[ServiceKey]*WrapperTrafficPolicy
|
||||||
|
|
||||||
ServiceWrappers map[string]*ServiceWrapper
|
ServiceWrappers map[string]*ServiceWrapper
|
||||||
|
|||||||
@@ -18,10 +18,48 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
networking "istio.io/api/networking/v1alpha3"
|
||||||
"istio.io/istio/pilot/pkg/model"
|
"istio.io/istio/pilot/pkg/model"
|
||||||
"istio.io/istio/pkg/config"
|
"istio.io/istio/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestWildcardHostForSSLPassthrough(t *testing.T) {
|
||||||
|
server := CreateSSLPassthroughServer("", 443, "")
|
||||||
|
assert.Equal(t, []string{"*"}, server.Hosts)
|
||||||
|
|
||||||
|
vs := NewWrapperVirtualService("", &WrapperConfig{})
|
||||||
|
assert.Equal(t, []string{"*"}, vs.VirtualService.Hosts)
|
||||||
|
|
||||||
|
route := CreateTLSRoute("", []*networking.RouteDestination{{Weight: 100}})
|
||||||
|
assert.Equal(t, []string{"*"}, route.Match[0].SniHosts)
|
||||||
|
vs.VirtualService.Tls = append(vs.VirtualService.Tls, route)
|
||||||
|
assert.True(t, vs.HasTLSRouteForHost(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassthroughTLSHostOwnerNilMapAllowsStandaloneConversion(t *testing.T) {
|
||||||
|
cfg := &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "tls-passthrough",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// A nil owner map means the caller did not prepare ownership from the full ingress snapshot.
|
||||||
|
assert.True(t, IsPassthroughTLSHostOwner(&ConvertOptions{}, cfg, "example.com"))
|
||||||
|
assert.Nil(t, PassthroughTLSHostOwner(&ConvertOptions{}, "example.com"))
|
||||||
|
|
||||||
|
// A non-nil owner map means ownership has been prepared and missing hosts have no owner.
|
||||||
|
options := &ConvertOptions{
|
||||||
|
PassthroughTLSHostOwners: map[string]*config.Config{},
|
||||||
|
}
|
||||||
|
assert.False(t, IsPassthroughTLSHostOwner(options, cfg, "example.com"))
|
||||||
|
assert.Nil(t, PassthroughTLSHostOwner(options, "example.com"))
|
||||||
|
|
||||||
|
options.PassthroughTLSHostOwners["example.com"] = cfg
|
||||||
|
assert.True(t, IsPassthroughTLSHostOwner(options, cfg, "example.com"))
|
||||||
|
assert.Equal(t, cfg, PassthroughTLSHostOwner(options, "example.com"))
|
||||||
|
}
|
||||||
|
|
||||||
func TestIngressDomainCache(t *testing.T) {
|
func TestIngressDomainCache(t *testing.T) {
|
||||||
cache := NewIngressDomainCache()
|
cache := NewIngressDomainCache()
|
||||||
assert.NotNil(t, cache)
|
assert.NotNil(t, cache)
|
||||||
|
|||||||
@@ -409,7 +409,7 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
|||||||
Protocol: string(protocol.HTTP),
|
Protocol: string(protocol.HTTP),
|
||||||
Name: common.CreateConvertedName("http-80-ingress", c.options.ClusterId.String()),
|
Name: common.CreateConvertedName("http-80-ingress", c.options.ClusterId.String()),
|
||||||
},
|
},
|
||||||
Hosts: []string{rule.Host},
|
Hosts: []string{common.WildcardHost(rule.Host)},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add new gateway, builder
|
// Add new gateway, builder
|
||||||
@@ -422,6 +422,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.
|
// There are no tls settings, so just skip.
|
||||||
if len(ingressV1Beta.TLS) == 0 {
|
if len(ingressV1Beta.TLS) == 0 {
|
||||||
continue
|
continue
|
||||||
@@ -470,6 +509,14 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
|||||||
|
|
||||||
domainBuilder.SecretName = path.Join(c.options.ClusterId.String(), cfg.Namespace, secretName)
|
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.
|
// There is a matching secret and the gateway has already a tls secret.
|
||||||
// We should report the duplicated tls secret event.
|
// We should report the duplicated tls secret event.
|
||||||
if wrapperGateway.IsHTTPS() {
|
if wrapperGateway.IsHTTPS() {
|
||||||
@@ -487,7 +534,7 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
|||||||
Protocol: string(protocol.HTTPS),
|
Protocol: string(protocol.HTTPS),
|
||||||
Name: common.CreateConvertedName("https-443-ingress", c.options.ClusterId.String()),
|
Name: common.CreateConvertedName("https-443-ingress", c.options.ClusterId.String()),
|
||||||
},
|
},
|
||||||
Hosts: []string{rule.Host},
|
Hosts: []string{common.WildcardHost(rule.Host)},
|
||||||
Tls: &networking.ServerTLSSettings{
|
Tls: &networking.ServerTLSSettings{
|
||||||
Mode: networking.ServerTLSSettings_SIMPLE,
|
Mode: networking.ServerTLSSettings_SIMPLE,
|
||||||
CredentialName: credentials.ToKubernetesIngressResource(c.options.RawClusterId, secretNamespace, secretName),
|
CredentialName: credentials.ToKubernetesIngressResource(c.options.RawClusterId, secretNamespace, secretName),
|
||||||
@@ -515,6 +562,19 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
|
|||||||
return nil
|
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
|
cfg := wrapper.Config
|
||||||
ingressV1, ok := cfg.Spec.(ingress.IngressSpec)
|
ingressV1, ok := cfg.Spec.(ingress.IngressSpec)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -546,12 +606,7 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
|
|||||||
|
|
||||||
wrapperVS, exist := convertOptions.VirtualServices[rule.Host]
|
wrapperVS, exist := convertOptions.VirtualServices[rule.Host]
|
||||||
if !exist {
|
if !exist {
|
||||||
wrapperVS = &common.WrapperVirtualService{
|
wrapperVS = common.NewWrapperVirtualService(rule.Host, wrapper)
|
||||||
VirtualService: &networking.VirtualService{
|
|
||||||
Hosts: []string{rule.Host},
|
|
||||||
},
|
|
||||||
WrapperConfig: wrapper,
|
|
||||||
}
|
|
||||||
convertOptions.VirtualServices[rule.Host] = wrapperVS
|
convertOptions.VirtualServices[rule.Host] = wrapperVS
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -579,7 +634,11 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
|
|||||||
pathType = common.PrefixRegex
|
pathType = common.PrefixRegex
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch *httpPath.PathType {
|
ingressPathType := defaultPathType
|
||||||
|
if httpPath.PathType != nil {
|
||||||
|
ingressPathType = *httpPath.PathType
|
||||||
|
}
|
||||||
|
switch ingressPathType {
|
||||||
case ingress.PathTypeExact:
|
case ingress.PathTypeExact:
|
||||||
pathType = common.Exact
|
pathType = common.Exact
|
||||||
case ingress.PathTypePrefix:
|
case ingress.PathTypePrefix:
|
||||||
@@ -661,9 +720,84 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
|
|||||||
common.SortHTTPRoutes(routes)
|
common.SortHTTPRoutes(routes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if common.HasPassthroughTLSHostOwner(convertOptions, cfg) ||
|
||||||
|
(convertOptions.PassthroughTLSHostOwners == nil && wrapper.AnnotationsConfig.IsSSLPassthrough()) {
|
||||||
|
return c.ConvertTLSRoute(convertOptions, wrapper)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
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
|
||||||
|
ingressV1Beta, 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(ingressV1Beta.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 ingressV1Beta.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) ApplyDefaultBackend(convertOptions *common.ConvertOptions, wrapper *common.WrapperConfig) error {
|
func (c *controller) ApplyDefaultBackend(convertOptions *common.ConvertOptions, wrapper *common.WrapperConfig) error {
|
||||||
if convertOptions == nil {
|
if convertOptions == nil {
|
||||||
return fmt.Errorf("convertOptions is nil")
|
return fmt.Errorf("convertOptions is nil")
|
||||||
@@ -691,12 +825,7 @@ func (c *controller) ApplyDefaultBackend(convertOptions *common.ConvertOptions,
|
|||||||
wirecardVS, exist := convertOptions.VirtualServices[host]
|
wirecardVS, exist := convertOptions.VirtualServices[host]
|
||||||
if !exist || !wirecardVS.ConfiguredDefaultBackend {
|
if !exist || !wirecardVS.ConfiguredDefaultBackend {
|
||||||
if !exist {
|
if !exist {
|
||||||
wirecardVS = &common.WrapperVirtualService{
|
wirecardVS = common.NewWrapperVirtualService(host, wrapper)
|
||||||
VirtualService: &networking.VirtualService{
|
|
||||||
Hosts: []string{host},
|
|
||||||
},
|
|
||||||
WrapperConfig: wrapper,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
specDefaultBackend := c.createDefaultRoute(wrapper, ingressV1Beta1.Backend, "*")
|
specDefaultBackend := c.createDefaultRoute(wrapper, ingressV1Beta1.Backend, "*")
|
||||||
@@ -790,7 +919,11 @@ func (c *controller) ApplyCanaryIngress(convertOptions *common.ConvertOptions, w
|
|||||||
pathType = common.PrefixRegex
|
pathType = common.PrefixRegex
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch *httpPath.PathType {
|
ingressPathType := defaultPathType
|
||||||
|
if httpPath.PathType != nil {
|
||||||
|
ingressPathType = *httpPath.PathType
|
||||||
|
}
|
||||||
|
switch ingressPathType {
|
||||||
case ingress.PathTypeExact:
|
case ingress.PathTypeExact:
|
||||||
pathType = common.Exact
|
pathType = common.Exact
|
||||||
case ingress.PathTypePrefix:
|
case ingress.PathTypePrefix:
|
||||||
@@ -1092,6 +1225,53 @@ func (c *controller) backendToRouteDestination(backend *ingress.IngressBackend,
|
|||||||
}, common.Normal
|
}, 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.ServiceName == "" {
|
||||||
|
if config != nil && len(config.McpDestination) > 0 {
|
||||||
|
return httpRouteDestinationToRouteDestination(config.McpDestination), common.Normal
|
||||||
|
}
|
||||||
|
return nil, common.InvalidBackendService
|
||||||
|
}
|
||||||
|
|
||||||
|
port := &networking.PortSelector{}
|
||||||
|
if backend.ServicePort.Type == intstr.Int {
|
||||||
|
port.Number = uint32(backend.ServicePort.IntVal)
|
||||||
|
} else {
|
||||||
|
resolvedPort, err := resolveNamedPort(backend, namespace, c.serviceLister)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.PortNameResolveError
|
||||||
|
}
|
||||||
|
port.Number = uint32(resolvedPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []*networking.RouteDestination{
|
||||||
|
{
|
||||||
|
Destination: &networking.Destination{
|
||||||
|
Host: util.CreateServiceFQDN(namespace, backend.ServiceName),
|
||||||
|
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(backend *ingress.IngressBackend, namespace string, serviceLister listerv1.ServiceLister) (int32, error) {
|
func resolveNamedPort(backend *ingress.IngressBackend, namespace string, serviceLister listerv1.ServiceLister) (int32, error) {
|
||||||
if backend == nil {
|
if backend == nil {
|
||||||
return 0, fmt.Errorf("ingressBackend is nil")
|
return 0, fmt.Errorf("ingressBackend is nil")
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ package ingress
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/v2/pkg/cert"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"istio.io/api/networking/v1alpha3"
|
"istio.io/api/networking/v1alpha3"
|
||||||
"istio.io/istio/pilot/pkg/model"
|
"istio.io/istio/pilot/pkg/model"
|
||||||
@@ -68,6 +70,962 @@ func TestIngressControllerApplies(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: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/",
|
||||||
|
Backend: ingress.IngressBackend{
|
||||||
|
ServiceName: "app",
|
||||||
|
ServicePort: intstr.FromInt(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() != v1alpha3.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 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: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/",
|
||||||
|
Backend: ingress.IngressBackend{
|
||||||
|
ServiceName: "app",
|
||||||
|
ServicePort: intstr.FromInt(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{}
|
||||||
|
wrapper := &common.WrapperConfig{
|
||||||
|
Config: &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "tls-passthrough-canary",
|
||||||
|
},
|
||||||
|
Spec: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/",
|
||||||
|
Backend: ingress.IngressBackend{
|
||||||
|
ServiceName: "app-canary",
|
||||||
|
ServicePort: intstr.FromInt(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: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/",
|
||||||
|
Backend: ingress.IngressBackend{
|
||||||
|
ServiceName: "primary",
|
||||||
|
ServicePort: intstr.FromInt(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: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/",
|
||||||
|
Backend: ingress.IngressBackend{
|
||||||
|
ServiceName: "duplicate",
|
||||||
|
ServicePort: intstr.FromInt(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 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: ingress.IngressSpec{
|
||||||
|
TLS: []ingress.IngressTLS{
|
||||||
|
{Hosts: []string{"example.com"}},
|
||||||
|
},
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/",
|
||||||
|
Backend: ingress.IngressBackend{
|
||||||
|
ServiceName: "primary",
|
||||||
|
ServicePort: intstr.FromInt(443),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AnnotationsConfig: &annotations.Ingress{},
|
||||||
|
}
|
||||||
|
duplicate := &common.WrapperConfig{
|
||||||
|
Config: &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "tls-passthrough-duplicate",
|
||||||
|
},
|
||||||
|
Spec: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/",
|
||||||
|
Backend: ingress.IngressBackend{
|
||||||
|
ServiceName: "duplicate",
|
||||||
|
ServicePort: intstr.FromInt(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 TestBackendToTLSRouteDestinationRejectsEmptyMCPDestination(t *testing.T) {
|
||||||
|
c := controller{}
|
||||||
|
backend := &ingress.IngressBackend{}
|
||||||
|
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 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: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
ingressV1Beta1Rule("example.com", ingressV1Beta1Path("/", "root", 443)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AnnotationsConfig: &annotations.Ingress{},
|
||||||
|
}
|
||||||
|
passthrough := &common.WrapperConfig{
|
||||||
|
Config: &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "passthrough",
|
||||||
|
},
|
||||||
|
Spec: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
ingressV1Beta1Rule("example.com", ingressV1Beta1Path("/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() != v1alpha3.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: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
ingressV1Beta1Rule("example.com", ingressV1Beta1Path("/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: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
ingressV1Beta1Rule("example.com", ingressV1Beta1Path("/", "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() != v1alpha3.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: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/health",
|
||||||
|
Backend: ingress.IngressBackend{
|
||||||
|
ServiceName: "health",
|
||||||
|
ServicePort: intstr.FromInt(8443),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/",
|
||||||
|
Backend: ingress.IngressBackend{
|
||||||
|
ServiceName: "root",
|
||||||
|
ServicePort: intstr.FromInt(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: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/",
|
||||||
|
Backend: ingress.IngressBackend{
|
||||||
|
ServiceName: "first",
|
||||||
|
ServicePort: intstr.FromInt(443),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/",
|
||||||
|
Backend: ingress.IngressBackend{
|
||||||
|
ServiceName: "second",
|
||||||
|
ServicePort: intstr.FromInt(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 []ingress.IngressRule
|
||||||
|
wantHosts []string
|
||||||
|
wantRoutes map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "root path first",
|
||||||
|
rules: []ingress.IngressRule{
|
||||||
|
ingressV1Beta1Rule("first.example.com", ingressV1Beta1Path("/", "first", 443)),
|
||||||
|
ingressV1Beta1Rule("middle.example.com", ingressV1Beta1Path("/health", "middle", 8443)),
|
||||||
|
ingressV1Beta1Rule("last.example.com", ingressV1Beta1Path("/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: []ingress.IngressRule{
|
||||||
|
ingressV1Beta1Rule("first.example.com", ingressV1Beta1Path("/health", "first", 8443)),
|
||||||
|
ingressV1Beta1Rule("middle.example.com", ingressV1Beta1Path("/", "middle", 443)),
|
||||||
|
ingressV1Beta1Rule("last.example.com", ingressV1Beta1Path("/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: []ingress.IngressRule{
|
||||||
|
ingressV1Beta1Rule("first.example.com", ingressV1Beta1Path("/health", "first", 8443)),
|
||||||
|
ingressV1Beta1Rule("middle.example.com", ingressV1Beta1Path("/health", "middle", 8443)),
|
||||||
|
ingressV1Beta1Rule("last.example.com", ingressV1Beta1Path("/", "last", 443)),
|
||||||
|
},
|
||||||
|
wantHosts: []string{"last.example.com"},
|
||||||
|
wantRoutes: map[string]string{
|
||||||
|
"last.example.com": "last.default.svc.cluster.local",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple root hosts",
|
||||||
|
rules: []ingress.IngressRule{
|
||||||
|
ingressV1Beta1Rule("first.example.com", ingressV1Beta1Path("/", "first", 443)),
|
||||||
|
ingressV1Beta1Rule("middle.example.com", ingressV1Beta1Path("/", "middle", 443)),
|
||||||
|
ingressV1Beta1Rule("last.example.com", ingressV1Beta1Path("/", "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: ingress.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 ingressV1Beta1Path(path, service string, port int32) ingress.HTTPIngressPath {
|
||||||
|
return ingress.HTTPIngressPath{
|
||||||
|
Path: path,
|
||||||
|
Backend: ingress.IngressBackend{
|
||||||
|
ServiceName: service,
|
||||||
|
ServicePort: intstr.FromInt(int(port)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ingressV1Beta1Rule(host string, paths ...ingress.HTTPIngressPath) ingress.IngressRule {
|
||||||
|
return ingress.IngressRule{
|
||||||
|
Host: host,
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: paths,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSLPassthroughUsesRootPathBackend(t *testing.T) {
|
||||||
|
c := controller{}
|
||||||
|
wrapper := &common.WrapperConfig{
|
||||||
|
Config: &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "tls-passthrough-root",
|
||||||
|
},
|
||||||
|
Spec: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/api",
|
||||||
|
Backend: ingress.IngressBackend{
|
||||||
|
ServiceName: "api",
|
||||||
|
ServicePort: intstr.FromInt(8443),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "/",
|
||||||
|
Backend: ingress.IngressBackend{
|
||||||
|
ServiceName: "root",
|
||||||
|
ServicePort: intstr.FromInt(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: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/",
|
||||||
|
Backend: ingress.IngressBackend{
|
||||||
|
ServiceName: "root",
|
||||||
|
ServicePort: intstr.FromInt(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: ingress.IngressSpec{
|
||||||
|
Rules: []ingress.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "example.com",
|
||||||
|
IngressRuleValue: ingress.IngressRuleValue{
|
||||||
|
HTTP: &ingress.HTTPIngressRuleValue{
|
||||||
|
Paths: []ingress.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/api",
|
||||||
|
Backend: ingress.IngressBackend{
|
||||||
|
ServiceName: "api",
|
||||||
|
ServicePort: intstr.FromInt(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 testApplyCanaryIngress(t *testing.T, c common.IngressController) {
|
func testApplyCanaryIngress(t *testing.T, c common.IngressController) {
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
description string
|
description string
|
||||||
|
|||||||
@@ -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 {
|
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.
|
// Ignore canary config.
|
||||||
if wrapper.AnnotationsConfig.IsCanary() {
|
if wrapper.AnnotationsConfig.IsCanary() {
|
||||||
return nil
|
return nil
|
||||||
@@ -382,7 +389,7 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
|||||||
Protocol: string(protocol.HTTP),
|
Protocol: string(protocol.HTTP),
|
||||||
Name: common.CreateConvertedName("http-"+strconv.FormatUint(uint64(c.options.GatewayHttpPort), 10)+"-ingress", string(c.options.ClusterId)),
|
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
|
// 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.
|
// There are no tls settings, so just skip.
|
||||||
if len(ingressV1.TLS) == 0 {
|
if len(ingressV1.TLS) == 0 {
|
||||||
continue
|
continue
|
||||||
@@ -443,6 +489,14 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
|||||||
domainBuilder.Protocol = common.HTTPS
|
domainBuilder.Protocol = common.HTTPS
|
||||||
domainBuilder.SecretName = path.Join(c.options.ClusterId.String(), cfg.Namespace, secretName)
|
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.
|
// There is a matching secret and the gateway has already a tls secret.
|
||||||
// We should report the duplicated tls secret event.
|
// We should report the duplicated tls secret event.
|
||||||
if wrapperGateway.IsHTTPS() {
|
if wrapperGateway.IsHTTPS() {
|
||||||
@@ -460,7 +514,7 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
|||||||
Protocol: string(protocol.HTTPS),
|
Protocol: string(protocol.HTTPS),
|
||||||
Name: common.CreateConvertedName("https-"+strconv.FormatUint(uint64(c.options.GatewayHttpsPort), 10)+"-ingress", string(c.options.ClusterId)),
|
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{
|
Tls: &networking.ServerTLSSettings{
|
||||||
Mode: networking.ServerTLSSettings_SIMPLE,
|
Mode: networking.ServerTLSSettings_SIMPLE,
|
||||||
CredentialName: credentials.ToKubernetesIngressResource(c.options.RawClusterId, secretNamespace, secretName),
|
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 {
|
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.
|
// Canary ingress will be processed in the end.
|
||||||
if wrapper.AnnotationsConfig.IsCanary() {
|
if wrapper.AnnotationsConfig.IsCanary() {
|
||||||
convertOptions.CanaryIngresses = append(convertOptions.CanaryIngresses, wrapper)
|
convertOptions.CanaryIngresses = append(convertOptions.CanaryIngresses, wrapper)
|
||||||
return nil
|
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
|
cfg := wrapper.Config
|
||||||
ingressV1, ok := cfg.Spec.(ingress.IngressSpec)
|
ingressV1, ok := cfg.Spec.(ingress.IngressSpec)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -515,12 +589,7 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
|
|||||||
|
|
||||||
wrapperVS, exist := convertOptions.VirtualServices[rule.Host]
|
wrapperVS, exist := convertOptions.VirtualServices[rule.Host]
|
||||||
if !exist {
|
if !exist {
|
||||||
wrapperVS = &common.WrapperVirtualService{
|
wrapperVS = common.NewWrapperVirtualService(rule.Host, wrapper)
|
||||||
VirtualService: &networking.VirtualService{
|
|
||||||
Hosts: []string{rule.Host},
|
|
||||||
},
|
|
||||||
WrapperConfig: wrapper,
|
|
||||||
}
|
|
||||||
convertOptions.VirtualServices[rule.Host] = wrapperVS
|
convertOptions.VirtualServices[rule.Host] = wrapperVS
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,7 +618,11 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
|
|||||||
pathType = common.PrefixRegex
|
pathType = common.PrefixRegex
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch *httpPath.PathType {
|
ingressPathType := defaultPathType
|
||||||
|
if httpPath.PathType != nil {
|
||||||
|
ingressPathType = *httpPath.PathType
|
||||||
|
}
|
||||||
|
switch ingressPathType {
|
||||||
case ingress.PathTypeExact:
|
case ingress.PathTypeExact:
|
||||||
pathType = common.Exact
|
pathType = common.Exact
|
||||||
case ingress.PathTypePrefix:
|
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
|
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 {
|
func (c *controller) generateHttpMatches(pathType common.PathType, path string, wrapperVS *common.WrapperVirtualService) []*networking.HTTPMatchRequest {
|
||||||
var httpMatches []*networking.HTTPMatchRequest
|
var httpMatches []*networking.HTTPMatchRequest
|
||||||
|
|
||||||
@@ -689,12 +837,7 @@ func (c *controller) ApplyDefaultBackend(convertOptions *common.ConvertOptions,
|
|||||||
wirecardVS, exist := convertOptions.VirtualServices[host]
|
wirecardVS, exist := convertOptions.VirtualServices[host]
|
||||||
if !exist || !wirecardVS.ConfiguredDefaultBackend {
|
if !exist || !wirecardVS.ConfiguredDefaultBackend {
|
||||||
if !exist {
|
if !exist {
|
||||||
wirecardVS = &common.WrapperVirtualService{
|
wirecardVS = common.NewWrapperVirtualService(host, wrapper)
|
||||||
VirtualService: &networking.VirtualService{
|
|
||||||
Hosts: []string{host},
|
|
||||||
},
|
|
||||||
WrapperConfig: wrapper,
|
|
||||||
}
|
|
||||||
convertOptions.VirtualServices[host] = wirecardVS
|
convertOptions.VirtualServices[host] = wirecardVS
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -782,7 +925,11 @@ func (c *controller) ApplyCanaryIngress(convertOptions *common.ConvertOptions, w
|
|||||||
pathType = common.PrefixRegex
|
pathType = common.PrefixRegex
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch *httpPath.PathType {
|
ingressPathType := defaultPathType
|
||||||
|
if httpPath.PathType != nil {
|
||||||
|
ingressPathType = *httpPath.PathType
|
||||||
|
}
|
||||||
|
switch ingressPathType {
|
||||||
case ingress.PathTypeExact:
|
case ingress.PathTypeExact:
|
||||||
pathType = common.Exact
|
pathType = common.Exact
|
||||||
case ingress.PathTypePrefix:
|
case ingress.PathTypePrefix:
|
||||||
@@ -1074,6 +1221,54 @@ func (c *controller) backendToRouteDestination(backend *ingress.IngressBackend,
|
|||||||
}, common.Normal
|
}, 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) {
|
func resolveNamedPort(service *ingress.IngressServiceBackend, namespace string, serviceLister listerv1.ServiceLister) (int32, error) {
|
||||||
svc, err := serviceLister.Services(namespace).Get(service.Name)
|
svc, err := serviceLister.Services(namespace).Get(service.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -15,12 +15,17 @@
|
|||||||
package ingressv1
|
package ingressv1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"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/alibaba/higress/v2/pkg/ingress/kube/common"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
networking "istio.io/api/networking/v1alpha3"
|
networking "istio.io/api/networking/v1alpha3"
|
||||||
|
"istio.io/istio/pkg/config"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
v1 "k8s.io/api/networking/v1"
|
v1 "k8s.io/api/networking/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/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