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
186 changes: 117 additions & 69 deletions internal/gatewayapi/clienttrafficpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,9 +315,6 @@ func resolveCTPolicyTargetRef(policy *egv1a1.ClientTrafficPolicy, gateways map[t

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 All @@ -327,6 +324,14 @@ func validatePortOverlapForClientTrafficPolicy(l *ListenerContext, xds *ir.Xds,
}
}

var tcpIR *ir.TCPListener
Copy link
Contributor

Choose a reason for hiding this comment

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

tcpIR is unused here, lets add it when needed ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure.

for _, tcp := range xds.TCP {
if tcp.Name == irListenerName {
tcpIR = tcp
break
}
}

// IR must exist since we're past validation
if httpIR != nil {
// Get a list of all other non-TLS listeners on this Gateway that share a port with
Expand Down Expand Up @@ -354,6 +359,12 @@ func validatePortOverlapForClientTrafficPolicy(l *ListenerContext, xds *ir.Xds,
}
}
}
if tcpIR != nil {
// No port overlap exists in TCP listeners, TLS listeners have their own filter chains.
// Just return.
return nil
}

return nil
}

Expand All @@ -366,10 +377,8 @@ func (t *Translator) translateClientTrafficPolicyForListener(policy *egv1a1.Clie
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 +387,41 @@ func (t *Translator) translateClientTrafficPolicyForListener(policy *egv1a1.Clie
}
}

// 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
}

// 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 +465,37 @@ func (t *Translator) translateClientTrafficPolicyForListener(policy *egv1a1.Clie
}

// 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
}

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 +507,20 @@ func translateListenerTCPKeepalive(tcpKeepAlive *egv1a1.TCPKeepalive, httpIR *ir
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)
}
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)
}
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 +571,12 @@ func translateClientTimeout(clientTimeout *egv1a1.ClientTimeout, httpIR *ir.HTTP
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 +676,49 @@ func translateHTTP2Settings(http2Settings *egv1a1.HTTP2Settings, httpIR *ir.HTTP
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 +736,58 @@ func (t *Translator) translateListenerTLSParameters(policy *egv1a1.ClientTraffic
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
}

secretBytes, ok := secret.Data[caCertKey]
if !ok || len(secretBytes) == 0 {
return fmt.Errorf(
return irTLSConfig, fmt.Errorf(
"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)
}

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
}

configMapBytes, ok := configMap.Data[caCertKey]
if !ok || len(configMapBytes) == 0 {
return fmt.Errorf(
return irTLSConfig, fmt.Errorf(
"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)
}

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))
}
}

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 +800,7 @@ func translateListenerConnection(connection *egv1a1.Connection, httpIR *ir.HTTPL
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 +811,14 @@ func translateListenerConnection(connection *egv1a1.Connection, httpIR *ir.HTTPL
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
Loading
Loading