Skip to content

Commit 64e5093

Browse files
fix(translator): reject CA Secrets with multiple PEM certs (#7763)
* docs: update API references and bump crd-ref-docs to 0.2.0 * fix(translator): reject CA Secrets with multiple PEM certs * update CHANGELOG Signed-off-by: Jintao Zhang <zhangjintao9020@gmail.com> --------- Signed-off-by: Jintao Zhang <zhangjintao9020@gmail.com>
1 parent 5e139f5 commit 64e5093

File tree

6 files changed

+96
-16
lines changed

6 files changed

+96
-16
lines changed

.tools_versions.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ skaffold: "2.16.1"
1111
# renovate: datasource=github-releases depName=kubernetes-sigs/controller-runtime
1212
setup-envtest: "0.21.0"
1313
# renovate: datasource=github-releases depName=elastic/crd-ref-docs
14-
crd-ref-docs: "0.1.0"
14+
crd-ref-docs: "0.2.0"
1515
# renovate: datasource=github-releases depName=mikefarah/yq
1616
yq: "4.47.2"
1717
# renovate: datasource=github-releases depName=jstemmer/go-junit-report

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ Adding a new version? You'll need three changes:
126126
that KIC deletes the certificates of listeners on dataplane pods deleted when
127127
KIC is running under the control of Kong gateway operator.
128128
[#7666](https://github.com/Kong/kubernetes-ingress-controller/pull/7666)
129+
- Reject CA Secrets with multiple PEM certs.
130+
[#7763](https://github.com/Kong/kubernetes-ingress-controller/pull/7763)
129131

130132
## [3.5.1]
131133

docs/api-reference.md

Lines changed: 4 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/incubator-api-reference.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ KongServiceFacade allows creating separate Kong Services for a single Kubernetes
1616
Service. It can be used as Kubernetes Ingress' backend (via its path's `backend.resource`
1717
field). It's designed to enable creating two "virtual" Services in Kong that will point
1818
to the same Kubernetes Service, but will have different configuration (e.g. different
19-
set of plugins, different load balancing algorithm, etc.).<br /><br />
20-
KongServiceFacade requires `kubernetes.io/ingress.class` annotation with a value
19+
set of plugins, different load balancing algorithm, etc.).<br /><br />KongServiceFacade requires `kubernetes.io/ingress.class` annotation with a value
2120
matching the ingressClass of the Kong Ingress Controller (`kong` by default) to be reconciled.
2221

2322
<!-- kong_service_facade description placeholder -->

internal/dataplane/translator/translate_cacerts.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package translator
22

33
import (
4+
"bytes"
45
"crypto/x509"
56
"encoding/json"
67
"encoding/pem"
@@ -82,11 +83,29 @@ func (t *Translator) getCACerts() []kong.CACertificate {
8283
}
8384

8485
func toKongCACertificate(caCertBytes []byte, object client.Object, secretID string) (kong.CACertificate, error) {
85-
pemBlock, _ := pem.Decode(caCertBytes)
86-
if pemBlock == nil {
86+
rest := bytes.TrimSpace(caCertBytes)
87+
pemBlocks := make([][]byte, 0, 1)
88+
89+
for len(rest) > 0 {
90+
block, r := pem.Decode(rest)
91+
if block == nil {
92+
return kong.CACertificate{}, errors.New("invalid PEM block")
93+
}
94+
if block.Type != "CERTIFICATE" {
95+
return kong.CACertificate{}, errors.New("invalid PEM block type")
96+
}
97+
pemBlocks = append(pemBlocks, block.Bytes)
98+
rest = bytes.TrimSpace(r)
99+
}
100+
101+
if len(pemBlocks) == 0 {
87102
return kong.CACertificate{}, errors.New("invalid PEM block")
88103
}
89-
x509Cert, err := x509.ParseCertificate(pemBlock.Bytes)
104+
if len(pemBlocks) > 1 {
105+
return kong.CACertificate{}, errors.New("multiple PEM certificates found")
106+
}
107+
108+
x509Cert, err := x509.ParseCertificate(pemBlocks[0])
90109
if err != nil {
91110
return kong.CACertificate{}, errors.New("failed to parse certificate")
92111
}
@@ -97,9 +116,12 @@ func toKongCACertificate(caCertBytes []byte, object client.Object, secretID stri
97116
return kong.CACertificate{}, errors.New("expired")
98117
}
99118

119+
// Re-encode a single clean CERTIFICATE PEM block to ensure only one is sent to Kong.
120+
singlePEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: pemBlocks[0]})
121+
100122
return kong.CACertificate{
101123
ID: kong.String(secretID),
102-
Cert: kong.String(string(caCertBytes)),
124+
Cert: kong.String(string(singlePEM)),
103125
Tags: util.GenerateTagsForObject(object),
104126
}, nil
105127
}

test/integration/translation_failures_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"testing"
1010
"time"
1111

12+
"github.com/google/uuid"
1213
"github.com/kong/go-kong/kong"
1314
"github.com/kong/kubernetes-testing-framework/pkg/clusters"
1415
"github.com/kong/kubernetes-testing-framework/pkg/utils/kubernetes/generators"
@@ -29,6 +30,7 @@ import (
2930
"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
3031
"github.com/kong/kubernetes-ingress-controller/v3/test"
3132
"github.com/kong/kubernetes-ingress-controller/v3/test/consts"
33+
"github.com/kong/kubernetes-ingress-controller/v3/test/helpers/certificate"
3234
"github.com/kong/kubernetes-ingress-controller/v3/test/internal/helpers"
3335
testutils "github.com/kong/kubernetes-ingress-controller/v3/test/util"
3436
)
@@ -51,6 +53,38 @@ func TestTranslationFailures(t *testing.T) {
5153
// that we expect translation failure warning events to be created for.
5254
translationFailureTrigger func(t *testing.T, cleaner *clusters.Cleaner, ns string) expectedTranslationFailure
5355
}{
56+
{
57+
name: "CA secret with multiple PEMs",
58+
translationFailureTrigger: func(t *testing.T, cleaner *clusters.Cleaner, ns string) expectedTranslationFailure {
59+
createdSecret, err := env.Cluster().Client().CoreV1().Secrets(ns).Create(ctx, multiPEMCASecret(ns, uuid.NewString()), metav1.CreateOptions{})
60+
require.NoError(t, err)
61+
cleaner.Add(createdSecret)
62+
63+
return expectedTranslationFailure{
64+
causingObjects: []client.Object{createdSecret},
65+
reasonContains: "multiple PEM certificates found",
66+
}
67+
},
68+
},
69+
{
70+
name: "CA secret with multiple PEMs referred by a plugin",
71+
translationFailureTrigger: func(t *testing.T, cleaner *clusters.Cleaner, ns string) expectedTranslationFailure {
72+
createdSecret, err := env.Cluster().Client().CoreV1().Secrets(ns).Create(ctx, multiPEMCASecret(ns, invalidCASecretID), metav1.CreateOptions{})
73+
require.NoError(t, err)
74+
cleaner.Add(createdSecret)
75+
76+
c, err := clientset.NewForConfig(env.Cluster().Config())
77+
require.NoError(t, err)
78+
createdPlugin, err := c.ConfigurationV1().KongPlugins(ns).Create(ctx, pluginUsingInvalidCACert(ns), metav1.CreateOptions{})
79+
require.NoError(t, err)
80+
cleaner.Add(createdPlugin)
81+
82+
return expectedTranslationFailure{
83+
causingObjects: []client.Object{createdSecret, createdPlugin},
84+
reasonContains: "multiple PEM certificates found",
85+
}
86+
},
87+
},
5488
{
5589
name: "invalid CA secret",
5690
translationFailureTrigger: func(t *testing.T, cleaner *clusters.Cleaner, ns string) expectedTranslationFailure {
@@ -363,6 +397,34 @@ func invalidCASecret(ns string) *corev1.Secret {
363397
}
364398
}
365399

400+
func multiPEMCASecret(ns, id string) *corev1.Secret {
401+
ca1, _ := certificate.MustGenerateCertPEMFormat(
402+
certificate.WithCommonName("test-ca-1"),
403+
certificate.WithCATrue(),
404+
)
405+
ca2, _ := certificate.MustGenerateCertPEMFormat(
406+
certificate.WithCommonName("test-ca-2"),
407+
certificate.WithCATrue(),
408+
)
409+
410+
return &corev1.Secret{
411+
ObjectMeta: metav1.ObjectMeta{
412+
Name: testutils.RandomName(testTranslationFailuresObjectsPrefix),
413+
Namespace: ns,
414+
Labels: map[string]string{
415+
"konghq.com/ca-cert": "true",
416+
},
417+
Annotations: map[string]string{
418+
annotations.IngressClassKey: consts.IngressClass,
419+
},
420+
},
421+
StringData: map[string]string{
422+
"id": id,
423+
"cert": string(ca1) + string(ca2),
424+
},
425+
}
426+
}
427+
366428
func pluginUsingInvalidCACert(ns string) *configurationv1.KongPlugin {
367429
return &configurationv1.KongPlugin{
368430
ObjectMeta: metav1.ObjectMeta{

0 commit comments

Comments
 (0)