diff --git a/charts/gateway-helm/templates/_rbac.tpl b/charts/gateway-helm/templates/_rbac.tpl index 9c80ff3a868..88cad0d885d 100644 --- a/charts/gateway-helm/templates/_rbac.tpl +++ b/charts/gateway-helm/templates/_rbac.tpl @@ -139,6 +139,7 @@ apiGroups: - gateway.networking.k8s.io resources: - gatewayclasses +- backendtlspolicies verbs: - get - list @@ -164,6 +165,7 @@ apiGroups: - gateway.networking.k8s.io resources: - gatewayclasses/status +- backendtlspolicies/status verbs: - update {{- end }} diff --git a/internal/gatewayapi/backendtlspolicy.go b/internal/gatewayapi/backendtlspolicy.go new file mode 100644 index 00000000000..17e09d6e566 --- /dev/null +++ b/internal/gatewayapi/backendtlspolicy.go @@ -0,0 +1,45 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package gatewayapi + +import ( + "sigs.k8s.io/gateway-api/apis/v1alpha2" +) + +func (t *Translator) ProcessBackendTLSPoliciesAncestorRef(backendTLSPolicies []*v1alpha2.BackendTLSPolicy, gateways []*GatewayContext) []*v1alpha2.BackendTLSPolicy { + + var res []*v1alpha2.BackendTLSPolicy + + for _, btlsPolicy := range backendTLSPolicies { + + policy := btlsPolicy.DeepCopy() + res = append(res, policy) + + if policy.Status.Ancestors != nil { + for k, status := range policy.Status.Ancestors { + exist := false + for _, gwContext := range gateways { + gw := gwContext.Gateway + if gw.Name == string(status.AncestorRef.Name) && gw.Namespace == NamespaceDerefOrAlpha(status.AncestorRef.Namespace, "default") { + for _, lis := range gw.Spec.Listeners { + if lis.Name == *status.AncestorRef.SectionName { + exist = true + } + } + } + } + + if !exist { + policy.Status.Ancestors = append(policy.Status.Ancestors[:k], policy.Status.Ancestors[k+1:]...) + } + } + } else { + policy.Status.Ancestors = []v1alpha2.PolicyAncestorStatus{} + } + } + + return res +} diff --git a/internal/gatewayapi/resource.go b/internal/gatewayapi/resource.go index f855cc3bb11..6a8f37e33b1 100644 --- a/internal/gatewayapi/resource.go +++ b/internal/gatewayapi/resource.go @@ -51,6 +51,7 @@ type Resources struct { ClientTrafficPolicies []*egv1a1.ClientTrafficPolicy `json:"clientTrafficPolicies,omitempty" yaml:"clientTrafficPolicies,omitempty"` BackendTrafficPolicies []*egv1a1.BackendTrafficPolicy `json:"backendTrafficPolicies,omitempty" yaml:"backendTrafficPolicies,omitempty"` SecurityPolicies []*egv1a1.SecurityPolicy `json:"securityPolicies,omitempty" yaml:"securityPolicies,omitempty"` + BackendTLSPolicies []*gwapiv1a2.BackendTLSPolicy `json:"backendTLSPolicies,omitempty" yaml:"backendTLSPolicies,omitempty"` } func NewResources() *Resources { @@ -70,6 +71,7 @@ func NewResources() *Resources { ClientTrafficPolicies: []*egv1a1.ClientTrafficPolicy{}, BackendTrafficPolicies: []*egv1a1.BackendTrafficPolicy{}, SecurityPolicies: []*egv1a1.SecurityPolicy{}, + BackendTLSPolicies: []*gwapiv1a2.BackendTLSPolicy{}, } } diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index d913283e034..0a8713c8799 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -7,6 +7,7 @@ package gatewayapi import ( "fmt" + "strconv" "strings" "time" @@ -1096,6 +1097,7 @@ func (t *Translator) processDestination(backendRefContext BackendRefContext, addrType *ir.DestinationAddressType ) protocol := inspectAppProtocolByRouteKind(routeType) + var backendTLS *ir.TLSUpstreamConfig switch KindDerefOr(backendRef.Kind, KindService) { case KindServiceImport: serviceImport := resources.GetServiceImport(backendNamespace, string(backendRef.Name)) @@ -1146,6 +1148,7 @@ func (t *Translator) processDestination(backendRefContext BackendRefContext, uint32(*backendRef.Port)) endpoints = append(endpoints, ep) } + backendTLS = t.processBackendTLSPolicy(*backendRef, backendNamespace, parentRef, resources) } // TODO: support mixed endpointslice address type for the same backendRef @@ -1162,6 +1165,7 @@ func (t *Translator) processDestination(backendRefContext BackendRefContext, Protocol: protocol, Endpoints: endpoints, AddressType: addrType, + TLS: backendTLS, } return ds, weight } @@ -1315,3 +1319,128 @@ func getIREndpointsFromEndpointSlice(endpointSlice *discoveryv1.EndpointSlice, p return endpoints } + +func GetTargetBackendReference(backendRef gwapiv1a1.BackendRef, namespace string) gwapiv1a1.PolicyTargetReferenceWithSectionName { + ref := gwapiv1a1.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gwapiv1a1.PolicyTargetReference{ + Group: func() gwapiv1a1.Group { + if backendRef.Group == nil { + return "" + } + return *backendRef.Group + }(), + Kind: func() gwapiv1.Kind { + if backendRef.Kind == nil { + return "Service" + } + return *backendRef.Kind + }(), + Name: backendRef.Name, + Namespace: NamespacePtr(NamespaceDerefOr(backendRef.Namespace, namespace)), + }, + SectionName: func() *gwapiv1.SectionName { + if backendRef.Port != nil { + return SectionNamePtr(strconv.Itoa(int(*backendRef.Port))) + } + return nil + }(), + } + return ref +} + +func backendTLSTargetMatched(policy gwapiv1a1.BackendTLSPolicy, target gwapiv1a1.PolicyTargetReferenceWithSectionName) bool { + + policyTarget := policy.Spec.TargetRef + + if target.Group == policyTarget.Group && + target.Kind == policyTarget.Kind && + target.Name == policyTarget.Name && + NamespaceDerefOr(policyTarget.Namespace, policy.Namespace) == string(*target.Namespace) { + if policyTarget.SectionName != nil && *policyTarget.SectionName != *target.SectionName { + return false + } + return true + } + return false +} + +func getBackendTLSPolicy(policies []*gwapiv1a1.BackendTLSPolicy, backendRef gwapiv1a1.BackendRef, backendNamespace string) *gwapiv1a1.BackendTLSPolicy { + target := GetTargetBackendReference(backendRef, backendNamespace) + for _, policy := range policies { + if backendTLSTargetMatched(*policy, target) { + return policy + } + } + return nil +} + +func getBackendTLSBundle(policies []*gwapiv1a1.BackendTLSPolicy, configmaps []*corev1.ConfigMap, backendRef gwapiv1a1.BackendRef, backendNamespace string) (*ir.TLSUpstreamConfig, error) { + + backendTLSPolicy := getBackendTLSPolicy(policies, backendRef, backendNamespace) + + if backendTLSPolicy == nil { + return nil, nil + } + + tlsBundle := &ir.TLSUpstreamConfig{} + + caRefMap := make(map[string]string) + + for _, caRef := range backendTLSPolicy.Spec.TLS.CACertRefs { + caRefMap[string(caRef.Name)] = string(caRef.Kind) + } + + ca := "" + + for _, cmap := range configmaps { + if kind, ok := caRefMap[cmap.Name]; ok && kind == cmap.Kind { + if crt, dataOk := cmap.Data["ca.crt"]; dataOk { + if ca != "" { + ca += "\n" + } + ca += crt + } else { + return nil, fmt.Errorf("no ca found in configmap %s", cmap.Name) + } + } + } + + if ca == "" { + return nil, fmt.Errorf("no ca found in referred configmaps") + } + + tlsBundle.CACertificate.Certificate = []byte(ca) + + tlsBundle.CACertificate.Name = fmt.Sprintf("%s/%s-ca", backendTLSPolicy.Name, backendTLSPolicy.Namespace) + + tlsBundle.SNI = string(backendTLSPolicy.Spec.TLS.Hostname) + + return tlsBundle, nil +} + +func (t *Translator) processBackendTLSPolicy(backendRef gwapiv1.BackendRef, backendNamespace string, parentRef *RouteParentContext, resources *Resources) *ir.TLSUpstreamConfig { + tlsBundle, err := getBackendTLSBundle(resources.BackendTLSPolicies, resources.ConfigMaps, backendRef, backendNamespace) + if err == nil && tlsBundle == nil { + return nil + } + policy := getBackendTLSPolicy(resources.BackendTLSPolicies, backendRef, backendNamespace) + ancestor := gwapiv1a1.PolicyAncestorStatus{ + AncestorRef: gwapiv1a1.ParentReference{ + Group: parentRef.Group, + Kind: parentRef.Kind, + Namespace: parentRef.Namespace, + Name: parentRef.Name, + SectionName: parentRef.SectionName, + Port: parentRef.Port, + }, + ControllerName: gwapiv1.GatewayController(t.GatewayControllerName), + } + if err != nil { + messeg := status.Error2ConditionMsg(err) + status.SetBackendTLSPolicyCondition(policy, ancestor, gwapiv1a1.PolicyConditionAccepted, metav1.ConditionFalse, gwapiv1a1.PolicyReasonInvalid, messeg) + return nil + } else { + status.SetBackendTLSPolicyCondition(policy, ancestor, gwapiv1a1.PolicyConditionAccepted, metav1.ConditionTrue, gwapiv1a1.PolicyReasonAccepted, "BackendTLSPolicy is Accepted") + return tlsBundle + } +} diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go index a9cc6e555c6..4a7574b91d1 100644 --- a/internal/gatewayapi/runner/runner.go +++ b/internal/gatewayapi/runner/runner.go @@ -155,6 +155,11 @@ func (r *Runner) subscribeAndTranslate(ctx context.Context) { key := utils.NamespacedName(securityPolicy) r.ProviderResources.SecurityPolicyStatuses.Store(key, &securityPolicy.Status) } + for _, backendTLSPolicy := range result.BackendTLSPolicies { + backendTLSPolicy := backendTLSPolicy + key := utils.NamespacedName(backendTLSPolicy) + r.ProviderResources.BackendTLSPolicyStatuses.Store(key, &backendTLSPolicy.Status) + } } // Delete keys // There is a 1:1 mapping between infra and xds IR keys diff --git a/internal/gatewayapi/testdata/backendtlspolicy-ca-only.in.yaml b/internal/gatewayapi/testdata/backendtlspolicy-ca-only.in.yaml new file mode 100644 index 00000000000..391eeb060bf --- /dev/null +++ b/internal/gatewayapi/testdata/backendtlspolicy-ca-only.in.yaml @@ -0,0 +1,137 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-btls + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-btls + namespace: envoy-gateway + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-btls + sectionName: http + rules: + - matches: + - path: + type: Exact + value: "/exact" + backendRefs: + - name: http-backend + namespace: backends + port: 8080 + +referenceGrants: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: ReferenceGrant + metadata: + name: refg-route-svc + namespace: backends + spec: + from: + - group: gateway.networking.k8s.io + kind: HTTPRoute + namespace: envoy-gateway + - group: gateway.networking.k8s.io + kind: Gateway + namespace: envoy-gateway + - group: gateway.networking.k8s.io + kind: BackendTLSPolicy + namespace: policies + to: + - group: "" + kind: Service + +services: + - apiVersion: v1 + kind: Service + metadata: + name: http-backend + namespace: backends + spec: + clusterIP: 10.11.12.13 + ports: + - port: 8080 + name: http + protocol: TCP + targetPort: 8080 + + +endpointSlices: + - apiVersion: discovery.k8s.io/v1 + kind: EndpointSlice + metadata: + name: endpointslice-http-backend + namespace: backends + labels: + kubernetes.io/service-name: http-backend + addressType: IPv4 + ports: + - name: http + protocol: TCP + port: 8080 + endpoints: + - addresses: + - "10.244.0.11" + conditions: + ready: true + +configMaps: + - apiVersion: v1 + kind: ConfigMap + metadata: + name: ca-cmap + namespace: policies + data: + ca.crt: | + -----BEGIN CERTIFICATE----- + MIIDJzCCAg+gAwIBAgIUAl6UKIuKmzte81cllz5PfdN2IlIwDQYJKoZIhvcNAQEL + BQAwIzEQMA4GA1UEAwwHbXljaWVudDEPMA0GA1UECgwGa3ViZWRiMB4XDTIzMTAw + MjA1NDE1N1oXDTI0MTAwMTA1NDE1N1owIzEQMA4GA1UEAwwHbXljaWVudDEPMA0G + A1UECgwGa3ViZWRiMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwSTc + 1yj8HW62nynkFbXo4VXKv2jC0PM7dPVky87FweZcTKLoWQVPQE2p2kLDK6OEszmM + yyr+xxWtyiveremrWqnKkNTYhLfYPhgQkczib7eUalmFjUbhWdLvHakbEgCodn3b + kz57mInX2VpiDOKg4kyHfiuXWpiBqrCx0KNLpxo3DEQcFcsQTeTHzh4752GV04RU + Ti/GEWyzIsl4Rg7tGtAwmcIPgUNUfY2Q390FGqdH4ahn+mw/6aFbW31W63d9YJVq + ioyOVcaMIpM5B/c7Qc8SuhCI1YGhUyg4cRHLEw5VtikioyE3X04kna3jQAj54YbR + bpEhc35apKLB21HOUQIDAQABo1MwUTAdBgNVHQ4EFgQUyvl0VI5vJVSuYFXu7B48 + 6PbMEAowHwYDVR0jBBgwFoAUyvl0VI5vJVSuYFXu7B486PbMEAowDwYDVR0TAQH/ + BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAMLxrgFVMuNRq2wAwcBt7SnNR5Cfz + 2MvXq5EUmuawIUi9kaYjwdViDREGSjk7JW17vl576HjDkdfRwi4E28SydRInZf6J + i8HZcZ7caH6DxR335fgHVzLi5NiTce/OjNBQzQ2MJXVDd8DBmG5fyatJiOJQ4bWE + A7FlP0RdP3CO3GWE0M5iXOB2m1qWkE2eyO4UHvwTqNQLdrdAXgDQlbam9e4BG3Gg + d/6thAkWDbt/QNT+EJHDCvhDRKh1RuGHyg+Y+/nebTWWrFWsktRrbOoHCZiCpXI1 + 3eXE6nt0YkgtDxG22KqnhpAg9gUSs2hlhoxyvkzyF0mu6NhPlwAgnq7+/Q== + -----END CERTIFICATE----- +backendTLSPolicies: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: BackendTLSPolicy + metadata: + name: policy-btls + namespace: policies + spec: + targetRef: + group: '' + kind: Service + name: http-backend + namespace: backends + sectionName: "8080" + tls: + caCertRefs: + - name: ca-cmap + group: '' + kind: ConfigMap + hostname: example.com diff --git a/internal/gatewayapi/testdata/backendtlspolicy-ca-only.out.yaml b/internal/gatewayapi/testdata/backendtlspolicy-ca-only.out.yaml new file mode 100755 index 00000000000..b2488ea6103 --- /dev/null +++ b/internal/gatewayapi/testdata/backendtlspolicy-ca-only.out.yaml @@ -0,0 +1,169 @@ +backendTLSPolicies: +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: BackendTLSPolicy + metadata: + creationTimestamp: null + name: policy-btls + namespace: policies + spec: + targetRef: + group: "" + kind: Service + name: http-backend + namespace: backends + sectionName: "8080" + tls: + caCertRefs: + - group: "" + kind: ConfigMap + name: ca-cmap + hostname: example.com + status: + ancestors: + - ancestorRef: + name: gateway-btls + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: BackendTLSPolicy is Accepted + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-btls + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-btls + namespace: envoy-gateway + spec: + parentRefs: + - name: gateway-btls + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: http-backend + namespace: backends + port: 8080 + matches: + - path: + type: Exact + value: /exact + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-btls + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-btls: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-btls/http + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-btls + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-btls +xdsIR: + envoy-gateway/gateway-btls: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-btls/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/envoy-gateway/httproute-btls/rule/0 + settings: + - addressType: IP + endpoints: + - host: 10.244.0.11 + port: 8080 + protocol: HTTP + tls: + CACertificate: + certificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKekNDQWcrZ0F3SUJBZ0lVQWw2VUtJdUttenRlODFjbGx6NVBmZE4ySWxJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHQTFVRUNnd0dhM1ZpWldSaU1CNFhEVEl6TVRBdwpNakExTkRFMU4xb1hEVEkwTVRBd01UQTFOREUxTjFvd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHCkExVUVDZ3dHYTNWaVpXUmlNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdTVGMKMXlqOEhXNjJueW5rRmJYbzRWWEt2MmpDMFBNN2RQVmt5ODdGd2VaY1RLTG9XUVZQUUUycDJrTERLNk9Fc3ptTQp5eXIreHhXdHlpdmVyZW1yV3FuS2tOVFloTGZZUGhnUWtjemliN2VVYWxtRmpVYmhXZEx2SGFrYkVnQ29kbjNiCmt6NTdtSW5YMlZwaURPS2c0a3lIZml1WFdwaUJxckN4MEtOTHB4bzNERVFjRmNzUVRlVEh6aDQ3NTJHVjA0UlUKVGkvR0VXeXpJc2w0Umc3dEd0QXdtY0lQZ1VOVWZZMlEzOTBGR3FkSDRhaG4rbXcvNmFGYlczMVc2M2Q5WUpWcQppb3lPVmNhTUlwTTVCL2M3UWM4U3VoQ0kxWUdoVXlnNGNSSExFdzVWdGlraW95RTNYMDRrbmEzalFBajU0WWJSCmJwRWhjMzVhcEtMQjIxSE9VUUlEQVFBQm8xTXdVVEFkQmdOVkhRNEVGZ1FVeXZsMFZJNXZKVlN1WUZYdTdCNDgKNlBiTUVBb3dId1lEVlIwakJCZ3dGb0FVeXZsMFZJNXZKVlN1WUZYdTdCNDg2UGJNRUFvd0R3WURWUjBUQVFILwpCQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFNTHhyZ0ZWTXVOUnEyd0F3Y0J0N1NuTlI1Q2Z6CjJNdlhxNUVVbXVhd0lVaTlrYVlqd2RWaURSRUdTams3SlcxN3ZsNTc2SGpEa2RmUndpNEUyOFN5ZFJJblpmNkoKaThIWmNaN2NhSDZEeFIzMzVmZ0hWekxpNU5pVGNlL09qTkJRelEyTUpYVkRkOERCbUc1ZnlhdEppT0pRNGJXRQpBN0ZsUDBSZFAzQ08zR1dFME01aVhPQjJtMXFXa0UyZXlPNFVIdndUcU5RTGRyZEFYZ0RRbGJhbTllNEJHM0dnCmQvNnRoQWtXRGJ0L1FOVCtFSkhEQ3ZoRFJLaDFSdUdIeWcrWSsvbmViVFdXckZXc2t0UnJiT29IQ1ppQ3BYSTEKM2VYRTZudDBZa2d0RHhHMjJLcW5ocEFnOWdVU3MyaGxob3h5dmt6eUYwbXU2TmhQbHdBZ25xNysvUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + name: policy-btls/policies-ca + SNI: example.com + weight: 1 + hostname: '*' + isHTTP2: false + name: httproute/envoy-gateway/httproute-btls/rule/0/match/0/* + pathMatch: + distinct: false + exact: /exact + name: "" diff --git a/internal/gatewayapi/testdata/backendtlspolicy-default-ns.in.yaml b/internal/gatewayapi/testdata/backendtlspolicy-default-ns.in.yaml new file mode 100644 index 00000000000..f935b78dc7d --- /dev/null +++ b/internal/gatewayapi/testdata/backendtlspolicy-default-ns.in.yaml @@ -0,0 +1,137 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-btls + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-btls + namespace: envoy-gateway + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-btls + sectionName: http + rules: + - matches: + - path: + type: Exact + value: "/exact" + backendRefs: + - name: http-backend + namespace: default + port: 8080 + +referenceGrants: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: ReferenceGrant + metadata: + name: refg-route-svc + namespace: default + spec: + from: + - group: gateway.networking.k8s.io + kind: HTTPRoute + namespace: envoy-gateway + - group: gateway.networking.k8s.io + kind: Gateway + namespace: envoy-gateway + - group: gateway.networking.k8s.io + kind: BackendTLSPolicy + namespace: default + to: + - group: "" + kind: Service + +services: + - apiVersion: v1 + kind: Service + metadata: + name: http-backend + namespace: default + spec: + clusterIP: 10.11.12.13 + ports: + - port: 8080 + name: http + protocol: TCP + targetPort: 8080 + + +endpointSlices: + - apiVersion: discovery.k8s.io/v1 + kind: EndpointSlice + metadata: + name: endpointslice-http-backend + namespace: default + labels: + kubernetes.io/service-name: http-backend + addressType: IPv4 + ports: + - name: http + protocol: TCP + port: 8080 + endpoints: + - addresses: + - "10.244.0.11" + conditions: + ready: true + + +configMaps: + - apiVersion: v1 + kind: ConfigMap + metadata: + name: ca-cmap + namespace: default + data: + ca.crt: | + -----BEGIN CERTIFICATE----- + MIIDJzCCAg+gAwIBAgIUAl6UKIuKmzte81cllz5PfdN2IlIwDQYJKoZIhvcNAQEL + BQAwIzEQMA4GA1UEAwwHbXljaWVudDEPMA0GA1UECgwGa3ViZWRiMB4XDTIzMTAw + MjA1NDE1N1oXDTI0MTAwMTA1NDE1N1owIzEQMA4GA1UEAwwHbXljaWVudDEPMA0G + A1UECgwGa3ViZWRiMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwSTc + 1yj8HW62nynkFbXo4VXKv2jC0PM7dPVky87FweZcTKLoWQVPQE2p2kLDK6OEszmM + yyr+xxWtyiveremrWqnKkNTYhLfYPhgQkczib7eUalmFjUbhWdLvHakbEgCodn3b + kz57mInX2VpiDOKg4kyHfiuXWpiBqrCx0KNLpxo3DEQcFcsQTeTHzh4752GV04RU + Ti/GEWyzIsl4Rg7tGtAwmcIPgUNUfY2Q390FGqdH4ahn+mw/6aFbW31W63d9YJVq + ioyOVcaMIpM5B/c7Qc8SuhCI1YGhUyg4cRHLEw5VtikioyE3X04kna3jQAj54YbR + bpEhc35apKLB21HOUQIDAQABo1MwUTAdBgNVHQ4EFgQUyvl0VI5vJVSuYFXu7B48 + 6PbMEAowHwYDVR0jBBgwFoAUyvl0VI5vJVSuYFXu7B486PbMEAowDwYDVR0TAQH/ + BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAMLxrgFVMuNRq2wAwcBt7SnNR5Cfz + 2MvXq5EUmuawIUi9kaYjwdViDREGSjk7JW17vl576HjDkdfRwi4E28SydRInZf6J + i8HZcZ7caH6DxR335fgHVzLi5NiTce/OjNBQzQ2MJXVDd8DBmG5fyatJiOJQ4bWE + A7FlP0RdP3CO3GWE0M5iXOB2m1qWkE2eyO4UHvwTqNQLdrdAXgDQlbam9e4BG3Gg + d/6thAkWDbt/QNT+EJHDCvhDRKh1RuGHyg+Y+/nebTWWrFWsktRrbOoHCZiCpXI1 + 3eXE6nt0YkgtDxG22KqnhpAg9gUSs2hlhoxyvkzyF0mu6NhPlwAgnq7+/Q== + -----END CERTIFICATE----- +backendTLSPolicies: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: BackendTLSPolicy + metadata: + name: policy-btls + namespace: default + spec: + targetRef: + group: '' + kind: Service + name: http-backend + sectionName: "8080" + tls: + caCertRefs: + - name: ca-cmap + group: '' + kind: ConfigMap + hostname: example.com diff --git a/internal/gatewayapi/testdata/backendtlspolicy-default-ns.out.yaml b/internal/gatewayapi/testdata/backendtlspolicy-default-ns.out.yaml new file mode 100755 index 00000000000..b9497558a9f --- /dev/null +++ b/internal/gatewayapi/testdata/backendtlspolicy-default-ns.out.yaml @@ -0,0 +1,168 @@ +backendTLSPolicies: +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: BackendTLSPolicy + metadata: + creationTimestamp: null + name: policy-btls + namespace: default + spec: + targetRef: + group: "" + kind: Service + name: http-backend + sectionName: "8080" + tls: + caCertRefs: + - group: "" + kind: ConfigMap + name: ca-cmap + hostname: example.com + status: + ancestors: + - ancestorRef: + name: gateway-btls + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: BackendTLSPolicy is Accepted + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-btls + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-btls + namespace: envoy-gateway + spec: + parentRefs: + - name: gateway-btls + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: http-backend + namespace: default + port: 8080 + matches: + - path: + type: Exact + value: /exact + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-btls + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-btls: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-btls/http + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-btls + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-btls +xdsIR: + envoy-gateway/gateway-btls: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-btls/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/envoy-gateway/httproute-btls/rule/0 + settings: + - addressType: IP + endpoints: + - host: 10.244.0.11 + port: 8080 + protocol: HTTP + tls: + CACertificate: + certificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKekNDQWcrZ0F3SUJBZ0lVQWw2VUtJdUttenRlODFjbGx6NVBmZE4ySWxJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHQTFVRUNnd0dhM1ZpWldSaU1CNFhEVEl6TVRBdwpNakExTkRFMU4xb1hEVEkwTVRBd01UQTFOREUxTjFvd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHCkExVUVDZ3dHYTNWaVpXUmlNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdTVGMKMXlqOEhXNjJueW5rRmJYbzRWWEt2MmpDMFBNN2RQVmt5ODdGd2VaY1RLTG9XUVZQUUUycDJrTERLNk9Fc3ptTQp5eXIreHhXdHlpdmVyZW1yV3FuS2tOVFloTGZZUGhnUWtjemliN2VVYWxtRmpVYmhXZEx2SGFrYkVnQ29kbjNiCmt6NTdtSW5YMlZwaURPS2c0a3lIZml1WFdwaUJxckN4MEtOTHB4bzNERVFjRmNzUVRlVEh6aDQ3NTJHVjA0UlUKVGkvR0VXeXpJc2w0Umc3dEd0QXdtY0lQZ1VOVWZZMlEzOTBGR3FkSDRhaG4rbXcvNmFGYlczMVc2M2Q5WUpWcQppb3lPVmNhTUlwTTVCL2M3UWM4U3VoQ0kxWUdoVXlnNGNSSExFdzVWdGlraW95RTNYMDRrbmEzalFBajU0WWJSCmJwRWhjMzVhcEtMQjIxSE9VUUlEQVFBQm8xTXdVVEFkQmdOVkhRNEVGZ1FVeXZsMFZJNXZKVlN1WUZYdTdCNDgKNlBiTUVBb3dId1lEVlIwakJCZ3dGb0FVeXZsMFZJNXZKVlN1WUZYdTdCNDg2UGJNRUFvd0R3WURWUjBUQVFILwpCQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFNTHhyZ0ZWTXVOUnEyd0F3Y0J0N1NuTlI1Q2Z6CjJNdlhxNUVVbXVhd0lVaTlrYVlqd2RWaURSRUdTams3SlcxN3ZsNTc2SGpEa2RmUndpNEUyOFN5ZFJJblpmNkoKaThIWmNaN2NhSDZEeFIzMzVmZ0hWekxpNU5pVGNlL09qTkJRelEyTUpYVkRkOERCbUc1ZnlhdEppT0pRNGJXRQpBN0ZsUDBSZFAzQ08zR1dFME01aVhPQjJtMXFXa0UyZXlPNFVIdndUcU5RTGRyZEFYZ0RRbGJhbTllNEJHM0dnCmQvNnRoQWtXRGJ0L1FOVCtFSkhEQ3ZoRFJLaDFSdUdIeWcrWSsvbmViVFdXckZXc2t0UnJiT29IQ1ppQ3BYSTEKM2VYRTZudDBZa2d0RHhHMjJLcW5ocEFnOWdVU3MyaGxob3h5dmt6eUYwbXU2TmhQbHdBZ25xNysvUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + name: policy-btls/default-ca + SNI: example.com + weight: 1 + hostname: '*' + isHTTP2: false + name: httproute/envoy-gateway/httproute-btls/rule/0/match/0/* + pathMatch: + distinct: false + exact: /exact + name: "" diff --git a/internal/gatewayapi/testdata/backendtlspolicy-invalid-ca.in.yaml b/internal/gatewayapi/testdata/backendtlspolicy-invalid-ca.in.yaml new file mode 100644 index 00000000000..d11c1a5f289 --- /dev/null +++ b/internal/gatewayapi/testdata/backendtlspolicy-invalid-ca.in.yaml @@ -0,0 +1,119 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-btls + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-btls + namespace: envoy-gateway + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-btls + sectionName: http + rules: + - matches: + - path: + type: Exact + value: "/exact" + backendRefs: + - name: http-backend + namespace: backends + port: 8080 + +referenceGrants: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: ReferenceGrant + metadata: + name: refg-route-svc + namespace: backends + spec: + from: + - group: gateway.networking.k8s.io + kind: HTTPRoute + namespace: envoy-gateway + - group: gateway.networking.k8s.io + kind: Gateway + namespace: envoy-gateway + - group: gateway.networking.k8s.io + kind: BackendTLSPolicy + namespace: policies + to: + - group: "" + kind: Service + +services: + - apiVersion: v1 + kind: Service + metadata: + name: http-backend + namespace: backends + spec: + clusterIP: 10.11.12.13 + ports: + - port: 8080 + name: http + protocol: TCP + targetPort: 8080 + + +endpointSlices: + - apiVersion: discovery.k8s.io/v1 + kind: EndpointSlice + metadata: + name: endpointslice-http-backend + namespace: backends + labels: + kubernetes.io/service-name: http-backend + addressType: IPv4 + ports: + - name: http + protocol: TCP + port: 8080 + endpoints: + - addresses: + - "10.244.0.11" + conditions: + ready: true + +configMaps: + - apiVersion: v1 + kind: ConfigMap + metadata: + name: no-ca-cmap + namespace: policies + data: + garbage.crt: | + itsAllGarbage +backendTLSPolicies: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: BackendTLSPolicy + metadata: + name: policy-btls + namespace: policies + spec: + targetRef: + group: '' + kind: Service + name: http-backend + namespace: backends + sectionName: "8080" + tls: + caCertRefs: + - name: no-ca-cmap + group: '' + kind: ConfigMap + hostname: example.com diff --git a/internal/gatewayapi/testdata/backendtlspolicy-invalid-ca.out.yaml b/internal/gatewayapi/testdata/backendtlspolicy-invalid-ca.out.yaml new file mode 100755 index 00000000000..ae583f55e47 --- /dev/null +++ b/internal/gatewayapi/testdata/backendtlspolicy-invalid-ca.out.yaml @@ -0,0 +1,164 @@ +backendTLSPolicies: +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: BackendTLSPolicy + metadata: + creationTimestamp: null + name: policy-btls + namespace: policies + spec: + targetRef: + group: "" + kind: Service + name: http-backend + namespace: backends + sectionName: "8080" + tls: + caCertRefs: + - group: "" + kind: ConfigMap + name: no-ca-cmap + hostname: example.com + status: + ancestors: + - ancestorRef: + name: gateway-btls + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: No ca found in configmap no-ca-cmap + reason: Invalid + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-btls + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-btls + namespace: envoy-gateway + spec: + parentRefs: + - name: gateway-btls + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: http-backend + namespace: backends + port: 8080 + matches: + - path: + type: Exact + value: /exact + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-btls + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-btls: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-btls/http + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-btls + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-btls +xdsIR: + envoy-gateway/gateway-btls: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-btls/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/envoy-gateway/httproute-btls/rule/0 + settings: + - addressType: IP + endpoints: + - host: 10.244.0.11 + port: 8080 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + name: httproute/envoy-gateway/httproute-btls/rule/0/match/0/* + pathMatch: + distinct: false + exact: /exact + name: "" diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index 5163deca337..9a62a78fb94 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -9,6 +9,7 @@ import ( "golang.org/x/exp/maps" "k8s.io/apimachinery/pkg/runtime/schema" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + egv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" "github.com/envoyproxy/gateway/internal/ir" @@ -17,6 +18,7 @@ import ( const ( KindConfigMap = "ConfigMap" KindClientTrafficPolicy = "ClientTrafficPolicy" + KindBackendTLSPolicy = "BackendTLSPolicy" KindEnvoyProxy = "EnvoyProxy" KindGateway = "Gateway" KindGatewayClass = "GatewayClass" @@ -108,6 +110,7 @@ func newTranslateResult(gateways []*GatewayContext, clientTrafficPolicies []*egv1a1.ClientTrafficPolicy, backendTrafficPolicies []*egv1a1.BackendTrafficPolicy, securityPolicies []*egv1a1.SecurityPolicy, + backendTLSPolicies []*egv1a2.BackendTLSPolicy, xdsIR XdsIRMap, infraIR InfraIRMap) *TranslateResult { translateResult := &TranslateResult{ XdsIR: xdsIR, @@ -136,6 +139,7 @@ func newTranslateResult(gateways []*GatewayContext, translateResult.ClientTrafficPolicies = append(translateResult.ClientTrafficPolicies, clientTrafficPolicies...) translateResult.BackendTrafficPolicies = append(translateResult.BackendTrafficPolicies, backendTrafficPolicies...) translateResult.SecurityPolicies = append(translateResult.SecurityPolicies, securityPolicies...) + translateResult.BackendTLSPolicies = append(translateResult.BackendTLSPolicies, backendTLSPolicies...) return translateResult } @@ -200,12 +204,15 @@ func (t *Translator) Translate(resources *Resources) *TranslateResult { securityPolicies := t.ProcessSecurityPolicies( resources.SecurityPolicies, gateways, routes, resources, xdsIR) + backendTLSPolicies := t.ProcessBackendTLSPoliciesAncestorRef( + resources.BackendTLSPolicies, gateways) + // Sort xdsIR based on the Gateway API spec sortXdsIRMap(xdsIR) return newTranslateResult(gateways, httpRoutes, grpcRoutes, tlsRoutes, tcpRoutes, udpRoutes, clientTrafficPolicies, backendTrafficPolicies, - securityPolicies, xdsIR, infraIR) + securityPolicies, backendTLSPolicies, xdsIR, infraIR) } diff --git a/internal/gatewayapi/zz_generated.deepcopy.go b/internal/gatewayapi/zz_generated.deepcopy.go index eb31a82d5e9..30c6c2938b1 100644 --- a/internal/gatewayapi/zz_generated.deepcopy.go +++ b/internal/gatewayapi/zz_generated.deepcopy.go @@ -227,6 +227,17 @@ func (in *Resources) DeepCopyInto(out *Resources) { } } } + if in.BackendTLSPolicies != nil { + in, out := &in.BackendTLSPolicies, &out.BackendTLSPolicies + *out = make([]*v1alpha2.BackendTLSPolicy, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(v1alpha2.BackendTLSPolicy) + (*in).DeepCopyInto(*out) + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Resources. diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 91b0cebf794..4877c9e8698 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -825,6 +825,8 @@ type DestinationSetting struct { Endpoints []*DestinationEndpoint `json:"endpoints,omitempty" yaml:"endpoints,omitempty"` // AddressTypeState specifies the state of DestinationEndpoint address type. AddressType *DestinationAddressType `json:"addressType,omitempty" yaml:"addressType,omitempty"` + + TLS *TLSUpstreamConfig `json:"tls,omitempty" yaml:"tls,omitempty"` } // Validate the fields within the RouteDestination structure @@ -1733,3 +1735,10 @@ type BackOffPolicy struct { // MaxInterval is the maximum interval between retries. MaxInterval *metav1.Duration `json:"maxInterval,omitempty"` } + +// TLSUpstreamConfig contains sni and ca file in []byte format. +// +k8s:deepcopy-gen=true +type TLSUpstreamConfig struct { + SNI string + CACertificate TLSCACertificate +} diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 52923b43f8c..ead04ca4e48 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -353,6 +353,11 @@ func (in *DestinationSetting) DeepCopyInto(out *DestinationSetting) { *out = new(DestinationAddressType) **out = **in } + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(TLSUpstreamConfig) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DestinationSetting. @@ -2074,6 +2079,22 @@ func (in *TLSInspectorConfig) DeepCopy() *TLSInspectorConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSUpstreamConfig) DeepCopyInto(out *TLSUpstreamConfig) { + *out = *in + in.CACertificate.DeepCopyInto(&out.CACertificate) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSUpstreamConfig. +func (in *TLSUpstreamConfig) DeepCopy() *TLSUpstreamConfig { + if in == nil { + return nil + } + out := new(TLSUpstreamConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TextAccessLog) DeepCopyInto(out *TextAccessLog) { *out = *in diff --git a/internal/message/types.go b/internal/message/types.go index 2d7fdfc2c6e..ded83524326 100644 --- a/internal/message/types.go +++ b/internal/message/types.go @@ -94,12 +94,14 @@ type PolicyStatuses struct { BackendTrafficPolicyStatuses watchable.Map[types.NamespacedName, *egv1a1.BackendTrafficPolicyStatus] EnvoyPatchPolicyStatuses watchable.Map[types.NamespacedName, *egv1a1.EnvoyPatchPolicyStatus] SecurityPolicyStatuses watchable.Map[types.NamespacedName, *egv1a1.SecurityPolicyStatus] + BackendTLSPolicyStatuses watchable.Map[types.NamespacedName, *gwapiv1a2.PolicyStatus] } func (p *PolicyStatuses) Close() { p.ClientTrafficPolicyStatuses.Close() p.SecurityPolicyStatuses.Close() p.EnvoyPatchPolicyStatuses.Close() + p.BackendTLSPolicyStatuses.Close() } // XdsIR message diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index 1d700daf034..f7de037f8c0 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -308,6 +308,23 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, _ reconcile.Reques // Add the referenced Secrets in SecurityPolicies to the resourceTree r.processSecurityPolicySecretRefs(ctx, gwcResource, resourceMappings) + // Add all BackendTLSPolies + backendTLSPolicies := gwapiv1a2.BackendTLSPolicyList{} + if err := r.client.List(ctx, &backendTLSPolicies); err != nil { + return reconcile.Result{}, fmt.Errorf("error listing BackendTLSPolicies: %w", err) + } + + for _, policy := range backendTLSPolicies.Items { + policy := policy + // Discard Status to reduce memory consumption in watchable + // It will be recomputed by the gateway-api layer + policy.Status = gwapiv1a2.PolicyStatus{} // todo ? + gwcResource.BackendTLSPolicies = append(gwcResource.BackendTLSPolicies, &policy) + } + + // Add the referenced Secrets and ConfigMaps in BackendTLSPolicies to the resourceTree + r.processBackendTLSPolicyConfigMapRefs(ctx, gwcResource, resourceMappings) + // For this particular Gateway, and all associated objects, check whether the // namespace exists. Add to the resourceTree. for ns := range resourceMappings.allAssociatedNamespaces { @@ -934,7 +951,7 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M return err } - // Watch ConfigMap CRUDs and process affected ClienTraffiPolicies. + // Watch ConfigMap CRUDs and process affected ClienTraffiPolicies and BackendTLSPolicies. configMapPredicates := []predicate.Predicate{ predicate.GenerationChangedPredicate{}, predicate.NewPredicateFuncs(r.validateConfigMapForReconcile), @@ -1044,6 +1061,24 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M return err } + // Watch BackendTLSPolicy + btlsPredicates := []predicate.Predicate{predicate.GenerationChangedPredicate{}} + if r.namespaceLabel != nil { + btlsPredicates = append(btlsPredicates, predicate.NewPredicateFuncs(r.hasMatchingNamespaceLabels)) + } + + if err := c.Watch( + source.Kind(mgr.GetCache(), &gwapiv1a2.BackendTLSPolicy{}), + handler.EnqueueRequestsFromMapFunc(r.enqueueClass), + btlsPredicates..., + ); err != nil { + return err + } + + if err := addBtlsIndexers(ctx, mgr); err != nil { + return err + } + r.log.Info("Watching gatewayAPI related objects") // Watch any additional GVKs from the registered extension. @@ -1173,3 +1208,40 @@ func (r *gatewayAPIReconciler) serviceImportCRDExists(mgr manager.Manager) bool return serviceImportFound } + +func (r *gatewayAPIReconciler) processBackendTLSPolicyConfigMapRefs(ctx context.Context, resourceTree *gatewayapi.Resources, resourceMap *resourceMappings) { + for _, policy := range resourceTree.BackendTLSPolicies { + tls := policy.Spec.TLS + + if tls.CACertRefs != nil { + for _, caCertRef := range tls.CACertRefs { + if string(caCertRef.Kind) == gatewayapi.KindConfigMap { + caRefNew := gwapiv1b1.SecretObjectReference{ + Group: gatewayapi.GroupPtr(string(caCertRef.Group)), + Kind: gatewayapi.KindPtr(string(caCertRef.Kind)), + Name: caCertRef.Name, + Namespace: gatewayapi.NamespacePtr(policy.Namespace), + } + if err := r.processConfigMapRef( + ctx, + resourceMap, + resourceTree, + gatewayapi.KindBackendTLSPolicy, + policy.Namespace, + policy.Name, + caRefNew); err != nil { + // we don't return an error here, because we want to continue + // reconciling the rest of the ClientTrafficPolicies despite that this + // reference is invalid. + // This ClientTrafficPolicy will be marked as invalid in its status + // when translating to IR because the referenced configmap can't be + // found. + r.log.Error(err, + "failed to process CACertificateRef for BackendTLSPolicy", + "policy", policy, "caCertificateRef", caCertRef.Name) + } + } + } + } + } +} diff --git a/internal/provider/kubernetes/indexers.go b/internal/provider/kubernetes/indexers.go index f4daed5960e..3e638a17aae 100644 --- a/internal/provider/kubernetes/indexers.go +++ b/internal/provider/kubernetes/indexers.go @@ -37,6 +37,7 @@ const ( backendSecurityPolicyIndex = "backendSecurityPolicyIndex" configMapCtpIndex = "configMapCtpIndex" secretCtpIndex = "secretCtpIndex" + configMapBtlsIndex = "configMapBtlsIndex" ) func addReferenceGrantIndexers(ctx context.Context, mgr manager.Manager) error { @@ -481,3 +482,32 @@ func secretCtpIndexFunc(rawObj client.Object) []string { } return secretReferences } + +// addBtlsIndexers adds indexing on BackendTLSPolicy, for ConfigMap objects that are +// referenced in BackendTLSPolicy objects. This helps in querying for BackendTLSPolicies that are +// affected by a particular ConfigMap CRUD. +func addBtlsIndexers(ctx context.Context, mgr manager.Manager) error { + if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.BackendTLSPolicy{}, configMapBtlsIndex, configMapBtlsIndexFunc); err != nil { + return err + } + + return nil +} + +func configMapBtlsIndexFunc(rawObj client.Object) []string { + btls := rawObj.(*gwapiv1a2.BackendTLSPolicy) + var configMapReferences []string + if btls.Spec.TLS.CACertRefs != nil { + for _, caCertRef := range btls.Spec.TLS.CACertRefs { + if string(caCertRef.Kind) == gatewayapi.KindConfigMap { + configMapReferences = append(configMapReferences, + types.NamespacedName{ + Namespace: btls.Namespace, + Name: string(caCertRef.Name), + }.String(), + ) + } + } + } + return configMapReferences +} diff --git a/internal/provider/kubernetes/predicates.go b/internal/provider/kubernetes/predicates.go index c929721dccc..2939a5753a0 100644 --- a/internal/provider/kubernetes/predicates.go +++ b/internal/provider/kubernetes/predicates.go @@ -504,5 +504,17 @@ func (r *gatewayAPIReconciler) validateConfigMapForReconcile(obj client.Object) return false } + btlsList := &gwapiv1a2.BackendTLSPolicyList{} + if err := r.client.List(context.Background(), btlsList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(configMapBtlsIndex, utils.NamespacedName(configMap).String()), + }); err != nil { + r.log.Error(err, "unable to find associated BackendTLSPolicy") + return false + } + + if len(btlsList.Items) == 0 { + return false + } + return true } diff --git a/internal/provider/kubernetes/status.go b/internal/provider/kubernetes/status.go index f26ae4c877a..1e780a19689 100644 --- a/internal/provider/kubernetes/status.go +++ b/internal/provider/kubernetes/status.go @@ -334,6 +334,36 @@ func (r *gatewayAPIReconciler) subscribeAndUpdateStatus(ctx context.Context) { ) r.log.Info("securityPolicy status subscriber shutting down") }() + + // BackendTLSPolicy object status updater + go func() { + message.HandleSubscription(message.Metadata{Runner: string(v1alpha1.LogComponentProviderRunner), Message: "backendtlspolicy-status"}, r.resources.BackendTLSPolicyStatuses.Subscribe(ctx), + func(update message.Update[types.NamespacedName, *gwapiv1a2.PolicyStatus], errChan chan error) { + // skip delete updates. + if update.Delete { + return + } + key := update.Key + val := update.Value + r.statusUpdater.Send(status.Update{ + NamespacedName: key, + Resource: new(gwapiv1a2.BackendTLSPolicy), + Mutator: status.MutatorFunc(func(obj client.Object) client.Object { + t, ok := obj.(*gwapiv1a2.BackendTLSPolicy) + if !ok { + err := fmt.Errorf("unsupported object type %T", obj) + errChan <- err + panic(err) + } + tCopy := t.DeepCopy() + tCopy.Status = *val + return tCopy + }), + }) + }, + ) + r.log.Info("backendTlsPolicy status subscriber shutting down") + }() } func (r *gatewayAPIReconciler) updateStatusForGateway(ctx context.Context, gtw *gwapiv1.Gateway) { diff --git a/internal/status/backendtlspolicy.go b/internal/status/backendtlspolicy.go new file mode 100644 index 00000000000..313cab2f595 --- /dev/null +++ b/internal/status/backendtlspolicy.go @@ -0,0 +1,32 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package status + +import ( + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + gwv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" +) + +func SetBackendTLSPolicyCondition(c *gwv1a2.BackendTLSPolicy, policyAnces gwv1a2.PolicyAncestorStatus, conditionType gwv1a2.PolicyConditionType, status metav1.ConditionStatus, reason gwv1a2.PolicyConditionReason, message string) { + + if c.Status.Ancestors == nil { + c.Status.Ancestors = []gwv1a2.PolicyAncestorStatus{} + } + + cond := newCondition(string(conditionType), status, string(reason), message, time.Now(), c.Generation) + for i, ancestor := range c.Status.Ancestors { + if ancestor.AncestorRef.Name == policyAnces.AncestorRef.Name && + (ancestor.AncestorRef.Namespace == nil || *ancestor.AncestorRef.Namespace == *policyAnces.AncestorRef.Namespace) { + c.Status.Ancestors[i].Conditions = MergeConditions(c.Status.Ancestors[i].Conditions, cond) + return + } + } + len := len(c.Status.Ancestors) + c.Status.Ancestors = append(c.Status.Ancestors, policyAnces) + c.Status.Ancestors[len].Conditions = MergeConditions(c.Status.Ancestors[len].Conditions, cond) +} diff --git a/internal/status/status.go b/internal/status/status.go index 5ffa6ed4ad1..4f945e2ca9c 100644 --- a/internal/status/status.go +++ b/internal/status/status.go @@ -170,6 +170,7 @@ func (u *UpdateWriter) Send(update Update) { // EnvoyPatchPolicy // ClientTrafficPolicy // SecurityPolicy +// BackendTLSPolicy func isStatusEqual(objA, objB interface{}) bool { opts := cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime") switch a := objA.(type) { @@ -239,6 +240,12 @@ func isStatusEqual(objA, objB interface{}) bool { return true } } + case gwapiv1a2.BackendTLSPolicy: + if b, ok := objB.(*gwapiv1a2.BackendTLSPolicy); ok { + if cmp.Equal(a.Status, b.Status, opts) { + return true + } + } } return false diff --git a/internal/xds/translator/testdata/in/xds-ir/http-route-with-tlsbundle.yaml b/internal/xds/translator/testdata/in/xds-ir/http-route-with-tlsbundle.yaml new file mode 100644 index 00000000000..e28cd14d655 --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/http-route-with-tlsbundle.yaml @@ -0,0 +1,34 @@ +http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-btls/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/envoy-gateway/httproute-btls/rule/0 + settings: + - addressType: IP + endpoints: + - host: 10.244.0.11 + port: 8080 + protocol: HTTP + tls: + CACertificate: + certificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKekNDQWcrZ0F3SUJBZ0lVQWw2VUtJdUttenRlODFjbGx6NVBmZE4ySWxJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHQTFVRUNnd0dhM1ZpWldSaU1CNFhEVEl6TVRBdwpNakExTkRFMU4xb1hEVEkwTVRBd01UQTFOREUxTjFvd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHCkExVUVDZ3dHYTNWaVpXUmlNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdTVGMKMXlqOEhXNjJueW5rRmJYbzRWWEt2MmpDMFBNN2RQVmt5ODdGd2VaY1RLTG9XUVZQUUUycDJrTERLNk9Fc3ptTQp5eXIreHhXdHlpdmVyZW1yV3FuS2tOVFloTGZZUGhnUWtjemliN2VVYWxtRmpVYmhXZEx2SGFrYkVnQ29kbjNiCmt6NTdtSW5YMlZwaURPS2c0a3lIZml1WFdwaUJxckN4MEtOTHB4bzNERVFjRmNzUVRlVEh6aDQ3NTJHVjA0UlUKVGkvR0VXeXpJc2w0Umc3dEd0QXdtY0lQZ1VOVWZZMlEzOTBGR3FkSDRhaG4rbXcvNmFGYlczMVc2M2Q5WUpWcQppb3lPVmNhTUlwTTVCL2M3UWM4U3VoQ0kxWUdoVXlnNGNSSExFdzVWdGlraW95RTNYMDRrbmEzalFBajU0WWJSCmJwRWhjMzVhcEtMQjIxSE9VUUlEQVFBQm8xTXdVVEFkQmdOVkhRNEVGZ1FVeXZsMFZJNXZKVlN1WUZYdTdCNDgKNlBiTUVBb3dId1lEVlIwakJCZ3dGb0FVeXZsMFZJNXZKVlN1WUZYdTdCNDg2UGJNRUFvd0R3WURWUjBUQVFILwpCQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFNTHhyZ0ZWTXVOUnEyd0F3Y0J0N1NuTlI1Q2Z6CjJNdlhxNUVVbXVhd0lVaTlrYVlqd2RWaURSRUdTams3SlcxN3ZsNTc2SGpEa2RmUndpNEUyOFN5ZFJJblpmNkoKaThIWmNaN2NhSDZEeFIzMzVmZ0hWekxpNU5pVGNlL09qTkJRelEyTUpYVkRkOERCbUc1ZnlhdEppT0pRNGJXRQpBN0ZsUDBSZFAzQ08zR1dFME01aVhPQjJtMXFXa0UyZXlPNFVIdndUcU5RTGRyZEFYZ0RRbGJhbTllNEJHM0dnCmQvNnRoQWtXRGJ0L1FOVCtFSkhEQ3ZoRFJLaDFSdUdIeWcrWSsvbmViVFdXckZXc2t0UnJiT29IQ1ppQ3BYSTEKM2VYRTZudDBZa2d0RHhHMjJLcW5ocEFnOWdVU3MyaGxob3h5dmt6eUYwbXU2TmhQbHdBZ25xNysvUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + name: policy-btls/policies-ca + SNI: example.com + weight: 1 + hostname: '*' + name: httproute/envoy-gateway/httproute-btls/rule/0/match/0/* + pathMatch: + distinct: false + exact: /exact + name: "" diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-with-tlsbundle.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-with-tlsbundle.clusters.yaml new file mode 100755 index 00000000000..d5baadb9ba1 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/http-route-with-tlsbundle.clusters.yaml @@ -0,0 +1,30 @@ +- circuitBreakers: + thresholds: + - retryBudget: + budgetPercent: + value: 100 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/envoy-gateway/httproute-btls/rule/0 + lbPolicy: LEAST_REQUEST + name: httproute/envoy-gateway/httproute-btls/rule/0 + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + commonTlsContext: + validationContextSdsSecretConfig: + name: policy-btls/policies-ca + sdsConfig: + ads: {} + resourceApiVersion: V3 + sni: example.com + type: EDS diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-with-tlsbundle.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-with-tlsbundle.endpoints.yaml new file mode 100755 index 00000000000..90d97a4e2f6 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/http-route-with-tlsbundle.endpoints.yaml @@ -0,0 +1,12 @@ +- clusterName: httproute/envoy-gateway/httproute-btls/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 10.244.0.11 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/envoy-gateway/httproute-btls/rule/0/backend/0 diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-with-tlsbundle.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-with-tlsbundle.listeners.yaml new file mode 100755 index 00000000000..4bdeddbb3fc --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/http-route-with-tlsbundle.listeners.yaml @@ -0,0 +1,34 @@ +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + defaultFilterChain: + filters: + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + commonHttpProtocolOptions: + headersWithUnderscoresAction: REJECT_REQUEST + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 + maxConcurrentStreams: 100 + httpFilters: + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + suppressEnvoyHeaders: true + mergeSlashes: true + normalizePath: true + pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT + rds: + configSource: + ads: {} + resourceApiVersion: V3 + routeConfigName: envoy-gateway/gateway-btls/http + serverHeaderTransformation: PASS_THROUGH + statPrefix: http + useRemoteAddress: true + drainType: MODIFY_ONLY + name: envoy-gateway/gateway-btls/http + perConnectionBufferLimitBytes: 32768 diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-with-tlsbundle.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-with-tlsbundle.routes.yaml new file mode 100755 index 00000000000..bd4f9cfe7e2 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/http-route-with-tlsbundle.routes.yaml @@ -0,0 +1,14 @@ +- ignorePortInHostMatching: true + name: envoy-gateway/gateway-btls/http + virtualHosts: + - domains: + - '*' + name: envoy-gateway/gateway-btls/http/* + routes: + - match: + path: /exact + name: httproute/envoy-gateway/httproute-btls/rule/0/match/0/* + route: + cluster: httproute/envoy-gateway/httproute-btls/rule/0 + upgradeConfigs: + - upgradeType: websocket diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-with-tlsbundle.secrets.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-with-tlsbundle.secrets.yaml new file mode 100755 index 00000000000..be757009ca1 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/http-route-with-tlsbundle.secrets.yaml @@ -0,0 +1,4 @@ +- name: policy-btls/policies-ca + validationContext: + trustedCa: + inlineBytes: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKekNDQWcrZ0F3SUJBZ0lVQWw2VUtJdUttenRlODFjbGx6NVBmZE4ySWxJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHQTFVRUNnd0dhM1ZpWldSaU1CNFhEVEl6TVRBdwpNakExTkRFMU4xb1hEVEkwTVRBd01UQTFOREUxTjFvd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHCkExVUVDZ3dHYTNWaVpXUmlNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdTVGMKMXlqOEhXNjJueW5rRmJYbzRWWEt2MmpDMFBNN2RQVmt5ODdGd2VaY1RLTG9XUVZQUUUycDJrTERLNk9Fc3ptTQp5eXIreHhXdHlpdmVyZW1yV3FuS2tOVFloTGZZUGhnUWtjemliN2VVYWxtRmpVYmhXZEx2SGFrYkVnQ29kbjNiCmt6NTdtSW5YMlZwaURPS2c0a3lIZml1WFdwaUJxckN4MEtOTHB4bzNERVFjRmNzUVRlVEh6aDQ3NTJHVjA0UlUKVGkvR0VXeXpJc2w0Umc3dEd0QXdtY0lQZ1VOVWZZMlEzOTBGR3FkSDRhaG4rbXcvNmFGYlczMVc2M2Q5WUpWcQppb3lPVmNhTUlwTTVCL2M3UWM4U3VoQ0kxWUdoVXlnNGNSSExFdzVWdGlraW95RTNYMDRrbmEzalFBajU0WWJSCmJwRWhjMzVhcEtMQjIxSE9VUUlEQVFBQm8xTXdVVEFkQmdOVkhRNEVGZ1FVeXZsMFZJNXZKVlN1WUZYdTdCNDgKNlBiTUVBb3dId1lEVlIwakJCZ3dGb0FVeXZsMFZJNXZKVlN1WUZYdTdCNDg2UGJNRUFvd0R3WURWUjBUQVFILwpCQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFNTHhyZ0ZWTXVOUnEyd0F3Y0J0N1NuTlI1Q2Z6CjJNdlhxNUVVbXVhd0lVaTlrYVlqd2RWaURSRUdTams3SlcxN3ZsNTc2SGpEa2RmUndpNEUyOFN5ZFJJblpmNkoKaThIWmNaN2NhSDZEeFIzMzVmZ0hWekxpNU5pVGNlL09qTkJRelEyTUpYVkRkOERCbUc1ZnlhdEppT0pRNGJXRQpBN0ZsUDBSZFAzQ08zR1dFME01aVhPQjJtMXFXa0UyZXlPNFVIdndUcU5RTGRyZEFYZ0RRbGJhbTllNEJHM0dnCmQvNnRoQWtXRGJ0L1FOVCtFSkhEQ3ZoRFJLaDFSdUdIeWcrWSsvbmViVFdXckZXc2t0UnJiT29IQ1ppQ3BYSTEKM2VYRTZudDBZa2d0RHhHMjJLcW5ocEFnOWdVU3MyaGxob3h5dmt6eUYwbXU2TmhQbHdBZ25xNysvUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index 4ce82ca543f..c59953f8b32 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -19,6 +19,8 @@ import ( tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" matcherv3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" + "github.com/envoyproxy/go-control-plane/pkg/wellknown" + "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/wrapperspb" extensionTypes "github.com/envoyproxy/gateway/internal/extension/types" @@ -504,10 +506,26 @@ func processXdsCluster(tCtx *types.ResourceVersionTable, httpRoute *ir.HTTPRoute endpointType = EndpointTypeStatic } + var tSocket *corev3.TransportSocket + + if httpRoute.Destination.Settings[0].TLS != nil { + CaSecret := buildXdsUpstreamTLSCASecret(httpRoute.Destination.Settings[0].TLS) + if err := tCtx.AddXdsResource(resourcev3.SecretType, CaSecret); err != nil { + return err + } + // for upstreamTLS , a fixed sni can be used. use auto_sni otherwise + // https://www.envoyproxy.io/docs/envoy/latest/faq/configuration/sni#faq-how-to-setup-sni:~:text=For%20clusters%2C%20a,for%20trust%20anchor. + temp, err := buildXdsUpstreamTLSSocketWthCert(httpRoute.Destination.Settings[0].TLS) + if err != nil { + return err + } + tSocket = temp + } + if err := addXdsCluster(tCtx, &xdsClusterArgs{ name: httpRoute.Destination.Name, settings: httpRoute.Destination.Settings, - tSocket: nil, + tSocket: tSocket, endpointType: endpointType, loadBalancer: httpRoute.LoadBalancer, proxyProtocol: httpRoute.ProxyProtocol, @@ -572,3 +590,51 @@ func addXdsCluster(tCtx *types.ResourceVersionTable, args *xdsClusterArgs) error } return nil } + +const ( + DefaultEndpointType EndpointType = iota + Static + EDS +) + +func buildXdsUpstreamTLSCASecret(tlsConfig *ir.TLSUpstreamConfig) *tlsv3.Secret { + // Build the tls secret + return &tlsv3.Secret{ + Name: tlsConfig.CACertificate.Name, + Type: &tlsv3.Secret_ValidationContext{ + ValidationContext: &tlsv3.CertificateValidationContext{ + TrustedCa: &corev3.DataSource{ + Specifier: &corev3.DataSource_InlineBytes{InlineBytes: tlsConfig.CACertificate.Certificate}, + }, + }, + }, + } +} + +func buildXdsUpstreamTLSSocketWthCert(tlsConfig *ir.TLSUpstreamConfig) (*corev3.TransportSocket, error) { + + tlsCtx := &tlsv3.UpstreamTlsContext{ + CommonTlsContext: &tlsv3.CommonTlsContext{ + TlsCertificateSdsSecretConfigs: nil, + ValidationContextType: &tlsv3.CommonTlsContext_ValidationContextSdsSecretConfig{ + ValidationContextSdsSecretConfig: &tlsv3.SdsSecretConfig{ + Name: tlsConfig.CACertificate.Name, + SdsConfig: makeConfigSource(), + }, + }, + }, + Sni: tlsConfig.SNI, + } + + tlsCtxAny, err := anypb.New(tlsCtx) + if err != nil { + return nil, err + } + + return &corev3.TransportSocket{ + Name: wellknown.TransportSocketTLS, + ConfigType: &corev3.TransportSocket_TypedConfig{ + TypedConfig: tlsCtxAny, + }, + }, nil +} diff --git a/internal/xds/translator/translator_test.go b/internal/xds/translator/translator_test.go index c3a5b609e58..78f307cd68e 100644 --- a/internal/xds/translator/translator_test.go +++ b/internal/xds/translator/translator_test.go @@ -90,6 +90,10 @@ func TestTranslateXds(t *testing.T) { { name: "http-route-dns-cluster", }, + { + name: "http-route-with-tlsbundle", + requireSecrets: true, + }, { name: "simple-tls", requireSecrets: true,