diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c33fc97fa7..0e25bcee78 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -103,8 +103,8 @@ jobs: with: context: ${{ inputs.tag != '' && 'git' || 'workflow' }} images: | - name=ghcr.io/nginx/nginx-gateway-fabric,enable=${{ inputs.image == 'ngf' && github.event_name != 'pull_request' }} - name=ghcr.io/nginx/nginx-gateway-fabric/nginx,enable=${{ inputs.image == 'nginx' && github.event_name != 'pull_request' }} + name=ghcr.io/${{ github.repository_owner }}/nginx-gateway-fabric,enable=${{ inputs.image == 'ngf' && github.event_name != 'pull_request' }} + name=ghcr.io/${{ github.repository_owner }}/nginx-gateway-fabric/nginx,enable=${{ inputs.image == 'nginx' && github.event_name != 'pull_request' }} name=docker-mgmt.nginx.com/nginx-gateway-fabric/nginx-plus,enable=${{ inputs.image == 'plus' && github.event_name != 'pull_request' }} name=us-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/nginx-gateway-fabric/nginx-plus,enable=${{ inputs.image == 'plus' && github.event_name != 'pull_request' }} name=localhost:5000/nginx-gateway-fabric/${{ inputs.image }} diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index e899bdaa40..18d3d044cf 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -424,6 +424,7 @@ var _ = Describe("ChangeProcessor", func() { var ( gcUpdated *v1.GatewayClass diffNsTLSSecret, sameNsTLSSecret *apiv1.Secret + diffNsTLSCert, sameNsTLSCert *graph.CertificateBundle hr1, hr1Updated, hr2 *v1.HTTPRoute gr1, gr1Updated, gr2 *v1.GRPCRoute tr1, tr1Updated, tr2 *v1alpha2.TLSRoute @@ -594,8 +595,19 @@ var _ = Describe("ChangeProcessor", func() { apiv1.TLSPrivateKeyKey: key, }, } + sameNsTLSCert = graph.NewCertificateBundle( + types.NamespacedName{Namespace: sameNsTLSSecret.Namespace, Name: sameNsTLSSecret.Name}, + "Secret", + &graph.Certificate{ + TLSCert: cert, + TLSPrivateKey: key, + }, + ) diffNsTLSSecret = &apiv1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + }, ObjectMeta: metav1.ObjectMeta{ Name: "different-ns-tls-secret", Namespace: "cert-ns", @@ -607,6 +619,15 @@ var _ = Describe("ChangeProcessor", func() { }, } + diffNsTLSCert = graph.NewCertificateBundle( + types.NamespacedName{Namespace: diffNsTLSSecret.Namespace, Name: diffNsTLSSecret.Name}, + "Secret", + &graph.Certificate{ + TLSCert: cert, + TLSPrivateKey: key, + }, + ) + gw1 = createGateway( "gateway-1", createHTTPListener(), @@ -1157,6 +1178,14 @@ var _ = Describe("ChangeProcessor", func() { expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ Source: diffNsTLSSecret, + CertBundle: graph.NewCertificateBundle( + types.NamespacedName{Namespace: diffNsTLSSecret.Namespace, Name: diffNsTLSSecret.Name}, + "Secret", + &graph.Certificate{ + TLSCert: cert, + TLSPrivateKey: key, + }, + ), } expGraph.ReferencedServices = nil @@ -1191,6 +1220,14 @@ var _ = Describe("ChangeProcessor", func() { expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ Source: diffNsTLSSecret, + CertBundle: graph.NewCertificateBundle( + types.NamespacedName{Namespace: diffNsTLSSecret.Namespace, Name: diffNsTLSSecret.Name}, + "Secret", + &graph.Certificate{ + TLSCert: cert, + TLSPrivateKey: key, + }, + ), } processAndValidateGraph(expGraph) @@ -1211,6 +1248,14 @@ var _ = Describe("ChangeProcessor", func() { expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ Source: diffNsTLSSecret, + CertBundle: graph.NewCertificateBundle( + types.NamespacedName{Namespace: diffNsTLSSecret.Namespace, Name: diffNsTLSSecret.Name}, + "Secret", + &graph.Certificate{ + TLSCert: cert, + TLSPrivateKey: key, + }, + ), } processAndValidateGraph(expGraph) @@ -1221,7 +1266,8 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(trServiceRefGrant) expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, } processAndValidateGraph(expGraph) @@ -1232,7 +1278,8 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(gatewayAPICRDUpdated) expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, } expGraph.GatewayClass.Conditions = conditions.NewGatewayClassSupportedVersionBestEffort( @@ -1249,7 +1296,8 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(gatewayAPICRDSameVersion) expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, } expGraph.GatewayClass.Conditions = conditions.NewGatewayClassSupportedVersionBestEffort( @@ -1268,7 +1316,8 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(gatewayAPICRD) expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, } processAndValidateGraph(expGraph) @@ -1284,7 +1333,8 @@ var _ = Describe("ChangeProcessor", func() { listener80 := getListenerByName(expGraph.Gateway, httpListenerName) listener80.Routes[httpRouteKey1].Source.SetGeneration(hr1Updated.Generation) expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, } processAndValidateGraph(expGraph) @@ -1301,7 +1351,8 @@ var _ = Describe("ChangeProcessor", func() { listener80 := getListenerByName(expGraph.Gateway, httpListenerName) listener80.Routes[grpcRouteKey1].Source.SetGeneration(gr1Updated.Generation) expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, } processAndValidateGraph(expGraph) @@ -1315,7 +1366,8 @@ var _ = Describe("ChangeProcessor", func() { tlsListener.L4Routes[trKey1].Source.SetGeneration(tr1Updated.Generation) expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, } processAndValidateGraph(expGraph) @@ -1327,7 +1379,8 @@ var _ = Describe("ChangeProcessor", func() { expGraph.Gateway.Source.Generation = gw1Updated.Generation expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, } processAndValidateGraph(expGraph) @@ -1339,7 +1392,8 @@ var _ = Describe("ChangeProcessor", func() { expGraph.GatewayClass.Source.Generation = gcUpdated.Generation expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, } processAndValidateGraph(expGraph) @@ -1350,7 +1404,8 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(diffNsTLSSecret) expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, } processAndValidateGraph(expGraph) @@ -1359,7 +1414,8 @@ var _ = Describe("ChangeProcessor", func() { When("no changes are captured", func() { It("returns nil graph", func() { expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, } changed, graphCfg := processor.Process() @@ -1373,7 +1429,8 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(sameNsTLSSecret) expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, } changed, graphCfg := processor.Process() @@ -1390,7 +1447,8 @@ var _ = Describe("ChangeProcessor", func() { {Namespace: "test", Name: "gateway-2"}: gw2, } expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, } processAndValidateGraph(expGraph) @@ -1413,7 +1471,8 @@ var _ = Describe("ChangeProcessor", func() { FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), } expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, } processAndValidateGraph(expGraph) @@ -1447,7 +1506,8 @@ var _ = Describe("ChangeProcessor", func() { } expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, } processAndValidateGraph(expGraph) @@ -1487,7 +1547,8 @@ var _ = Describe("ChangeProcessor", func() { } expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, } processAndValidateGraph(expGraph) @@ -1534,7 +1595,8 @@ var _ = Describe("ChangeProcessor", func() { sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) listener443.ResolvedSecret = sameNsTLSSecretRef expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ - Source: sameNsTLSSecret, + Source: sameNsTLSSecret, + CertBundle: sameNsTLSCert, } delete(expGraph.ReferencedServices, expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName) @@ -1585,7 +1647,8 @@ var _ = Describe("ChangeProcessor", func() { sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) listener443.ResolvedSecret = sameNsTLSSecretRef expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ - Source: sameNsTLSSecret, + Source: sameNsTLSSecret, + CertBundle: sameNsTLSCert, } delete(expGraph.ReferencedServices, expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName) @@ -1629,7 +1692,8 @@ var _ = Describe("ChangeProcessor", func() { sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) listener443.ResolvedSecret = sameNsTLSSecretRef expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ - Source: sameNsTLSSecret, + Source: sameNsTLSSecret, + CertBundle: sameNsTLSCert, } delete(expGraph.ReferencedServices, expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName) @@ -1670,7 +1734,8 @@ var _ = Describe("ChangeProcessor", func() { sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) listener443.ResolvedSecret = sameNsTLSSecretRef expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ - Source: sameNsTLSSecret, + Source: sameNsTLSSecret, + CertBundle: sameNsTLSCert, } expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} diff --git a/internal/mode/static/state/dataplane/configuration.go b/internal/mode/static/state/dataplane/configuration.go index 3b4760d331..d3c86fcb3e 100644 --- a/internal/mode/static/state/dataplane/configuration.go +++ b/internal/mode/static/state/dataplane/configuration.go @@ -6,7 +6,6 @@ import ( "fmt" "sort" - apiv1 "k8s.io/api/core/v1" discoveryV1 "k8s.io/api/discovery/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -70,13 +69,16 @@ func BuildConfiguration( BackendGroups: backendGroups, SSLKeyPairs: buildSSLKeyPairs(g.ReferencedSecrets, g.Gateway.Listeners), Version: configVersion, - CertBundles: buildCertBundles(g.ReferencedCaCertConfigMaps, backendGroups), - Telemetry: buildTelemetry(g), - BaseHTTPConfig: baseHTTPConfig, - Logging: buildLogging(g), - NginxPlus: nginxPlus, - MainSnippets: buildSnippetsForContext(g.SnippetsFilters, ngfAPIv1alpha1.NginxContextMain), - AuxiliarySecrets: buildAuxiliarySecrets(g.PlusSecrets), + CertBundles: buildCertBundles( + buildRefCertificateBundles(g.ReferencedSecrets, g.ReferencedCaCertConfigMaps), + backendGroups, + ), + Telemetry: buildTelemetry(g), + BaseHTTPConfig: baseHTTPConfig, + Logging: buildLogging(g), + NginxPlus: nginxPlus, + MainSnippets: buildSnippetsForContext(g.SnippetsFilters, ngfAPIv1alpha1.NginxContextMain), + AuxiliarySecrets: buildAuxiliarySecrets(g.PlusSecrets), } return config @@ -226,10 +228,10 @@ func buildSSLKeyPairs( id := generateSSLKeyPairID(*l.ResolvedSecret) secret := secrets[*l.ResolvedSecret] // The Data map keys are guaranteed to exist by the graph package. - // the Source field is guaranteed to be non-nil by the graph package. + // the CertBundle field is guaranteed to be non-nil by the graph package. keyPairs[id] = SSLKeyPair{ - Cert: secret.Source.Data[apiv1.TLSCertKey], - Key: secret.Source.Data[apiv1.TLSPrivateKeyKey], + Cert: secret.CertBundle.Cert.TLSCert, + Key: secret.CertBundle.Cert.TLSPrivateKey, } } } @@ -237,8 +239,29 @@ func buildSSLKeyPairs( return keyPairs } +func buildRefCertificateBundles( + secrets map[types.NamespacedName]*graph.Secret, + configMaps map[types.NamespacedName]*graph.CaCertConfigMap, +) []graph.CertificateBundle { + bundles := []graph.CertificateBundle{} + + for _, secret := range secrets { + if secret.CertBundle != nil { + bundles = append(bundles, *secret.CertBundle) + } + } + + for _, configMap := range configMaps { + if configMap.CertBundle != nil { + bundles = append(bundles, *configMap.CertBundle) + } + } + + return bundles +} + func buildCertBundles( - caCertConfigMaps map[types.NamespacedName]*graph.CaCertConfigMap, + refCertBundles []graph.CertificateBundle, backendGroups []BackendGroup, ) map[CertBundleID]CertBundle { bundles := make(map[CertBundleID]CertBundle) @@ -260,18 +283,16 @@ func buildCertBundles( } } - for cmName, cm := range caCertConfigMaps { - id := generateCertBundleID(cmName) + for _, bundle := range refCertBundles { + id := generateCertBundleID(bundle.Name) if _, exists := refByBG[id]; exists { - if cm.CACert != nil || len(cm.CACert) > 0 { - // the cert could be base64 encoded or plaintext - data := make([]byte, base64.StdEncoding.DecodedLen(len(cm.CACert))) - _, err := base64.StdEncoding.Decode(data, cm.CACert) - if err != nil { - data = cm.CACert - } - bundles[id] = data + // the cert could be base64 encoded or plaintext + data := make([]byte, base64.StdEncoding.DecodedLen(len(bundle.Cert.CACert))) + _, err := base64.StdEncoding.Decode(data, bundle.Cert.CACert) + if err != nil { + data = bundle.Cert.CACert } + bundles[id] = data } } @@ -779,11 +800,11 @@ func generateSSLKeyPairID(secret types.NamespacedName) SSLKeyPairID { return SSLKeyPairID(fmt.Sprintf("ssl_keypair_%s_%s", secret.Namespace, secret.Name)) } -// generateCertBundleID generates an ID for the certificate bundle based on the ConfigMap namespaced name. +// generateCertBundleID generates an ID for the certificate bundle based on the ConfigMap/Secret namespaced name. // It is guaranteed to be unique per unique namespaced name. // The ID is safe to use as a file name. -func generateCertBundleID(configMap types.NamespacedName) CertBundleID { - return CertBundleID(fmt.Sprintf("cert_bundle_%s_%s", configMap.Namespace, configMap.Name)) +func generateCertBundleID(caCertRef types.NamespacedName) CertBundleID { + return CertBundleID(fmt.Sprintf("cert_bundle_%s_%s", caCertRef.Namespace, caCertRef.Name)) } // buildTelemetry generates the Otel configuration. diff --git a/internal/mode/static/state/dataplane/configuration_test.go b/internal/mode/static/state/dataplane/configuration_test.go index 0117afecdd..d8219a1c9a 100644 --- a/internal/mode/static/state/dataplane/configuration_test.go +++ b/internal/mode/static/state/dataplane/configuration_test.go @@ -680,6 +680,14 @@ func TestBuildConfiguration(t *testing.T) { apiv1.TLSPrivateKeyKey: []byte("privateKey-1"), }, }, + CertBundle: graph.NewCertificateBundle( + secret1NsName, + "Secret", + &graph.Certificate{ + TLSCert: []byte("cert-1"), + TLSPrivateKey: []byte("privateKey-1"), + }, + ), } secret2NsName := types.NamespacedName{Namespace: "test", Name: "secret-2"} @@ -694,6 +702,14 @@ func TestBuildConfiguration(t *testing.T) { apiv1.TLSPrivateKeyKey: []byte("privateKey-2"), }, }, + CertBundle: graph.NewCertificateBundle( + secret2NsName, + "Secret", + &graph.Certificate{ + TLSCert: []byte("cert-2"), + TLSPrivateKey: []byte("privateKey-2"), + }, + ), } listener80 := v1.Listener{ @@ -811,7 +827,13 @@ func TestBuildConfiguration(t *testing.T) { "ca.crt": "cert-1", }, }, - CACert: []byte("cert-1"), + CertBundle: graph.NewCertificateBundle( + types.NamespacedName{Namespace: "test", Name: "configmap-1"}, + "ConfigMap", + &graph.Certificate{ + CACert: []byte("cert-1"), + }, + ), }, {Namespace: "test", Name: "configmap-2"}: { Source: &apiv1.ConfigMap{ @@ -823,7 +845,13 @@ func TestBuildConfiguration(t *testing.T) { "ca.crt": []byte("cert-2"), }, }, - CACert: []byte("cert-2"), + CertBundle: graph.NewCertificateBundle( + types.NamespacedName{Namespace: "test", Name: "configmap-2"}, + "ConfigMap", + &graph.Certificate{ + CACert: []byte("cert-2"), + }, + ), }, } diff --git a/internal/mode/static/state/graph/backend_tls_policy.go b/internal/mode/static/state/graph/backend_tls_policy.go index eb385fa79b..4e00eecc56 100644 --- a/internal/mode/static/state/graph/backend_tls_policy.go +++ b/internal/mode/static/state/graph/backend_tls_policy.go @@ -2,9 +2,11 @@ package graph import ( "fmt" + "slices" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/validation/field" + v1 "sigs.k8s.io/gateway-api/apis/v1" "sigs.k8s.io/gateway-api/apis/v1alpha3" "github.com/nginx/nginx-gateway-fabric/internal/framework/conditions" @@ -31,6 +33,7 @@ type BackendTLSPolicy struct { func processBackendTLSPolicies( backendTLSPolicies map[types.NamespacedName]*v1alpha3.BackendTLSPolicy, configMapResolver *configMapResolver, + secretResolver *secretResolver, ctlrName string, gateway *Gateway, ) map[types.NamespacedName]*BackendTLSPolicy { @@ -42,7 +45,7 @@ func processBackendTLSPolicies( for nsname, backendTLSPolicy := range backendTLSPolicies { var caCertRef types.NamespacedName - valid, ignored, conds := validateBackendTLSPolicy(backendTLSPolicy, configMapResolver, ctlrName) + valid, ignored, conds := validateBackendTLSPolicy(backendTLSPolicy, configMapResolver, secretResolver, ctlrName) if valid && !ignored && backendTLSPolicy.Spec.Validation.CACertificateRefs != nil { caCertRef = types.NamespacedName{ @@ -68,6 +71,7 @@ func processBackendTLSPolicies( func validateBackendTLSPolicy( backendTLSPolicy *v1alpha3.BackendTLSPolicy, configMapResolver *configMapResolver, + secretResolver *secretResolver, ctlrName string, ) (valid, ignored bool, conds []conditions.Condition) { valid = true @@ -93,7 +97,7 @@ func validateBackendTLSPolicy( conds = append(conds, staticConds.NewPolicyInvalid(msg)) case len(caCertRefs) > 0: - if err := validateBackendTLSCACertRef(backendTLSPolicy, configMapResolver); err != nil { + if err := validateBackendTLSCACertRef(backendTLSPolicy, configMapResolver, secretResolver); err != nil { valid = false conds = append(conds, staticConds.NewPolicyInvalid( fmt.Sprintf("invalid CACertificateRef: %s", err.Error()))) @@ -124,30 +128,49 @@ func validateBackendTLSHostname(btp *v1alpha3.BackendTLSPolicy) error { return nil } -func validateBackendTLSCACertRef(btp *v1alpha3.BackendTLSPolicy, configMapResolver *configMapResolver) error { +func validateBackendTLSCACertRef( + btp *v1alpha3.BackendTLSPolicy, + configMapResolver *configMapResolver, + secretResolver *secretResolver, +) error { if len(btp.Spec.Validation.CACertificateRefs) != 1 { path := field.NewPath("tls.cacertrefs") valErr := field.TooMany(path, len(btp.Spec.Validation.CACertificateRefs), 1) return valErr } - if btp.Spec.Validation.CACertificateRefs[0].Kind != "ConfigMap" { + + selectedCertRef := btp.Spec.Validation.CACertificateRefs[0] + allowedCaCertKinds := []v1.Kind{"ConfigMap", "Secret"} + + if !slices.Contains(allowedCaCertKinds, selectedCertRef.Kind) { path := field.NewPath("tls.cacertrefs[0].kind") - valErr := field.NotSupported(path, btp.Spec.Validation.CACertificateRefs[0].Kind, []string{"ConfigMap"}) + valErr := field.NotSupported(path, btp.Spec.Validation.CACertificateRefs[0].Kind, allowedCaCertKinds) return valErr } - if btp.Spec.Validation.CACertificateRefs[0].Group != "" && - btp.Spec.Validation.CACertificateRefs[0].Group != "core" { + if selectedCertRef.Group != "" && + selectedCertRef.Group != "core" { path := field.NewPath("tls.cacertrefs[0].group") - valErr := field.NotSupported(path, btp.Spec.Validation.CACertificateRefs[0].Group, []string{"", "core"}) + valErr := field.NotSupported(path, selectedCertRef.Group, []string{"", "core"}) return valErr } nsName := types.NamespacedName{ Namespace: btp.Namespace, - Name: string(btp.Spec.Validation.CACertificateRefs[0].Name), + Name: string(selectedCertRef.Name), } - if err := configMapResolver.resolve(nsName); err != nil { - path := field.NewPath("tls.cacertrefs[0]") - return field.Invalid(path, btp.Spec.Validation.CACertificateRefs[0], err.Error()) + + switch selectedCertRef.Kind { + case "ConfigMap": + if err := configMapResolver.resolve(nsName); err != nil { + path := field.NewPath("tls.cacertrefs[0]") + return field.Invalid(path, selectedCertRef, err.Error()) + } + case "Secret": + if err := secretResolver.resolve(nsName); err != nil { + path := field.NewPath("tls.cacertrefs[0]") + return field.Invalid(path, selectedCertRef, err.Error()) + } + default: + return fmt.Errorf("invalid certificate reference kind %q", selectedCertRef.Kind) } return nil } diff --git a/internal/mode/static/state/graph/backend_tls_policy_test.go b/internal/mode/static/state/graph/backend_tls_policy_test.go index 7b4e794a20..cea42d64e9 100644 --- a/internal/mode/static/state/graph/backend_tls_policy_test.go +++ b/internal/mode/static/state/graph/backend_tls_policy_test.go @@ -75,7 +75,7 @@ func TestProcessBackendTLSPoliciesEmpty(t *testing.T) { t.Parallel() g := NewWithT(t) - processed := processBackendTLSPolicies(test.backendTLSPolicies, nil, "test", test.gateway) + processed := processBackendTLSPolicies(test.backendTLSPolicies, nil, nil, "test", test.gateway) g.Expect(processed).To(Equal(test.expected)) }) @@ -83,6 +83,7 @@ func TestProcessBackendTLSPoliciesEmpty(t *testing.T) { } func TestValidateBackendTLSPolicy(t *testing.T) { + const testSecretName string = "test-secret" targetRefNormalCase := []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ { LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ @@ -100,6 +101,14 @@ func TestValidateBackendTLSPolicy(t *testing.T) { }, } + localObjectRefSecretNormalCase := []gatewayv1.LocalObjectReference{ + { + Kind: "Secret", + Name: gatewayv1.ObjectName(testSecretName), + Group: "", + }, + } + localObjectRefInvalidName := []gatewayv1.LocalObjectReference{ { Kind: "ConfigMap", @@ -196,6 +205,23 @@ func TestValidateBackendTLSPolicy(t *testing.T) { }, isValid: true, }, + { + name: "normal case with ca cert ref secrets", + tlsPolicy: &v1alpha3.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tls-policy", + Namespace: "test", + }, + Spec: v1alpha3.BackendTLSPolicySpec{ + TargetRefs: targetRefNormalCase, + Validation: v1alpha3.BackendTLSPolicyValidation{ + CACertificateRefs: localObjectRefSecretNormalCase, + Hostname: "foo.test.com", + }, + }, + }, + isValid: true, + }, { name: "normal case with ca cert refs and 16 ancestors including us", tlsPolicy: &v1alpha3.BackendTLSPolicy{ @@ -390,7 +416,7 @@ func TestValidateBackendTLSPolicy(t *testing.T) { Namespace: "test", }, Data: map[string]string{ - "ca.crt": caBlock, + CAKey: caBlock, }, }, {Namespace: "test", Name: "invalid"}: { @@ -399,26 +425,53 @@ func TestValidateBackendTLSPolicy(t *testing.T) { Namespace: "test", }, Data: map[string]string{ - "ca.crt": "invalid", + CAKey: "invalid", + }, + }, + } + + secretMaps := map[types.NamespacedName]*v1.Secret{ + {Namespace: "test", Name: testSecretName}: { + ObjectMeta: metav1.ObjectMeta{ + Name: testSecretName, + Namespace: "test", + }, + Type: v1.SecretTypeTLS, + Data: map[string][]byte{ + v1.TLSCertKey: cert, + v1.TLSPrivateKeyKey: key, + CAKey: []byte(caBlock), + }, + }, + {Namespace: "test", Name: "invalid-secret"}: { + ObjectMeta: metav1.ObjectMeta{ + Name: "invalid-secret", + Namespace: "test", + }, + Data: map[string][]byte{ + v1.TLSCertKey: invalidCert, + v1.TLSPrivateKeyKey: invalidKey, + CAKey: []byte("invalid-cert"), }, }, } configMapResolver := newConfigMapResolver(configMaps) + secretMapResolver := newSecretResolver(secretMaps) for _, test := range tests { t.Run(test.name, func(t *testing.T) { g := NewWithT(t) - valid, ignored, conds := validateBackendTLSPolicy(test.tlsPolicy, configMapResolver, "test") + valid, ignored, conds := validateBackendTLSPolicy(test.tlsPolicy, configMapResolver, secretMapResolver, "test") - g.Expect(valid).To(Equal(test.isValid)) - g.Expect(ignored).To(Equal(test.ignored)) if !test.isValid && !test.ignored { g.Expect(conds).To(HaveLen(1)) } else { g.Expect(conds).To(BeEmpty()) } + g.Expect(valid).To(Equal(test.isValid)) + g.Expect(ignored).To(Equal(test.ignored)) }) } } diff --git a/internal/mode/static/state/graph/certificate_bundle.go b/internal/mode/static/state/graph/certificate_bundle.go new file mode 100644 index 0000000000..4567fad074 --- /dev/null +++ b/internal/mode/static/state/graph/certificate_bundle.go @@ -0,0 +1,75 @@ +package graph + +import ( + "crypto/tls" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + + "k8s.io/apimachinery/pkg/types" + v1 "sigs.k8s.io/gateway-api/apis/v1" +) + +// CAKey certificate key for optional root certificate authority. +const CAKey = "ca.crt" + +// CertificateBundle is used to submit certificate data to nginx that is kubernetes aware. +type CertificateBundle struct { + Cert *Certificate + + Name types.NamespacedName + Kind v1.Kind +} + +// Certificate houses the real certificate data that is sent to the configurator. +type Certificate struct { + // TLSCert is the SSL certificate used to send to CA. + TLSCert []byte + // TLSPrivateKey is the cryptographic key for encrpyting traffic during secure TLS. + TLSPrivateKey []byte + // CACert is the root certificate authority. + CACert []byte +} + +// NewCertificateBundle generates a kubernetes aware certificate that is used during the configurator for nginx. +func NewCertificateBundle(name types.NamespacedName, kind string, cert *Certificate) *CertificateBundle { + return &CertificateBundle{ + Name: name, + Kind: v1.Kind(kind), + Cert: cert, + } +} + +// validateTLS checks to make sure a ssl certificate key pair is valid. +func validateTLS(tlsCert, tlsPrivateKey []byte) error { + _, err := tls.X509KeyPair(tlsCert, tlsPrivateKey) + if err != nil { + return fmt.Errorf("tls secret is invalid: %w", err) + } + + return nil +} + +// validateCA validates the ca.crt entry in the Certificate. If it is valid, the function returns nil. +func validateCA(caData []byte) error { + data := make([]byte, base64.StdEncoding.DecodedLen(len(caData))) + _, err := base64.StdEncoding.Decode(data, caData) + if err != nil { + data = caData + } + block, _ := pem.Decode(data) + if block == nil { + return fmt.Errorf("the data field %q must hold a valid CERTIFICATE PEM block", CAKey) + } + if block.Type != "CERTIFICATE" { + return fmt.Errorf("the data field %q must hold a valid CERTIFICATE PEM block, but got %q", CAKey, block.Type) + } + + _, err = x509.ParseCertificate(block.Bytes) + if err != nil { + return fmt.Errorf("failed to validate certificate: %w", err) + } + + return nil +} diff --git a/internal/mode/static/state/graph/certificate_bundle_test.go b/internal/mode/static/state/graph/certificate_bundle_test.go new file mode 100644 index 0000000000..bc26f19176 --- /dev/null +++ b/internal/mode/static/state/graph/certificate_bundle_test.go @@ -0,0 +1,104 @@ +package graph + +import ( + "encoding/base64" + "testing" + + . "github.com/onsi/gomega" +) + +func TestValidateTLS(t *testing.T) { + t.Parallel() + tests := []struct { + expectedErr string + name string + tlsCert []byte + tlsPrivateKey []byte + }{ + { + name: "valid tls key pair", + tlsCert: cert, + tlsPrivateKey: key, + }, + { + name: "invalid tls cert valid key", + tlsCert: invalidCert, + tlsPrivateKey: key, + expectedErr: "tls secret is invalid: x509: malformed certificate", + }, + { + name: "invalid tls private key valid cert", + tlsCert: cert, + tlsPrivateKey: invalidKey, + expectedErr: "tls secret is invalid: tls: failed to parse private key", + }, + { + name: "invalid tls cert key pair", + tlsCert: invalidCert, + tlsPrivateKey: invalidKey, + expectedErr: "tls secret is invalid: x509: malformed certificate", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + g := NewWithT(t) + err := validateTLS(test.tlsCert, test.tlsPrivateKey) + if test.expectedErr != "" { + g.Expect(err).To(HaveOccurred()) + g.Expect(err).To(MatchError(test.expectedErr)) + } else { + g.Expect(err).ToNot(HaveOccurred()) + } + }) + } +} + +func TestValidateCA(t *testing.T) { + t.Parallel() + base64Data := make([]byte, base64.StdEncoding.EncodedLen(len(caBlock))) + base64.StdEncoding.Encode(base64Data, []byte(caBlock)) + + tests := []struct { + name string + data []byte + errorExpected bool + }{ + { + name: "valid base64", + data: base64Data, + errorExpected: false, + }, + { + name: "valid plain text", + data: []byte(caBlock), + errorExpected: false, + }, + { + name: "invalid pem", + data: []byte("invalid"), + errorExpected: true, + }, + { + name: "invalid type", + data: []byte(caBlockInvalidType), + errorExpected: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + err := validateCA(test.data) + if test.errorExpected { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).ToNot(HaveOccurred()) + } + }) + } +} diff --git a/internal/mode/static/state/graph/configmaps.go b/internal/mode/static/state/graph/configmaps.go index 23bec08c63..de914f9120 100644 --- a/internal/mode/static/state/graph/configmaps.go +++ b/internal/mode/static/state/graph/configmaps.go @@ -1,9 +1,6 @@ package graph import ( - "crypto/x509" - "encoding/base64" - "encoding/pem" "errors" "fmt" @@ -11,14 +8,11 @@ import ( "k8s.io/apimachinery/pkg/types" ) -const CAKey = "ca.crt" - // CaCertConfigMap represents a ConfigMap resource that holds CA Cert data. type CaCertConfigMap struct { // Source holds the actual ConfigMap resource. Can be nil if the ConfigMap does not exist. - Source *apiv1.ConfigMap - // CACert holds the actual CA Cert data. - CACert []byte + Source *apiv1.ConfigMap + CertBundle *CertificateBundle } type caCertConfigMapEntry struct { @@ -49,7 +43,7 @@ func (r *configMapResolver) resolve(nsname types.NamespacedName) error { cm, exist := r.clusterConfigMaps[nsname] var validationErr error - var caCert []byte + cert := &Certificate{} if !exist { validationErr = errors.New("ConfigMap does not exist") @@ -57,24 +51,24 @@ func (r *configMapResolver) resolve(nsname types.NamespacedName) error { if cm.Data != nil { if _, exists := cm.Data[CAKey]; exists { validationErr = validateCA([]byte(cm.Data[CAKey])) - caCert = []byte(cm.Data[CAKey]) + cert.CACert = []byte(cm.Data[CAKey]) } } if cm.BinaryData != nil { if _, exists := cm.BinaryData[CAKey]; exists { validationErr = validateCA(cm.BinaryData[CAKey]) - caCert = cm.BinaryData[CAKey] + cert.CACert = cm.BinaryData[CAKey] } } - if len(caCert) == 0 { + if len(cert.CACert) == 0 { validationErr = fmt.Errorf("ConfigMap does not have the data or binaryData field %v", CAKey) } } r.resolvedCaCertConfigMaps[nsname] = &caCertConfigMapEntry{ caCertConfigMap: CaCertConfigMap{ - Source: cm, - CACert: caCert, + Source: cm, + CertBundle: NewCertificateBundle(nsname, "ConfigMap", cert), }, err: validationErr, } @@ -97,26 +91,3 @@ func (r *configMapResolver) getResolvedConfigMaps() map[types.NamespacedName]*Ca return resolved } - -// validateCA validates the ca.crt entry in the ConfigMap. If it is valid, the function returns nil. -func validateCA(caData []byte) error { - data := make([]byte, base64.StdEncoding.DecodedLen(len(caData))) - _, err := base64.StdEncoding.Decode(data, caData) - if err != nil { - data = caData - } - block, _ := pem.Decode(data) - if block == nil { - return fmt.Errorf("the data field %s must hold a valid CERTIFICATE PEM block", CAKey) - } - if block.Type != "CERTIFICATE" { - return fmt.Errorf("the data field %s must hold a valid CERTIFICATE PEM block, but got '%s'", CAKey, block.Type) - } - - _, err = x509.ParseCertificate(block.Bytes) - if err != nil { - return fmt.Errorf("failed to validate certificate: %w", err) - } - - return nil -} diff --git a/internal/mode/static/state/graph/configmaps_test.go b/internal/mode/static/state/graph/configmaps_test.go index 852d7c8917..ca5c725a38 100644 --- a/internal/mode/static/state/graph/configmaps_test.go +++ b/internal/mode/static/state/graph/configmaps_test.go @@ -1,7 +1,6 @@ package graph import ( - "encoding/base64" "testing" . "github.com/onsi/gomega" @@ -87,53 +86,6 @@ UdxohGqleWFMQ3UNLOvc9Fk+q72ryg== ` ) -func TestValidateCA(t *testing.T) { - t.Parallel() - base64Data := make([]byte, base64.StdEncoding.EncodedLen(len(caBlock))) - base64.StdEncoding.Encode(base64Data, []byte(caBlock)) - - tests := []struct { - name string - data []byte - errorExpected bool - }{ - { - name: "valid base64", - data: base64Data, - errorExpected: false, - }, - { - name: "valid plain text", - data: []byte(caBlock), - errorExpected: false, - }, - { - name: "invalid pem", - data: []byte("invalid"), - errorExpected: true, - }, - { - name: "invalid type", - data: []byte(caBlockInvalidType), - errorExpected: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - err := validateCA(test.data) - if test.errorExpected { - g.Expect(err).To(HaveOccurred()) - } else { - g.Expect(err).ToNot(HaveOccurred()) - } - }) - } -} - func TestResolve(t *testing.T) { configMaps := map[types.NamespacedName]*v1.ConfigMap{ {Namespace: "test", Name: "configmap1"}: { diff --git a/internal/mode/static/state/graph/graph.go b/internal/mode/static/state/graph/graph.go index 82dc2712d9..834eff4de0 100644 --- a/internal/mode/static/state/graph/graph.go +++ b/internal/mode/static/state/graph/graph.go @@ -59,7 +59,7 @@ type Graph struct { Routes map[RouteKey]*L7Route // L4Routes hold L4Route resources. L4Routes map[L4RouteKey]*L4Route - // ReferencedSecrets includes Secrets referenced by Gateway Listeners, including invalid ones. + // ReferencedSecrets includes Secrets referenced by Gateway Listeners or BackendTLSPolicies, including invalid ones. // It is different from the other maps, because it includes entries for Secrets that do not exist // in the cluster. We need such entries so that we can query the Graph to determine if a Secret is referenced // by the Gateway, including the case when the Secret is newly created. @@ -230,6 +230,7 @@ func BuildGraph( processedBackendTLSPolicies := processBackendTLSPolicies( state.BackendTLSPolicies, configMapResolver, + secretResolver, controllerName, gw, ) diff --git a/internal/mode/static/state/graph/graph_test.go b/internal/mode/static/state/graph/graph_test.go index a9e327662b..fe96db4402 100644 --- a/internal/mode/static/state/graph/graph_test.go +++ b/internal/mode/static/state/graph/graph_test.go @@ -41,6 +41,9 @@ func TestBuildGraph(t *testing.T) { } cm := &v1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + }, ObjectMeta: metav1.ObjectMeta{ Name: "configmap", Namespace: "service", @@ -338,6 +341,9 @@ func TestBuildGraph(t *testing.T) { } secret := &v1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + }, ObjectMeta: metav1.ObjectMeta{ Namespace: testNs, Name: "secret", @@ -888,6 +894,10 @@ func TestBuildGraph(t *testing.T) { ReferencedSecrets: map[types.NamespacedName]*Secret{ client.ObjectKeyFromObject(secret): { Source: secret, + CertBundle: NewCertificateBundle(client.ObjectKeyFromObject(secret), "Secret", &Certificate{ + TLSCert: cert, + TLSPrivateKey: key, + }), }, }, ReferencedNamespaces: map[types.NamespacedName]*v1.Namespace{ @@ -900,7 +910,9 @@ func TestBuildGraph(t *testing.T) { ReferencedCaCertConfigMaps: map[types.NamespacedName]*CaCertConfigMap{ client.ObjectKeyFromObject(cm): { Source: cm, - CACert: []byte(caBlock), + CertBundle: NewCertificateBundle(client.ObjectKeyFromObject(cm), "ConfigMap", &Certificate{ + CACert: []byte(caBlock), + }), }, }, BackendTLSPolicies: map[types.NamespacedName]*BackendTLSPolicy{ @@ -1162,7 +1174,9 @@ func TestIsReferenced(t *testing.T) { ReferencedCaCertConfigMaps: map[types.NamespacedName]*CaCertConfigMap{ client.ObjectKeyFromObject(baseConfigMap): { Source: baseConfigMap, - CACert: []byte(caBlock), + CertBundle: NewCertificateBundle(client.ObjectKeyFromObject(baseConfigMap), "ConfigMap", &Certificate{ + CACert: []byte(caBlock), + }), }, }, } diff --git a/internal/mode/static/state/graph/secret.go b/internal/mode/static/state/graph/secret.go index c7d339aae4..7515c514f7 100644 --- a/internal/mode/static/state/graph/secret.go +++ b/internal/mode/static/state/graph/secret.go @@ -1,7 +1,6 @@ package graph import ( - "crypto/tls" "errors" "fmt" @@ -13,6 +12,9 @@ import ( type Secret struct { // Source holds the actual Secret resource. Can be nil if the Secret does not exist. Source *apiv1.Secret + + // CertBundle holds actual certificate data. + CertBundle *CertificateBundle } type secretEntry struct { @@ -43,6 +45,7 @@ func (r *secretResolver) resolve(nsname types.NamespacedName) error { secret, exist := r.clusterSecrets[nsname] var validationErr error + var certBundle *CertificateBundle switch { case !exist: @@ -53,15 +56,28 @@ func (r *secretResolver) resolve(nsname types.NamespacedName) error { default: // A TLS Secret is guaranteed to have these data fields. - _, err := tls.X509KeyPair(secret.Data[apiv1.TLSCertKey], secret.Data[apiv1.TLSPrivateKeyKey]) - if err != nil { - validationErr = fmt.Errorf("TLS secret is invalid: %w", err) + cert := &Certificate{ + TLSCert: secret.Data[apiv1.TLSCertKey], + TLSPrivateKey: secret.Data[apiv1.TLSPrivateKeyKey], + } + validationErr = validateTLS(cert.TLSCert, cert.TLSPrivateKey) + + // Not always guaranteed to have a ca certificate in the secret. + // Cert-Manager puts this at ca.crt and thus this is statically placed like so. + // To follow the convention setup by kubernetes for a service account root ca + // for optional root certificate authority + if _, exists := secret.Data[CAKey]; exists { + cert.CACert = secret.Data[CAKey] + validationErr = validateCA(cert.CACert) } + + certBundle = NewCertificateBundle(nsname, "Secret", cert) } r.resolvedSecrets[nsname] = &secretEntry{ Secret: Secret{ - Source: secret, + Source: secret, + CertBundle: certBundle, }, err: validationErr, } diff --git a/internal/mode/static/state/graph/secret_test.go b/internal/mode/static/state/graph/secret_test.go index 8c386b8528..3ef2fbeec3 100644 --- a/internal/mode/static/state/graph/secret_test.go +++ b/internal/mode/static/state/graph/secret_test.go @@ -93,6 +93,19 @@ func TestSecretResolver(t *testing.T) { Type: apiv1.SecretTypeTLS, } + validSecret3 = &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "secret-3", + }, + Data: map[string][]byte{ + apiv1.TLSCertKey: cert, + apiv1.TLSPrivateKeyKey: key, + CAKey: []byte(caBlock), + }, + Type: apiv1.SecretTypeTLS, + } + invalidSecretType = &apiv1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", @@ -129,6 +142,19 @@ func TestSecretResolver(t *testing.T) { Type: apiv1.SecretTypeTLS, } + invalidSecretCaCert = &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "invalid-ca-key", + }, + Data: map[string][]byte{ + apiv1.TLSCertKey: cert, + apiv1.TLSPrivateKeyKey: key, + CAKey: invalidCert, + }, + Type: apiv1.SecretTypeTLS, + } + secretNotExistNsName = types.NamespacedName{ Namespace: "test", Name: "not-exist", @@ -137,11 +163,13 @@ func TestSecretResolver(t *testing.T) { resolver := newSecretResolver( map[types.NamespacedName]*apiv1.Secret{ - client.ObjectKeyFromObject(validSecret1): validSecret1, - client.ObjectKeyFromObject(validSecret2): validSecret2, // we're not going to resolve it - client.ObjectKeyFromObject(invalidSecretType): invalidSecretType, - client.ObjectKeyFromObject(invalidSecretCert): invalidSecretCert, - client.ObjectKeyFromObject(invalidSecretKey): invalidSecretKey, + client.ObjectKeyFromObject(validSecret1): validSecret1, + client.ObjectKeyFromObject(validSecret2): validSecret2, // we're not going to resolve it + client.ObjectKeyFromObject(validSecret3): validSecret3, + client.ObjectKeyFromObject(invalidSecretType): invalidSecretType, + client.ObjectKeyFromObject(invalidSecretCert): invalidSecretCert, + client.ObjectKeyFromObject(invalidSecretKey): invalidSecretKey, + client.ObjectKeyFromObject(invalidSecretCaCert): invalidSecretCaCert, }) tests := []struct { @@ -157,6 +185,10 @@ func TestSecretResolver(t *testing.T) { name: "valid secret, again", nsname: client.ObjectKeyFromObject(validSecret1), }, + { + name: "valid secret, with ca certificate", + nsname: client.ObjectKeyFromObject(validSecret3), + }, { name: "doesn't exist", nsname: secretNotExistNsName, @@ -175,12 +207,17 @@ func TestSecretResolver(t *testing.T) { { name: "invalid secret cert", nsname: client.ObjectKeyFromObject(invalidSecretCert), - expectedErrMsg: "TLS secret is invalid: x509: malformed certificate", + expectedErrMsg: "tls secret is invalid: x509: malformed certificate", }, { name: "invalid secret key", nsname: client.ObjectKeyFromObject(invalidSecretKey), - expectedErrMsg: "TLS secret is invalid: tls: failed to parse private key", + expectedErrMsg: "tls secret is invalid: tls: failed to parse private key", + }, + { + name: "invalid secret ca cert", + nsname: client.ObjectKeyFromObject(invalidSecretCaCert), + expectedErrMsg: "failed to validate certificate: x509: malformed certificate", }, } @@ -201,15 +238,43 @@ func TestSecretResolver(t *testing.T) { expectedResolved := map[types.NamespacedName]*Secret{ client.ObjectKeyFromObject(validSecret1): { Source: validSecret1, + CertBundle: NewCertificateBundle(client.ObjectKeyFromObject(validSecret1), "Secret", &Certificate{ + TLSCert: cert, + TLSPrivateKey: key, + }), + }, + client.ObjectKeyFromObject(validSecret3): { + Source: validSecret3, + CertBundle: NewCertificateBundle(client.ObjectKeyFromObject(validSecret3), "Secret", &Certificate{ + TLSCert: cert, + TLSPrivateKey: key, + CACert: []byte(caBlock), + }), }, client.ObjectKeyFromObject(invalidSecretType): { Source: invalidSecretType, }, client.ObjectKeyFromObject(invalidSecretCert): { Source: invalidSecretCert, + CertBundle: NewCertificateBundle(client.ObjectKeyFromObject(invalidSecretCert), "Secret", &Certificate{ + TLSCert: invalidCert, + TLSPrivateKey: key, + }), }, client.ObjectKeyFromObject(invalidSecretKey): { Source: invalidSecretKey, + CertBundle: NewCertificateBundle(client.ObjectKeyFromObject(invalidSecretKey), "Secret", &Certificate{ + TLSCert: cert, + TLSPrivateKey: invalidKey, + }), + }, + client.ObjectKeyFromObject(invalidSecretCaCert): { + Source: invalidSecretCaCert, + CertBundle: NewCertificateBundle(client.ObjectKeyFromObject(invalidSecretCaCert), "Secret", &Certificate{ + TLSCert: cert, + TLSPrivateKey: key, + CACert: invalidCert, + }), }, secretNotExistNsName: { Source: nil,