Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(policy): Add CTP support for TCP/TLS listeners #3337

Merged
merged 13 commits into from
May 22, 2024
172 changes: 103 additions & 69 deletions internal/gatewayapi/clienttrafficpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,9 +315,6 @@

func validatePortOverlapForClientTrafficPolicy(l *ListenerContext, xds *ir.Xds, attachedToGateway bool) error {
// Find Listener IR
// TODO: Support TLSRoute and TCPRoute once
// https://github.com/envoyproxy/gateway/issues/1635 is completed

irListenerName := irListenerName(l)
var httpIR *ir.HTTPListener
for _, http := range xds.HTTP {
Expand Down Expand Up @@ -366,10 +363,8 @@
gwXdsIR := xdsIR[irKey]

// Find Listener IR
// TODO: Support TLSRoute and TCPRoute once
// https://github.com/envoyproxy/gateway/issues/1635 is completed

irListenerName := irListenerName(l)

var httpIR *ir.HTTPListener
for _, http := range gwXdsIR.HTTP {
if http.Name == irListenerName {
Expand All @@ -378,19 +373,41 @@
}
}

// IR must exist since we're past validation
if httpIR != nil {
// Translate TCPKeepalive
translateListenerTCPKeepalive(policy.Spec.TCPKeepalive, httpIR)

// Translate Connection
if err := translateListenerConnection(policy.Spec.Connection, httpIR); err != nil {
return err
var tcpIR *ir.TCPListener
for _, tcp := range gwXdsIR.TCP {
if tcp.Name == irListenerName {
tcpIR = tcp
break
}
}

// HTTP and TCP listeners can both be configured by common fields below.
var (
keepalive *ir.TCPKeepalive
connection *ir.Connection
enableProxyProtocol bool
tlsConfig *ir.TLSConfig
err error
)

// Build common IR shared by HTTP and TCP listeners, return early if some field is invalid.
// Translate TCPKeepalive
keepalive, err = buildKeepAlive(policy.Spec.TCPKeepalive)
if err != nil {
return err

Check warning on line 397 in internal/gatewayapi/clienttrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/clienttrafficpolicy.go#L397

Added line #L397 was not covered by tests
}

// Translate Connection
connection, err = buildConnection(policy.Spec.Connection)
if err != nil {
return err
}

// Translate Proxy Protocol
translateListenerProxyProtocol(policy.Spec.EnableProxyProtocol, httpIR)
// Translate Proxy Protocol
enableProxyProtocol = buildProxyProtocol(policy.Spec.EnableProxyProtocol)

// IR must exist since we're past validation
if httpIR != nil {
// Translate Client IP Detection
translateClientIPDetection(policy.Spec.ClientIPDetection, httpIR)

Expand Down Expand Up @@ -434,17 +451,37 @@
}

// Translate TLS parameters
if err := t.translateListenerTLSParameters(policy, httpIR, resources); err != nil {
tlsConfig, err = t.buildListenerTLSParameters(policy, httpIR.TLS, resources)
if err != nil {
return err
}

httpIR.TCPKeepalive = keepalive
httpIR.Connection = connection
httpIR.EnableProxyProtocol = enableProxyProtocol
httpIR.TLS = tlsConfig
}

if tcpIR != nil {
// Translate TLS parameters
tlsConfig, err = t.buildListenerTLSParameters(policy, tcpIR.TLS, resources)
if err != nil {
return err

Check warning on line 469 in internal/gatewayapi/clienttrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/clienttrafficpolicy.go#L469

Added line #L469 was not covered by tests
}

tcpIR.TCPKeepalive = keepalive
tcpIR.Connection = connection
tcpIR.EnableProxyProtocol = enableProxyProtocol
tcpIR.TLS = tlsConfig
}

return nil
}

func translateListenerTCPKeepalive(tcpKeepAlive *egv1a1.TCPKeepalive, httpIR *ir.HTTPListener) {
func buildKeepAlive(tcpKeepAlive *egv1a1.TCPKeepalive) (*ir.TCPKeepalive, error) {
// Return early if not set
if tcpKeepAlive == nil {
return
return nil, nil
}

irTCPKeepalive := &ir.TCPKeepalive{}
Expand All @@ -456,20 +493,20 @@
if tcpKeepAlive.IdleTime != nil {
d, err := time.ParseDuration(string(*tcpKeepAlive.IdleTime))
if err != nil {
return
return nil, fmt.Errorf("invalid IdleTime value %s", *tcpKeepAlive.IdleTime)

Check warning on line 496 in internal/gatewayapi/clienttrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/clienttrafficpolicy.go#L496

Added line #L496 was not covered by tests
}
irTCPKeepalive.IdleTime = ptr.To(uint32(d.Seconds()))
}

if tcpKeepAlive.Interval != nil {
d, err := time.ParseDuration(string(*tcpKeepAlive.Interval))
if err != nil {
return
return nil, fmt.Errorf("invalid Interval value %s", *tcpKeepAlive.Interval)

Check warning on line 504 in internal/gatewayapi/clienttrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/clienttrafficpolicy.go#L504

Added line #L504 was not covered by tests
}
irTCPKeepalive.Interval = ptr.To(uint32(d.Seconds()))
}

httpIR.TCPKeepalive = irTCPKeepalive
return irTCPKeepalive, nil
}

func translatePathSettings(pathSettings *egv1a1.PathSettings, httpIR *ir.HTTPListener) {
Expand Down Expand Up @@ -520,15 +557,12 @@
return nil
}

func translateListenerProxyProtocol(enableProxyProtocol *bool, httpIR *ir.HTTPListener) {
// Return early if not set
if enableProxyProtocol == nil {
return
func buildProxyProtocol(enableProxyProtocol *bool) bool {
if enableProxyProtocol != nil && *enableProxyProtocol {
return true
}

if *enableProxyProtocol {
httpIR.EnableProxyProtocol = true
}
return false
}

func translateClientIPDetection(clientIPDetection *egv1a1.ClientIPDetectionSettings, httpIR *ir.HTTPListener) {
Expand Down Expand Up @@ -628,49 +662,49 @@
return errs
}

func (t *Translator) translateListenerTLSParameters(policy *egv1a1.ClientTrafficPolicy,
httpIR *ir.HTTPListener, resources *Resources,
) error {
func (t *Translator) buildListenerTLSParameters(policy *egv1a1.ClientTrafficPolicy,
irTLSConfig *ir.TLSConfig, resources *Resources,
) (*ir.TLSConfig, error) {
// Return if this listener isn't a TLS listener. There has to be
// at least one certificate defined, which would cause httpIR to
// at least one certificate defined, which would cause httpIR/tcpIR to
// have a TLS structure.
if httpIR.TLS == nil {
return nil
if irTLSConfig == nil {
return nil, nil
}

tlsParams := policy.Spec.TLS

// Make sure that the negotiated TLS protocol version is as expected if TLS is used,
// regardless of if TLS parameters were used in the ClientTrafficPolicy or not
httpIR.TLS.MinVersion = ptr.To(ir.TLSv12)
httpIR.TLS.MaxVersion = ptr.To(ir.TLSv13)

if tlsParams != nil && len(tlsParams.ALPNProtocols) > 0 {
httpIR.TLS.ALPNProtocols = make([]string, len(tlsParams.ALPNProtocols))
for i := range tlsParams.ALPNProtocols {
httpIR.TLS.ALPNProtocols[i] = string(tlsParams.ALPNProtocols[i])
}
}
irTLSConfig.MinVersion = ptr.To(ir.TLSv12)
irTLSConfig.MaxVersion = ptr.To(ir.TLSv13)

// Return early if not set
if tlsParams == nil {
return nil
return irTLSConfig, nil
}

if len(tlsParams.ALPNProtocols) > 0 {
irTLSConfig.ALPNProtocols = make([]string, len(tlsParams.ALPNProtocols))
for i := range tlsParams.ALPNProtocols {
irTLSConfig.ALPNProtocols[i] = string(tlsParams.ALPNProtocols[i])
}
}

if tlsParams.MinVersion != nil {
httpIR.TLS.MinVersion = ptr.To(ir.TLSVersion(*tlsParams.MinVersion))
irTLSConfig.MinVersion = ptr.To(ir.TLSVersion(*tlsParams.MinVersion))
}
if tlsParams.MaxVersion != nil {
httpIR.TLS.MaxVersion = ptr.To(ir.TLSVersion(*tlsParams.MaxVersion))
irTLSConfig.MaxVersion = ptr.To(ir.TLSVersion(*tlsParams.MaxVersion))
}
if len(tlsParams.Ciphers) > 0 {
httpIR.TLS.Ciphers = tlsParams.Ciphers
irTLSConfig.Ciphers = tlsParams.Ciphers
}
if len(tlsParams.ECDHCurves) > 0 {
httpIR.TLS.ECDHCurves = tlsParams.ECDHCurves
irTLSConfig.ECDHCurves = tlsParams.ECDHCurves
}
if len(tlsParams.SignatureAlgorithms) > 0 {
httpIR.TLS.SignatureAlgorithms = tlsParams.SignatureAlgorithms
irTLSConfig.SignatureAlgorithms = tlsParams.SignatureAlgorithms
}

if tlsParams.ClientValidation != nil {
Expand All @@ -688,56 +722,58 @@
if caCertRef.Kind == nil || string(*caCertRef.Kind) == KindSecret { // nolint
secret, err := t.validateSecretRef(false, from, caCertRef, resources)
if err != nil {
return err
return irTLSConfig, err

Check warning on line 725 in internal/gatewayapi/clienttrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/clienttrafficpolicy.go#L725

Added line #L725 was not covered by tests
}

secretBytes, ok := secret.Data[caCertKey]
if !ok || len(secretBytes) == 0 {
return fmt.Errorf(
return irTLSConfig, fmt.Errorf(

Check warning on line 730 in internal/gatewayapi/clienttrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/clienttrafficpolicy.go#L730

Added line #L730 was not covered by tests
"caCertificateRef not found in secret %s", caCertRef.Name)
}

if err := validateCertificate(secretBytes); err != nil {
return fmt.Errorf("invalid certificate in secret %s: %w", caCertRef.Name, err)
return irTLSConfig, fmt.Errorf(
"invalid certificate in secret %s: %w", caCertRef.Name, err)

Check warning on line 736 in internal/gatewayapi/clienttrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/clienttrafficpolicy.go#L735-L736

Added lines #L735 - L736 were not covered by tests
}

irCACert.Certificate = append(irCACert.Certificate, secretBytes...)

} else if string(*caCertRef.Kind) == KindConfigMap {
configMap, err := t.validateConfigMapRef(false, from, caCertRef, resources)
if err != nil {
return err
return irTLSConfig, err

Check warning on line 744 in internal/gatewayapi/clienttrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/clienttrafficpolicy.go#L744

Added line #L744 was not covered by tests
}

configMapBytes, ok := configMap.Data[caCertKey]
if !ok || len(configMapBytes) == 0 {
return fmt.Errorf(
return irTLSConfig, fmt.Errorf(

Check warning on line 749 in internal/gatewayapi/clienttrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/clienttrafficpolicy.go#L749

Added line #L749 was not covered by tests
"caCertificateRef not found in configMap %s", caCertRef.Name)
}

if err := validateCertificate([]byte(configMapBytes)); err != nil {
return fmt.Errorf("invalid certificate in configmap %s: %w", caCertRef.Name, err)
return irTLSConfig, fmt.Errorf(
"invalid certificate in configmap %s: %w", caCertRef.Name, err)

Check warning on line 755 in internal/gatewayapi/clienttrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/clienttrafficpolicy.go#L754-L755

Added lines #L754 - L755 were not covered by tests
}

irCACert.Certificate = append(irCACert.Certificate, configMapBytes...)
} else {
return fmt.Errorf("unsupported caCertificateRef kind:%s", string(*caCertRef.Kind))
return irTLSConfig, fmt.Errorf(
"unsupported caCertificateRef kind:%s", string(*caCertRef.Kind))

Check warning on line 761 in internal/gatewayapi/clienttrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/clienttrafficpolicy.go#L760-L761

Added lines #L760 - L761 were not covered by tests
}
}

if len(irCACert.Certificate) > 0 {
httpIR.TLS.CACertificate = irCACert
httpIR.TLS.RequireClientCertificate = !tlsParams.ClientValidation.Optional
irTLSConfig.CACertificate = irCACert
irTLSConfig.RequireClientCertificate = !tlsParams.ClientValidation.Optional
}
}

return nil
return irTLSConfig, nil
}

func translateListenerConnection(connection *egv1a1.Connection, httpIR *ir.HTTPListener) error {
// Return early if not set
func buildConnection(connection *egv1a1.Connection) (*ir.Connection, error) {
if connection == nil {
return nil
return nil, nil
}

irConnection := &ir.Connection{}
Expand All @@ -750,7 +786,7 @@
if connection.ConnectionLimit.CloseDelay != nil {
d, err := time.ParseDuration(string(*connection.ConnectionLimit.CloseDelay))
if err != nil {
return fmt.Errorf("invalid CloseDelay value %s", *connection.ConnectionLimit.CloseDelay)
return nil, fmt.Errorf("invalid CloseDelay value %s", *connection.ConnectionLimit.CloseDelay)
}
irConnectionLimit.CloseDelay = ptr.To(metav1.Duration{Duration: d})
}
Expand All @@ -761,16 +797,14 @@
if connection.BufferLimit != nil {
bufferLimit, ok := connection.BufferLimit.AsInt64()
if !ok {
return fmt.Errorf("invalid BufferLimit value %s", connection.BufferLimit.String())
return nil, fmt.Errorf("invalid BufferLimit value %s", connection.BufferLimit.String())
}
if bufferLimit < 0 || bufferLimit > math.MaxUint32 {
return fmt.Errorf("BufferLimit value %s is out of range, must be between 0 and %d",
return nil, fmt.Errorf("BufferLimit value %s is out of range, must be between 0 and %d",
connection.BufferLimit.String(), math.MaxUint32)
}
irConnection.BufferLimitBytes = ptr.To(uint32(bufferLimit))
}

httpIR.Connection = irConnection

return nil
return irConnection, nil
}
6 changes: 6 additions & 0 deletions internal/gatewayapi/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap
Name: irListenerName(listener),
Address: "0.0.0.0",
Port: uint32(containerPort),

// Gateway is processed firstly, then ClientTrafficPolicy, then xRoute.
// TLS field should be added to TCPListener as ClientTrafficPolicy will affect
// Listener TLS. Then TCPRoute whose TLS should be configured as Terminate just
// refers to the Listener TLS.
TLS: irTLSConfigs(listener.tlsSecrets),
}
xdsIR[irKey].TCP = append(xdsIR[irKey].TCP, irListener)
case gwapiv1.UDPProtocolType:
Expand Down
24 changes: 11 additions & 13 deletions internal/gatewayapi/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -1062,18 +1062,6 @@ func (t *Translator) processTCPRouteParentRefs(tcpRoute *TCPRouteContext, resour
accepted = true
irKey := t.getIRKey(listener.gateway)

var tls *ir.TLS
if len(listener.tlsSecrets) > 0 {
tls = &ir.TLS{
Terminate: irTLSConfigs(listener.tlsSecrets),
}
}

if listener.Hostname != nil {
tls.TLSInspectorConfig = &ir.TLSInspectorConfig{
SNIs: []string{string(*listener.Hostname)},
}
}
gwXdsIR := xdsIR[irKey]
irListener := gwXdsIR.GetTCPListener(irListenerName(listener))
if irListener != nil {
Expand All @@ -1083,8 +1071,18 @@ func (t *Translator) processTCPRouteParentRefs(tcpRoute *TCPRouteContext, resour
Name: irRouteDestinationName(tcpRoute, -1 /*rule index*/),
Settings: destSettings,
},
TLS: tls,
}

if irListener.TLS != nil {
irRoute.TLS = &ir.TLS{Terminate: irListener.TLS}

if listener.Hostname != nil {
irRoute.TLS.TLSInspectorConfig = &ir.TLSInspectorConfig{
SNIs: []string{string(*listener.Hostname)},
}
}
}

irListener.Routes = append(irListener.Routes, irRoute)

}
Expand Down
Loading