Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions api/v1alpha1/envoyproxy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,19 @@ type EnvoyProxySpec struct {
// +kubebuilder:validation:Enum=Replace;StrategicMerge;JSONMerge
// +optional
MergeType *MergeType `json:"mergeType,omitempty"`
// SDSConfig defines configuration for Envoy's Secret Discovery Service (SDS)
// and controls how Envoy proxies fetch secrets from the control plane.
//
// +optional
SDSConfig *SDSConfig `json:"sdsConfig,omitempty"`
}

type SDSConfig struct {
// AllowedNamespaces selects namespaces where reading SDS settings from a secret is allowed.
// If unspecified, it allows reading SDS settings from a secret in any namespace.
//
// +optional
AllowedNamespaces *metav1.LabelSelector `json:"allowedNamespaces,omitempty"`
}

// EnvoyProxyGeoIP defines shared GeoIP provider settings for EnvoyProxy.
Expand Down
25 changes: 25 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -11253,6 +11253,60 @@ spec:
RoutingType can be set to "Service" to use the Service Cluster IP for routing to the backend,
or it can be set to "Endpoint" to use Endpoint routing. The default is "Endpoint".
type: string
sdsConfig:
description: |-
SDSConfig defines configuration for Envoy's Secret Discovery Service (SDS)
and controls how Envoy proxies fetch secrets from the control plane.
properties:
allowedNamespaces:
description: |-
AllowedNamespaces selects namespaces where reading SDS settings from a secret is allowed.
If unspecified, it allows reading SDS settings from a secret in any namespace.
properties:
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: |-
A label selector requirement is a selector that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: |-
operator represents a key's relationship to a set of values.
Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: |-
values is an array of string values. If the operator is In or NotIn,
the values array must be non-empty. If the operator is Exists or DoesNotExist,
the values array must be empty. This array is replaced during a strategic
merge patch.
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
description: |-
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions, whose key field is "key", the
operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
type: object
shutdown:
description: Shutdown defines configuration for graceful envoy shutdown
process.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11252,6 +11252,60 @@ spec:
RoutingType can be set to "Service" to use the Service Cluster IP for routing to the backend,
or it can be set to "Endpoint" to use Endpoint routing. The default is "Endpoint".
type: string
sdsConfig:
description: |-
SDSConfig defines configuration for Envoy's Secret Discovery Service (SDS)
and controls how Envoy proxies fetch secrets from the control plane.
properties:
allowedNamespaces:
description: |-
AllowedNamespaces selects namespaces where reading SDS settings from a secret is allowed.
If unspecified, it allows reading SDS settings from a secret in any namespace.
properties:
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: |-
A label selector requirement is a selector that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: |-
operator represents a key's relationship to a set of values.
Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: |-
values is an array of string values. If the operator is In or NotIn,
the values array must be non-empty. If the operator is Exists or DoesNotExist,
the values array must be empty. This array is replaced during a strategic
merge patch.
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
description: |-
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions, whose key field is "key", the
operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
type: object
shutdown:
description: Shutdown defines configuration for graceful envoy shutdown
process.
Expand Down
66 changes: 53 additions & 13 deletions internal/gatewayapi/backendtlspolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"reflect"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
Expand Down Expand Up @@ -97,6 +98,11 @@ func (t *Translator) applyBackendTLSSetting(
err error
)

var envoyproxy *egv1a1.EnvoyProxy
if gtwCtx != nil {
envoyproxy = gtwCtx.envoyProxy
}

// If the backendRef is a Backend resource, we need to check if it has TLS settings.
if KindDerefOr(backendRef.Kind, resource.KindService) == egv1a1.KindBackend {
backend := t.GetBackend(backendNamespace, string(backendRef.Name))
Expand All @@ -105,14 +111,15 @@ func (t *Translator) applyBackendTLSSetting(
}
if backend.Spec.TLS != nil {
// Get the server certificate validation settings from Backend resource.
if backendValidationTLSConfig, err = t.processServerValidationTLSSettings(backend); err != nil {
if backendValidationTLSConfig, err = t.processServerValidationTLSSettings(envoyproxy, backend); err != nil {
return nil, err
}

// Get the client certificate and common TLS settings from Backend resource.
if backend.Spec.TLS.BackendTLSConfig != nil {
if backendClientTLSConfig, err = t.processClientTLSSettings(
backend.Spec.TLS.BackendTLSConfig, &ResourceMetadata{
envoyproxy, backend.Spec.TLS.BackendTLSConfig,
&ResourceMetadata{
Name: backend.Name,
Namespace: backend.Namespace,
Kind: egv1a1.KindBackend,
Expand All @@ -124,7 +131,7 @@ func (t *Translator) applyBackendTLSSetting(
}

// Get the backend certificate validation settings from BackendTLSPolicy.
if btpValidationTLSConfig, err = t.processBackendTLSPolicy(backendRef, backendNamespace, parent, resources); err != nil {
if btpValidationTLSConfig, err = t.processBackendTLSPolicy(envoyproxy, backendRef, backendNamespace, parent, resources); err != nil {
return nil, err
}

Expand All @@ -144,7 +151,7 @@ func (t *Translator) applyBackendTLSSetting(
// Get the client certificate and common TLS settings from EnvoyProxy resource.
if gtwBackendTLSConfig, owner := gtwCtx.GetBackendTLSConfig(); gtwBackendTLSConfig != nil {
if envoyProxyClientTLSConfig, err = t.processClientTLSSettings(
gtwBackendTLSConfig, owner); err != nil {
envoyproxy, gtwBackendTLSConfig, owner); err != nil {
return nil, err
}
}
Expand Down Expand Up @@ -250,6 +257,7 @@ func mergeClientTLSConfigs(
}

func (t *Translator) processServerValidationTLSSettings(
envoyproxy *egv1a1.EnvoyProxy,
backend *egv1a1.Backend,
) (*ir.TLSUpstreamConfig, error) {
tlsConfig := &ir.TLSUpstreamConfig{
Expand All @@ -269,7 +277,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, sds, err := t.getCaCertsFromCARefs(nil, caRefs, resource.ResourceMetadata{
caCert, sds, err := t.getCaCertsFromCARefs(nil, envoyproxy, caRefs, resource.ResourceMetadata{
Name: backend.Name,
Namespace: backend.Namespace,
Kind: resource.KindBackendTLSPolicy,
Expand All @@ -289,6 +297,7 @@ func (t *Translator) processServerValidationTLSSettings(
}

func (t *Translator) processBackendTLSPolicy(
envoyproxy *egv1a1.EnvoyProxy,
backendRef gwapiv1.BackendObjectReference,
backendNamespace string,
parent gwapiv1.ParentReference,
Expand All @@ -299,7 +308,7 @@ func (t *Translator) processBackendTLSPolicy(
return nil, nil
}

tlsBundle, err := t.getBackendTLSBundle(policy)
tlsBundle, err := t.getBackendTLSBundle(envoyproxy, policy)
ancestorRefs := getAncestorRefs(policy)
ancestorRefs = append(ancestorRefs, &parent)

Expand Down Expand Up @@ -351,6 +360,7 @@ func (t *Translator) processBackendTLSPolicy(
}

func (t *Translator) processClientTLSSettings(
envoyproxy *egv1a1.EnvoyProxy,
clientTLS *egv1a1.BackendTLSConfig,
owner *ResourceMetadata,
) (*ir.TLSConfig, error) {
Expand Down Expand Up @@ -406,8 +416,8 @@ func (t *Translator) processClientTLSSettings(
}
// 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")
if err := t.validateSDSRef(envoyproxy, secret.Namespace); err != nil {
return tlsConfig, err
}
// For SDS reference secrets, extract the SDS secret name and URL from data
s, err := ir.NewSDSConfig(secret)
Expand All @@ -428,6 +438,35 @@ func (t *Translator) processClientTLSSettings(
return tlsConfig, nil
}

func (t *Translator) validateSDSRef(ep *egv1a1.EnvoyProxy, ns string) error {
if !t.SDSSecretRefEnabled {
return fmt.Errorf("SDS Secret reference is not enabled in EnvoyGateway configuration")
}

// Empty namespaces selector means all namespaces are allowed
if ep == nil || ep.Spec.SDSConfig == nil || ep.Spec.SDSConfig.AllowedNamespaces == nil {
return nil
}

secretNs := t.GetNamespace(ns)
if secretNs == nil {
// this should not happen, just in case.
return fmt.Errorf("namespace %s not found for SDS Secret reference", ns)
}

sdsSelector := ep.Spec.SDSConfig.AllowedNamespaces
selector, err := metav1.LabelSelectorAsSelector(sdsSelector)
if err != nil {
return fmt.Errorf("invalid AllowedNamespaces for SDS Secret reference: %w", err)
}

if !selector.Matches(labels.Set(secretNs.Labels)) {
return fmt.Errorf("SDS Secret reference is not allowed in namespace: %s", secretNs.Name)
Comment on lines +463 to +464

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve namespace labels before matching SDS selectors

When sdsConfig.allowedNamespaces is used with the file provider or egctl translate, this match sees empty labels because the shared YAML loader constructs Namespace resources with only ObjectMeta.Name (internal/gatewayapi/resource/load.go:278-286, reached from internal/provider/file/resources.go:54). A manifest that includes Namespace labels and an SDS Secret in that namespace will therefore be rejected even though the selector should match; the loader needs to preserve Namespace metadata before this selector check can work outside the Kubernetes provider.

Useful? React with 👍 / 👎.

}

return nil
}

func backendTLSTargetMatched(policy *gwapiv1.BackendTLSPolicy, target gwapiv1.LocalPolicyTargetReferenceWithSectionName, backendNamespace string) bool {
for _, currTarget := range policy.Spec.TargetRefs {
if target.Group == currTarget.Group &&
Expand Down Expand Up @@ -460,7 +499,7 @@ func (t *Translator) getBackendTLSPolicy(
return nil
}

func (t *Translator) getBackendTLSBundle(backendTLSPolicy *gwapiv1.BackendTLSPolicy) (*ir.TLSUpstreamConfig, error) {
func (t *Translator) getBackendTLSBundle(envoyproxy *egv1a1.EnvoyProxy, backendTLSPolicy *gwapiv1.BackendTLSPolicy) (*ir.TLSUpstreamConfig, error) {
// Translate SubjectAltNames from gwapiv1a3 to ir
subjectAltNames := make([]ir.SubjectAltName, 0, len(backendTLSPolicy.Spec.Validation.SubjectAltNames))
for _, san := range backendTLSPolicy.Spec.Validation.SubjectAltNames {
Expand Down Expand Up @@ -491,7 +530,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, sds, err := t.getCaCertsFromCARefs(nil, caRefs, resource.ResourceMetadata{
caCert, sds, err := t.getCaCertsFromCARefs(nil, envoyproxy, caRefs, resource.ResourceMetadata{
Group: egv1a1.GroupName,
Name: backendTLSPolicy.Name,
Namespace: backendTLSPolicy.Namespace,
Expand Down Expand Up @@ -525,7 +564,8 @@ 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,
func (t *Translator) getCaCertsFromCARefs(resources *resource.Resources, envoyproxy *egv1a1.EnvoyProxy,
caCertificates []gwapiv1.ObjectReference, meta resource.ResourceMetadata,
) (caCert []byte, sds *ir.SDSConfig, err error) {
ca := ""
foundSupportedRef := false
Expand Down Expand Up @@ -581,8 +621,8 @@ func (t *Translator) getCaCertsFromCARefs(resources *resource.Resources, caCerti
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 err := t.validateSDSRef(envoyproxy, secret.Namespace); err != nil {
return nil, nil, err
}
if foundSDSConfig != nil {
return nil, nil, fmt.Errorf("multiple SDS reference secrets are not supported")
Expand Down
4 changes: 2 additions & 2 deletions internal/gatewayapi/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, sds, err := t.getCaCertsFromCARefs(resources, gtwDefaultFrontendTLSValidation.Validation.CACertificateRefs, resource.ResourceMetadata{
caCert, sds, err := t.getCaCertsFromCARefs(resources, gtw.envoyProxy, gtwDefaultFrontendTLSValidation.Validation.CACertificateRefs, resource.ResourceMetadata{
Group: gwapiv1.GroupVersion.Group,
Kind: resource.KindGateway,
Name: gtw.Name,
Expand Down Expand Up @@ -88,7 +88,7 @@ func (t *Translator) ProcessGatewayTLS(gateways []*GatewayContext, resources *re
gtwPerPortCaCertificate[portValidation.Port] = nil
continue
}
caCert, sds, err := t.getCaCertsFromCARefs(resources, portValidation.TLS.Validation.CACertificateRefs, resource.ResourceMetadata{
caCert, sds, err := t.getCaCertsFromCARefs(resources, gtw.envoyProxy, portValidation.TLS.Validation.CACertificateRefs, resource.ResourceMetadata{
Group: gwapiv1.GroupVersion.Group,
Kind: resource.KindGateway,
Name: gtw.Name,
Expand Down
2 changes: 1 addition & 1 deletion internal/gatewayapi/listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1225,7 +1225,7 @@ func TestProcessServerValidationTLSSettings(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
translator := &Translator{}
actual, err := translator.processServerValidationTLSSettings(tc.input)
actual, err := translator.processServerValidationTLSSettings(nil, tc.input)
require.NoError(t, err)
require.Equal(t, tc.expected, actual)
})
Expand Down
Loading