Skip to content

Commit 008287a

Browse files
fix(translator): reject CA Secrets with multiple PEM certs (#2671)
Signed-off-by: Jintao Zhang <[email protected]>
1 parent 9ebfdaa commit 008287a

File tree

3 files changed

+90
-4
lines changed

3 files changed

+90
-4
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@
155155
- Translate `healtchchecks.thershold` in `KongUpstreamPolicy` to the
156156
`healthchecks.thershold` field in Kong upstreams.
157157
[#2662](https://github.com/Kong/kong-operator/pull/2662)
158+
- Reject CA Secrets with multiple PEM certs.
159+
[#2671](https://github.com/Kong/kong-operator/pull/2671)
158160

159161
## [v2.0.5]
160162

ingress-controller/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
}

ingress-controller/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"
@@ -27,6 +28,7 @@ import (
2728
"github.com/kong/kong-operator/ingress-controller/internal/util"
2829
"github.com/kong/kong-operator/ingress-controller/test"
2930
"github.com/kong/kong-operator/ingress-controller/test/consts"
31+
"github.com/kong/kong-operator/ingress-controller/test/helpers/certificate"
3032
"github.com/kong/kong-operator/ingress-controller/test/internal/helpers"
3133
testutils "github.com/kong/kong-operator/ingress-controller/test/util"
3234
"github.com/kong/kong-operator/pkg/clientset"
@@ -50,6 +52,38 @@ func TestTranslationFailures(t *testing.T) {
5052
// that we expect translation failure warning events to be created for.
5153
translationFailureTrigger func(t *testing.T, cleaner *clusters.Cleaner, ns string) expectedTranslationFailure
5254
}{
55+
{
56+
name: "CA secret with multiple PEMs",
57+
translationFailureTrigger: func(t *testing.T, cleaner *clusters.Cleaner, ns string) expectedTranslationFailure {
58+
createdSecret, err := env.Cluster().Client().CoreV1().Secrets(ns).Create(ctx, multiPEMCASecret(ns, uuid.NewString()), metav1.CreateOptions{})
59+
require.NoError(t, err)
60+
cleaner.Add(createdSecret)
61+
62+
return expectedTranslationFailure{
63+
causingObjects: []client.Object{createdSecret},
64+
reasonContains: "multiple PEM certificates found",
65+
}
66+
},
67+
},
68+
{
69+
name: "CA secret with multiple PEMs referred by a plugin",
70+
translationFailureTrigger: func(t *testing.T, cleaner *clusters.Cleaner, ns string) expectedTranslationFailure {
71+
createdSecret, err := env.Cluster().Client().CoreV1().Secrets(ns).Create(ctx, multiPEMCASecret(ns, invalidCASecretID), metav1.CreateOptions{})
72+
require.NoError(t, err)
73+
cleaner.Add(createdSecret)
74+
75+
c, err := clientset.NewForConfig(env.Cluster().Config())
76+
require.NoError(t, err)
77+
createdPlugin, err := c.ConfigurationV1().KongPlugins(ns).Create(ctx, pluginUsingInvalidCACert(ns), metav1.CreateOptions{})
78+
require.NoError(t, err)
79+
cleaner.Add(createdPlugin)
80+
81+
return expectedTranslationFailure{
82+
causingObjects: []client.Object{createdSecret, createdPlugin},
83+
reasonContains: "multiple PEM certificates found",
84+
}
85+
},
86+
},
5387
{
5488
name: "invalid CA secret",
5589
translationFailureTrigger: func(t *testing.T, cleaner *clusters.Cleaner, ns string) expectedTranslationFailure {
@@ -362,6 +396,34 @@ func invalidCASecret(ns string) *corev1.Secret {
362396
}
363397
}
364398

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

0 commit comments

Comments
 (0)