Skip to content

Commit

Permalink
Add disable path overlap validation flag
Browse files Browse the repository at this point in the history
  ## What this PR does / why we need it:

In #5651 there was a
request to throw an error when there are two ingresses defining the same
host and path, which was implemented as part of the validation webhook.

  Despite of this there are clear rules on the ingress controller that
describes what the controller would do in [such situation (the oldest
rule wins)](https://github.com/kubernetes/ingress-nginx/blob/main/docs/how-it-works.md?plain=1#L27)

  Some users are relying on this validation behaviour to prevent
misconfigurations, but there are use cases where allowing it, maybe
temporarily, is helpful. Those use cases includes:

  - Splitting large ingresses objects in smaller ones #10820
  - Moving ingress objects between namespaces without downtime (like when you transfer an ingress from team A that owns namespace A to team B that owns namespace B) #10090

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

  ## Types of changes

- [ ] Bug fix (non-breaking change which fixes an issue)
- [X] New feature (non-breaking change which adds functionality)
- [ ] CVE Report (Scanner found CVE and adding report)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation only

  ## Which issue/s this PR fixes

fixes #10820
fixes #10090

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->

  ## How Has This Been Tested?

building an image and testing it in a local cluster, will update later
with some real life scenarios

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
  ## Checklist:

- [X] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [X] I've read the [CONTRIBUTION](https://github.com/kubernetes/ingress-nginx/blob/main/CONTRIBUTING.md) guide
- [X] I have added unit and/or e2e tests to cover my changes.
- [X] All new and existing tests passed.

Change-Id: I9d4124d1c36876b06d63100cd10988eaf2f41db9
  • Loading branch information
Fsero committed Jan 30, 2024
1 parent e00b45b commit 3cdcd47
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 39 deletions.
17 changes: 11 additions & 6 deletions internal/ingress/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,11 @@ type Configuration struct {

IngressClassConfiguration *ingressclass.Configuration

ValidationWebhook string
ValidationWebhookCertPath string
ValidationWebhookKeyPath string
DisableFullValidationTest bool
ValidationWebhook string
ValidationWebhookCertPath string
ValidationWebhookKeyPath string
DisableFullValidationTest bool
DisablePathOverlapValidation bool

GlobalExternalAuth *ngx_config.GlobalExternalAuth
MaxmindEditionFiles *[]string
Expand Down Expand Up @@ -402,7 +403,7 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
startTest := time.Now().UnixNano() / 1000000
_, servers, pcfg := n.getConfiguration(ings)

err = checkOverlap(ing, servers)
err = checkOverlap(ing, servers, n.cfg.DisablePathOverlapValidation)
if err != nil {
n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name)
return err
Expand Down Expand Up @@ -1779,7 +1780,7 @@ func externalNamePorts(name string, svc *apiv1.Service) *apiv1.ServicePort {
}
}

func checkOverlap(ing *networking.Ingress, servers []*ingress.Server) error {
func checkOverlap(ing *networking.Ingress, servers []*ingress.Server, disablePathOverlapValidation bool) error {
for _, rule := range ing.Spec.Rules {
if rule.HTTP == nil {
continue
Expand Down Expand Up @@ -1814,6 +1815,10 @@ func checkOverlap(ing *networking.Ingress, servers []*ingress.Server) error {
}
}

if disablePathOverlapValidation {
return nil
}

// path overlap. Check if one of the ingresses has a canary annotation
isCanaryEnabled, annotationErr := parser.GetBoolAnnotation("canary", ing, canary.CanaryAnnotations.Annotations)
for _, existing := range existingIngresses {
Expand Down
77 changes: 77 additions & 0 deletions internal/ingress/controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,83 @@ func TestCheckIngress(t *testing.T) {
t.Errorf("with a new ingress without error, no error should be returned")
}
})

t.Run("When there is a duplicated ingress with same host and path it should error", func(t *testing.T) {
ing := &networking.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ingress",
Namespace: "test-namespace",
Annotations: map[string]string{
"kubernetes.io/ingress.class": "nginx",
},
},
Spec: networking.IngressSpec{
Rules: []networking.IngressRule{
{
Host: "example.com",
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/",
PathType: &pathTypePrefix,
Backend: networking.IngressBackend{
Service: &networking.IngressServiceBackend{
Name: "http-svc",
Port: networking.ServiceBackendPort{
Number: 80,
Name: "http",
},
},
},
},
},
},
},
},
},
},
}

nginx.store = &fakeIngressStore{
ingresses: []*ingress.Ingress{
{
Ingress: *ing,
ParsedAnnotations: &annotations.Ingress{},
},
},
}
duplicatedIngress := ing.DeepCopy()
duplicatedIngress.ObjectMeta.Name = "duplicated-ingress"

nginx.cfg.DisablePathOverlapValidation = false
nginx.command = testNginxTestCommand{
t: t,
err: nil,
expected: "_,example.com",
}

err = nginx.CheckIngress(duplicatedIngress)
if err == nil {
t.Errorf("expected errors but noone occurred")
}
t.Run("if disablePathOverlap is enabled should not throw any error", func(t *testing.T) {
duplicatedIngress := ing.DeepCopy()
duplicatedIngress.ObjectMeta.Name = "duplicated-ingress"

nginx.cfg.DisablePathOverlapValidation = true
nginx.command = testNginxTestCommand{
t: t,
err: nil,
expected: "_,example.com",
}

err = nginx.CheckIngress(duplicatedIngress)
if err != nil {
t.Errorf("expected no errors but one %+v occurred", err)
}
})
})
})

t.Run("When the ingress is marked as deleted", func(t *testing.T) {
Expand Down
72 changes: 39 additions & 33 deletions pkg/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,11 @@ Takes the form "<host>:port". If not provided, no admission controller is starte
`The path of the validating webhook certificate PEM.`)
validationWebhookKey = flags.String("validating-webhook-key", "",
`The path of the validating webhook key PEM.`)
disablePathOverlapValidation = flags.Bool("disable-path-overlap-validation", false,
`Disable path overlap validation. Validation webhook blocks creating ingresses with the same hostname and path in the same ingressClass.
These flags turn off this validation as it helps split ingresses or move them between namespaces as it relies on the existing model rules:
'If the same path for the same host is defined in more than one Ingress, the oldest rule wins'`)

disableFullValidationTest = flags.Bool("disable-full-test", false,
`Disable full test of all merged ingresses at the admission stage and tests the template of the ingress being created or updated (full test of all ingresses is enabled by default).`)

Expand Down Expand Up @@ -320,39 +325,40 @@ https://blog.maxmind.com/2019/12/18/significant-changes-to-accessing-and-using-g
ngx_config.EnableSSLChainCompletion = *enableSSLChainCompletion

config := &controller.Configuration{
APIServerHost: *apiserverHost,
KubeConfigFile: *kubeConfigFile,
UpdateStatus: *updateStatus,
ElectionID: *electionID,
EnableProfiling: *profiling,
EnableMetrics: *enableMetrics,
MetricsPerHost: *metricsPerHost,
MetricsBuckets: histogramBuckets,
ReportStatusClasses: *reportStatusClasses,
ExcludeSocketMetrics: *excludeSocketMetrics,
MonitorMaxBatchSize: *monitorMaxBatchSize,
DisableServiceExternalName: *disableServiceExternalName,
EnableSSLPassthrough: *enableSSLPassthrough,
ResyncPeriod: *resyncPeriod,
DefaultService: *defaultSvc,
Namespace: *watchNamespace,
WatchNamespaceSelector: namespaceSelector,
ConfigMapName: *configMap,
TCPConfigMapName: *tcpConfigMapName,
UDPConfigMapName: *udpConfigMapName,
DisableFullValidationTest: *disableFullValidationTest,
DefaultSSLCertificate: *defSSLCertificate,
DeepInspector: *deepInspector,
PublishService: *publishSvc,
PublishStatusAddress: *publishStatusAddress,
UpdateStatusOnShutdown: *updateStatusOnShutdown,
ShutdownGracePeriod: *shutdownGracePeriod,
PostShutdownGracePeriod: *postShutdownGracePeriod,
UseNodeInternalIP: *useNodeInternalIP,
SyncRateLimit: *syncRateLimit,
HealthCheckHost: *healthzHost,
DynamicConfigurationRetries: *dynamicConfigurationRetries,
EnableTopologyAwareRouting: *enableTopologyAwareRouting,
APIServerHost: *apiserverHost,
KubeConfigFile: *kubeConfigFile,
UpdateStatus: *updateStatus,
ElectionID: *electionID,
EnableProfiling: *profiling,
EnableMetrics: *enableMetrics,
MetricsPerHost: *metricsPerHost,
MetricsBuckets: histogramBuckets,
ReportStatusClasses: *reportStatusClasses,
ExcludeSocketMetrics: *excludeSocketMetrics,
MonitorMaxBatchSize: *monitorMaxBatchSize,
DisableServiceExternalName: *disableServiceExternalName,
EnableSSLPassthrough: *enableSSLPassthrough,
ResyncPeriod: *resyncPeriod,
DefaultService: *defaultSvc,
Namespace: *watchNamespace,
WatchNamespaceSelector: namespaceSelector,
ConfigMapName: *configMap,
TCPConfigMapName: *tcpConfigMapName,
UDPConfigMapName: *udpConfigMapName,
DisableFullValidationTest: *disableFullValidationTest,
DisablePathOverlapValidation: *disablePathOverlapValidation,
DefaultSSLCertificate: *defSSLCertificate,
DeepInspector: *deepInspector,
PublishService: *publishSvc,
PublishStatusAddress: *publishStatusAddress,
UpdateStatusOnShutdown: *updateStatusOnShutdown,
ShutdownGracePeriod: *shutdownGracePeriod,
PostShutdownGracePeriod: *postShutdownGracePeriod,
UseNodeInternalIP: *useNodeInternalIP,
SyncRateLimit: *syncRateLimit,
HealthCheckHost: *healthzHost,
DynamicConfigurationRetries: *dynamicConfigurationRetries,
EnableTopologyAwareRouting: *enableTopologyAwareRouting,
ListenPorts: &ngx_config.ListenPorts{
Default: *defServerPort,
Health: *healthzPort,
Expand Down

0 comments on commit 3cdcd47

Please sign in to comment.