diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy_secret.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy_secret.yml new file mode 100644 index 000000000..f37cb88e2 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy_secret.yml @@ -0,0 +1,78 @@ +--- +kind: GatewayClass +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-gateway-class +spec: + controllerName: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + kinds: + - kind: HTTPRoute + group: gateway.networking.k8s.io + namespaces: + from: Same + +--- +kind: HTTPRoute +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: http-app-1 + namespace: default +spec: + parentRefs: + - name: my-gateway + kind: Gateway + group: gateway.networking.k8s.io + hostnames: + - "foo.com" + rules: + - matches: + - path: + type: Exact + value: /bar + backendRefs: + - name: whoami + port: 80 + weight: 1 + kind: Service + group: "" + +--- +kind: BackendTLSPolicy +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: policy-1 + namespace: default +spec: + targetRefs: + - group: "" + kind: Service + name: whoami + validation: + hostname: whoami + caCertificateRefs: + - group: "" + kind: Secret + name: ca-file + +--- +apiVersion: v1 +kind: Secret +metadata: + name: ca-file + namespace: default +data: + ca.crt: Q0ExLXNlY3JldA== diff --git a/pkg/provider/kubernetes/gateway/httproute.go b/pkg/provider/kubernetes/gateway/httproute.go index 9028b024c..539c18881 100644 --- a/pkg/provider/kubernetes/gateway/httproute.go +++ b/pkg/provider/kubernetes/gateway/httproute.go @@ -585,38 +585,59 @@ func (p *Provider) loadServersTransport(namespace string, policy *gatev1.Backend } for _, caCertRef := range policy.Spec.Validation.CACertificateRefs { - if (caCertRef.Group != "" && caCertRef.Group != groupCore) || caCertRef.Kind != "ConfigMap" { + if (caCertRef.Group != "" && caCertRef.Group != groupCore) || (caCertRef.Kind != "ConfigMap" && caCertRef.Kind != "Secret") { return nil, metav1.Condition{ Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs), Status: metav1.ConditionFalse, ObservedGeneration: policy.Generation, LastTransitionTime: metav1.Now(), Reason: string(gatev1.BackendTLSPolicyReasonInvalidKind), - Message: "Only ConfigMaps are supported", + Message: "Only ConfigMaps and Secrets are supported", } } - configMap, err := p.client.GetConfigMap(namespace, string(caCertRef.Name)) - if err != nil { + caCRT := "" + var crtMissing bool + + switch caCertRef.Kind { + case "ConfigMap": + configmap, err := p.client.GetConfigMap(namespace, string(caCertRef.Name)) + if err != nil { + return nil, metav1.Condition{ + Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs), + Status: metav1.ConditionFalse, + ObservedGeneration: policy.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.BackendTLSPolicyReasonInvalidCACertificateRef), + Message: fmt.Sprintf("getting configmap %s/%s: %s", namespace, string(caCertRef.Name), err), + } + } + caCRT, crtMissing = configmap.Data["ca.crt"] + case "Secret": + secret, err := p.client.GetSecret(namespace, string(caCertRef.Name)) + if err != nil { + return nil, metav1.Condition{ + Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs), + Status: metav1.ConditionFalse, + ObservedGeneration: policy.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.BackendTLSPolicyReasonInvalidCACertificateRef), + Message: fmt.Sprintf("getting secret %s/%s: %s", namespace, string(caCertRef.Name), err), + } + } + var crt []byte + crt, crtMissing = secret.Data["ca.crt"] + caCRT = string(crt) + } + + if !crtMissing { return nil, metav1.Condition{ Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs), Status: metav1.ConditionFalse, ObservedGeneration: policy.Generation, LastTransitionTime: metav1.Now(), Reason: string(gatev1.BackendTLSPolicyReasonInvalidCACertificateRef), - Message: fmt.Sprintf("getting configmap %s/%s: %s", namespace, string(caCertRef.Name), err), - } - } - - caCRT, ok := configMap.Data["ca.crt"] - if !ok { - return nil, metav1.Condition{ - Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs), - Status: metav1.ConditionFalse, - ObservedGeneration: policy.Generation, - LastTransitionTime: metav1.Now(), - Reason: string(gatev1.BackendTLSPolicyReasonInvalidCACertificateRef), - Message: fmt.Sprintf("configmap %s/%s does not have a ca.crt", namespace, string(caCertRef.Name)), + Message: fmt.Sprintf("%s %s/%s does not have a ca.crt", caCertRef.Kind, namespace, string(caCertRef.Name)), } } diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go index b6b19d80e..f0b17357f 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes_test.go +++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go @@ -2363,6 +2363,76 @@ func TestLoadHTTPRoutes(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "Simple HTTPRoute and BackendTLSPolicy with kind secret CA certificate", + paths: []string{"services.yml", "httproute/with_backend_tls_policy_secret.yml"}, + entryPoints: map[string]Entrypoint{"web": { + Address: ":80", + }}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-1c0cf64bde37d9d0df06": { + EntryPoints: []string{"web"}, + Service: "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-1c0cf64bde37d9d0df06-wrr", + Rule: "Host(`foo.com`) && Path(`/bar`)", + Priority: 100008, + RuleSyntax: "default", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-1c0cf64bde37d9d0df06-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-whoami-http-80", + Weight: ptr.To(1), + }, + }, + }, + }, + "default-whoami-http-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + Servers: []dynamic.Server{ + { + URL: "https://10.10.0.1:80", + }, + { + URL: "https://10.10.0.2:80", + }, + }, + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + ServersTransport: "default-whoami-http-80", + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{ + "default-whoami-http-80": { + ServerName: "whoami", + RootCAs: []types.FileOrContent{ + "CA1-secret", + }, + }, + }, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, { desc: "Simple HTTPRoute and BackendTLSPolicy with System CA", paths: []string{"services.yml", "httproute/with_backend_tls_policy_system.yml"},