Skip to content

Commit 6988320

Browse files
czeslavorainest
andauthored
feat(parser): propagate CA certificate failures (#3114)
Refactor the logging for plugin failures related to CA certificates. Forward CA certificate failures to the failure collector, to attach events to affected objects. Co-authored-by: Travis Raines <[email protected]>
1 parent 0e0685f commit 6988320

File tree

5 files changed

+127
-64
lines changed

5 files changed

+127
-64
lines changed

internal/dataplane/parser/parser.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ func (p *Parser) Build() (*kongstate.KongState, []TranslationFailure) {
126126
result.Certificates = mergeCerts(p.logger, ingressCerts, gatewayCerts)
127127

128128
// populate CA certificates in Kong
129-
result.CACertificates = getCACerts(p.logger, p.storer, result.Plugins)
129+
result.CACertificates = p.getCACerts()
130130

131131
return &result, p.popTranslationFailures()
132132
}
@@ -182,14 +182,32 @@ func (p *Parser) EnableRegexPathPrefix() {
182182
p.flagEnabledRegexPathPrefix = true
183183
}
184184

185-
func (p *Parser) popTranslationFailures() []TranslationFailure {
186-
return p.failuresCollector.PopTranslationFailures()
187-
}
188-
189185
// -----------------------------------------------------------------------------
190186
// Parser - Private Methods
191187
// -----------------------------------------------------------------------------
192188

189+
// registerTranslationFailure should be called when any Kubernetes object translation failure is encountered.
190+
func (p *Parser) registerTranslationFailure(reason string, causingObjects ...client.Object) {
191+
p.failuresCollector.PushTranslationFailure(reason, causingObjects...)
192+
p.logTranslationFailure(reason, causingObjects...)
193+
}
194+
195+
// logTranslationFailure logs an error message signaling that a translation error has occurred along with its reason
196+
// for every causing object.
197+
func (p *Parser) logTranslationFailure(reason string, causingObjects ...client.Object) {
198+
for _, obj := range causingObjects {
199+
p.logger.WithFields(logrus.Fields{
200+
"name": obj.GetName(),
201+
"namespace": obj.GetNamespace(),
202+
"GVK": obj.GetObjectKind().GroupVersionKind().String(),
203+
}).Errorf("translation failed: %s", reason)
204+
}
205+
}
206+
207+
func (p *Parser) popTranslationFailures() []TranslationFailure {
208+
return p.failuresCollector.PopTranslationFailures()
209+
}
210+
193211
func knativeIngressToNetworkingTLS(tls []knative.IngressTLS) []netv1beta1.IngressTLS {
194212
var result []netv1beta1.IngressTLS
195213

internal/dataplane/parser/parser_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -920,7 +920,7 @@ func TestCACertificate(t *testing.T) {
920920
secrets := []*corev1.Secret{
921921
{
922922
ObjectMeta: metav1.ObjectMeta{
923-
Name: "foo",
923+
Name: "valid-cert",
924924
Namespace: "default",
925925
Labels: map[string]string{
926926
"konghq.com/ca-cert": "true",
@@ -936,7 +936,7 @@ func TestCACertificate(t *testing.T) {
936936
},
937937
{
938938
ObjectMeta: metav1.ObjectMeta{
939-
Name: "bar",
939+
Name: "missing-cert-key",
940940
Namespace: "non-default",
941941
Labels: map[string]string{
942942
"konghq.com/ca-cert": "true",
@@ -952,7 +952,7 @@ func TestCACertificate(t *testing.T) {
952952
},
953953
{
954954
ObjectMeta: metav1.ObjectMeta{
955-
Name: "baz",
955+
Name: "missing-id-key",
956956
Namespace: "non-default",
957957
Labels: map[string]string{
958958
"konghq.com/ca-cert": "true",
@@ -968,7 +968,7 @@ func TestCACertificate(t *testing.T) {
968968
},
969969
{
970970
ObjectMeta: metav1.ObjectMeta{
971-
Name: "baz",
971+
Name: "expired-cert",
972972
Namespace: "non-default",
973973
Labels: map[string]string{
974974
"konghq.com/ca-cert": "true",
@@ -990,7 +990,7 @@ func TestCACertificate(t *testing.T) {
990990
assert.Nil(err)
991991
p := mustNewParser(t, store)
992992
state, translationFailures := p.Build()
993-
require.Empty(t, translationFailures)
993+
assert.Len(translationFailures, 3)
994994
assert.NotNil(state)
995995

996996
assert.Equal(1, len(state.CACertificates))

internal/dataplane/parser/translate_secrets.go

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,43 @@ package parser
22

33
import (
44
"crypto/x509"
5+
"encoding/json"
56
"encoding/pem"
67
"errors"
8+
"fmt"
79
"time"
810

911
"github.com/kong/go-kong/kong"
10-
"github.com/sirupsen/logrus"
1112
corev1 "k8s.io/api/core/v1"
13+
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
14+
"sigs.k8s.io/controller-runtime/pkg/client"
1215

13-
"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/kongstate"
1416
"github.com/kong/kubernetes-ingress-controller/v2/internal/store"
1517
)
1618

1719
// getCACerts translates CA certificates Secrets to kong.CACertificates. It ensures every certificate's structure and
18-
// validity. In case of violation of any validation rule, a secret gets skipped in a result and error message is logged
19-
// with affected plugins for context.
20-
func getCACerts(log logrus.FieldLogger, storer store.Storer, plugins []kongstate.Plugin) []kong.CACertificate {
21-
caCertSecrets, err := storer.ListCACerts()
20+
// validity. It skips Secrets that do not contain a valid certificate and reports translation failures for them.
21+
func (p *Parser) getCACerts() []kong.CACertificate {
22+
caCertSecrets, err := p.storer.ListCACerts()
2223
if err != nil {
23-
log.WithError(err).Error("failed to list CA certs")
24+
p.logger.WithError(err).Error("failed to list CA certs")
2425
return nil
2526
}
2627

2728
var caCerts []kong.CACertificate
2829
for _, certSecret := range caCertSecrets {
29-
log := log.WithFields(logrus.Fields{
30-
"secret_name": certSecret.Name,
31-
"secret_namespace": certSecret.Namespace,
32-
})
33-
3430
idBytes, ok := certSecret.Data["id"]
3531
if !ok {
36-
log.Error("skipping synchronisation, invalid CA certificate: missing 'id' field in data")
32+
p.registerTranslationFailure("invalid CA certificate: missing 'id' field in data", certSecret)
3733
continue
3834
}
3935
secretID := string(idBytes)
4036

4137
caCert, err := toKongCACertificate(certSecret, secretID)
4238
if err != nil {
43-
logWithAffectedPlugins(log, plugins, secretID).WithError(err).
44-
Error("skipping synchronisation, invalid CA certificate")
39+
relatedObjects := getPluginsAssociatedWithCACertSecret(secretID, p.storer)
40+
relatedObjects = append(relatedObjects, certSecret.DeepCopy())
41+
p.registerTranslationFailure(fmt.Sprintf("invalid CA certificate: %s", err), relatedObjects...)
4542
continue
4643
}
4744

@@ -77,30 +74,33 @@ func toKongCACertificate(certSecret *corev1.Secret, secretID string) (kong.CACer
7774
}, nil
7875
}
7976

80-
func logWithAffectedPlugins(log logrus.FieldLogger, plugins []kongstate.Plugin, secretID string) logrus.FieldLogger {
81-
affectedPlugins := getPluginsAssociatedWithCACertSecret(plugins, secretID)
82-
return log.WithField("affected_plugins", affectedPlugins)
83-
}
84-
85-
func getPluginsAssociatedWithCACertSecret(plugins []kongstate.Plugin, secretID string) []string {
86-
refersToSecret := func(pluginConfig map[string]interface{}) bool {
87-
caCertReferences, ok := pluginConfig["ca_certificates"].([]string)
88-
if !ok {
77+
func getPluginsAssociatedWithCACertSecret(secretID string, storer store.Storer) []client.Object {
78+
refersToSecret := func(pluginConfig v1.JSON) bool {
79+
cfg := struct {
80+
CACertificates []string `json:"ca_certificates,omitempty"`
81+
}{}
82+
err := json.Unmarshal(pluginConfig.Raw, &cfg)
83+
if err != nil {
8984
return false
9085
}
9186

92-
for _, reference := range caCertReferences {
87+
for _, reference := range cfg.CACertificates {
9388
if reference == secretID {
9489
return true
9590
}
9691
}
9792
return false
9893
}
9994

100-
var affectedPlugins []string
101-
for _, p := range plugins {
102-
if refersToSecret(p.Config) && p.Name != nil {
103-
affectedPlugins = append(affectedPlugins, *p.Name)
95+
var affectedPlugins []client.Object
96+
for _, p := range storer.ListKongPlugins() {
97+
if refersToSecret(p.Config) {
98+
affectedPlugins = append(affectedPlugins, p.DeepCopy())
99+
}
100+
}
101+
for _, p := range storer.ListKongClusterPlugins() {
102+
if refersToSecret(p.Config) {
103+
affectedPlugins = append(affectedPlugins, p.DeepCopy())
104104
}
105105
}
106106

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,60 @@
11
package parser
22

33
import (
4+
"fmt"
45
"testing"
56

6-
"github.com/kong/go-kong/kong"
77
"github.com/stretchr/testify/require"
8+
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"sigs.k8s.io/controller-runtime/pkg/client"
811

9-
"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/kongstate"
12+
"github.com/kong/kubernetes-ingress-controller/v2/internal/annotations"
13+
"github.com/kong/kubernetes-ingress-controller/v2/internal/store"
14+
kongv1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1"
1015
)
1116

1217
func TestGetPluginsAssociatedWithCACertSecret(t *testing.T) {
13-
secretID := "8a3753e0-093b-43d9-9d39-27985c987d92" //nolint:gosec
14-
plugins := []kongstate.Plugin{
15-
{
16-
Plugin: kong.Plugin{
17-
Name: kong.String("associated-plugin"),
18-
Config: map[string]interface{}{
19-
"ca_certificates": []string{secretID},
20-
},
18+
kongPluginWithSecret := func(name, secretID string) *kongv1.KongPlugin {
19+
return &kongv1.KongPlugin{
20+
ObjectMeta: metav1.ObjectMeta{
21+
Name: name,
2122
},
22-
},
23-
{
24-
Plugin: kong.Plugin{
25-
Name: kong.String("another-associated-plugin"),
26-
Config: map[string]interface{}{
27-
"ca_certificates": []string{secretID},
28-
},
23+
Config: v1.JSON{
24+
Raw: []byte(fmt.Sprintf(`{"ca_certificates": ["%s"]}`, secretID)),
2925
},
30-
},
31-
{
32-
Plugin: kong.Plugin{
33-
Name: kong.String("non-associated-plugin"),
26+
}
27+
}
28+
kongClusterPluginWithSecret := func(name, secretID string) *kongv1.KongClusterPlugin {
29+
return &kongv1.KongClusterPlugin{
30+
ObjectMeta: metav1.ObjectMeta{
31+
Name: name,
32+
Annotations: map[string]string{annotations.IngressClassKey: annotations.DefaultIngressClass},
33+
},
34+
Config: v1.JSON{
35+
Raw: []byte(fmt.Sprintf(`{"ca_certificates": ["%s"]}`, secretID)),
3436
},
35-
},
37+
}
3638
}
3739

38-
associatedPlugins := getPluginsAssociatedWithCACertSecret(plugins, secretID)
39-
require.ElementsMatch(t, []string{"associated-plugin", "another-associated-plugin"}, associatedPlugins)
40+
//nolint:gosec
41+
const (
42+
secretID = "8a3753e0-093b-43d9-9d39-27985c987d92"
43+
anotherSecretID = "99fa09c7-f849-4449-891e-19b9a0015763"
44+
)
45+
var (
46+
associatedPlugin = kongPluginWithSecret("associated_plugin", secretID)
47+
nonAssociatedPlugin = kongPluginWithSecret("non_associated_plugin", anotherSecretID)
48+
associatedClusterPlugin = kongClusterPluginWithSecret("associated_cluster_plugin", secretID)
49+
nonAssociatedClusterPlugin = kongClusterPluginWithSecret("non_associated_cluster_plugin", anotherSecretID)
50+
)
51+
storer, err := store.NewFakeStore(store.FakeObjects{
52+
KongPlugins: []*kongv1.KongPlugin{associatedPlugin, nonAssociatedPlugin},
53+
KongClusterPlugins: []*kongv1.KongClusterPlugin{associatedClusterPlugin, nonAssociatedClusterPlugin},
54+
})
55+
require.NoError(t, err)
56+
57+
gotPlugins := getPluginsAssociatedWithCACertSecret(secretID, storer)
58+
expectedPlugins := []client.Object{associatedPlugin, associatedClusterPlugin}
59+
require.ElementsMatch(t, expectedPlugins, gotPlugins, "expected plugins do not match actual ones")
4060
}

internal/store/store.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ type Storer interface {
9898
ListKnativeIngresses() ([]*knative.Ingress, error)
9999
ListGlobalKongPlugins() ([]*kongv1.KongPlugin, error)
100100
ListGlobalKongClusterPlugins() ([]*kongv1.KongClusterPlugin, error)
101+
ListKongPlugins() []*kongv1.KongPlugin
102+
ListKongClusterPlugins() []*kongv1.KongClusterPlugin
101103
ListKongConsumers() []*kongv1.KongConsumer
102104
ListCACerts() ([]*corev1.Secret, error)
103105
}
@@ -920,7 +922,6 @@ func (s Store) ListKongConsumers() []*kongv1.KongConsumer {
920922
// This function remains only to provide warnings to users with old configuration.
921923
func (s Store) ListGlobalKongPlugins() ([]*kongv1.KongPlugin, error) {
922924
var plugins []*kongv1.KongPlugin
923-
// var globalPlugins []*configurationv1.KongPlugin
924925
req, err := labels.NewRequirement("global", selection.Equals, []string{"true"})
925926
if err != nil {
926927
return nil, err
@@ -963,6 +964,30 @@ func (s Store) ListGlobalKongClusterPlugins() ([]*kongv1.KongClusterPlugin, erro
963964
return plugins, nil
964965
}
965966

967+
// ListKongClusterPlugins lists all KongClusterPlugins that match expected ingress.class annotation.
968+
func (s Store) ListKongClusterPlugins() []*kongv1.KongClusterPlugin {
969+
var plugins []*kongv1.KongClusterPlugin
970+
for _, item := range s.stores.ClusterPlugin.List() {
971+
p, ok := item.(*kongv1.KongClusterPlugin)
972+
if ok && s.isValidIngressClass(&p.ObjectMeta, annotations.IngressClassKey, s.getIngressClassHandling()) {
973+
plugins = append(plugins, p)
974+
}
975+
}
976+
return plugins
977+
}
978+
979+
// ListKongPlugins lists all KongPlugins.
980+
func (s Store) ListKongPlugins() []*kongv1.KongPlugin {
981+
var plugins []*kongv1.KongPlugin
982+
for _, item := range s.stores.Plugin.List() {
983+
p, ok := item.(*kongv1.KongPlugin)
984+
if ok {
985+
plugins = append(plugins, p)
986+
}
987+
}
988+
return plugins
989+
}
990+
966991
// ListCACerts returns all Secrets containing the label
967992
// "konghq.com/ca-cert"="true".
968993
func (s Store) ListCACerts() ([]*corev1.Secret, error) {

0 commit comments

Comments
 (0)