diff --git a/internal/adc/translator/apisixtls.go b/internal/adc/translator/apisixtls.go index 2f05facf02..d2a346a22d 100644 --- a/internal/adc/translator/apisixtls.go +++ b/internal/adc/translator/apisixtls.go @@ -54,22 +54,10 @@ func (t *Translator) TranslateApisixTls(tctx *provider.TranslateContext, tls *ap snis[i] = string(host) } - // Create SSL object - ssl := &adctypes.SSL{ - Metadata: adctypes.Metadata{ - ID: id.GenID(tls.Namespace + "_" + tls.Name), - Labels: label.GenLabel(tls), - }, - Certificates: []adctypes.Certificate{ - { - Certificate: string(cert), - Key: string(key), - }, - }, - Snis: snis, - } + labels := label.GenLabel(tls) // Handle mutual TLS client configuration if present + var client *adctypes.ClientClass if tls.Spec.Client != nil { caSecretKey := types.NamespacedName{ Namespace: tls.Spec.Client.CASecret.Namespace, @@ -85,13 +73,37 @@ func (t *Translator) TranslateApisixTls(tctx *provider.TranslateContext, tls *ap return nil, err } depth := int64(tls.Spec.Client.Depth) - ssl.Client = &adctypes.ClientClass{ + client = &adctypes.ClientClass{ CA: string(ca), Depth: &depth, SkipMtlsURIRegex: tls.Spec.Client.SkipMTLSUriRegex, } } - result.SSL = append(result.SSL, ssl) + // Create one SSL object per SNI to maintain consistency with Ingress and Gateway API. + // Using namespace + secretName + sni as the ID ensures: + // 1. Different ApisixTls with same cert+sni will share the same SSL (expected behavior) + // 2. Same ApisixTls with same cert but different SNIs will have separate SSL objects + // 3. Consistent behavior across all SSL configuration methods (Ingress, Gateway, ApisixTls) + for _, sni := range snis { + ssl := &adctypes.SSL{ + Metadata: adctypes.Metadata{ + Labels: labels, + // Generate unique ID based on namespace, secret name, and SNI + // This allows the same wildcard certificate to be used for multiple SNIs + ID: id.GenID(secretKey.Namespace + "_" + secretKey.Name + "_" + sni), + }, + Certificates: []adctypes.Certificate{ + { + Certificate: string(cert), + Key: string(key), + }, + }, + Snis: []string{sni}, + Client: client, + } + result.SSL = append(result.SSL, ssl) + } + return result, nil } diff --git a/internal/adc/translator/gateway.go b/internal/adc/translator/gateway.go index db28484527..e4396744fb 100644 --- a/internal/adc/translator/gateway.go +++ b/internal/adc/translator/gateway.go @@ -22,7 +22,6 @@ import ( "encoding/json" "encoding/pem" "fmt" - "slices" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" @@ -50,7 +49,7 @@ func (t *Translator) TranslateGateway(tctx *provider.TranslateContext, obj *gate result.SSL = append(result.SSL, ssl...) } } - result.SSL = mergeSSLWithSameID(result.SSL) + // No longer need to merge SSLs since each SNI now has a unique ID based on namespace+secretName+sni rk := utils.NamespacedNameKind(obj) gatewayProxy, ok := tctx.GatewayProxies[rk] @@ -86,9 +85,6 @@ func (t *Translator) translateSecret(tctx *provider.TranslateContext, listener g ns = string(*ref.Namespace) } if listener.TLS.CertificateRefs[0].Kind != nil && *listener.TLS.CertificateRefs[0].Kind == internaltypes.KindSecret { - sslObj := &adctypes.SSL{ - Snis: []string{}, - } name := listener.TLS.CertificateRefs[0].Name secretNN := types.NamespacedName{Namespace: ns, Name: string(ref.Name)} secret := tctx.Secrets[secretNN] @@ -104,13 +100,11 @@ func (t *Translator) translateSecret(tctx *provider.TranslateContext, listener g t.Log.Error(err, "extract key pair", "secret", secretNN) return nil, err } - sslObj.Certificates = append(sslObj.Certificates, adctypes.Certificate{ - Certificate: string(cert), - Key: string(key), - }) - // we doesn't allow wildcard hostname + + // Collect SNIs from listener hostname or certificate + var snis []string if listener.Hostname != nil && *listener.Hostname != "" { - sslObj.Snis = append(sslObj.Snis, string(*listener.Hostname)) + snis = []string{string(*listener.Hostname)} } else { hosts, err := extractHost(cert) if err != nil { @@ -120,13 +114,34 @@ func (t *Translator) translateSecret(tctx *provider.TranslateContext, listener g t.Log.Info("no valid hostname found in certificate", "secret", secretNN.String()) continue } - sslObj.Snis = append(sslObj.Snis, hosts...) + snis = hosts + } + + // Create one SSL object per SNI to avoid conflicts when the same certificate + // is used across multiple Gateway listeners with different SNIs. + // Using namespace + secretName + sni as the ID ensures: + // 1. Different Gateways with same cert+sni will share the same SSL (expected behavior) + // 2. Same Gateway with same cert but different SNIs will have separate SSL objects + // 3. No overwrites in MemDB due to ID collision + for _, sni := range snis { + sslObj := &adctypes.SSL{ + Metadata: adctypes.Metadata{ + Labels: label.GenLabel(obj), + // Generate unique ID based on namespace, secret name, and SNI + // This allows the same wildcard certificate to be used for multiple SNIs + ID: id.GenID(secretNN.Namespace + "_" + secretNN.Name + "_" + sni), + }, + Certificates: []adctypes.Certificate{ + { + Certificate: string(cert), + Key: string(key), + }, + }, + Snis: []string{sni}, + } + t.Log.V(1).Info("generated ssl id", "ssl id", sslObj.ID, "secret", secretNN.String(), "sni", sni) + sslObjs = append(sslObjs, sslObj) } - // Note: use cert as id to avoid duplicate certificate across ssl objects - sslObj.ID = id.GenID(string(cert)) - t.Log.V(1).Info("generated ssl id", "ssl id", sslObj.ID, "secret", secretNN.String()) - sslObj.Labels = label.GenLabel(obj) - sslObjs = append(sslObjs, sslObj) } } @@ -241,47 +256,3 @@ func (t *Translator) fillPluginMetadataFromGatewayProxy(pluginMetadata adctypes. pluginMetadata[pluginName] = pluginConfig } } - -// mergeSSLWithSameID merge ssl with same id -func mergeSSLWithSameID(sslList []*adctypes.SSL) []*adctypes.SSL { - if len(sslList) <= 1 { - return sslList - } - - // create a map to store ssl with same id - sslMap := make(map[string]*adctypes.SSL) - for _, ssl := range sslList { - if existing, exists := sslMap[ssl.ID]; exists { - // if ssl with same id exists, merge their snis - // use map to deduplicate - sniMap := make(map[string]struct{}) - // add existing snis - for _, sni := range existing.Snis { - sniMap[sni] = struct{}{} - } - // add new snis - for _, sni := range ssl.Snis { - sniMap[sni] = struct{}{} - } - // rebuild deduplicated snis list - newSnis := make([]string, 0, len(sniMap)) - for sni := range sniMap { - newSnis = append(newSnis, sni) - } - - slices.Sort(newSnis) - // update existing ssl object - existing.Snis = newSnis - } else { - slices.Sort(ssl.Snis) - // if new ssl id, add to map - sslMap[ssl.ID] = ssl - } - } - - mergedSSL := make([]*adctypes.SSL, 0, len(sslMap)) - for _, ssl := range sslMap { - mergedSSL = append(mergedSSL, ssl) - } - return mergedSSL -} diff --git a/internal/adc/translator/ingress.go b/internal/adc/translator/ingress.go index f17b159fa7..243d570c38 100644 --- a/internal/adc/translator/ingress.go +++ b/internal/adc/translator/ingress.go @@ -33,7 +33,7 @@ import ( internaltypes "github.com/apache/apisix-ingress-controller/internal/types" ) -func (t *Translator) translateIngressTLS(ingressTLS *networkingv1.IngressTLS, secret *corev1.Secret, labels map[string]string) (*adctypes.SSL, error) { +func (t *Translator) translateIngressTLS(ingressTLS *networkingv1.IngressTLS, secret *corev1.Secret, labels map[string]string) ([]*adctypes.SSL, error) { // extract the key pair from the secret cert, key, err := extractKeyPair(secret, true) if err != nil { @@ -52,21 +52,36 @@ func (t *Translator) translateIngressTLS(ingressTLS *networkingv1.IngressTLS, se return nil, fmt.Errorf("no hosts found in ingress TLS") } - ssl := &adctypes.SSL{ - Metadata: adctypes.Metadata{ - Labels: labels, - }, - Certificates: []adctypes.Certificate{ - { - Certificate: string(cert), - Key: string(key), + // Create one SSL object per SNI to avoid conflicts when the same certificate + // is used across multiple Ingresses with different SNIs. + // Using namespace + secretName + sni as the ID ensures: + // 1. Different Ingresses with same cert+sni will share the same SSL (expected behavior) + // 2. Same Ingress with same cert but different SNIs will have separate SSL objects + // 3. No overwrites in MemDB due to ID collision + namespace := labels[label.LabelNamespace] + secretName := secret.Name + + ssls := make([]*adctypes.SSL, 0, len(hosts)) + for _, host := range hosts { + ssl := &adctypes.SSL{ + Metadata: adctypes.Metadata{ + Labels: labels, + // Generate unique ID based on namespace, secret name, and SNI + // This allows the same wildcard certificate to be used for multiple SNIs + ID: id.GenID(namespace + "_" + secretName + "_" + host), + }, + Certificates: []adctypes.Certificate{ + { + Certificate: string(cert), + Key: string(key), + }, }, - }, - Snis: hosts, + Snis: []string{host}, + } + ssls = append(ssls, ssl) } - ssl.ID = id.GenID(string(cert)) - return ssl, nil + return ssls, nil } func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *networkingv1.Ingress) (*TranslateResult, error) { @@ -86,12 +101,12 @@ func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *netw if secret == nil { continue } - ssl, err := t.translateIngressTLS(&tls, secret, labels) + ssls, err := t.translateIngressTLS(&tls, secret, labels) if err != nil { return nil, err } - result.SSL = append(result.SSL, ssl) + result.SSL = append(result.SSL, ssls...) } // process Ingress rules, convert to Service and Route objects