Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@
- Translate `healtchchecks.thershold` in `KongUpstreamPolicy` to the
`healthchecks.thershold` field in Kong upstreams.
[#2662](https://github.com/Kong/kong-operator/pull/2662)
- Reject CA Secrets with multiple PEM certs.
[#2671](https://github.com/Kong/kong-operator/pull/2671)

## [v2.0.5]

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package translator

import (
"bytes"
"crypto/x509"
"encoding/json"
"encoding/pem"
Expand Down Expand Up @@ -82,11 +83,29 @@ func (t *Translator) getCACerts() []kong.CACertificate {
}

func toKongCACertificate(caCertBytes []byte, object client.Object, secretID string) (kong.CACertificate, error) {
pemBlock, _ := pem.Decode(caCertBytes)
if pemBlock == nil {
rest := bytes.TrimSpace(caCertBytes)
pemBlocks := make([][]byte, 0, 1)

for len(rest) > 0 {
block, r := pem.Decode(rest)
if block == nil {
return kong.CACertificate{}, errors.New("invalid PEM block")
}
if block.Type != "CERTIFICATE" {
return kong.CACertificate{}, errors.New("invalid PEM block type")
}
pemBlocks = append(pemBlocks, block.Bytes)
rest = bytes.TrimSpace(r)
}

if len(pemBlocks) == 0 {
return kong.CACertificate{}, errors.New("invalid PEM block")
}
x509Cert, err := x509.ParseCertificate(pemBlock.Bytes)
if len(pemBlocks) > 1 {
return kong.CACertificate{}, errors.New("multiple PEM certificates found")
}

x509Cert, err := x509.ParseCertificate(pemBlocks[0])
if err != nil {
return kong.CACertificate{}, errors.New("failed to parse certificate")
}
Expand All @@ -97,9 +116,12 @@ func toKongCACertificate(caCertBytes []byte, object client.Object, secretID stri
return kong.CACertificate{}, errors.New("expired")
}

// Re-encode a single clean CERTIFICATE PEM block to ensure only one is sent to Kong.
singlePEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: pemBlocks[0]})

return kong.CACertificate{
ID: kong.String(secretID),
Cert: kong.String(string(caCertBytes)),
Cert: kong.String(string(singlePEM)),
Tags: util.GenerateTagsForObject(object),
}, nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"testing"
"time"

"github.com/google/uuid"
"github.com/kong/go-kong/kong"
"github.com/kong/kubernetes-testing-framework/pkg/clusters"
"github.com/kong/kubernetes-testing-framework/pkg/utils/kubernetes/generators"
Expand All @@ -27,6 +28,7 @@ import (
"github.com/kong/kong-operator/ingress-controller/internal/util"
"github.com/kong/kong-operator/ingress-controller/test"
"github.com/kong/kong-operator/ingress-controller/test/consts"
"github.com/kong/kong-operator/ingress-controller/test/helpers/certificate"
"github.com/kong/kong-operator/ingress-controller/test/internal/helpers"
testutils "github.com/kong/kong-operator/ingress-controller/test/util"
"github.com/kong/kong-operator/pkg/clientset"
Expand All @@ -50,6 +52,38 @@ func TestTranslationFailures(t *testing.T) {
// that we expect translation failure warning events to be created for.
translationFailureTrigger func(t *testing.T, cleaner *clusters.Cleaner, ns string) expectedTranslationFailure
}{
{
name: "CA secret with multiple PEMs",
translationFailureTrigger: func(t *testing.T, cleaner *clusters.Cleaner, ns string) expectedTranslationFailure {
createdSecret, err := env.Cluster().Client().CoreV1().Secrets(ns).Create(ctx, multiPEMCASecret(ns, uuid.NewString()), metav1.CreateOptions{})
require.NoError(t, err)
cleaner.Add(createdSecret)

return expectedTranslationFailure{
causingObjects: []client.Object{createdSecret},
reasonContains: "multiple PEM certificates found",
}
},
},
{
name: "CA secret with multiple PEMs referred by a plugin",
translationFailureTrigger: func(t *testing.T, cleaner *clusters.Cleaner, ns string) expectedTranslationFailure {
createdSecret, err := env.Cluster().Client().CoreV1().Secrets(ns).Create(ctx, multiPEMCASecret(ns, invalidCASecretID), metav1.CreateOptions{})
require.NoError(t, err)
cleaner.Add(createdSecret)

c, err := clientset.NewForConfig(env.Cluster().Config())
require.NoError(t, err)
createdPlugin, err := c.ConfigurationV1().KongPlugins(ns).Create(ctx, pluginUsingInvalidCACert(ns), metav1.CreateOptions{})
require.NoError(t, err)
cleaner.Add(createdPlugin)

return expectedTranslationFailure{
causingObjects: []client.Object{createdSecret, createdPlugin},
reasonContains: "multiple PEM certificates found",
}
},
},
{
name: "invalid CA secret",
translationFailureTrigger: func(t *testing.T, cleaner *clusters.Cleaner, ns string) expectedTranslationFailure {
Expand Down Expand Up @@ -362,6 +396,34 @@ func invalidCASecret(ns string) *corev1.Secret {
}
}

func multiPEMCASecret(ns, id string) *corev1.Secret {
ca1, _ := certificate.MustGenerateCertPEMFormat(
certificate.WithCommonName("test-ca-1"),
certificate.WithCATrue(),
)
ca2, _ := certificate.MustGenerateCertPEMFormat(
certificate.WithCommonName("test-ca-2"),
certificate.WithCATrue(),
)

return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: testutils.RandomName(testTranslationFailuresObjectsPrefix),
Namespace: ns,
Labels: map[string]string{
"konghq.com/ca-cert": "true",
},
Annotations: map[string]string{
annotations.IngressClassKey: consts.IngressClass,
},
},
StringData: map[string]string{
"id": id,
"cert": string(ca1) + string(ca2),
},
}
}

func pluginUsingInvalidCACert(ns string) *configurationv1.KongPlugin {
return &configurationv1.KongPlugin{
ObjectMeta: metav1.ObjectMeta{
Expand Down
Loading