diff --git a/api/v1alpha1/envoygateway_types.go b/api/v1alpha1/envoygateway_types.go
index 9026878ee4..8d3fa614af 100644
--- a/api/v1alpha1/envoygateway_types.go
+++ b/api/v1alpha1/envoygateway_types.go
@@ -312,6 +312,8 @@ type ExtensionAPISettings struct {
// DisableLua determines if Lua EnvoyExtensionPolicies should be disabled.
// If set to true, the Lua EnvoyExtensionPolicy feature will be disabled.
DisableLua bool `json:"disableLua"`
+ // EnableSDSSecretRef enables read SDS(Secret Discovery Service) settings from a secret(with type gateway.envoyproxy.io/sds).
+ EnableSDSSecretRef bool `json:"enableSDSSecretRef"`
}
// EnvoyGatewayProvider defines the desired configuration of a provider.
diff --git a/api/v1alpha1/shared_types.go b/api/v1alpha1/shared_types.go
index 27178d2545..5450cb4913 100644
--- a/api/v1alpha1/shared_types.go
+++ b/api/v1alpha1/shared_types.go
@@ -16,6 +16,9 @@ import (
)
const (
+ // SDSSecretType is the type for secrets that reference SDS configuration
+ SDSSecretType = "gateway.envoyproxy.io/sds"
+
// DefaultDeploymentReplicas is the default number of deployment replicas.
DefaultDeploymentReplicas = 1
// DefaultDeploymentCPUResourceRequests for deployment cpu resource
diff --git a/internal/gatewayapi/backendtlspolicy.go b/internal/gatewayapi/backendtlspolicy.go
index f1f863ea2e..a5f00f24e9 100644
--- a/internal/gatewayapi/backendtlspolicy.go
+++ b/internal/gatewayapi/backendtlspolicy.go
@@ -262,7 +262,6 @@ func (t *Translator) processServerValidationTLSSettings(
if !tlsConfig.InsecureSkipVerify {
tlsConfig.UseSystemTrustStore = ptr.Deref(backend.Spec.TLS.WellKnownCACertificates, "") == gwapiv1.WellKnownCACertificatesSystem
-
if tlsConfig.UseSystemTrustStore {
tlsConfig.CACertificate = &ir.TLSCACertificate{
Name: fmt.Sprintf("%s/%s-ca", backend.Name, backend.Namespace),
@@ -270,7 +269,7 @@ func (t *Translator) processServerValidationTLSSettings(
} else if len(backend.Spec.TLS.CACertificateRefs) > 0 {
caRefs := getObjectReferences(gwapiv1.Namespace(backend.Namespace), backend.Spec.TLS.CACertificateRefs)
// Backend doesn't allow cross-namespace reference, so pass nil resources here.
- caCert, err := t.getCaCertsFromCARefs(nil, caRefs, resource.ResourceMetadata{
+ caCert, sds, err := t.getCaCertsFromCARefs(nil, caRefs, resource.ResourceMetadata{
Name: backend.Name,
Namespace: backend.Namespace,
Kind: resource.KindBackendTLSPolicy,
@@ -282,6 +281,7 @@ func (t *Translator) processServerValidationTLSSettings(
tlsConfig.CACertificate = &ir.TLSCACertificate{
Certificate: caCert,
Name: fmt.Sprintf("%s/%s-ca", backend.Name, backend.Namespace),
+ SDS: sds,
}
}
}
@@ -404,8 +404,27 @@ func (t *Translator) processClientTLSSettings(
)
return tlsConfig, err
}
- tlsConfig.ClientCertificates = append(tlsConfig.ClientCertificates, getTLSCertificateFromSecret(secret))
+ // Check if this is an SDS reference secret
+ if secret.Type == egv1a1.SDSSecretType {
+ if !t.SDSSecretRefEnabled {
+ return tlsConfig, fmt.Errorf("SDS Secret reference is not enabled in EnvoyGateway configuration")
+ }
+ // For SDS reference secrets, extract the SDS secret name and URL from data
+ s, err := ir.NewSDSConfig(secret)
+ if err != nil {
+ return tlsConfig, fmt.Errorf("invalid SDS reference secret: %w", err)
+ }
+ tlsConfig.ClientCertificates = []ir.TLSCertificate{
+ {
+ SDS: s,
+ },
+ }
+ } else {
+ // Regular secret processing
+ tlsConfig.ClientCertificates = append(tlsConfig.ClientCertificates, getTLSCertificateFromSecret(secret))
+ }
}
+
return tlsConfig, nil
}
@@ -472,7 +491,7 @@ func (t *Translator) getBackendTLSBundle(backendTLSPolicy *gwapiv1.BackendTLSPol
caRefs := getObjectReferences(gwapiv1.Namespace(backendTLSPolicy.Namespace), backendTLSPolicy.Spec.Validation.CACertificateRefs)
// BackendTLSPolicy doesn't allow cross-namespace reference,
// so pass nil resources here
- caCert, err := t.getCaCertsFromCARefs(nil, caRefs, resource.ResourceMetadata{
+ caCert, sds, err := t.getCaCertsFromCARefs(nil, caRefs, resource.ResourceMetadata{
Group: egv1a1.GroupName,
Name: backendTLSPolicy.Name,
Namespace: backendTLSPolicy.Namespace,
@@ -481,10 +500,13 @@ func (t *Translator) getBackendTLSBundle(backendTLSPolicy *gwapiv1.BackendTLSPol
if err != nil {
return nil, err
}
+
tlsBundle.CACertificate = &ir.TLSCACertificate{
Certificate: caCert,
Name: fmt.Sprintf("%s/%s-ca", backendTLSPolicy.Name, backendTLSPolicy.Namespace),
+ SDS: sds,
}
+
return tlsBundle, nil
}
@@ -503,9 +525,11 @@ func getObjectReferences(ns gwapiv1.Namespace, refs []gwapiv1.LocalObjectReferen
// getCaCertsFromCARefs retrieves CA certificates from the given CA refs. It supports ConfigMap, Secret, and ClusterTrustBundle kinds.
// TODO: move out of backendtlspolicy.go
-func (t *Translator) getCaCertsFromCARefs(resources *resource.Resources, caCertificates []gwapiv1.ObjectReference, meta resource.ResourceMetadata) ([]byte, error) {
+func (t *Translator) getCaCertsFromCARefs(resources *resource.Resources, caCertificates []gwapiv1.ObjectReference, meta resource.ResourceMetadata,
+) (caCert []byte, sds *ir.SDSConfig, err error) {
ca := ""
foundSupportedRef := false
+ var foundSDSConfig *ir.SDSConfig
for _, caRef := range caCertificates {
kind := string(caRef.Kind)
var caRefNs string
@@ -531,7 +555,7 @@ func (t *Translator) getCaCertsFromCARefs(resources *resource.Resources, caCerti
},
resources.ReferenceGrants,
) {
- return nil, fmt.Errorf("%w for caCertificateRef %s/%s (kind: %s, namespace: %s)", ErrRefNotPermitted, caRef.Group, caRef.Name, kind, caRefNs)
+ return nil, nil, fmt.Errorf("%w for caCertificateRef %s/%s (kind: %s, namespace: %s)", ErrRefNotPermitted, caRef.Group, caRef.Name, kind, caRefNs)
}
}
@@ -546,25 +570,41 @@ func (t *Translator) getCaCertsFromCARefs(resources *resource.Resources, caCerti
}
ca += crt
} else {
- return nil, fmt.Errorf("no ca found in configmap %s", cm.Name)
+ return nil, nil, fmt.Errorf("no ca found in configmap %s", cm.Name)
}
} else {
- return nil, fmt.Errorf("configmap %s not found in namespace %s", caRef.Name, caRefNs)
+ return nil, nil, fmt.Errorf("configmap %s not found in namespace %s", caRef.Name, caRefNs)
}
case resource.KindSecret:
foundSupportedRef = true
secret := t.GetSecret(caRefNs, string(caRef.Name))
if secret != nil {
+ // Check if this is an SDS reference secret
+ if secret.Type == egv1a1.SDSSecretType {
+ if !t.SDSSecretRefEnabled {
+ return nil, nil, fmt.Errorf("SDS Secret reference is not enabled in EnvoyGateway configuration")
+ }
+ if foundSDSConfig != nil {
+ return nil, nil, fmt.Errorf("multiple SDS reference secrets are not supported")
+ }
+ // For SDS reference secrets, extract the SDS secret name and URL from data
+ foundSDSConfig, err = ir.NewSDSConfig(secret)
+ if err != nil {
+ return nil, nil, fmt.Errorf("invalid SDS reference secret %s: %w", secret.Name, err)
+ }
+ continue
+ }
+ // Regular secret processing
if crt, dataOk := getOrFirstFromData(secret.Data, CACertKey); dataOk {
if ca != "" {
ca += "\n"
}
ca += string(crt)
} else {
- return nil, fmt.Errorf("no ca found in secret %s", secret.Name)
+ return nil, nil, fmt.Errorf("no ca found in secret %s", secret.Name)
}
} else {
- return nil, fmt.Errorf("secret %s not found in namespace %s", caRef.Name, caRefNs)
+ return nil, nil, fmt.Errorf("secret %s not found in namespace %s", caRef.Name, caRefNs)
}
case resource.KindClusterTrustBundle:
foundSupportedRef = true
@@ -575,18 +615,29 @@ func (t *Translator) getCaCertsFromCARefs(resources *resource.Resources, caCerti
}
ca += ctb.Spec.TrustBundle
} else {
- return nil, fmt.Errorf("cluster trust bundle %s not found", caRef.Name)
+ return nil, nil, fmt.Errorf("cluster trust bundle %s not found", caRef.Name)
}
}
}
+ // Validate that SDS is not mixed with regular certificates
+ if foundSDSConfig != nil && ca != "" {
+ return nil, nil, fmt.Errorf("cannot mix SDS reference secrets with other CA certificate types")
+ }
+
+ // Return SDS config if found
+ if foundSDSConfig != nil {
+ return nil, foundSDSConfig, nil
+ }
+
+ // Return regular certificates if found
if ca == "" {
if !foundSupportedRef {
- return nil, fmt.Errorf("%w in caCertificateRefs", ErrInvalidCACertificateKind)
+ return nil, nil, fmt.Errorf("%w in caCertificateRefs", ErrInvalidCACertificateKind)
}
- return nil, ErrNoValidCACertificate
+ return nil, nil, ErrNoValidCACertificate
}
- return []byte(ca), nil
+ return []byte(ca), nil, nil
}
func getAncestorRefs(policy *gwapiv1.BackendTLSPolicy) []*gwapiv1.ParentReference {
diff --git a/internal/gatewayapi/listener.go b/internal/gatewayapi/listener.go
index a9f01a7e48..c46d6d559c 100644
--- a/internal/gatewayapi/listener.go
+++ b/internal/gatewayapi/listener.go
@@ -51,7 +51,7 @@ func (t *Translator) ProcessGatewayTLS(gateways []*GatewayContext, resources *re
gtwDefaultFrontendTLSValidation := gtw.Spec.TLS.Frontend.Default
allowInsecureFallback := false
if gtwDefaultFrontendTLSValidation.Validation != nil {
- caCert, err := t.getCaCertsFromCARefs(resources, gtwDefaultFrontendTLSValidation.Validation.CACertificateRefs, resource.ResourceMetadata{
+ caCert, sds, err := t.getCaCertsFromCARefs(resources, gtwDefaultFrontendTLSValidation.Validation.CACertificateRefs, resource.ResourceMetadata{
Group: gwapiv1.GroupVersion.Group,
Kind: resource.KindGateway,
Name: gtw.Name,
@@ -70,6 +70,7 @@ func (t *Translator) ProcessGatewayTLS(gateways []*GatewayContext, resources *re
TLSCACertificate: &ir.TLSCACertificate{
Name: irGatewayTLSCACertName(gtw.Gateway, "default"),
Certificate: caCert,
+ SDS: sds,
},
Mode: frontendValidationMode(gtwDefaultFrontendTLSValidation.Validation.Mode),
}
@@ -87,7 +88,7 @@ func (t *Translator) ProcessGatewayTLS(gateways []*GatewayContext, resources *re
gtwPerPortCaCertificate[portValidation.Port] = nil
continue
}
- caCert, err := t.getCaCertsFromCARefs(resources, portValidation.TLS.Validation.CACertificateRefs, resource.ResourceMetadata{
+ caCert, sds, err := t.getCaCertsFromCARefs(resources, portValidation.TLS.Validation.CACertificateRefs, resource.ResourceMetadata{
Group: gwapiv1.GroupVersion.Group,
Kind: resource.KindGateway,
Name: gtw.Name,
@@ -106,6 +107,7 @@ func (t *Translator) ProcessGatewayTLS(gateways []*GatewayContext, resources *re
TLSCACertificate: &ir.TLSCACertificate{
Name: irGatewayTLSCACertName(gtw.Gateway, strconv.Itoa(int(portValidation.Port))),
Certificate: caCert,
+ SDS: sds,
},
Mode: frontendValidationMode(portValidation.TLS.Validation.Mode),
}
diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go
index 04c48ca598..7cea2a53ae 100644
--- a/internal/gatewayapi/runner/runner.go
+++ b/internal/gatewayapi/runner/runner.go
@@ -265,6 +265,7 @@ func (r *Runner) subscribeAndTranslate(sub <-chan watchable.Snapshot[string, *re
GlobalRateLimitEnabled: r.EnvoyGateway.RateLimit != nil,
EnvoyPatchPolicyEnabled: r.EnvoyGateway.ExtensionAPIs != nil && r.EnvoyGateway.ExtensionAPIs.EnableEnvoyPatchPolicy,
BackendEnabled: r.EnvoyGateway.ExtensionAPIs != nil && r.EnvoyGateway.ExtensionAPIs.EnableBackend,
+ SDSSecretRefEnabled: r.EnvoyGateway.ExtensionAPIs != nil && r.EnvoyGateway.ExtensionAPIs.EnableSDSSecretRef,
ControllerNamespace: r.ControllerNamespace,
GatewayNamespaceMode: r.EnvoyGateway.GatewayNamespaceMode(),
MergeGateways: gatewayapi.IsMergeGatewaysEnabled(resources),
diff --git a/internal/gatewayapi/testdata/sds-disabled.in.yaml b/internal/gatewayapi/testdata/sds-disabled.in.yaml
new file mode 100644
index 0000000000..fe641e087d
--- /dev/null
+++ b/internal/gatewayapi/testdata/sds-disabled.in.yaml
@@ -0,0 +1,95 @@
+envoyProxyForGatewayClass:
+ apiVersion: gateway.envoyproxy.io/v1alpha1
+ kind: EnvoyProxy
+ metadata:
+ namespace: envoy-gateway-system
+ name: test
+ spec:
+ backendTLS:
+ clientCertificateRef:
+ kind: Secret
+ name: sds-client-cert-default
+ alpnProtocols:
+ # without this, need to enforce mTLS with PeerAuthentication
+ - istio
+gateways:
+ - apiVersion: gateway.networking.k8s.io/v1
+ kind: Gateway
+ metadata:
+ namespace: envoy-gateway
+ name: gateway-1
+ spec:
+ gatewayClassName: envoy-gateway-class
+ listeners:
+ - name: http
+ protocol: HTTP
+ port: 80
+ allowedRoutes:
+ namespaces:
+ from: All
+backends:
+ - apiVersion: gateway.envoyproxy.io/v1alpha1
+ kind: Backend
+ metadata:
+ name: spire-uds
+ namespace: envoy-gateway-system
+ spec:
+ endpoints:
+ - unix:
+ path: /var/run/secrets/workload-spiffe-uds/socket
+ type: Endpoints
+backendTLSPolicies:
+ - apiVersion: gateway.networking.k8s.io/v1alpha2
+ kind: BackendTLSPolicy
+ metadata:
+ name: policy-btls
+ namespace: default
+ spec:
+ targetRefs:
+ - kind: Service
+ name: service-1
+ validation:
+ caCertificateRefs:
+ - name: sds-client-cert-rootca
+ kind: Secret
+ hostname: example.com
+httpRoutes:
+ - apiVersion: gateway.networking.k8s.io/v1
+ kind: HTTPRoute
+ metadata:
+ name: httproute-1
+ namespace: default
+ spec:
+ parentRefs:
+ - namespace: envoy-gateway
+ name: gateway-1
+ sectionName: http
+ rules:
+ - matches:
+ - path:
+ type: Exact
+ value: "/exact"
+ backendRefs:
+ - name: service-1
+ port: 8080
+secrets:
+ - apiVersion: v1
+ kind: Secret
+ metadata:
+ name: sds-client-cert-default
+ namespace: envoy-gateway-system
+ type: gateway.envoyproxy.io/sds
+ data:
+ # /var/run/secrets/workload-spiffe-uds/socket
+ url: L3Zhci9ydW4vc2VjcmV0cy93b3JrbG9hZC1zcGlmZmUtdWRzL3NvY2tldA==
+ name: ZGVmYXVsdA== # base64 for "default"
+ - apiVersion: v1
+ kind: Secret
+ metadata:
+ name: sds-client-cert-rootca
+ namespace: default
+ type: gateway.envoyproxy.io/sds
+ data:
+ # /var/run/secrets/workload-spiffe-uds/socket
+ url: L3Zhci9ydW4vc2VjcmV0cy93b3JrbG9hZC1zcGlmZmUtdWRzL3NvY2tldA==
+ secretName: Uk9PVENB # base64 for "ROOTCA"
diff --git a/internal/gatewayapi/testdata/sds-disabled.out.yaml b/internal/gatewayapi/testdata/sds-disabled.out.yaml
new file mode 100644
index 0000000000..18dcbce870
--- /dev/null
+++ b/internal/gatewayapi/testdata/sds-disabled.out.yaml
@@ -0,0 +1,225 @@
+backendTLSPolicies:
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+ kind: BackendTLSPolicy
+ metadata:
+ name: policy-btls
+ namespace: default
+ spec:
+ targetRefs:
+ - group: ""
+ kind: Service
+ name: service-1
+ validation:
+ caCertificateRefs:
+ - group: ""
+ kind: Secret
+ name: sds-client-cert-rootca
+ hostname: example.com
+ status:
+ ancestors:
+ - ancestorRef:
+ name: gateway-1
+ namespace: envoy-gateway
+ sectionName: http
+ conditions:
+ - lastTransitionTime: null
+ message: SDS Secret reference is not enabled in EnvoyGateway configuration.
+ reason: NoValidCACertificate
+ status: "False"
+ type: Accepted
+ - lastTransitionTime: null
+ message: SDS Secret reference is not enabled in EnvoyGateway configuration.
+ reason: InvalidCACertificateRef
+ status: "False"
+ type: ResolvedRefs
+ controllerName: gateway.envoyproxy.io/gatewayclass-controller
+backends:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+ kind: Backend
+ metadata:
+ name: spire-uds
+ namespace: envoy-gateway-system
+ spec:
+ endpoints:
+ - unix:
+ path: /var/run/secrets/workload-spiffe-uds/socket
+ type: Endpoints
+ status:
+ conditions:
+ - lastTransitionTime: null
+ message: The Backend was accepted
+ reason: Accepted
+ status: "True"
+ type: Accepted
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+ kind: Gateway
+ metadata:
+ name: gateway-1
+ 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:
+ name: httproute-1
+ namespace: default
+ spec:
+ parentRefs:
+ - name: gateway-1
+ namespace: envoy-gateway
+ sectionName: http
+ rules:
+ - backendRefs:
+ - name: service-1
+ 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: 'Failed to process route rule 0 backendRef 0: SDS Secret reference
+ is not enabled in EnvoyGateway configuration.'
+ reason: InvalidBackendTLS
+ status: "False"
+ type: ResolvedRefs
+ controllerName: gateway.envoyproxy.io/gatewayclass-controller
+ parentRef:
+ name: gateway-1
+ namespace: envoy-gateway
+ sectionName: http
+infraIR:
+ envoy-gateway/gateway-1:
+ proxy:
+ config:
+ apiVersion: gateway.envoyproxy.io/v1alpha1
+ kind: EnvoyProxy
+ metadata:
+ name: test
+ namespace: envoy-gateway-system
+ spec:
+ backendTLS:
+ alpnProtocols:
+ - istio
+ clientCertificateRef:
+ kind: Secret
+ name: sds-client-cert-default
+ logging: {}
+ status: {}
+ listeners:
+ - name: envoy-gateway/gateway-1/http
+ ports:
+ - containerPort: 10080
+ name: http-80
+ protocol: HTTP
+ servicePort: 80
+ metadata:
+ labels:
+ gateway.envoyproxy.io/owning-gateway-name: gateway-1
+ gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
+ ownerReference:
+ kind: GatewayClass
+ name: envoy-gateway-class
+ name: envoy-gateway/gateway-1
+ namespace: envoy-gateway-system
+xdsIR:
+ envoy-gateway/gateway-1:
+ accessLog:
+ json:
+ - path: /dev/stdout
+ globalResources:
+ proxyServiceCluster:
+ metadata:
+ kind: Service
+ name: envoy-envoy-gateway-gateway-1-196ae069
+ namespace: envoy-gateway-system
+ sectionName: "8080"
+ name: envoy-gateway/gateway-1
+ settings:
+ - addressType: IP
+ endpoints:
+ - host: 7.6.5.4
+ port: 8080
+ zone: zone1
+ metadata:
+ kind: Service
+ name: envoy-envoy-gateway-gateway-1-196ae069
+ namespace: envoy-gateway-system
+ sectionName: "8080"
+ name: envoy-gateway/gateway-1
+ protocol: TCP
+ http:
+ - address: 0.0.0.0
+ externalPort: 80
+ hostnames:
+ - '*'
+ metadata:
+ kind: Gateway
+ name: gateway-1
+ namespace: envoy-gateway
+ sectionName: http
+ name: envoy-gateway/gateway-1/http
+ path:
+ escapedSlashesAction: UnescapeAndRedirect
+ mergeSlashes: true
+ port: 10080
+ routes:
+ - directResponse:
+ statusCode: 500
+ hostname: '*'
+ isHTTP2: false
+ metadata:
+ kind: HTTPRoute
+ name: httproute-1
+ namespace: default
+ name: httproute/default/httproute-1/rule/0/match/0/*
+ pathMatch:
+ distinct: false
+ exact: /exact
+ name: ""
+ readyListener:
+ address: 0.0.0.0
+ ipFamily: IPv4
+ path: /ready
+ port: 19003
diff --git a/internal/gatewayapi/testdata/sds-invalid.in.yaml b/internal/gatewayapi/testdata/sds-invalid.in.yaml
new file mode 100644
index 0000000000..d96ccf4fab
--- /dev/null
+++ b/internal/gatewayapi/testdata/sds-invalid.in.yaml
@@ -0,0 +1,256 @@
+gateways:
+ - apiVersion: gateway.networking.k8s.io/v1
+ kind: Gateway
+ metadata:
+ name: gateway-1
+ namespace: envoy-gateway
+ spec:
+ gatewayClassName: envoy-gateway-class
+ listeners:
+ - name: http
+ protocol: HTTP
+ port: 80
+ allowedRoutes:
+ namespaces:
+ from: All
+backends:
+ - apiVersion: gateway.envoyproxy.io/v1alpha1
+ kind: Backend
+ metadata:
+ name: spire-uds
+ namespace: envoy-gateway-system
+ spec:
+ endpoints:
+ - unix:
+ path: /var/run/secrets/workload-spiffe-uds/socket
+ type: Endpoints
+backendTLSPolicies:
+ # Test case 1: Multiple SDS reference secrets (should be rejected)
+ - apiVersion: gateway.networking.k8s.io/v1alpha2
+ kind: BackendTLSPolicy
+ metadata:
+ name: policy-multiple-sds
+ namespace: default
+ spec:
+ targetRefs:
+ - kind: Service
+ name: service-1
+ validation:
+ caCertificateRefs:
+ - name: sds-ref-1
+ kind: Secret
+ - name: sds-ref-2
+ kind: Secret
+ hostname: example.com
+ # Test case 2: Mixing SDS with ConfigMap (should be rejected)
+ - apiVersion: gateway.networking.k8s.io/v1alpha2
+ kind: BackendTLSPolicy
+ metadata:
+ name: policy-sds-with-configmap
+ namespace: default
+ spec:
+ targetRefs:
+ - kind: Service
+ name: service-2
+ validation:
+ caCertificateRefs:
+ - name: sds-ref-1
+ kind: Secret
+ - name: ca-configmap
+ kind: ConfigMap
+ hostname: example.com
+ # Test case 3: Mixing SDS with regular Secret (should be rejected)
+ - apiVersion: gateway.networking.k8s.io/v1alpha2
+ kind: BackendTLSPolicy
+ metadata:
+ name: policy-sds-with-secret
+ namespace: default
+ spec:
+ targetRefs:
+ - kind: Service
+ name: service-3
+ validation:
+ caCertificateRefs:
+ - name: sds-ref-1
+ kind: Secret
+ - name: regular-ca-secret
+ kind: Secret
+ hostname: example.com
+httpRoutes:
+ - apiVersion: gateway.networking.k8s.io/v1
+ kind: HTTPRoute
+ metadata:
+ name: httproute-1
+ namespace: default
+ spec:
+ parentRefs:
+ - namespace: envoy-gateway
+ name: gateway-1
+ sectionName: http
+ rules:
+ - matches:
+ - path:
+ type: Exact
+ value: "/service1"
+ backendRefs:
+ - name: service-1
+ port: 8080
+ - matches:
+ - path:
+ type: Exact
+ value: "/service2"
+ backendRefs:
+ - name: service-2
+ port: 8080
+ - matches:
+ - path:
+ type: Exact
+ value: "/service3"
+ backendRefs:
+ - name: service-3
+ port: 8080
+services:
+ - apiVersion: v1
+ kind: Service
+ metadata:
+ name: service-1
+ namespace: default
+ spec:
+ clusterIP: 10.11.12.13
+ ports:
+ - port: 8080
+ name: http
+ protocol: TCP
+ targetPort: 8080
+ - apiVersion: v1
+ kind: Service
+ metadata:
+ name: service-2
+ namespace: default
+ spec:
+ clusterIP: 10.11.12.14
+ ports:
+ - port: 8080
+ name: http
+ protocol: TCP
+ targetPort: 8080
+ - apiVersion: v1
+ kind: Service
+ metadata:
+ name: service-3
+ namespace: default
+ spec:
+ clusterIP: 10.11.12.15
+ ports:
+ - port: 8080
+ name: http
+ protocol: TCP
+ targetPort: 8080
+endpointSlices:
+ - apiVersion: discovery.k8s.io/v1
+ kind: EndpointSlice
+ metadata:
+ name: endpointslice-service-1
+ namespace: default
+ labels:
+ kubernetes.io/service-name: service-1
+ addressType: IPv4
+ ports:
+ - name: http
+ protocol: TCP
+ port: 8080
+ endpoints:
+ - addresses:
+ - "10.244.0.11"
+ conditions:
+ ready: true
+ - apiVersion: discovery.k8s.io/v1
+ kind: EndpointSlice
+ metadata:
+ name: endpointslice-service-2
+ namespace: default
+ labels:
+ kubernetes.io/service-name: service-2
+ addressType: IPv4
+ ports:
+ - name: http
+ protocol: TCP
+ port: 8080
+ endpoints:
+ - addresses:
+ - "10.244.0.12"
+ conditions:
+ ready: true
+ - apiVersion: discovery.k8s.io/v1
+ kind: EndpointSlice
+ metadata:
+ name: endpointslice-service-3
+ namespace: default
+ labels:
+ kubernetes.io/service-name: service-3
+ addressType: IPv4
+ ports:
+ - name: http
+ protocol: TCP
+ port: 8080
+ endpoints:
+ - addresses:
+ - "10.244.0.13"
+ conditions:
+ ready: true
+secrets:
+ # SDS reference secrets
+ - apiVersion: v1
+ kind: Secret
+ metadata:
+ name: sds-ref-1
+ namespace: default
+ type: gateway.envoyproxy.io/sds
+ data:
+ url: L3Zhci9ydW4vc2VjcmV0cy93b3JrbG9hZC1zcGlmZmUtdWRzL3NvY2tldA== # /var/run/secrets/workload-spiffe-uds/socket
+ secretName: Uk9PVENB # ROOTCA
+ - apiVersion: v1
+ kind: Secret
+ metadata:
+ name: sds-ref-2
+ namespace: default
+ type: gateway.envoyproxy.io/sds
+ data:
+ url: L3Zhci9ydW4vc2VjcmV0cy93b3JrbG9hZC1zcGlmZmUtdWRzL3NvY2tldA== # /var/run/secrets/workload-spiffe-uds/socket
+ secretName: Uk9PVENBMg== # ROOTCA2
+ # Regular secret with CA certificate
+ - apiVersion: v1
+ kind: Secret
+ metadata:
+ name: regular-ca-secret
+ namespace: default
+ type: Opaque
+ data:
+ ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKekNDQWcrZ0F3SUJBZ0lVQWw2UWJFSGFLaWxVSzhUT3ppM2xJS2kxMDFRd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0l6RU5NQXNHQTFVRUF3d0VkR1Z6ZERFVE1CRUdBMVVFQ2d3S1pYaGhiWEJzWlMxSmJtTXdIaGNOTWpRdwpOekU1TVRRd09UUXhXaGNOTWprd056RTRNVFF3T1RReFdqQWpNUTB3Q3dZRFZRUUREQVIwWlhOME1STXdFUVlEClZRUUtEQXBsZUdGdGNHeGxMVWx1WXpDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUIKQU1OdWRiamhkQXo4Ky9zSjJvaFA2T0ZNQ3Z0WWpQMXJPQUtTZFh1RGtVOXNNd09uakUrZk5FZGl5VTBpcDBPeQpwQWtNR0RqbFFXZDQvRGxIN0hvV3o3UVJVSGt5emo0eWhONG5jSTlOMXZzQXdoaGQvNFM2WngwTE1YeTRvL0k4ClB2YnpjNFVjbUhBMlFGS2VpWGtoWEd1YkFVYWtEUjR4TGVSR3J4ZlhqaStPUmZwemY2V1pRNS9QWjRnMFdJZ3QKblhjZDFINVdGeTlOSUtCaGxJekJSZk1JMjMwT1BuNnNibkpiR1paZy8rQ29Ia2VFNDJzQ3VRQjFKZjVpcFlsNgoyNWJJSXVhcE9CaldMaDJYNWhsT1BvbWowOGxTUSsyd2VHNTdvRmVNOEgxbitxTFRuYU9jRmtoL3NLdGlvOEd1ClQ4REt1T1pINlVGN3pLL3ZGb1VDQXdFQUFhTlRNRkV3SFFZRFZSME9CQllFRkZxMWp3eHZPN1o5cHZBY1ZybGgKN01OZFdONGxNQjhHQTFVZEl3UVlNQmFBRkZxMWp3eHZPN1o5cHZBY1ZybGg3TU5kV040bE1BOEdBMVVkRXdFQgovd1FGTUFNQkFmOHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBRjNERVVmcnc1a0YzbnZXRzkyMHNEQW01cC9PCmNsdCtCbXpnclF2UmtKOTdjWWl3akZKU2s3dmFXNk1UZk1pS2pRSE1mN2J3L1R5QlBPb3ROZy82OHRHZ2s1S3YKcmJ0RGxMWHJ5eXZNZ3VWVDJBOUVNd1dTQzBCR3JNRzRldlhqNG56U3dNTFh1aFhtRE5LbndHcmc1QnpwbHhONwpqMnp4NkFreXp4T3FOY2tKa0l2dW1oOHFkZXRGaU9rQ09pcFJ1SDF0RjdPbWk4WnBuY2E5a2czZzJhUlBOVTJqCkNybTdpRjVTQjJBM3dVLzQ2citaaHZRdFlCQWp6OVB1UmlGcUZQcmduNWF6VU5qcFRlS2RqZGxNcWRKMGlLRGcKY0Jaa3U5eEp3aDJQL3RWbGlaelorNWo0MExhMzY2QW1KUGdCSkFRRGdpWHNySUl6MC9qZHdNRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
+configMaps:
+ - apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: ca-configmap
+ namespace: default
+ data:
+ ca.crt: |
+ -----BEGIN CERTIFICATE-----
+ MIIDJzCCAg+gAwIBAgIUAl6QbEHaKilUK8TOzi3lIKi101QwDQYJKoZIhvcNAQEL
+ BQAwIzENMAsGA1UEAwwEdGVzdDETMBEGA1UECgwKZXhhbXBsZS1JbmMwHhcNMjQw
+ NzE5MTQwOTQxWhcNMjkwNzE4MTQwOTQxWjAjMQ0wCwYDVQQDDAR0ZXN0MRMwEQYD
+ VQQKDApFeGFtcGxlLUluYzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ AMNudbjhdAz8+/sJ2ohP6OFMCvtYjP1rOAKSdXuDkU9sMwOnjE+fNEdiyU0ip0Oy
+ pAkMGDjlQWd4/DlH7HoWz7QRUHkyzj4yhN4ncI9N1vsAwhHd/4S6Zx0LMXy4o/I8
+ Pvbzc4UcmHA2QFKeiXkhXGubAUakDR4xLeRGrxfXji+ORfpzf6WZQ5/PZ4g0WIgt
+ nXcd1H5WFy9NIKBhlIzBRfMI230OPn6sbnJbGZZg/+CoHkeE42sCuQB1Jf5ipYl6
+ 25bIIuapOBjWLh2X5hlOPomj08lSQ+2weG57oFeM8H1n+qLTnaOcFkh/sKtio8Gu
+ T8DKuOZH6UF7zK/vFoUCAwEAAaNTMFEwHQYDVR0OBBYEFFq1jwxvO7Z9pvAcVrlh
+ 7MNdWN4lMB8GA1UdIwQYMBaAFFq1jwxvO7Z9pvAcVrlh7MNdWN4lMA8GA1UdEwEB
+ /wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAF3DEUfrw5kF3nvWG920sDAm5p/O
+ clt+BmzgrQvRkJ97cYiwjFJSk7vaW6MTfMiKjQHMf7bw/TyBPOotNg/68tGgk5Kv
+ rbtDlLXryyvMguVT2A9EMwWSC0BGrMG4evXj4nzSwMLXuhXmDNKnwGrg5BzplxN7
+ j2zx6AkyxxOqNckJkIvumh8qdetFiOkCOipRuH1tF7Omi8Zpnca9kg3g2aRPNU2j
+ Crm7iF5SB2A3wU/46r+ZhvQtYBAjz9PuRiFqFPrgn5azUNjpTeKdjdlMqdJ0iKDg
+ cBZku9xJwh2P/tVliZyZ+5j40La366AmJPgBJAQDgiXsrIIz0/jdwME=
+ -----END CERTIFICATE-----
diff --git a/internal/gatewayapi/testdata/sds-invalid.out.yaml b/internal/gatewayapi/testdata/sds-invalid.out.yaml
new file mode 100644
index 0000000000..eef14eadff
--- /dev/null
+++ b/internal/gatewayapi/testdata/sds-invalid.out.yaml
@@ -0,0 +1,329 @@
+backendTLSPolicies:
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+ kind: BackendTLSPolicy
+ metadata:
+ name: policy-multiple-sds
+ namespace: default
+ spec:
+ targetRefs:
+ - group: ""
+ kind: Service
+ name: service-1
+ validation:
+ caCertificateRefs:
+ - group: ""
+ kind: Secret
+ name: sds-ref-1
+ - group: ""
+ kind: Secret
+ name: sds-ref-2
+ hostname: example.com
+ status:
+ ancestors:
+ - ancestorRef:
+ name: gateway-1
+ namespace: envoy-gateway
+ sectionName: http
+ conditions:
+ - lastTransitionTime: null
+ message: Multiple SDS reference secrets are not supported.
+ reason: NoValidCACertificate
+ status: "False"
+ type: Accepted
+ - lastTransitionTime: null
+ message: Multiple SDS reference secrets are not supported.
+ reason: InvalidCACertificateRef
+ status: "False"
+ type: ResolvedRefs
+ controllerName: gateway.envoyproxy.io/gatewayclass-controller
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+ kind: BackendTLSPolicy
+ metadata:
+ name: policy-sds-with-configmap
+ namespace: default
+ spec:
+ targetRefs:
+ - group: ""
+ kind: Service
+ name: service-2
+ validation:
+ caCertificateRefs:
+ - group: ""
+ kind: Secret
+ name: sds-ref-1
+ - group: ""
+ kind: ConfigMap
+ name: ca-configmap
+ hostname: example.com
+ status:
+ ancestors:
+ - ancestorRef:
+ name: gateway-1
+ namespace: envoy-gateway
+ sectionName: http
+ conditions:
+ - lastTransitionTime: null
+ message: Cannot mix SDS reference secrets with other CA certificate types.
+ reason: NoValidCACertificate
+ status: "False"
+ type: Accepted
+ - lastTransitionTime: null
+ message: Cannot mix SDS reference secrets with other CA certificate types.
+ reason: InvalidCACertificateRef
+ status: "False"
+ type: ResolvedRefs
+ controllerName: gateway.envoyproxy.io/gatewayclass-controller
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+ kind: BackendTLSPolicy
+ metadata:
+ name: policy-sds-with-secret
+ namespace: default
+ spec:
+ targetRefs:
+ - group: ""
+ kind: Service
+ name: service-3
+ validation:
+ caCertificateRefs:
+ - group: ""
+ kind: Secret
+ name: sds-ref-1
+ - group: ""
+ kind: Secret
+ name: regular-ca-secret
+ hostname: example.com
+ status:
+ ancestors:
+ - ancestorRef:
+ name: gateway-1
+ namespace: envoy-gateway
+ sectionName: http
+ conditions:
+ - lastTransitionTime: null
+ message: Cannot mix SDS reference secrets with other CA certificate types.
+ reason: NoValidCACertificate
+ status: "False"
+ type: Accepted
+ - lastTransitionTime: null
+ message: Cannot mix SDS reference secrets with other CA certificate types.
+ reason: InvalidCACertificateRef
+ status: "False"
+ type: ResolvedRefs
+ controllerName: gateway.envoyproxy.io/gatewayclass-controller
+backends:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+ kind: Backend
+ metadata:
+ name: spire-uds
+ namespace: envoy-gateway-system
+ spec:
+ endpoints:
+ - unix:
+ path: /var/run/secrets/workload-spiffe-uds/socket
+ type: Endpoints
+ status:
+ conditions:
+ - lastTransitionTime: null
+ message: The Backend was accepted
+ reason: Accepted
+ status: "True"
+ type: Accepted
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+ kind: Gateway
+ metadata:
+ name: gateway-1
+ 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:
+ name: httproute-1
+ namespace: default
+ spec:
+ parentRefs:
+ - name: gateway-1
+ namespace: envoy-gateway
+ sectionName: http
+ rules:
+ - backendRefs:
+ - name: service-1
+ port: 8080
+ matches:
+ - path:
+ type: Exact
+ value: /service1
+ - backendRefs:
+ - name: service-2
+ port: 8080
+ matches:
+ - path:
+ type: Exact
+ value: /service2
+ - backendRefs:
+ - name: service-3
+ port: 8080
+ matches:
+ - path:
+ type: Exact
+ value: /service3
+ status:
+ parents:
+ - conditions:
+ - lastTransitionTime: null
+ message: Route is accepted
+ reason: Accepted
+ status: "True"
+ type: Accepted
+ - lastTransitionTime: null
+ message: |-
+ Failed to process route rule 0 backendRef 0: multiple SDS reference secrets are not supported.
+ Failed to process route rule 1 backendRef 0: cannot mix SDS reference secrets with other CA certificate types.
+ Failed to process route rule 2 backendRef 0: cannot mix SDS reference secrets with other CA certificate types.
+ reason: InvalidBackendTLS
+ status: "False"
+ type: ResolvedRefs
+ controllerName: gateway.envoyproxy.io/gatewayclass-controller
+ parentRef:
+ name: gateway-1
+ namespace: envoy-gateway
+ sectionName: http
+infraIR:
+ envoy-gateway/gateway-1:
+ proxy:
+ listeners:
+ - name: envoy-gateway/gateway-1/http
+ ports:
+ - containerPort: 10080
+ name: http-80
+ protocol: HTTP
+ servicePort: 80
+ metadata:
+ labels:
+ gateway.envoyproxy.io/owning-gateway-name: gateway-1
+ gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
+ ownerReference:
+ kind: GatewayClass
+ name: envoy-gateway-class
+ name: envoy-gateway/gateway-1
+ namespace: envoy-gateway-system
+xdsIR:
+ envoy-gateway/gateway-1:
+ accessLog:
+ json:
+ - path: /dev/stdout
+ globalResources:
+ proxyServiceCluster:
+ metadata:
+ kind: Service
+ name: envoy-envoy-gateway-gateway-1-196ae069
+ namespace: envoy-gateway-system
+ sectionName: "8080"
+ name: envoy-gateway/gateway-1
+ settings:
+ - addressType: IP
+ endpoints:
+ - host: 7.6.5.4
+ port: 8080
+ zone: zone1
+ metadata:
+ kind: Service
+ name: envoy-envoy-gateway-gateway-1-196ae069
+ namespace: envoy-gateway-system
+ sectionName: "8080"
+ name: envoy-gateway/gateway-1
+ protocol: TCP
+ http:
+ - address: 0.0.0.0
+ externalPort: 80
+ hostnames:
+ - '*'
+ metadata:
+ kind: Gateway
+ name: gateway-1
+ namespace: envoy-gateway
+ sectionName: http
+ name: envoy-gateway/gateway-1/http
+ path:
+ escapedSlashesAction: UnescapeAndRedirect
+ mergeSlashes: true
+ port: 10080
+ routes:
+ - directResponse:
+ statusCode: 500
+ hostname: '*'
+ isHTTP2: false
+ metadata:
+ kind: HTTPRoute
+ name: httproute-1
+ namespace: default
+ name: httproute/default/httproute-1/rule/0/match/0/*
+ pathMatch:
+ distinct: false
+ exact: /service1
+ name: ""
+ - directResponse:
+ statusCode: 500
+ hostname: '*'
+ isHTTP2: false
+ metadata:
+ kind: HTTPRoute
+ name: httproute-1
+ namespace: default
+ name: httproute/default/httproute-1/rule/1/match/0/*
+ pathMatch:
+ distinct: false
+ exact: /service2
+ name: ""
+ - directResponse:
+ statusCode: 500
+ hostname: '*'
+ isHTTP2: false
+ metadata:
+ kind: HTTPRoute
+ name: httproute-1
+ namespace: default
+ name: httproute/default/httproute-1/rule/2/match/0/*
+ pathMatch:
+ distinct: false
+ exact: /service3
+ name: ""
+ readyListener:
+ address: 0.0.0.0
+ ipFamily: IPv4
+ path: /ready
+ port: 19003
diff --git a/internal/gatewayapi/testdata/sds.in.yaml b/internal/gatewayapi/testdata/sds.in.yaml
new file mode 100644
index 0000000000..37a28ea6a6
--- /dev/null
+++ b/internal/gatewayapi/testdata/sds.in.yaml
@@ -0,0 +1,95 @@
+envoyProxyForGatewayClass:
+ apiVersion: gateway.envoyproxy.io/v1alpha1
+ kind: EnvoyProxy
+ metadata:
+ namespace: envoy-gateway-system
+ name: test
+ spec:
+ backendTLS:
+ clientCertificateRef:
+ kind: Secret
+ name: sds-client-cert-default
+ alpnProtocols:
+ # without this, need to enforce mTLS with PeerAuthentication
+ - istio
+gateways:
+ - apiVersion: gateway.networking.k8s.io/v1
+ kind: Gateway
+ metadata:
+ namespace: envoy-gateway
+ name: gateway-1
+ spec:
+ gatewayClassName: envoy-gateway-class
+ listeners:
+ - name: http
+ protocol: HTTP
+ port: 80
+ allowedRoutes:
+ namespaces:
+ from: All
+backends:
+ - apiVersion: gateway.envoyproxy.io/v1alpha1
+ kind: Backend
+ metadata:
+ name: spire-uds
+ namespace: envoy-gateway-system
+ spec:
+ endpoints:
+ - unix:
+ path: /var/run/secrets/workload-spiffe-uds/socket
+ type: Endpoints
+backendTLSPolicies:
+ - apiVersion: gateway.networking.k8s.io/v1alpha2
+ kind: BackendTLSPolicy
+ metadata:
+ name: policy-btls
+ namespace: default
+ spec:
+ targetRefs:
+ - kind: Service
+ name: service-1
+ validation:
+ caCertificateRefs:
+ - name: sds-client-cert-rootca
+ kind: Secret
+ hostname: example.com
+httpRoutes:
+ - apiVersion: gateway.networking.k8s.io/v1
+ kind: HTTPRoute
+ metadata:
+ name: httproute-1
+ namespace: default
+ spec:
+ parentRefs:
+ - namespace: envoy-gateway
+ name: gateway-1
+ sectionName: http
+ rules:
+ - matches:
+ - path:
+ type: Exact
+ value: "/exact"
+ backendRefs:
+ - name: service-1
+ port: 8080
+secrets:
+ - apiVersion: v1
+ kind: Secret
+ metadata:
+ name: sds-client-cert-default
+ namespace: envoy-gateway-system
+ type: gateway.envoyproxy.io/sds
+ data:
+ # /var/run/secrets/workload-spiffe-uds/socket
+ url: L3Zhci9ydW4vc2VjcmV0cy93b3JrbG9hZC1zcGlmZmUtdWRzL3NvY2tldA==
+ secretName: ZGVmYXVsdA== # base64 for "default"
+ - apiVersion: v1
+ kind: Secret
+ metadata:
+ name: sds-client-cert-rootca
+ namespace: default
+ type: gateway.envoyproxy.io/sds
+ data:
+ # /var/run/secrets/workload-spiffe-uds/socket
+ url: L3Zhci9ydW4vc2VjcmV0cy93b3JrbG9hZC1zcGlmZmUtdWRzL3NvY2tldA==
+ secretName: Uk9PVENB # base64 for "ROOTCA"
diff --git a/internal/gatewayapi/testdata/sds.out.yaml b/internal/gatewayapi/testdata/sds.out.yaml
new file mode 100644
index 0000000000..3c5c2fd868
--- /dev/null
+++ b/internal/gatewayapi/testdata/sds.out.yaml
@@ -0,0 +1,255 @@
+backendTLSPolicies:
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+ kind: BackendTLSPolicy
+ metadata:
+ name: policy-btls
+ namespace: default
+ spec:
+ targetRefs:
+ - group: ""
+ kind: Service
+ name: service-1
+ validation:
+ caCertificateRefs:
+ - group: ""
+ kind: Secret
+ name: sds-client-cert-rootca
+ hostname: example.com
+ status:
+ ancestors:
+ - ancestorRef:
+ name: gateway-1
+ namespace: envoy-gateway
+ sectionName: http
+ conditions:
+ - lastTransitionTime: null
+ message: Resolved all the Object references.
+ reason: ResolvedRefs
+ status: "True"
+ type: ResolvedRefs
+ - lastTransitionTime: null
+ message: Policy has been accepted.
+ reason: Accepted
+ status: "True"
+ type: Accepted
+ controllerName: gateway.envoyproxy.io/gatewayclass-controller
+backends:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+ kind: Backend
+ metadata:
+ name: spire-uds
+ namespace: envoy-gateway-system
+ spec:
+ endpoints:
+ - unix:
+ path: /var/run/secrets/workload-spiffe-uds/socket
+ type: Endpoints
+ status:
+ conditions:
+ - lastTransitionTime: null
+ message: The Backend was accepted
+ reason: Accepted
+ status: "True"
+ type: Accepted
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+ kind: Gateway
+ metadata:
+ name: gateway-1
+ 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:
+ name: httproute-1
+ namespace: default
+ spec:
+ parentRefs:
+ - name: gateway-1
+ namespace: envoy-gateway
+ sectionName: http
+ rules:
+ - backendRefs:
+ - name: service-1
+ 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-1
+ namespace: envoy-gateway
+ sectionName: http
+infraIR:
+ envoy-gateway/gateway-1:
+ proxy:
+ config:
+ apiVersion: gateway.envoyproxy.io/v1alpha1
+ kind: EnvoyProxy
+ metadata:
+ name: test
+ namespace: envoy-gateway-system
+ spec:
+ backendTLS:
+ alpnProtocols:
+ - istio
+ clientCertificateRef:
+ kind: Secret
+ name: sds-client-cert-default
+ logging: {}
+ status: {}
+ listeners:
+ - name: envoy-gateway/gateway-1/http
+ ports:
+ - containerPort: 10080
+ name: http-80
+ protocol: HTTP
+ servicePort: 80
+ metadata:
+ labels:
+ gateway.envoyproxy.io/owning-gateway-name: gateway-1
+ gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
+ ownerReference:
+ kind: GatewayClass
+ name: envoy-gateway-class
+ name: envoy-gateway/gateway-1
+ namespace: envoy-gateway-system
+xdsIR:
+ envoy-gateway/gateway-1:
+ accessLog:
+ json:
+ - path: /dev/stdout
+ globalResources:
+ proxyServiceCluster:
+ metadata:
+ kind: Service
+ name: envoy-envoy-gateway-gateway-1-196ae069
+ namespace: envoy-gateway-system
+ sectionName: "8080"
+ name: envoy-gateway/gateway-1
+ settings:
+ - addressType: IP
+ endpoints:
+ - host: 7.6.5.4
+ port: 8080
+ zone: zone1
+ metadata:
+ kind: Service
+ name: envoy-envoy-gateway-gateway-1-196ae069
+ namespace: envoy-gateway-system
+ sectionName: "8080"
+ name: envoy-gateway/gateway-1
+ protocol: TCP
+ http:
+ - address: 0.0.0.0
+ externalPort: 80
+ hostnames:
+ - '*'
+ metadata:
+ kind: Gateway
+ name: gateway-1
+ namespace: envoy-gateway
+ sectionName: http
+ name: envoy-gateway/gateway-1/http
+ path:
+ escapedSlashesAction: UnescapeAndRedirect
+ mergeSlashes: true
+ port: 10080
+ routes:
+ - destination:
+ metadata:
+ kind: HTTPRoute
+ name: httproute-1
+ namespace: default
+ name: httproute/default/httproute-1/rule/0
+ settings:
+ - addressType: IP
+ endpoints:
+ - host: 7.7.7.7
+ port: 8080
+ metadata:
+ kind: Service
+ name: service-1
+ namespace: default
+ sectionName: "8080"
+ name: httproute/default/httproute-1/rule/0/backend/0
+ protocol: HTTP
+ tls:
+ alpnProtocols:
+ - istio
+ caCertificate:
+ name: policy-btls/default-ca
+ sds:
+ secretName: ROOTCA
+ url: /var/run/secrets/workload-spiffe-uds/socket
+ clientCertificates:
+ - name: ""
+ sds:
+ secretName: default
+ url: /var/run/secrets/workload-spiffe-uds/socket
+ sni: example.com
+ weight: 1
+ hostname: '*'
+ isHTTP2: false
+ metadata:
+ kind: HTTPRoute
+ name: httproute-1
+ namespace: default
+ name: httproute/default/httproute-1/rule/0/match/0/*
+ pathMatch:
+ distinct: false
+ exact: /exact
+ name: ""
+ readyListener:
+ address: 0.0.0.0
+ ipFamily: IPv4
+ path: /ready
+ port: 19003
diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go
index 005eb6a426..6163cc6410 100644
--- a/internal/gatewayapi/translator.go
+++ b/internal/gatewayapi/translator.go
@@ -101,6 +101,10 @@ type Translator struct {
// BackendEnabled when the Backend feature is enabled.
BackendEnabled bool
+ // SDSSecretRefEnabled is true if EnvoyProxy can reference SDS secrets using the sds-ref type Secret.
+ // This could be enabled by Envoy Gateway configuration.
+ SDSSecretRefEnabled bool
+
// ExtensionGroupKinds stores the group/kind for all resources
// introduced by an Extension so that the translator can
// store referenced resources in the IR for later use.
diff --git a/internal/gatewayapi/translator_test.go b/internal/gatewayapi/translator_test.go
index 926c6993b1..1d72662fc8 100644
--- a/internal/gatewayapi/translator_test.go
+++ b/internal/gatewayapi/translator_test.go
@@ -55,6 +55,7 @@ func TestTranslate(t *testing.T) {
GatewayNamespaceMode bool
RunningOnHost bool
LuaEnvoyExtensionPolicyDisabled bool
+ SDSEnabled bool
}{
{
name: "envoypatchpolicy-invalid-feature-disabled",
@@ -82,6 +83,16 @@ func TestTranslate(t *testing.T) {
name: "envoyextensionpolicy-lua-feature-disabled",
LuaEnvoyExtensionPolicyDisabled: true,
},
+ {
+ name: "sds",
+ BackendEnabled: true,
+ SDSEnabled: true,
+ },
+ {
+ name: "sds-invalid",
+ BackendEnabled: true,
+ SDSEnabled: true,
+ },
}
inputFiles, err := filepath.Glob(filepath.Join("testdata", "*.in.yaml"))
@@ -106,6 +117,7 @@ func TestTranslate(t *testing.T) {
gatewayNamespaceMode := false
runningOnHost := false
luaEnvoyExtensionPolicyDisabled := false
+ sdsEnabled := false
for _, config := range testCasesConfig {
if config.name == strings.Split(filepath.Base(inputFile), ".")[0] {
@@ -114,6 +126,7 @@ func TestTranslate(t *testing.T) {
gatewayNamespaceMode = config.GatewayNamespaceMode
runningOnHost = config.RunningOnHost
luaEnvoyExtensionPolicyDisabled = config.LuaEnvoyExtensionPolicyDisabled
+ sdsEnabled = config.SDSEnabled
}
}
@@ -123,6 +136,7 @@ func TestTranslate(t *testing.T) {
GlobalRateLimitEnabled: true,
EnvoyPatchPolicyEnabled: envoyPatchPolicyEnabled,
BackendEnabled: backendEnabled,
+ SDSSecretRefEnabled: sdsEnabled,
ControllerNamespace: "envoy-gateway-system",
MergeGateways: IsMergeGatewaysEnabled(resources),
GatewayNamespaceMode: gatewayNamespaceMode,
diff --git a/internal/ir/xds.go b/internal/ir/xds.go
index 6e54220d4b..f94f8083ee 100644
--- a/internal/ir/xds.go
+++ b/internal/ir/xds.go
@@ -16,6 +16,7 @@ import (
"net/netip"
"time"
+ corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -471,6 +472,8 @@ type TLSConfig struct {
type TLSCertificate struct {
// Name of the Secret object.
Name string `json:"name" yaml:"name"`
+ // SDS holds the configuration for a Secret Discovery Service (SDS) server.
+ SDS *SDSConfig `json:"sds,omitempty" yaml:"sds,omitempty"`
// Certificate can be either a client or server certificate.
Certificate []byte `json:"certificate,omitempty" yaml:"certificate,omitempty"`
// PrivateKey for the server.
@@ -479,6 +482,35 @@ type TLSCertificate struct {
OCSPStaple []byte `json:"ocspStaple,omitempty" yaml:"ocspStaple,omitempty"`
}
+// SDSConfig holds the configuration for a Secret Discovery Service (SDS) server.
+// +k8s:deepcopy-gen=true
+type SDSConfig struct {
+ // SecretName is an identifier for the SDS configuration.
+ SecretName string `json:"secretName" yaml:"secretName"`
+ // URL is the URL of the SDS server
+ URL string `json:"url" yaml:"url"`
+
+ // TODO: support additional SDS configuration options
+ // such as TLS settings for the SDS server, or authentication credentials if needed.
+}
+
+func NewSDSConfig(s *corev1.Secret) (*SDSConfig, error) {
+ sdsSecretName, hasSecretName := s.Data["secretName"]
+ sdsURLBytes, hasURL := s.Data["url"]
+ // TODO: support more sds options if needed.
+ if !hasSecretName || len(sdsSecretName) == 0 {
+ return nil, fmt.Errorf("no secretName found in SDS reference secret %s/%s", s.Namespace, s.Name)
+ }
+ if !hasURL || len(sdsURLBytes) == 0 {
+ return nil, fmt.Errorf("no url found in SDS reference secret %s/%s", s.Namespace, s.Name)
+ }
+
+ return &SDSConfig{
+ SecretName: string(sdsSecretName),
+ URL: string(sdsURLBytes),
+ }, nil
+}
+
// TLSCrl holds a single CRL's details
// +k8s:deepcopy-gen=true
type TLSCrl struct {
@@ -497,6 +529,8 @@ type TLSCACertificate struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
// Certificate content.
Certificate []byte `json:"certificate,omitempty" yaml:"certificate,omitempty"`
+ // SDS holds the configuration for a Secret Discovery Service (SDS) server.
+ SDS *SDSConfig `json:"sds,omitempty" yaml:"sds,omitempty"`
}
// SubjectAltName holds the subject alternative name for the certificate
diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go
index 5b47d06ccb..efe16b0338 100644
--- a/internal/ir/zz_generated.deepcopy.go
+++ b/internal/ir/zz_generated.deepcopy.go
@@ -4057,6 +4057,21 @@ func (in *RouteDestination) DeepCopy() *RouteDestination {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SDSConfig) DeepCopyInto(out *SDSConfig) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SDSConfig.
+func (in *SDSConfig) DeepCopy() *SDSConfig {
+ if in == nil {
+ return nil
+ }
+ out := new(SDSConfig)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SecurityFeatures) DeepCopyInto(out *SecurityFeatures) {
*out = *in
@@ -4507,6 +4522,11 @@ func (in *TLSCACertificate) DeepCopyInto(out *TLSCACertificate) {
*out = make([]byte, len(*in))
copy(*out, *in)
}
+ if in.SDS != nil {
+ in, out := &in.SDS, &out.SDS
+ *out = new(SDSConfig)
+ **out = **in
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSCACertificate.
@@ -4522,6 +4542,11 @@ func (in *TLSCACertificate) DeepCopy() *TLSCACertificate {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSCertificate) DeepCopyInto(out *TLSCertificate) {
*out = *in
+ if in.SDS != nil {
+ in, out := &in.SDS, &out.SDS
+ *out = new(SDSConfig)
+ **out = **in
+ }
if in.Certificate != nil {
in, out := &in.Certificate, &out.Certificate
*out = make([]byte, len(*in))
diff --git a/internal/xds/translator/listener.go b/internal/xds/translator/listener.go
index 4eb683fe36..42c894cbff 100644
--- a/internal/xds/translator/listener.go
+++ b/internal/xds/translator/listener.go
@@ -840,13 +840,19 @@ func buildDownstreamQUICTransportSocket(tlsConfig *ir.TLSConfig) (*corev3.Transp
},
}
- for _, tlsConfig := range tlsConfig.Certificates {
+ for _, cert := range tlsConfig.Certificates {
+ sdsConfig := &tlsv3.SdsSecretConfig{
+ Name: cert.Name,
+ SdsConfig: makeConfigSource(),
+ }
+ if cert.SDS != nil {
+ // Use external SDS server instead of ADS
+ clusterName := sdsClusterNameFromURL(cert.SDS.URL)
+ sdsConfig = sdsSecretConfig(cert.SDS.SecretName, clusterName)
+ }
tlsCtx.DownstreamTlsContext.CommonTlsContext.TlsCertificateSdsSecretConfigs = append(
tlsCtx.DownstreamTlsContext.CommonTlsContext.TlsCertificateSdsSecretConfigs,
- &tlsv3.SdsSecretConfig{
- Name: tlsConfig.Name,
- SdsConfig: makeConfigSource(),
- })
+ sdsConfig)
}
if tlsConfig.CACertificate != nil || tlsConfig.ClientValidationEnabled {
@@ -877,13 +883,19 @@ func buildXdsDownstreamTLSSocket(tlsConfig *ir.TLSConfig) (*corev3.TransportSock
},
}
- for _, tlsConfig := range tlsConfig.Certificates {
+ for _, cert := range tlsConfig.Certificates {
+ sdsConfig := &tlsv3.SdsSecretConfig{
+ Name: cert.Name,
+ SdsConfig: makeConfigSource(),
+ }
+ if cert.SDS != nil {
+ // Use external SDS server instead of ADS
+ clusterName := sdsClusterNameFromURL(cert.SDS.URL)
+ sdsConfig = sdsSecretConfig(cert.SDS.SecretName, clusterName)
+ }
tlsCtx.CommonTlsContext.TlsCertificateSdsSecretConfigs = append(
tlsCtx.CommonTlsContext.TlsCertificateSdsSecretConfigs,
- &tlsv3.SdsSecretConfig{
- Name: tlsConfig.Name,
- SdsConfig: makeConfigSource(),
- })
+ sdsConfig)
}
if tlsConfig.CACertificate != nil || tlsConfig.ClientValidationEnabled {
@@ -951,21 +963,27 @@ func setTLSValidationContext(tlsConfig *ir.TLSConfig, tlsCtx *tlsv3.CommonTlsCon
return
}
- sdsSecretConfig := &tlsv3.SdsSecretConfig{
+ sdsConfig := &tlsv3.SdsSecretConfig{
Name: tlsConfig.CACertificate.Name,
SdsConfig: makeConfigSource(),
}
+ if tlsConfig.CACertificate.SDS != nil {
+ // Use external SDS server instead of ADS
+ clusterName := sdsClusterNameFromURL(tlsConfig.CACertificate.SDS.URL)
+ sdsConfig = sdsSecretConfig(tlsConfig.CACertificate.SDS.SecretName, clusterName)
+ }
+
if needsDefaultValidationContext {
tlsCtx.ValidationContextType = &tlsv3.CommonTlsContext_CombinedValidationContext{
CombinedValidationContext: &tlsv3.CommonTlsContext_CombinedCertificateValidationContext{
DefaultValidationContext: validationContext,
- ValidationContextSdsSecretConfig: sdsSecretConfig,
+ ValidationContextSdsSecretConfig: sdsConfig,
},
}
} else {
tlsCtx.ValidationContextType = &tlsv3.CommonTlsContext_ValidationContextSdsSecretConfig{
- ValidationContextSdsSecretConfig: sdsSecretConfig,
+ ValidationContextSdsSecretConfig: sdsConfig,
}
}
}
@@ -1049,6 +1067,11 @@ func buildXdsTLSCertSecret(tlsConfig *ir.TLSCertificate) *tlsv3.Secret {
}
}
+ // For SDS based CA certificate, the secret will be generated by SDS server, so we can skip adding the secret here
+ if tlsConfig.SDS != nil {
+ return nil
+ }
+
return &tlsv3.Secret{
Name: tlsConfig.Name,
Type: &tlsv3.Secret_TlsCertificate{
@@ -1069,6 +1092,11 @@ func buildXdsTLSCaCertSecret(caCertificate *ir.TLSCACertificate, crl *ir.TLSCrl)
}
validationContext.OnlyVerifyLeafCertCrl = crl.OnlyVerifyLeafCertificate
}
+ // For SDS based CA certificate, the secret will be generated by SDS server, so we can skip adding the secret here
+ if caCertificate.SDS != nil {
+ return nil
+ }
+
return &tlsv3.Secret{
Name: caCertificate.Name,
Type: &tlsv3.Secret_ValidationContext{
diff --git a/internal/xds/translator/sds.go b/internal/xds/translator/sds.go
new file mode 100644
index 0000000000..71d3279c92
--- /dev/null
+++ b/internal/xds/translator/sds.go
@@ -0,0 +1,179 @@
+// 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 translator
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "strings"
+ "time"
+
+ cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
+ corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
+ endpoint "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
+ tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
+ resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
+ "google.golang.org/protobuf/types/known/durationpb"
+
+ "github.com/envoyproxy/gateway/internal/ir"
+ "github.com/envoyproxy/gateway/internal/xds/types"
+)
+
+const defaultConnectionTimeout = 10 * time.Second
+
+func sdsSecretConfig(secretName, clusterName string) *tlsv3.SdsSecretConfig {
+ return &tlsv3.SdsSecretConfig{
+ Name: secretName,
+ SdsConfig: &corev3.ConfigSource{
+ ConfigSourceSpecifier: &corev3.ConfigSource_ApiConfigSource{
+ ApiConfigSource: &corev3.ApiConfigSource{
+ ApiType: corev3.ApiConfigSource_GRPC,
+ GrpcServices: []*corev3.GrpcService{
+ {
+ TargetSpecifier: &corev3.GrpcService_EnvoyGrpc_{
+ EnvoyGrpc: &corev3.GrpcService_EnvoyGrpc{
+ ClusterName: clusterName,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+// sdsClusterNameFromURL generates a unique cluster name from an SDS URL
+func sdsClusterNameFromURL(url string) string {
+ // Sanitize the URL to create a valid cluster name
+ // For Unix domain sockets like "/var/run/sds", create a meaningful name
+ if strings.HasPrefix(url, "/") {
+ // Unix domain socket path
+ sanitized := strings.ReplaceAll(url, "/", "_")
+ sanitized = strings.Trim(sanitized, "_")
+ return fmt.Sprintf("sds_%s", sanitized)
+ }
+ // For other URLs, use a hash
+ hash := sha256.Sum256([]byte(url))
+ return fmt.Sprintf("sds_%s", hex.EncodeToString(hash[:8]))
+}
+
+// createSDSCluster creates an SDS cluster for the given URL
+func createSDSCluster(tCtx *types.ResourceVersionTable, sdsURL string) error {
+ clusterName := sdsClusterNameFromURL(sdsURL)
+
+ // Check if cluster already exists
+ if tCtx.XdsResources[resourcev3.ClusterType] != nil {
+ for _, resource := range tCtx.XdsResources[resourcev3.ClusterType] {
+ if c, ok := resource.(*cluster.Cluster); ok && c.Name == clusterName {
+ // Cluster already exists
+ return nil
+ }
+ }
+ }
+
+ // Create the cluster based on the URL type
+ c := &cluster.Cluster{
+ Name: clusterName,
+ ClusterDiscoveryType: &cluster.Cluster_Type{
+ Type: cluster.Cluster_STATIC,
+ },
+ LoadAssignment: &endpoint.ClusterLoadAssignment{
+ ClusterName: clusterName,
+ Endpoints: []*endpoint.LocalityLbEndpoints{
+ {
+ LbEndpoints: []*endpoint.LbEndpoint{
+ {
+ HostIdentifier: &endpoint.LbEndpoint_Endpoint{
+ Endpoint: &endpoint.Endpoint{
+ Address: &corev3.Address{
+ Address: &corev3.Address_Pipe{
+ Pipe: &corev3.Pipe{
+ Path: sdsURL,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ConnectTimeout: durationpb.New(defaultConnectionTimeout),
+ Http2ProtocolOptions: &corev3.Http2ProtocolOptions{},
+ }
+
+ if err := tCtx.AddXdsResource(resourcev3.ClusterType, c); err != nil {
+ return err
+ }
+ return nil
+}
+
+// processSDSClusters scans the IR for SDS URLs and creates clusters for them
+func processSDSClusters(tCtx *types.ResourceVersionTable, xdsIR *ir.Xds) error {
+ sdsURLs := make(map[string]bool)
+
+ // Collect SDS URLs from HTTP listeners
+ for _, httpListener := range xdsIR.HTTP {
+ for _, route := range httpListener.Routes {
+ if route.Destination == nil {
+ continue
+ }
+ for _, dest := range route.Destination.Settings {
+ if dest.TLS != nil {
+ // Check CA certificate
+ if caCert := dest.TLS.CACertificate; caCert != nil {
+ if caCert.SDS != nil && caCert.SDS.URL != "" {
+ sdsURLs[caCert.SDS.URL] = true
+ }
+ }
+ // Check client certificates
+ for _, cert := range dest.TLS.ClientCertificates {
+ if cert.SDS != nil && cert.SDS.URL != "" {
+ sdsURLs[cert.SDS.URL] = true
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Collect SDS URLs from TCP listeners
+ for _, tcpListener := range xdsIR.TCP {
+ for _, route := range tcpListener.Routes {
+ if route.Destination == nil {
+ continue
+ }
+ for _, dest := range route.Destination.Settings {
+ if dest.TLS != nil {
+ // Check CA certificate
+ if caCert := dest.TLS.CACertificate; caCert != nil {
+ if caCert.SDS != nil && caCert.SDS.URL != "" {
+ sdsURLs[caCert.SDS.URL] = true
+ }
+ }
+ // Check client certificates
+ for _, cert := range dest.TLS.ClientCertificates {
+ if cert.SDS != nil && cert.SDS.URL != "" {
+ sdsURLs[cert.SDS.URL] = true
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Create clusters for each unique SDS URL
+ for url := range sdsURLs {
+ if err := createSDSCluster(tCtx, url); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/internal/xds/translator/testdata/in/xds-ir/sds.yaml b/internal/xds/translator/testdata/in/xds-ir/sds.yaml
new file mode 100644
index 0000000000..8e05a6b992
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/sds.yaml
@@ -0,0 +1,58 @@
+http:
+ - address: 0.0.0.0
+ externalPort: 80
+ hostnames:
+ - "*"
+ metadata:
+ kind: Gateway
+ name: gateway-1
+ namespace: envoy-gateway
+ sectionName: http
+ name: envoy-gateway/gateway-1/http
+ path:
+ escapedSlashesAction: UnescapeAndRedirect
+ mergeSlashes: true
+ port: 10080
+ routes:
+ - destination:
+ metadata:
+ kind: HTTPRoute
+ name: httproute-1
+ namespace: default
+ name: httproute/default/httproute-1/rule/0
+ settings:
+ - addressType: IP
+ endpoints:
+ - host: 7.7.7.7
+ port: 8080
+ metadata:
+ kind: Service
+ name: service-1
+ namespace: default
+ sectionName: "8080"
+ name: httproute/default/httproute-1/rule/0/backend/0
+ protocol: HTTP
+ tls:
+ alpnProtocols:
+ - istio
+ caCertificate:
+ sds:
+ secretName: ROOTCA
+ url: /var/run/secrets/workload-spiffe-uds/socket
+ clientCertificates:
+ - sds:
+ secretName: default
+ url: /var/run/secrets/workload-spiffe-uds/socket
+ sni: example.com
+ weight: 1
+ hostname: "*"
+ isHTTP2: false
+ metadata:
+ kind: HTTPRoute
+ name: httproute-1
+ namespace: default
+ name: httproute/default/httproute-1/rule/0/match/0/*
+ pathMatch:
+ distinct: false
+ exact: /exact
+ name: ""
diff --git a/internal/xds/translator/testdata/out/xds-ir/sds.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/sds.clusters.yaml
new file mode 100644
index 0000000000..97e9bec4c6
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/sds.clusters.yaml
@@ -0,0 +1,89 @@
+- circuitBreakers:
+ thresholds:
+ - maxRetries: 1024
+ commonLbConfig: {}
+ connectTimeout: 10s
+ dnsLookupFamily: V4_PREFERRED
+ edsClusterConfig:
+ edsConfig:
+ ads: {}
+ resourceApiVersion: V3
+ serviceName: httproute/default/httproute-1/rule/0
+ ignoreHealthOnHostRemoval: true
+ loadBalancingPolicy:
+ policies:
+ - typedExtensionConfig:
+ name: envoy.load_balancing_policies.least_request
+ typedConfig:
+ '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest
+ localityLbConfig:
+ localityWeightedLbConfig: {}
+ metadata:
+ filterMetadata:
+ envoy-gateway:
+ resources:
+ - kind: HTTPRoute
+ name: httproute-1
+ namespace: default
+ name: httproute/default/httproute-1/rule/0
+ perConnectionBufferLimitBytes: 32768
+ transportSocket:
+ name: dummy.transport_socket
+ typedConfig:
+ '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
+ commonTlsContext: {}
+ transportSocketMatches:
+ - match:
+ name: httproute/default/httproute-1/rule/0/tls/0
+ name: httproute/default/httproute-1/rule/0/tls/0
+ transportSocket:
+ name: envoy.transport_sockets.tls
+ typedConfig:
+ '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
+ commonTlsContext:
+ alpnProtocols:
+ - istio
+ combinedValidationContext:
+ defaultValidationContext:
+ matchTypedSubjectAltNames:
+ - matcher:
+ exact: example.com
+ sanType: DNS
+ validationContextSdsSecretConfig:
+ name: ROOTCA
+ sdsConfig:
+ apiConfigSource:
+ apiType: GRPC
+ grpcServices:
+ - envoyGrpc:
+ clusterName: sds_var_run_secrets_workload-spiffe-uds_socket
+ tlsCertificateSdsSecretConfigs:
+ - name: default
+ sdsConfig:
+ apiConfigSource:
+ apiType: GRPC
+ grpcServices:
+ - envoyGrpc:
+ clusterName: sds_var_run_secrets_workload-spiffe-uds_socket
+ sni: example.com
+ type: EDS
+ typedExtensionProtocolOptions:
+ envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+ '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
+ autoConfig:
+ http2ProtocolOptions:
+ initialConnectionWindowSize: 1048576
+ initialStreamWindowSize: 65536
+ httpProtocolOptions: {}
+- connectTimeout: 10s
+ http2ProtocolOptions: {}
+ loadAssignment:
+ clusterName: sds_var_run_secrets_workload-spiffe-uds_socket
+ endpoints:
+ - lbEndpoints:
+ - endpoint:
+ address:
+ pipe:
+ path: /var/run/secrets/workload-spiffe-uds/socket
+ name: sds_var_run_secrets_workload-spiffe-uds_socket
+ type: STATIC
diff --git a/internal/xds/translator/testdata/out/xds-ir/sds.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/sds.endpoints.yaml
new file mode 100644
index 0000000000..153dbbf6c5
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/sds.endpoints.yaml
@@ -0,0 +1,24 @@
+- clusterName: httproute/default/httproute-1/rule/0
+ endpoints:
+ - lbEndpoints:
+ - endpoint:
+ address:
+ socketAddress:
+ address: 7.7.7.7
+ portValue: 8080
+ loadBalancingWeight: 1
+ metadata:
+ filterMetadata:
+ envoy.transport_socket_match:
+ name: httproute/default/httproute-1/rule/0/tls/0
+ loadBalancingWeight: 1
+ locality:
+ region: httproute/default/httproute-1/rule/0/backend/0
+ metadata:
+ filterMetadata:
+ envoy-gateway:
+ resources:
+ - kind: Service
+ name: service-1
+ namespace: default
+ sectionName: "8080"
diff --git a/internal/xds/translator/testdata/out/xds-ir/sds.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/sds.listeners.yaml
new file mode 100644
index 0000000000..4d0fe90c54
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/sds.listeners.yaml
@@ -0,0 +1,35 @@
+- 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-1/http
+ serverHeaderTransformation: PASS_THROUGH
+ statPrefix: http-10080
+ useRemoteAddress: true
+ name: envoy-gateway/gateway-1/http
+ maxConnectionsToAcceptPerSocketEvent: 1
+ name: envoy-gateway/gateway-1/http
+ perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/sds.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/sds.routes.yaml
new file mode 100644
index 0000000000..9b0bc0a534
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/sds.routes.yaml
@@ -0,0 +1,29 @@
+- ignorePortInHostMatching: true
+ name: envoy-gateway/gateway-1/http
+ virtualHosts:
+ - domains:
+ - '*'
+ metadata:
+ filterMetadata:
+ envoy-gateway:
+ resources:
+ - kind: Gateway
+ name: gateway-1
+ namespace: envoy-gateway
+ sectionName: http
+ name: envoy-gateway/gateway-1/http/*
+ routes:
+ - match:
+ path: /exact
+ metadata:
+ filterMetadata:
+ envoy-gateway:
+ resources:
+ - kind: HTTPRoute
+ name: httproute-1
+ namespace: default
+ name: httproute/default/httproute-1/rule/0/match/0/*
+ route:
+ cluster: httproute/default/httproute-1/rule/0
+ upgradeConfigs:
+ - upgradeType: websocket
diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go
index 43703124b6..76c2b0ffa8 100644
--- a/internal/xds/translator/translator.go
+++ b/internal/xds/translator/translator.go
@@ -137,6 +137,10 @@ func (t *Translator) Translate(xdsIR *ir.Xds) (*types.ResourceVersionTable, erro
errs = errors.Join(errs, err)
}
+ if err := processSDSClusters(tCtx, xdsIR); err != nil {
+ errs = errors.Join(errs, err)
+ }
+
if err := processClusterForTracing(tCtx, xdsIR.Tracing, xdsIR.Metrics); err != nil {
errs = errors.Join(errs, err)
}
@@ -409,18 +413,21 @@ func (t *Translator) processHTTPListenerXdsTranslation(
if httpListener.TLS != nil {
for c := range httpListener.TLS.Certificates {
secret := buildXdsTLSCertSecret(&httpListener.TLS.Certificates[c])
- if err = tCtx.AddXdsResource(resourcev3.SecretType, secret); err != nil {
- errs = errors.Join(errs, err)
+ if secret != nil {
+ if err = tCtx.AddXdsResource(resourcev3.SecretType, secret); err != nil {
+ errs = errors.Join(errs, err)
+ }
}
}
if httpListener.TLS.CACertificate != nil {
caSecret := buildXdsTLSCaCertSecret(httpListener.TLS.CACertificate, httpListener.TLS.Crl)
- if err = tCtx.AddXdsResource(resourcev3.SecretType, caSecret); err != nil {
- errs = errors.Join(errs, err)
+ if caSecret != nil {
+ if err = tCtx.AddXdsResource(resourcev3.SecretType, caSecret); err != nil {
+ errs = errors.Join(errs, err)
+ }
}
}
-
}
// add http route client certs
@@ -799,21 +806,27 @@ func (t *Translator) processTCPListenerXdsTranslation(
// add tls route client certs
for _, cert := range route.TLS.Terminate.ClientCertificates {
secret := buildXdsTLSCertSecret(&cert)
- if err := tCtx.AddXdsResource(resourcev3.SecretType, secret); err != nil {
- errs = errors.Join(errs, err)
+ if secret != nil {
+ if err := tCtx.AddXdsResource(resourcev3.SecretType, secret); err != nil {
+ errs = errors.Join(errs, err)
+ }
}
}
for _, s := range route.TLS.Terminate.Certificates {
secret := buildXdsTLSCertSecret(&s)
- if err := tCtx.AddXdsResource(resourcev3.SecretType, secret); err != nil {
- errs = errors.Join(errs, err)
+ if secret != nil {
+ if err := tCtx.AddXdsResource(resourcev3.SecretType, secret); err != nil {
+ errs = errors.Join(errs, err)
+ }
}
}
if route.TLS.Terminate.CACertificate != nil {
caSecret := buildXdsTLSCaCertSecret(route.TLS.Terminate.CACertificate, route.TLS.Terminate.Crl)
- if err := tCtx.AddXdsResource(resourcev3.SecretType, caSecret); err != nil {
- errs = errors.Join(errs, err)
+ if caSecret != nil {
+ if err := tCtx.AddXdsResource(resourcev3.SecretType, caSecret); err != nil {
+ errs = errors.Join(errs, err)
+ }
}
}
} else if route.Destination != nil {
@@ -1109,8 +1122,10 @@ func addXdsCluster(tCtx *types.ResourceVersionTable, args *xdsClusterArgs) error
if shouldValidateTLS {
// Create an SDS secret for the CA certificate - either with inline bytes or with a filesystem ref
secret := buildXdsUpstreamTLSCASecret(ds.TLS)
- if err := tCtx.AddXdsResource(resourcev3.SecretType, secret); err != nil {
- return err
+ if secret != nil {
+ if err := tCtx.AddXdsResource(resourcev3.SecretType, secret); err != nil {
+ return err
+ }
}
}
}
@@ -1179,6 +1194,12 @@ func buildXdsUpstreamTLSCASecret(tlsConfig *ir.TLSUpstreamConfig) *tlsv3.Secret
},
}
}
+
+ // For SDS based CA certificate, the secret will be generated by SDS server, so we can skip adding the secret here
+ if tlsConfig.CACertificate.SDS != nil {
+ return nil
+ }
+
return &tlsv3.Secret{
Name: tlsConfig.CACertificate.Name,
Type: &tlsv3.Secret_ValidationContext{
@@ -1199,6 +1220,12 @@ func buildValidationContext(tlsConfig *ir.TLSUpstreamConfig) (*tlsv3.CommonTlsCo
},
DefaultValidationContext: &tlsv3.CertificateValidationContext{},
}
+ if tlsConfig.CACertificate.SDS != nil {
+ // get secret from SDS
+ sds := tlsConfig.CACertificate.SDS
+ clusterName := sdsClusterNameFromURL(sds.URL)
+ validationContext.ValidationContextSdsSecretConfig = sdsSecretConfig(sds.SecretName, clusterName)
+ }
hasSANValidations := false
// 3. If SubjectAltNames are specified, Hostname can be used for certificate selection
@@ -1285,15 +1312,18 @@ func buildXdsUpstreamTLSSocketWthCert(tlsConfig *ir.TLSUpstreamConfig, requiresA
tlsCtx.CommonTlsContext.AlpnProtocols = tlsConfig.ALPNProtocols
}
- if len(tlsConfig.ClientCertificates) > 0 {
- for _, cert := range tlsConfig.ClientCertificates {
- tlsCtx.CommonTlsContext.TlsCertificateSdsSecretConfigs = append(
- tlsCtx.CommonTlsContext.TlsCertificateSdsSecretConfigs,
- &tlsv3.SdsSecretConfig{
- Name: cert.Name,
- SdsConfig: makeConfigSource(),
- })
+ for _, clientCert := range tlsConfig.ClientCertificates {
+ if sds := clientCert.SDS; sds != nil {
+ clusterName := sdsClusterNameFromURL(sds.URL)
+ sds := sdsSecretConfig(sds.SecretName, clusterName)
+ tlsCtx.CommonTlsContext.TlsCertificateSdsSecretConfigs = append(tlsCtx.CommonTlsContext.TlsCertificateSdsSecretConfigs, sds)
+ continue
}
+ tlsCtx.CommonTlsContext.TlsCertificateSdsSecretConfigs = append(tlsCtx.CommonTlsContext.TlsCertificateSdsSecretConfigs,
+ &tlsv3.SdsSecretConfig{
+ Name: clientCert.Name,
+ SdsConfig: makeConfigSource(),
+ })
}
tlsCtxAny, err := proto.ToAnyWithValidation(tlsCtx)
@@ -1315,8 +1345,10 @@ func processClientCertificates(tCtx *types.ResourceVersionTable, settings []*ir.
if st.TLS != nil {
for _, c := range st.TLS.ClientCertificates {
secret := buildXdsTLSCertSecret(&c)
- if err := tCtx.AddXdsResource(resourcev3.SecretType, secret); err != nil {
- errs = errors.Join(errs, err)
+ if secret != nil {
+ if err := tCtx.AddXdsResource(resourcev3.SecretType, secret); err != nil {
+ errs = errors.Join(errs, err)
+ }
}
}
}
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index f95ef5339a..a04adfb42e 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -2232,6 +2232,7 @@ _Appears in:_
| `enableEnvoyPatchPolicy` | _boolean_ | true | | EnableEnvoyPatchPolicy enables Envoy Gateway to
reconcile and implement the EnvoyPatchPolicy resources.
Warning: Enabling `EnvoyPatchPolicy` may lead to complete security compromise of your system.
Users with `EnvoyPatchPolicy` permissions can inject arbitrary configuration to proxies,
leading to high Confidentiality, Integrity and Availability risks. |
| `enableBackend` | _boolean_ | true | | EnableBackend enables Envoy Gateway to
reconcile and implement the Backend resources. |
| `disableLua` | _boolean_ | true | | DisableLua determines if Lua EnvoyExtensionPolicies should be disabled.
If set to true, the Lua EnvoyExtensionPolicy feature will be disabled. |
+| `enableSDSSecretRef` | _boolean_ | true | | EnableSDSSecretRef enables read SDS(Secret Discovery Service) settings from a secret(with type gateway.envoyproxy.io/sds). |
#### ExtensionHooks