Skip to content

Commit 5b5d313

Browse files
committed
feat: add certificate conflict detection to admission webhooks (#2603)
Signed-off-by: Ashing Zheng <[email protected]>
1 parent 0490f25 commit 5b5d313

File tree

13 files changed

+2510
-73
lines changed

13 files changed

+2510
-73
lines changed

internal/adc/translator/apisixtls.go

Lines changed: 3 additions & 2 deletions
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+
sslutils "github.com/apache/apisix-ingress-controller/internal/ssl"
3031
internaltypes "github.com/apache/apisix-ingress-controller/internal/types"
3132
)
3233

@@ -44,7 +45,7 @@ func (t *Translator) TranslateApisixTls(tctx *provider.TranslateContext, tls *ap
4445
}
4546

4647
// Extract cert and key from secret
47-
cert, key, err := extractKeyPair(secret, true)
48+
cert, key, err := sslutils.ExtractKeyPair(secret, true)
4849
if err != nil {
4950
return nil, err
5051
}
@@ -81,7 +82,7 @@ func (t *Translator) TranslateApisixTls(tctx *provider.TranslateContext, tls *ap
8182
return nil, fmt.Errorf("client CA secret %s not found", caSecretKey.String())
8283
}
8384

84-
ca, _, err := extractKeyPair(caSecret, false)
85+
ca, _, err := sslutils.ExtractKeyPair(caSecret, false)
8586
if err != nil {
8687
return nil, err
8788
}

internal/adc/translator/apisixupstream.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/apache/apisix-ingress-controller/api/adc"
3030
apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
3131
"github.com/apache/apisix-ingress-controller/internal/provider"
32+
sslutils "github.com/apache/apisix-ingress-controller/internal/ssl"
3233
"github.com/apache/apisix-ingress-controller/internal/utils"
3334
)
3435

@@ -187,7 +188,7 @@ func translateApisixUpstreamClientTLS(tctx *provider.TranslateContext, config *a
187188
return errors.Errorf("sercret %s not found", secretNN)
188189
}
189190

190-
cert, key, err := extractKeyPair(secret, true)
191+
cert, key, err := sslutils.ExtractKeyPair(secret, true)
191192
if err != nil {
192193
return err
193194
}

internal/adc/translator/gateway.go

Lines changed: 3 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,10 @@
1818
package translator
1919

2020
import (
21-
"crypto/x509"
2221
"encoding/json"
23-
"encoding/pem"
2422
"fmt"
2523

2624
"github.com/pkg/errors"
27-
corev1 "k8s.io/api/core/v1"
2825
"k8s.io/apimachinery/pkg/types"
2926
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
3027

@@ -33,6 +30,7 @@ import (
3330
"github.com/apache/apisix-ingress-controller/internal/controller/label"
3431
"github.com/apache/apisix-ingress-controller/internal/id"
3532
"github.com/apache/apisix-ingress-controller/internal/provider"
33+
sslutils "github.com/apache/apisix-ingress-controller/internal/ssl"
3634
internaltypes "github.com/apache/apisix-ingress-controller/internal/types"
3735
"github.com/apache/apisix-ingress-controller/internal/utils"
3836
)
@@ -97,7 +95,7 @@ func (t *Translator) translateSecret(tctx *provider.TranslateContext, listener g
9795
t.Log.Error(errors.New("secret data is nil"), "failed to get secret data", "secret", secretNN)
9896
return nil, fmt.Errorf("no secret data found for %s/%s", ns, name)
9997
}
100-
cert, key, err := extractKeyPair(secret, true)
98+
cert, key, err := sslutils.ExtractKeyPair(secret, true)
10199
if err != nil {
102100
t.Log.Error(err, "extract key pair", "secret", secretNN)
103101
return nil, err
@@ -110,7 +108,7 @@ func (t *Translator) translateSecret(tctx *provider.TranslateContext, listener g
110108
if listener.Hostname != nil && *listener.Hostname != "" {
111109
sslObj.Snis = append(sslObj.Snis, string(*listener.Hostname))
112110
} else {
113-
hosts, err := extractHost(cert)
111+
hosts, err := sslutils.ExtractHostsFromCertificate(cert)
114112
if err != nil {
115113
return nil, err
116114
}
@@ -137,68 +135,6 @@ func (t *Translator) translateSecret(tctx *provider.TranslateContext, listener g
137135
return sslObjs, nil
138136
}
139137

140-
func extractHost(cert []byte) ([]string, error) {
141-
block, _ := pem.Decode(cert)
142-
if block == nil {
143-
return nil, errors.New("parse certificate: not in PEM format")
144-
}
145-
der, err := x509.ParseCertificate(block.Bytes)
146-
if err != nil {
147-
return nil, errors.Wrap(err, "parse certificate")
148-
}
149-
hosts := make([]string, 0, len(der.DNSNames))
150-
for _, dnsName := range der.DNSNames {
151-
if dnsName != "*" {
152-
hosts = append(hosts, dnsName)
153-
}
154-
}
155-
return hosts, nil
156-
}
157-
158-
func extractKeyPair(s *corev1.Secret, hasPrivateKey bool) ([]byte, []byte, error) {
159-
if _, ok := s.Data["cert"]; ok {
160-
return extractApisixSecretKeyPair(s, hasPrivateKey)
161-
} else if _, ok := s.Data[corev1.TLSCertKey]; ok {
162-
return extractKubeSecretKeyPair(s, hasPrivateKey)
163-
} else if ca, ok := s.Data[corev1.ServiceAccountRootCAKey]; ok && !hasPrivateKey {
164-
return ca, nil, nil
165-
} else {
166-
return nil, nil, errors.New("unknown secret format")
167-
}
168-
}
169-
170-
func extractApisixSecretKeyPair(s *corev1.Secret, hasPrivateKey bool) (cert []byte, key []byte, err error) {
171-
var ok bool
172-
cert, ok = s.Data["cert"]
173-
if !ok {
174-
return nil, nil, errors.New("missing cert field")
175-
}
176-
177-
if hasPrivateKey {
178-
key, ok = s.Data["key"]
179-
if !ok {
180-
return nil, nil, errors.New("missing key field")
181-
}
182-
}
183-
return
184-
}
185-
186-
func extractKubeSecretKeyPair(s *corev1.Secret, hasPrivateKey bool) (cert []byte, key []byte, err error) {
187-
var ok bool
188-
cert, ok = s.Data[corev1.TLSCertKey]
189-
if !ok {
190-
return nil, nil, errors.New("missing cert field")
191-
}
192-
193-
if hasPrivateKey {
194-
key, ok = s.Data[corev1.TLSPrivateKeyKey]
195-
if !ok {
196-
return nil, nil, errors.New("missing key field")
197-
}
198-
}
199-
return
200-
}
201-
202138
// fillPluginsFromGatewayProxy fill plugins from GatewayProxy to given plugins
203139
func (t *Translator) fillPluginsFromGatewayProxy(plugins adctypes.GlobalRule, gatewayProxy *v1alpha1.GatewayProxy) {
204140
if gatewayProxy == nil {

internal/adc/translator/ingress.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,20 @@ import (
3232
"github.com/apache/apisix-ingress-controller/internal/controller/label"
3333
"github.com/apache/apisix-ingress-controller/internal/id"
3434
"github.com/apache/apisix-ingress-controller/internal/provider"
35+
sslutils "github.com/apache/apisix-ingress-controller/internal/ssl"
3536
internaltypes "github.com/apache/apisix-ingress-controller/internal/types"
3637
)
3738

3839
func (t *Translator) translateIngressTLS(namespace, name string, tlsIndex int, ingressTLS *networkingv1.IngressTLS, secret *corev1.Secret, labels map[string]string) (*adctypes.SSL, error) {
3940
// extract the key pair from the secret
40-
cert, key, err := extractKeyPair(secret, true)
41+
cert, key, err := sslutils.ExtractKeyPair(secret, true)
4142
if err != nil {
4243
return nil, err
4344
}
4445

4546
hosts := ingressTLS.Hosts
4647
if len(hosts) == 0 {
47-
certHosts, err := extractHost(cert)
48+
certHosts, err := sslutils.ExtractHostsFromCertificate(cert)
4849
if err != nil {
4950
return nil, err
5051
}

internal/controller/indexer/indexer.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const (
5050
IngressClassParametersRef = "ingressClassParametersRef"
5151
ConsumerGatewayRef = "consumerGatewayRef"
5252
PolicyTargetRefs = "targetRefs"
53+
TLSHostIndexRef = "tlsHostRefs"
5354
GatewayClassIndexRef = "gatewayClassRef"
5455
ApisixUpstreamRef = "apisixUpstreamRef"
5556
PluginConfigIndexRef = "pluginConfigRefs"
@@ -140,6 +141,16 @@ func setupGatewayIndexer(mgr ctrl.Manager) error {
140141
); err != nil {
141142
return err
142143
}
144+
145+
if err := mgr.GetFieldIndexer().IndexField(
146+
context.Background(),
147+
&gatewayv1.Gateway{},
148+
TLSHostIndexRef,
149+
GatewayTLSHostIndexFunc,
150+
); err != nil {
151+
return err
152+
}
153+
143154
return nil
144155
}
145156

@@ -460,6 +471,15 @@ func setupIngressIndexer(mgr ctrl.Manager) error {
460471
return err
461472
}
462473

474+
if err := mgr.GetFieldIndexer().IndexField(
475+
context.Background(),
476+
&networkingv1.Ingress{},
477+
TLSHostIndexRef,
478+
IngressTLSHostIndexFunc,
479+
); err != nil {
480+
return err
481+
}
482+
463483
return nil
464484
}
465485

@@ -925,6 +945,15 @@ func setupApisixTlsIndexer(mgr ctrl.Manager) error {
925945
return err
926946
}
927947

948+
if err := mgr.GetFieldIndexer().IndexField(
949+
context.Background(),
950+
&apiv2.ApisixTls{},
951+
TLSHostIndexRef,
952+
ApisixTlsHostIndexFunc,
953+
); err != nil {
954+
return err
955+
}
956+
928957
return nil
929958
}
930959

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package indexer
19+
20+
import (
21+
"sort"
22+
23+
networkingv1 "k8s.io/api/networking/v1"
24+
ctrl "sigs.k8s.io/controller-runtime"
25+
"sigs.k8s.io/controller-runtime/pkg/client"
26+
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
27+
28+
apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
29+
sslutil "github.com/apache/apisix-ingress-controller/internal/ssl"
30+
)
31+
32+
var (
33+
tlsHostIndexLogger = ctrl.Log.WithName("tls-host-indexer")
34+
// Empty host is used to match the resource which does not specify any explicit host.
35+
emptyHost = ""
36+
)
37+
38+
// GatewayTLSHostIndexFunc indexes Gateways by their TLS SNI hosts.
39+
func GatewayTLSHostIndexFunc(rawObj client.Object) []string {
40+
gateway, ok := rawObj.(*gatewayv1.Gateway)
41+
if !ok {
42+
return nil
43+
}
44+
if len(gateway.Spec.Listeners) == 0 {
45+
return nil
46+
}
47+
48+
hosts := make(map[string]struct{})
49+
50+
for _, listener := range gateway.Spec.Listeners {
51+
if listener.TLS == nil || len(listener.TLS.CertificateRefs) == 0 {
52+
continue
53+
}
54+
55+
hasExplicitHost := false
56+
if listener.Hostname != nil {
57+
candidates := sslutil.NormalizeHosts([]string{string(*listener.Hostname)})
58+
for _, host := range candidates {
59+
if host == "" {
60+
continue
61+
}
62+
hasExplicitHost = true
63+
hosts[host] = struct{}{}
64+
}
65+
}
66+
67+
if !hasExplicitHost {
68+
hosts[emptyHost] = struct{}{}
69+
}
70+
}
71+
72+
tlsHostIndexLogger.Info("GatewayTLSHostIndexFunc", "hosts", hostSetToSlice(hosts), "len", len(hostSetToSlice(hosts)))
73+
74+
return hostSetToSlice(hosts)
75+
}
76+
77+
// IngressTLSHostIndexFunc indexes Ingresses by their TLS SNI hosts.
78+
func IngressTLSHostIndexFunc(rawObj client.Object) []string {
79+
ingress, ok := rawObj.(*networkingv1.Ingress)
80+
if !ok {
81+
return nil
82+
}
83+
if len(ingress.Spec.TLS) == 0 {
84+
return nil
85+
}
86+
87+
hosts := make(map[string]struct{})
88+
for _, tls := range ingress.Spec.TLS {
89+
if tls.SecretName == "" {
90+
continue
91+
}
92+
93+
hasExplicitHost := false
94+
candidates := sslutil.NormalizeHosts(tls.Hosts)
95+
for _, host := range candidates {
96+
if host == "" {
97+
continue
98+
}
99+
hasExplicitHost = true
100+
hosts[host] = struct{}{}
101+
}
102+
103+
if !hasExplicitHost {
104+
hosts[emptyHost] = struct{}{}
105+
}
106+
}
107+
108+
return hostSetToSlice(hosts)
109+
}
110+
111+
// ApisixTlsHostIndexFunc indexes ApisixTls resources by their declared TLS hosts.
112+
func ApisixTlsHostIndexFunc(rawObj client.Object) []string {
113+
tls, ok := rawObj.(*apiv2.ApisixTls)
114+
if !ok {
115+
return nil
116+
}
117+
if len(tls.Spec.Hosts) == 0 {
118+
return nil
119+
}
120+
121+
hostSet := make(map[string]struct{}, len(tls.Spec.Hosts))
122+
for _, host := range tls.Spec.Hosts {
123+
for _, normalized := range sslutil.NormalizeHosts([]string{string(host)}) {
124+
if normalized == "" {
125+
continue
126+
}
127+
hostSet[normalized] = struct{}{}
128+
}
129+
}
130+
return hostSetToSlice(hostSet)
131+
}
132+
133+
func hostSetToSlice(set map[string]struct{}) []string {
134+
if len(set) == 0 {
135+
return nil
136+
}
137+
result := make([]string, 0, len(set))
138+
for host := range set {
139+
result = append(result, host)
140+
}
141+
sort.Strings(result)
142+
return result
143+
}

0 commit comments

Comments
 (0)