Skip to content

Commit

Permalink
feat: TLS listeners supported
Browse files Browse the repository at this point in the history
Signed-off-by: Mattia Lavacca <[email protected]>
  • Loading branch information
mlavacca committed Apr 22, 2024
1 parent 1929b68 commit aef7eb8
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@ metadata:
name: kong
spec:
controllerName: konghq.com/gateway-operator
parametersRef:
group: gateway-operator.konghq.com
kind: GatewayConfiguration
name: kong
namespace: default
---
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
Expand All @@ -24,3 +19,8 @@ spec:
- name: http2
protocol: HTTP
port: 8089
- name: tls
protocol: TLS
port: 9443
tls:
mode: Passthrough
12 changes: 12 additions & 0 deletions controller/controlplane/controller_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,14 @@ func TestSetControlPlaneDefaults(t *testing.T) {
Name: "CONTROLLER_ADMISSION_WEBHOOK_LISTEN",
Value: consts.ControlPlaneAdmissionWebhookEnvVarValue,
},
{
Name: "",
Value: consts.ControlPlaneAdmissionWebhookEnvVarValue,
},
{
Name: "CONTROLLER_FEATURE_GATES",
Value: "GatewayAlpha=true",
},
},
},
},
Expand Down Expand Up @@ -511,6 +519,10 @@ func TestSetControlPlaneDefaults(t *testing.T) {
Name: "CONTROLLER_ADMISSION_WEBHOOK_LISTEN",
Value: consts.ControlPlaneAdmissionWebhookEnvVarValue,
},
{
Name: "CONTROLLER_FEATURE_GATES",
Value: "GatewayAlpha=true",
},
},
},
},
Expand Down
6 changes: 6 additions & 0 deletions controller/gateway/controller_conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,10 @@ const (
// to express that more than one TLS secret has been set in the listener
// TLS configuration.
ListenerReasonTooManyTLSSecrets k8sutils.ConditionReason = "TooManyTLSSecrets"

// ListenereReasonInvalidTLSMode must be used with the Accepted condition
// to express that the listener has an invalid TLS mode.
// HTTPS can only be configured with mode Terminate, while TLS can only be
// be configured with mode Passthrough.
ListenereReasonInvalidTLSMode k8sutils.ConditionReason = "InvalidTLSMode"
)
128 changes: 72 additions & 56 deletions controller/gateway/controller_reconciler_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,8 @@ func generateDataPlaneNetworkPolicy(
var (
protocolTCP = corev1.ProtocolTCP
adminAPISSLPort = intstr.FromInt(consts.DataPlaneAdminAPIPort)
proxyPort = intstr.FromInt(consts.DataPlaneProxyPort)
proxySSLPort = intstr.FromInt(consts.DataPlaneProxySSLPort)
proxyPort = intstr.FromInt(consts.DataPlaneProxyHTTPPort)
proxySSLPort = intstr.FromInt(consts.DataPlaneProxyHTTPSPort)
metricsPort = intstr.FromInt(consts.DataPlaneMetricsPort)
)

Expand Down Expand Up @@ -527,9 +527,9 @@ func supportedRoutesByProtocol() map[gatewayv1.ProtocolType]map[gatewayv1.Kind]s
return map[gatewayv1.ProtocolType]map[gatewayv1.Kind]struct{}{
gatewayv1.HTTPProtocolType: {"HTTPRoute": {}},
gatewayv1.HTTPSProtocolType: {"HTTPRoute": {}},
gatewayv1.TLSProtocolType: {"TLSRoute": {}},

// L4 routes not supported yet
// gatewayv1.TLSProtocolType: {"TLSRoute": {}},
// TCP and UDP routes not supported yet
// gatewayv1.TCPProtocolType: {"TCPRoute": {}},
// gatewayv1.UDPProtocolType: {"UDPRoute": {}},
}
Expand Down Expand Up @@ -590,10 +590,25 @@ func (g *gatewayConditionsAndListenersAwareT) setAccepted() {
LastTransitionTime: metav1.Now(),
ObservedGeneration: g.Generation,
}
if listener.Protocol != gatewayv1.HTTPProtocolType && listener.Protocol != gatewayv1.HTTPSProtocolType {
if listener.Protocol != gatewayv1.HTTPProtocolType &&
listener.Protocol != gatewayv1.HTTPSProtocolType &&
listener.Protocol != gatewayv1.TLSProtocolType {
acceptedCondition.Status = metav1.ConditionFalse
acceptedCondition.Reason = string(gatewayv1.ListenerReasonUnsupportedProtocol)
}
// Only TLS terminate mode supported with HTTPS Listeners.
if listener.Protocol == gatewayv1.HTTPSProtocolType && *listener.TLS.Mode != gatewayv1.TLSModeTerminate {
acceptedCondition.Status = metav1.ConditionFalse
acceptedCondition.Reason = string(ListenereReasonInvalidTLSMode)
acceptedCondition.Message = "Only Terminate mode is supported with HTTPS listeners"
}

// Only TLS passthrough mode supported with TLS Listeners.
if listener.Protocol == gatewayv1.TLSProtocolType && *listener.TLS.Mode != gatewayv1.TLSModePassthrough {
acceptedCondition.Status = metav1.ConditionFalse
acceptedCondition.Reason = string(ListenereReasonInvalidTLSMode)
acceptedCondition.Message = "Only Passthrough mode is supported with TLS listeners"
}
listenerConditionsAware := listenerConditionsAware(&g.Status.Listeners[i])
listenerConditionsAware.SetConditions(append(listenerConditionsAware.Conditions, acceptedCondition))
}
Expand Down Expand Up @@ -685,9 +700,11 @@ func setDataPlaneIngressServicePorts(opts *operatorv1beta1.DataPlaneOptions, lis
}
switch l.Protocol {
case gatewayv1.HTTPSProtocolType:
port.TargetPort = intstr.FromInt(consts.DataPlaneProxySSLPort)
port.TargetPort = intstr.FromInt(consts.DataPlaneProxyHTTPSPort)
case gatewayv1.HTTPProtocolType:
port.TargetPort = intstr.FromInt(consts.DataPlaneProxyPort)
port.TargetPort = intstr.FromInt(consts.DataPlaneProxyHTTPPort)
case gatewayv1.TLSProtocolType:
port.TargetPort = intstr.FromInt(consts.DataPlaneProxyTLSPort)
default:
errs = errors.Join(errs, fmt.Errorf("listener %d uses unsupported protocol %s", i, l.Protocol))
continue
Expand All @@ -712,67 +729,66 @@ func getSupportedKindsWithResolvedRefsCondition(ctx context.Context, c client.Cl

message := ""
if listener.TLS != nil {
// We currently do not support TLSRoutes, hence only TLS termination supported.
if *listener.TLS.Mode != gatewayv1.TLSModeTerminate {
resolvedRefsCondition.Status = metav1.ConditionFalse
resolvedRefsCondition.Reason = string(gatewayv1.ListenerReasonInvalidCertificateRef)
message = conditionMessage(message, "Only Terminate mode is supported")
}
// We currently do not support more that one listener certificate.
if len(listener.TLS.CertificateRefs) != 1 {
if len(listener.TLS.CertificateRefs) > 1 {
resolvedRefsCondition.Reason = string(ListenerReasonTooManyTLSSecrets)
message = conditionMessage(message, "Only one certificate per listener is supported")
} else {

Check failure on line 736 in controller/gateway/controller_reconciler_utils.go

View workflow job for this annotation

GitHub Actions / lint

elseif: can replace 'else {if cond {}}' with 'else if cond {}' (gocritic)
isValidGroupKind := true
certificateRef := listener.TLS.CertificateRefs[0]
if certificateRef.Group != nil && *certificateRef.Group != "" && *certificateRef.Group != gatewayv1.Group(corev1.SchemeGroupVersion.Group) {
resolvedRefsCondition.Reason = string(gatewayv1.ListenerReasonInvalidCertificateRef)
message = conditionMessage(message, fmt.Sprintf("Group %s not supported in CertificateRef", *certificateRef.Group))
isValidGroupKind = false
}
if certificateRef.Kind != nil && *certificateRef.Kind != "" && *certificateRef.Kind != gatewayv1.Kind("Secret") {
resolvedRefsCondition.Reason = string(gatewayv1.ListenerReasonInvalidCertificateRef)
message = conditionMessage(message, fmt.Sprintf("Kind %s not supported in CertificateRef", *certificateRef.Kind))
isValidGroupKind = false
}
secretNamespace := gatewayNamespace
if certificateRef.Namespace != nil && *certificateRef.Namespace != "" {
secretNamespace = string(*certificateRef.Namespace)
}

var secretExists bool
if isValidGroupKind {
// Get the secret and check it exists.
certificateSecret := &corev1.Secret{}
err = c.Get(ctx, types.NamespacedName{
Namespace: secretNamespace,
Name: string(certificateRef.Name),
}, certificateSecret)
if err != nil {
if !k8serrors.IsNotFound(err) {
return
}
// check certificate references only when Terminate mode is used.
// Passthrough mode does not need a certificate.
if len(listener.TLS.CertificateRefs) != 0 {
isValidGroupKind := true
certificateRef := listener.TLS.CertificateRefs[0]
if certificateRef.Group != nil && *certificateRef.Group != "" && *certificateRef.Group != gatewayv1.Group(corev1.SchemeGroupVersion.Group) {
resolvedRefsCondition.Reason = string(gatewayv1.ListenerReasonInvalidCertificateRef)
message = conditionMessage(message, fmt.Sprintf("Referenced secret %s/%s does not exist", secretNamespace, certificateRef.Name))
} else {
secretExists = true
message = conditionMessage(message, fmt.Sprintf("Group %s not supported in CertificateRef", *certificateRef.Group))
isValidGroupKind = false
}
if certificateRef.Kind != nil && *certificateRef.Kind != "" && *certificateRef.Kind != gatewayv1.Kind("Secret") {
resolvedRefsCondition.Reason = string(gatewayv1.ListenerReasonInvalidCertificateRef)
message = conditionMessage(message, fmt.Sprintf("Kind %s not supported in CertificateRef", *certificateRef.Kind))
isValidGroupKind = false
}
secretNamespace := gatewayNamespace
if certificateRef.Namespace != nil && *certificateRef.Namespace != "" {
secretNamespace = string(*certificateRef.Namespace)
}
}

if secretExists {
// In case there is a cross-namespace reference, check if there is any referenceGrant allowing it.
if secretNamespace != gatewayNamespace {
referenceGrantList := &gatewayv1beta1.ReferenceGrantList{}
err = c.List(ctx, referenceGrantList, client.InNamespace(secretNamespace))
var secretExists bool
if isValidGroupKind {
// Get the secret and check it exists.
certificateSecret := &corev1.Secret{}
err = c.Get(ctx, types.NamespacedName{
Namespace: secretNamespace,
Name: string(certificateRef.Name),
}, certificateSecret)
if err != nil {
return
if !k8serrors.IsNotFound(err) {
return
}
resolvedRefsCondition.Reason = string(gatewayv1.ListenerReasonInvalidCertificateRef)
message = conditionMessage(message, fmt.Sprintf("Referenced secret %s/%s does not exist", secretNamespace, certificateRef.Name))
} else {
secretExists = true
}
if !isSecretCrossReferenceGranted(gatewayv1.Namespace(gatewayNamespace), certificateRef.Name, referenceGrantList.Items) {
resolvedRefsCondition.Reason = string(gatewayv1.ListenerReasonRefNotPermitted)
message = conditionMessage(message, fmt.Sprintf("Secret %s/%s reference not allowed by any referenceGrant", secretNamespace, certificateRef.Name))
}

if secretExists {
// In case there is a cross-namespace reference, check if there is any referenceGrant allowing it.
if secretNamespace != gatewayNamespace {
referenceGrantList := &gatewayv1beta1.ReferenceGrantList{}
err = c.List(ctx, referenceGrantList, client.InNamespace(secretNamespace))
if err != nil {
return
}
if !isSecretCrossReferenceGranted(gatewayv1.Namespace(gatewayNamespace), certificateRef.Name, referenceGrantList.Items) {
resolvedRefsCondition.Reason = string(gatewayv1.ListenerReasonRefNotPermitted)
message = conditionMessage(message, fmt.Sprintf("Secret %s/%s reference not allowed by any referenceGrant", secretNamespace, certificateRef.Name))
}
}
}
}

}
}

Expand Down
42 changes: 3 additions & 39 deletions controller/gateway/controller_reconciler_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,12 +553,12 @@ func TestSetDataPlaneIngressServicePorts(t *testing.T) {
{
Name: "http",
Port: 80,
TargetPort: intstr.FromInt(consts.DataPlaneProxyPort),
TargetPort: intstr.FromInt(consts.DataPlaneProxyHTTPPort),
},
{
Name: "https",
Port: 443,
TargetPort: intstr.FromInt(consts.DataPlaneProxySSLPort),
TargetPort: intstr.FromInt(consts.DataPlaneProxyHTTPSPort),
},
},
},
Expand All @@ -580,7 +580,7 @@ func TestSetDataPlaneIngressServicePorts(t *testing.T) {
{
Name: "http",
Port: 80,
TargetPort: intstr.FromInt(consts.DataPlaneProxyPort),
TargetPort: intstr.FromInt(consts.DataPlaneProxyHTTPPort),
},
},
expectedError: errors.New("listener 1 uses unsupported protocol UDP"),
Expand Down Expand Up @@ -979,42 +979,6 @@ func TestGetSupportedKindsWithResolvedRefsCondition(t *testing.T) {
ObservedGeneration: generation,
},
},
{
name: "tls with passthrough, HTTPS protocol, no allowed routes",
gatewayNamespace: "default",
listener: gatewayv1.Listener{
Protocol: gatewayv1.HTTPSProtocolType,
TLS: &gatewayv1.GatewayTLSConfig{
Mode: lo.ToPtr(gatewayv1.TLSModePassthrough),
CertificateRefs: []gatewayv1.SecretObjectReference{
{
Name: "test-secret",
},
},
},
},
secrets: []client.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test-secret",
Namespace: "default",
},
},
},
expectedSupportedKinds: []gatewayv1.RouteGroupKind{
{
Group: (*gatewayv1.Group)(&gatewayv1.GroupVersion.Group),
Kind: "HTTPRoute",
},
},
expectedResolvedRefsCondition: metav1.Condition{
Type: string(gatewayv1.ListenerConditionResolvedRefs),
Status: metav1.ConditionFalse,
Reason: string(gatewayv1.ListenerReasonInvalidCertificateRef),
Message: "Only Terminate mode is supported.",
ObservedGeneration: generation,
},
},
{
name: "tls bad-formed, multiple TLS secrets no cross-namespace reference",
gatewayNamespace: "default",
Expand Down
10 changes: 10 additions & 0 deletions controller/pkg/controlplane/controlplane.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ func SetDefaults(
changed = true
}

// If the feature gates are not set, set them to the default value.
const (
featureGates = "CONTROLLER_FEATURE_GATES"
defaultFeatureGates = "GatewayAlpha=true"
)
if k8sutils.EnvValueByName(container.Env, featureGates) == "" {
container.Env = k8sutils.UpdateEnv(container.Env, featureGates, defaultFeatureGates)
changed = true
}

if args.Namespace != "" && args.DataPlaneIngressServiceName != "" {
if _, isOverrideDisabled := dontOverride["CONTROLLER_PUBLISH_SERVICE"]; !isOverrideDisabled {
publishServiceNN := k8stypes.NamespacedName{Namespace: args.Namespace, Name: args.DataPlaneIngressServiceName}.String()
Expand Down
3 changes: 2 additions & 1 deletion internal/utils/dataplane/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ var KongDefaults = map[string]string{
"KONG_PORT_MAPS": "80:8000, 443:8443",
"KONG_PROXY_ACCESS_LOG": "/dev/stdout",
"KONG_PROXY_ERROR_LOG": "/dev/stderr",
"KONG_PROXY_LISTEN": fmt.Sprintf("0.0.0.0:%d reuseport backlog=16384, 0.0.0.0:%d http2 ssl reuseport backlog=16384", consts.DataPlaneProxyPort, consts.DataPlaneProxySSLPort),
"KONG_PROXY_LISTEN": fmt.Sprintf("0.0.0.0:%d reuseport backlog=16384, 0.0.0.0:%d http2 ssl reuseport backlog=16384", consts.DataPlaneProxyHTTPPort, consts.DataPlaneProxyHTTPSPort),
"KONG_STATUS_LISTEN": fmt.Sprintf("0.0.0.0:%d", consts.DataPlaneStatusPort),
"KONG_STREAM_LISTEN": fmt.Sprintf("0.0.0.0:%d ssl", consts.DataPlaneProxyTLSPort),

// TODO: reconfigure following https://github.com/Kong/gateway-operator/issues/7
"KONG_ADMIN_LISTEN": fmt.Sprintf("0.0.0.0:%d ssl reuseport backlog=16384", consts.DataPlaneAdminAPIPort),
Expand Down
7 changes: 5 additions & 2 deletions pkg/consts/dataplane.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,13 @@ const (
DataPlaneAdminAPIPort = 8444

// DataPlaneHTTPSPort is the port that the dataplane uses for HTTP.
DataPlaneProxyPort = 8000
DataPlaneProxyHTTPPort = 8000

// DataPlaneHTTPSPort is the port that the dataplane uses for HTTPS.
DataPlaneProxySSLPort = 8443
DataPlaneProxyHTTPSPort = 8443

// DataPlaneProxyTLSPort is the port that the dataplane uses for TLS.
DataPlaneProxyTLSPort = 9443

// DataPlaneMetricsPort is the port that the dataplane uses for metrics.
DataPlaneMetricsPort = 8100
Expand Down
9 changes: 7 additions & 2 deletions pkg/utils/kubernetes/resources/deployments.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,12 +295,17 @@ func GenerateDataPlaneContainer(image string) corev1.Container {
Ports: []corev1.ContainerPort{
{
Name: "proxy",
ContainerPort: consts.DataPlaneProxyPort,
ContainerPort: consts.DataPlaneProxyHTTPPort,
Protocol: corev1.ProtocolTCP,
},
{
Name: "proxy-ssl",
ContainerPort: consts.DataPlaneProxySSLPort,
ContainerPort: consts.DataPlaneProxyHTTPSPort,
Protocol: corev1.ProtocolTCP,
},
{
Name: "proxy-tls",
ContainerPort: consts.DataPlaneProxyTLSPort,
Protocol: corev1.ProtocolTCP,
},
{
Expand Down
6 changes: 3 additions & 3 deletions pkg/utils/kubernetes/resources/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,13 @@ var DefaultDataPlaneIngressServicePorts = []corev1.ServicePort{
Name: "http",
Protocol: corev1.ProtocolTCP,
Port: consts.DefaultHTTPPort,
TargetPort: intstr.FromInt(consts.DataPlaneProxyPort),
TargetPort: intstr.FromInt(consts.DataPlaneProxyHTTPPort),
},
{
Name: "https",
Protocol: corev1.ProtocolTCP,
Port: consts.DefaultHTTPSPort,
TargetPort: intstr.FromInt(consts.DataPlaneProxySSLPort),
TargetPort: intstr.FromInt(consts.DataPlaneProxyHTTPSPort),
},
}

Expand Down Expand Up @@ -145,7 +145,7 @@ func ServicePortsFromDataPlaneIngressOpt(dataplane *operatorv1beta1.DataPlane) S
newPorts := make([]corev1.ServicePort, 0)
alreadyUsedPorts := make(map[int32]struct{})
for _, p := range dataplane.Spec.Network.Services.Ingress.Ports {
targetPort := intstr.FromInt(consts.DataPlaneProxyPort)
targetPort := intstr.FromInt(consts.DataPlaneProxyHTTPPort)
if !cmp.Equal(p.TargetPort, intstr.IntOrString{}) {
targetPort = p.TargetPort
}
Expand Down
Loading

0 comments on commit aef7eb8

Please sign in to comment.