Skip to content

Commit 5f0d1af

Browse files
authored
fix: generate unique SSL IDs to prevent certificate conflicts across different hosts (#2592)
Signed-off-by: Ashing Zheng <[email protected]>
1 parent 1513202 commit 5f0d1af

File tree

10 files changed

+172
-86
lines changed

10 files changed

+172
-86
lines changed

api/adc/types.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,19 @@ func (n Upstream) MarshalJSON() ([]byte, error) {
473473
return json.Marshal((Alias)(n))
474474
}
475475

476+
func ComposeSSLName(kind, namespace, name string) string {
477+
p := make([]byte, 0, len(kind)+len(namespace)+len(name)+2)
478+
buf := bytes.NewBuffer(p)
479+
480+
buf.WriteString(kind)
481+
buf.WriteByte('_')
482+
buf.WriteString(namespace)
483+
buf.WriteByte('_')
484+
buf.WriteString(name)
485+
486+
return buf.String()
487+
}
488+
476489
// ComposeRouteName uses namespace, name and rule name to compose
477490
// the route name.
478491
func ComposeRouteName(namespace, name string, rule string) string {

internal/adc/translator/apisixtls.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/apache/apisix-ingress-controller/internal/controller/label"
2828
"github.com/apache/apisix-ingress-controller/internal/id"
2929
"github.com/apache/apisix-ingress-controller/internal/provider"
30+
internaltypes "github.com/apache/apisix-ingress-controller/internal/types"
3031
)
3132

3233
func (t *Translator) TranslateApisixTls(tctx *provider.TranslateContext, tls *apiv2.ApisixTls) (*TranslateResult, error) {
@@ -57,7 +58,7 @@ func (t *Translator) TranslateApisixTls(tctx *provider.TranslateContext, tls *ap
5758
// Create SSL object
5859
ssl := &adctypes.SSL{
5960
Metadata: adctypes.Metadata{
60-
ID: id.GenID(tls.Namespace + "_" + tls.Name),
61+
ID: id.GenID(adctypes.ComposeSSLName(internaltypes.KindApisixTls, tls.Namespace, tls.Name)),
6162
Labels: label.GenLabel(tls),
6263
},
6364
Certificates: []adctypes.Certificate{

internal/adc/translator/gateway.go

Lines changed: 2 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"encoding/json"
2323
"encoding/pem"
2424
"fmt"
25-
"slices"
2625

2726
"github.com/pkg/errors"
2827
corev1 "k8s.io/api/core/v1"
@@ -50,7 +49,6 @@ func (t *Translator) TranslateGateway(tctx *provider.TranslateContext, obj *gate
5049
result.SSL = append(result.SSL, ssl...)
5150
}
5251
}
53-
result.SSL = mergeSSLWithSameID(result.SSL)
5452

5553
rk := utils.NamespacedNameKind(obj)
5654
gatewayProxy, ok := tctx.GatewayProxies[rk]
@@ -80,7 +78,7 @@ func (t *Translator) translateSecret(tctx *provider.TranslateContext, listener g
8078
sslObjs := make([]*adctypes.SSL, 0)
8179
switch *listener.TLS.Mode {
8280
case gatewayv1.TLSModeTerminate:
83-
for _, ref := range listener.TLS.CertificateRefs {
81+
for refIndex, ref := range listener.TLS.CertificateRefs {
8482
ns := obj.GetNamespace()
8583
if ref.Namespace != nil {
8684
ns = string(*ref.Namespace)
@@ -122,8 +120,7 @@ func (t *Translator) translateSecret(tctx *provider.TranslateContext, listener g
122120
}
123121
sslObj.Snis = append(sslObj.Snis, hosts...)
124122
}
125-
// Note: use cert as id to avoid duplicate certificate across ssl objects
126-
sslObj.ID = id.GenID(string(cert))
123+
sslObj.ID = id.GenID(fmt.Sprintf("%s_%s_%d", adctypes.ComposeSSLName(internaltypes.KindGateway, obj.Namespace, obj.Name), listener.Name, refIndex))
127124
t.Log.V(1).Info("generated ssl id", "ssl id", sslObj.ID, "secret", secretNN.String())
128125
sslObj.Labels = label.GenLabel(obj)
129126
sslObjs = append(sslObjs, sslObj)
@@ -241,47 +238,3 @@ func (t *Translator) fillPluginMetadataFromGatewayProxy(pluginMetadata adctypes.
241238
pluginMetadata[pluginName] = pluginConfig
242239
}
243240
}
244-
245-
// mergeSSLWithSameID merge ssl with same id
246-
func mergeSSLWithSameID(sslList []*adctypes.SSL) []*adctypes.SSL {
247-
if len(sslList) <= 1 {
248-
return sslList
249-
}
250-
251-
// create a map to store ssl with same id
252-
sslMap := make(map[string]*adctypes.SSL)
253-
for _, ssl := range sslList {
254-
if existing, exists := sslMap[ssl.ID]; exists {
255-
// if ssl with same id exists, merge their snis
256-
// use map to deduplicate
257-
sniMap := make(map[string]struct{})
258-
// add existing snis
259-
for _, sni := range existing.Snis {
260-
sniMap[sni] = struct{}{}
261-
}
262-
// add new snis
263-
for _, sni := range ssl.Snis {
264-
sniMap[sni] = struct{}{}
265-
}
266-
// rebuild deduplicated snis list
267-
newSnis := make([]string, 0, len(sniMap))
268-
for sni := range sniMap {
269-
newSnis = append(newSnis, sni)
270-
}
271-
272-
slices.Sort(newSnis)
273-
// update existing ssl object
274-
existing.Snis = newSnis
275-
} else {
276-
slices.Sort(ssl.Snis)
277-
// if new ssl id, add to map
278-
sslMap[ssl.ID] = ssl
279-
}
280-
}
281-
282-
mergedSSL := make([]*adctypes.SSL, 0, len(sslMap))
283-
for _, ssl := range sslMap {
284-
mergedSSL = append(mergedSSL, ssl)
285-
}
286-
return mergedSSL
287-
}

internal/adc/translator/ingress.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import (
3333
internaltypes "github.com/apache/apisix-ingress-controller/internal/types"
3434
)
3535

36-
func (t *Translator) translateIngressTLS(ingressTLS *networkingv1.IngressTLS, secret *corev1.Secret, labels map[string]string) (*adctypes.SSL, error) {
36+
func (t *Translator) translateIngressTLS(namespace, name string, tlsIndex int, ingressTLS *networkingv1.IngressTLS, secret *corev1.Secret, labels map[string]string) (*adctypes.SSL, error) {
3737
// extract the key pair from the secret
3838
cert, key, err := extractKeyPair(secret, true)
3939
if err != nil {
@@ -64,7 +64,7 @@ func (t *Translator) translateIngressTLS(ingressTLS *networkingv1.IngressTLS, se
6464
},
6565
Snis: hosts,
6666
}
67-
ssl.ID = id.GenID(string(cert))
67+
ssl.ID = id.GenID(fmt.Sprintf("%s_%d", adctypes.ComposeSSLName(internaltypes.KindIngress, namespace, name), tlsIndex))
6868

6969
return ssl, nil
7070
}
@@ -75,7 +75,7 @@ func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *netw
7575
labels := label.GenLabel(obj)
7676

7777
// handle TLS configuration, convert to SSL objects
78-
for _, tls := range obj.Spec.TLS {
78+
for tlsIndex, tls := range obj.Spec.TLS {
7979
if tls.SecretName == "" {
8080
continue
8181
}
@@ -86,7 +86,7 @@ func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *netw
8686
if secret == nil {
8787
continue
8888
}
89-
ssl, err := t.translateIngressTLS(&tls, secret, labels)
89+
ssl, err := t.translateIngressTLS(obj.Namespace, obj.Name, tlsIndex, &tls, secret, labels)
9090
if err != nil {
9191
return nil, err
9292
}

internal/provider/init/init.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818
package init
1919

2020
import (
21+
"github.com/go-logr/logr"
22+
2123
"github.com/apache/apisix-ingress-controller/internal/controller/status"
2224
"github.com/apache/apisix-ingress-controller/internal/manager/readiness"
2325
"github.com/apache/apisix-ingress-controller/internal/provider"
2426
"github.com/apache/apisix-ingress-controller/internal/provider/apisix"
25-
"github.com/go-logr/logr"
2627
)
2728

2829
func init() {

internal/provider/register.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ import (
2121
"fmt"
2222
"net/http"
2323

24+
"github.com/go-logr/logr"
25+
2426
"github.com/apache/apisix-ingress-controller/internal/controller/status"
2527
"github.com/apache/apisix-ingress-controller/internal/manager/readiness"
26-
"github.com/go-logr/logr"
2728
)
2829

2930
type RegisterHandler interface {

test/e2e/crds/v2/tls.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,5 +336,107 @@ spec:
336336
assert.Contains(GinkgoT(), tls[0].Client.SkipMtlsURIRegex, skipMtlsUriRegex, "skip_mtls_uri_regex should be set")
337337
})
338338

339+
It("ApisixTls and Ingress with same certificate but different hosts", func() {
340+
By("create shared TLS secret")
341+
err := s.NewKubeTlsSecret("shared-tls-secret", Cert, Key)
342+
Expect(err).NotTo(HaveOccurred(), "creating shared TLS secret")
343+
344+
const apisixTlsSpec = `
345+
apiVersion: apisix.apache.org/v2
346+
kind: ApisixTls
347+
metadata:
348+
name: test-apisixtls-shared
349+
spec:
350+
ingressClassName: %s
351+
hosts:
352+
- api6.com
353+
secret:
354+
name: shared-tls-secret
355+
namespace: %s
356+
`
357+
358+
By("apply ApisixTls with api6.com")
359+
var apisixTls apiv2.ApisixTls
360+
tlsSpec := fmt.Sprintf(apisixTlsSpec, s.Namespace(), s.Namespace())
361+
applier.MustApplyAPIv2(types.NamespacedName{Namespace: s.Namespace(), Name: "test-apisixtls-shared"}, &apisixTls, tlsSpec)
362+
363+
const ingressYamlWithTLS = `
364+
apiVersion: networking.k8s.io/v1
365+
kind: Ingress
366+
metadata:
367+
name: test-ingress-tls-shared
368+
spec:
369+
ingressClassName: %s
370+
tls:
371+
- hosts:
372+
- api7.com
373+
secretName: shared-tls-secret
374+
rules:
375+
- host: api7.com
376+
http:
377+
paths:
378+
- path: /
379+
pathType: Prefix
380+
backend:
381+
service:
382+
name: httpbin-service-e2e-test
383+
port:
384+
number: 80
385+
`
386+
387+
By("apply Ingress with api7.com using same certificate")
388+
err = s.CreateResourceFromString(fmt.Sprintf(ingressYamlWithTLS, s.Namespace()))
389+
Expect(err).NotTo(HaveOccurred(), "creating Ingress")
390+
391+
By("verify two SSL objects exist in control plane")
392+
Eventually(func() bool {
393+
tls, err := s.DefaultDataplaneResource().SSL().List(context.Background())
394+
if err != nil {
395+
return false
396+
}
397+
return len(tls) == 2
398+
}).WithTimeout(30 * time.Second).ProbeEvery(1 * time.Second).Should(BeTrue())
399+
400+
tls, err := s.DefaultDataplaneResource().SSL().List(context.Background())
401+
assert.Nil(GinkgoT(), err, "list tls error")
402+
assert.Len(GinkgoT(), tls, 2, "should have exactly 2 SSL objects")
403+
404+
By("verify SSL objects have different IDs and SNIs")
405+
sniFound := make(map[string]bool)
406+
407+
for i := range tls {
408+
// Check certificate content is the same
409+
assert.Len(GinkgoT(), tls[i].Certificates, 1, "each SSL should have 1 certificate")
410+
assert.Equal(GinkgoT(), Cert, tls[i].Certificates[0].Certificate, "certificate should match")
411+
412+
// Track SNIs
413+
for _, sni := range tls[i].Snis {
414+
sniFound[sni] = true
415+
}
416+
}
417+
418+
By("verify both hosts are covered")
419+
assert.True(GinkgoT(), sniFound["api6.com"], "api6.com should be in SNIs")
420+
assert.True(GinkgoT(), sniFound["api7.com"], "api7.com should be in SNIs")
421+
422+
By("test HTTPS request to api6.com")
423+
Eventually(func() int {
424+
return s.NewAPISIXHttpsClient("api6.com").
425+
GET("/get").
426+
WithHost("api6.com").
427+
Expect().
428+
Raw().StatusCode
429+
}).WithTimeout(30 * time.Second).ProbeEvery(1 * time.Second).Should(Equal(http.StatusOK))
430+
431+
By("test HTTPS request to api7.com")
432+
Eventually(func() int {
433+
return s.NewAPISIXHttpsClient("api7.com").
434+
GET("/get").
435+
WithHost("api7.com").
436+
Expect().
437+
Raw().StatusCode
438+
}).WithTimeout(30 * time.Second).ProbeEvery(1 * time.Second).Should(Equal(http.StatusOK))
439+
})
440+
339441
})
340442
})

test/e2e/gatewayapi/gateway.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,8 @@ spec:
284284
Eventually(func() error {
285285
tls, err := s.DefaultDataplaneResource().SSL().List(context.Background())
286286
Expect(err).NotTo(HaveOccurred(), "list ssl")
287-
if len(tls) != 1 {
288-
return fmt.Errorf("expect 1 ssl, got %d", len(tls))
287+
if len(tls) != 2 {
288+
return fmt.Errorf("expect 2 ssl, got %d", len(tls))
289289
}
290290
if len(tls[0].Certificates) != 1 {
291291
return fmt.Errorf("expect 1 certificate, got %d", len(tls[0].Certificates))
@@ -305,7 +305,7 @@ spec:
305305
Eventually(func() string {
306306
tls, err := s.DefaultDataplaneResource().SSL().List(context.Background())
307307
Expect(err).NotTo(HaveOccurred(), "list ssl")
308-
if len(tls) < 1 {
308+
if len(tls) != 2 {
309309
return ""
310310
}
311311
if len(tls[0].Certificates) < 1 {

test/e2e/gatewayapi/tcproute.go

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -72,33 +72,25 @@ spec:
7272
NotTo(HaveOccurred(), "creating GatewayProxy")
7373

7474
// Create GatewayClass
75-
gatewayClassName := s.Namespace()
7675
Expect(s.CreateResourceFromString(s.GetGatewayClassYaml())).
7776
NotTo(HaveOccurred(), "creating GatewayClass")
78-
gcyaml, _ := s.GetResourceYaml("GatewayClass", gatewayClassName)
79-
s.ResourceApplied("GatewayClass", gatewayClassName, gcyaml, 1)
8077

8178
// Create Gateway with TCP listener
82-
gatewayName := s.Namespace()
83-
Expect(s.CreateResourceFromString(fmt.Sprintf(tcpGateway, gatewayName, gatewayClassName))).
79+
Expect(s.CreateResourceFromString(fmt.Sprintf(tcpGateway, s.Namespace(), s.Namespace()))).
8480
NotTo(HaveOccurred(), "creating Gateway")
85-
86-
gwyaml, _ := s.GetResourceYaml("Gateway", gatewayName)
87-
s.ResourceApplied("Gateway", gatewayName, gwyaml, 1)
8881
})
8982

9083
It("should route TCP traffic to backend service", func() {
91-
gatewayName := s.Namespace()
9284
By("creating TCPRoute")
93-
Expect(s.CreateResourceFromString(fmt.Sprintf(tcpRoute, gatewayName))).
85+
Expect(s.CreateResourceFromString(fmt.Sprintf(tcpRoute, s.Namespace()))).
9486
NotTo(HaveOccurred(), "creating TCPRoute")
9587

9688
// Verify TCPRoute status becomes programmed
9789
routeYaml, _ := s.GetResourceYaml("TCPRoute", "tcp-app-1")
9890
s.ResourceApplied("TCPRoute", "tcp-app-1", routeYaml, 1)
9991

10092
By("verifying TCPRoute is functional")
101-
s.HTTPOverTCPConnectAssert(true, time.Minute*5) // should be able to connect
93+
s.HTTPOverTCPConnectAssert(true, time.Minute*3) // should be able to connect
10294
By("sending TCP traffic to verify routing")
10395
s.RequestAssert(&scaffold.RequestAssert{
10496
Client: s.NewAPISIXClientOnTCPPort(),
@@ -113,7 +105,7 @@ spec:
113105
Expect(s.DeleteResource("TCPRoute", "tcp-app-1")).
114106
NotTo(HaveOccurred(), "deleting TCPRoute")
115107

116-
s.HTTPOverTCPConnectAssert(false, time.Minute*5)
108+
s.HTTPOverTCPConnectAssert(false, time.Minute*3)
117109
})
118110
})
119111
})

0 commit comments

Comments
 (0)