Skip to content

Add OverlappingTLSConfig condition #3709

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

Merged
merged 2 commits into from
Aug 8, 2025
Merged
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
41 changes: 38 additions & 3 deletions internal/controller/state/conditions/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ const (
ListenerMessageFailedNginxReload = "The Listener is not programmed due to a failure to " +
"reload nginx with the configuration"

// ListenerMessageOverlappingHostnames is a message used with the "OverlappingTLSConfig" condition when the
// condition is true due to overlapping hostnames.
ListenerMessageOverlappingHostnames = "Listener hostname overlaps with hostname(s) of other Listener(s) " +
"on the same port"

// RouteReasonBackendRefUnsupportedValue is used with the "ResolvedRefs" condition when one of the
// Route rules has a backendRef with an unsupported value.
RouteReasonBackendRefUnsupportedValue v1.RouteConditionReason = "UnsupportedValue"
Expand Down Expand Up @@ -501,13 +506,32 @@ func NewRouteResolvedRefsInvalidFilter(msg string) Condition {
}

// NewDefaultListenerConditions returns the default Conditions that must be present in the status of a Listener.
func NewDefaultListenerConditions() []Condition {
return []Condition{
// If existingConditions contains conflict-related conditions (like OverlappingTLSConfig or Conflicted),
// the NoConflicts condition is excluded to avoid conflicting condition states.
func NewDefaultListenerConditions(existingConditions []Condition) []Condition {
defaultConds := []Condition{
NewListenerAccepted(),
NewListenerProgrammed(),
NewListenerResolvedRefs(),
NewListenerNoConflicts(),
}

// Only add NoConflicts condition if there are no existing conflict-related conditions
if !hasConflictConditions(existingConditions) {
defaultConds = append(defaultConds, NewListenerNoConflicts())
}

return defaultConds
}

// hasConflictConditions checks if the listener has any conflict-related conditions.
func hasConflictConditions(conditions []Condition) bool {
for _, cond := range conditions {
if cond.Type == string(v1.ListenerConditionConflicted) ||
cond.Type == string(v1.ListenerConditionOverlappingTLSConfig) {
return true
}
}
return false
}

// NewListenerAccepted returns a Condition that indicates that the Listener is accepted.
Expand Down Expand Up @@ -681,6 +705,17 @@ func NewListenerRefNotPermitted(msg string) []Condition {
}
}

// NewListenerOverlappingTLSConfig returns a Condition that indicates overlapping TLS configuration
// between Listeners on the same port.
func NewListenerOverlappingTLSConfig(reason v1.ListenerConditionReason, msg string) Condition {
return Condition{
Type: string(v1.ListenerConditionOverlappingTLSConfig),
Status: metav1.ConditionTrue,
Reason: string(reason),
Message: msg,
}
}

// NewGatewayClassResolvedRefs returns a Condition that indicates that the parametersRef
// on the GatewayClass is resolved.
func NewGatewayClassResolvedRefs() Condition {
Expand Down
38 changes: 38 additions & 0 deletions internal/controller/state/graph/gateway_listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func newListenerConfiguratorFactory(
protectedPorts ProtectedPorts,
) *listenerConfiguratorFactory {
sharedPortConflictResolver := createPortConflictResolver()
sharedOverlappingTLSConfigResolver := createOverlappingTLSConfigResolver()

return &listenerConfiguratorFactory{
unsupportedProtocol: &listenerConfigurator{
Expand Down Expand Up @@ -123,6 +124,7 @@ func newListenerConfiguratorFactory(
},
conflictResolvers: []listenerConflictResolver{
sharedPortConflictResolver,
sharedOverlappingTLSConfigResolver,
},
externalReferenceResolvers: []listenerExternalReferenceResolver{
createExternalReferencesForTLSSecretsResolver(gw.Namespace, secretResolver, refGrantResolver),
Expand All @@ -137,6 +139,7 @@ func newListenerConfiguratorFactory(
},
conflictResolvers: []listenerConflictResolver{
sharedPortConflictResolver,
sharedOverlappingTLSConfigResolver,
},
externalReferenceResolvers: []listenerExternalReferenceResolver{},
},
Expand Down Expand Up @@ -591,3 +594,38 @@ func haveOverlap(hostname1, hostname2 *v1.Hostname) bool {
}
return matchesWildcard(h1, h2)
}

func createOverlappingTLSConfigResolver() listenerConflictResolver {
listenersByPort := make(map[v1.PortNumber][]*Listener)

return func(l *Listener) {
port := l.Source.Port

// Only check TLS-enabled listeners (HTTPS/TLS)
if l.Source.Protocol != v1.HTTPSProtocolType && l.Source.Protocol != v1.TLSProtocolType {
return
}

// Check for overlaps with existing listeners on this port
for _, existingListener := range listenersByPort[port] {
// Only check against other TLS-enabled listeners
if existingListener.Source.Protocol != v1.HTTPSProtocolType &&
existingListener.Source.Protocol != v1.TLSProtocolType {
continue
}

// Check for hostname overlap
if haveOverlap(l.Source.Hostname, existingListener.Source.Hostname) {
// Set condition on both listeners
cond := conditions.NewListenerOverlappingTLSConfig(
v1.ListenerReasonOverlappingHostnames,
conditions.ListenerMessageOverlappingHostnames,
)
l.Conditions = append(l.Conditions, cond)
existingListener.Conditions = append(existingListener.Conditions, cond)
}
}

listenersByPort[port] = append(listenersByPort[port], l)
}
}
Loading