From a3327c4430cf4a2cb35216776c2c9843281dec00 Mon Sep 17 00:00:00 2001 From: Sylvain Rabot Date: Fri, 18 Dec 2020 18:44:03 +0100 Subject: [PATCH 01/18] Add TLS certs expiration metric --- cmd/traefik/traefik.go | 21 ++++ cmd/traefik/traefik_test.go | 116 +++++++++++++++++++++++ docs/content/providers/docker.md | 6 +- docs/content/routing/providers/docker.md | 2 +- pkg/metrics/datadog.go | 34 +++---- pkg/metrics/datadog_test.go | 2 + pkg/metrics/influxdb.go | 34 +++---- pkg/metrics/influxdb_test.go | 19 ++++ pkg/metrics/metrics.go | 13 +++ pkg/metrics/prometheus.go | 24 +++-- pkg/metrics/prometheus_test.go | 14 +++ pkg/metrics/statsd.go | 34 +++---- pkg/metrics/statsd_test.go | 4 + pkg/tls/certificate_store.go | 9 +- pkg/tls/tlsmanager.go | 21 ++++ 15 files changed, 291 insertions(+), 62 deletions(-) create mode 100644 cmd/traefik/traefik_test.go diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index d55eaa6a6..bfe335c7a 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -2,6 +2,7 @@ package main import ( "context" + "crypto/x509" "encoding/json" stdlog "log" "net/http" @@ -14,6 +15,7 @@ import ( "github.com/coreos/go-systemd/daemon" assetfs "github.com/elazarl/go-bindata-assetfs" "github.com/go-acme/lego/v4/challenge" + gokitmetrics "github.com/go-kit/kit/metrics" "github.com/sirupsen/logrus" "github.com/traefik/paerser/cli" "github.com/traefik/traefik/v2/autogen/genstatic" @@ -260,6 +262,11 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err watcher.AddListener(func(conf dynamic.Configuration) { ctx := context.Background() tlsManager.UpdateConfigs(ctx, conf.TLS.Stores, conf.TLS.Options, conf.TLS.Certificates) + + gauge := metricsRegistry.TLSCertsNotAfterTimestampGauge() + for _, certificate := range tlsManager.GetCertificates() { + appendCertMetric(gauge, certificate) + } }) // Metrics @@ -432,6 +439,20 @@ func registerMetricClients(metricsConfig *types.Metrics) []metrics.Registry { return registries } +func appendCertMetric(gauge gokitmetrics.Gauge, certificate *x509.Certificate) { + sort.Strings(certificate.DNSNames) + + labels := []string{ + "cn", certificate.Subject.CommonName, + "serial", certificate.SerialNumber.String(), + "sans", strings.Join(certificate.DNSNames, ","), + } + + notAfter := float64(certificate.NotAfter.Unix()) + + gauge.With(labels...).Set(notAfter) +} + func setupAccessLog(conf *types.AccessLog) *accesslog.Handler { if conf == nil { return nil diff --git a/cmd/traefik/traefik_test.go b/cmd/traefik/traefik_test.go new file mode 100644 index 000000000..26bc8643c --- /dev/null +++ b/cmd/traefik/traefik_test.go @@ -0,0 +1,116 @@ +package main + +import ( + "crypto/x509" + "encoding/pem" + "strings" + "testing" + + "github.com/go-kit/kit/metrics" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// FooCert is a PEM-encoded TLS cert. +// generated from src/crypto/tls: +// go run generate_cert.go --rsa-bits 1024 --host foo.org,foo.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h +const fooCert = `-----BEGIN CERTIFICATE----- +MIICHzCCAYigAwIBAgIQXQFLeYRwc5X21t457t2xADANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw +MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQDCjn67GSs/khuGC4GNN+tVo1S+/eSHwr/hWzhfMqO7nYiXkFzmxi+u14CU +Pda6WOeps7T2/oQEFMxKKg7zYOqkLSbjbE0ZfosopaTvEsZm/AZHAAvoOrAsIJOn +SEiwy8h0tLA4z1SNR6rmIVQWyqBZEPAhBTQM1z7tFp48FakCFwIDAQABo3QwcjAO +BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUDHG3ASzeUezElup9zbPpBn/vjogwGwYDVR0RBBQwEoIH +Zm9vLm9yZ4IHZm9vLmNvbTANBgkqhkiG9w0BAQsFAAOBgQBT+VLMbB9u27tBX8Aw +ZrGY3rbNdBGhXVTksrjiF+6ZtDpD3iI56GH9zLxnqvXkgn3u0+Ard5TqF/xmdwVw +NY0V/aWYfcL2G2auBCQrPvM03ozRnVUwVfP23eUzX2ORNHCYhd2ObQx4krrhs7cJ +SWxtKwFlstoXY3K2g9oRD9UxdQ== +-----END CERTIFICATE-----` + +// BarCert is a PEM-encoded TLS cert. +// generated from src/crypto/tls: +// go run generate_cert.go --rsa-bits 1024 --host bar.org,bar.com --ca --start-date "Jan 1 00:00:00 1970" --duration=10000h +const barCert = `-----BEGIN CERTIFICATE----- +MIICHTCCAYagAwIBAgIQcuIcNEXzBHPoxna5S6wG4jANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMB4XDTcwMDEwMTAwMDAwMFoXDTcxMDIyMTE2MDAw +MFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC +gYEAqtcrP+KA7D6NjyztGNIPMup9KiBMJ8QL+preog/YHR7SQLO3kGFhpS3WKMab +SzMypC3ZX1PZjBP5ZzwaV3PFbuwlCkPlyxR2lOWmullgI7mjY0TBeYLDIclIzGRp +mpSDDSpkW1ay2iJDSpXjlhmwZr84hrCU7BRTQJo91fdsRTsCAwEAAaN0MHIwDgYD +VR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFK8jnzFQvBAgWtfzOyXY4VSkwrTXMBsGA1UdEQQUMBKCB2Jh +ci5vcmeCB2Jhci5jb20wDQYJKoZIhvcNAQELBQADgYEAJz0ifAExisC/ZSRhWuHz +7qs1i6Nd4+YgEVR8dR71MChP+AMxucY1/ajVjb9xlLys3GPE90TWSdVppabEVjZY +Oq11nPKc50ItTt8dMku6t0JHBmzoGdkN0V4zJCBqdQJxhop8JpYJ0S9CW0eT93h3 +ipYQSsmIINGtMXJ8VkP/MlM= +-----END CERTIFICATE-----` + +type gaugeMock struct { + metrics map[string]float64 + labels string +} + +func (g gaugeMock) With(labelValues ...string) metrics.Gauge { + g.labels = strings.Join(labelValues, ",") + return g +} + +func (g gaugeMock) Set(value float64) { + g.metrics[g.labels] = value +} + +func (g gaugeMock) Add(delta float64) { + panic("implement me") +} + +func TestAppendCertMetric(t *testing.T) { + testCases := []struct { + desc string + certs []string + expected map[string]float64 + }{ + { + desc: "No certs", + certs: []string{}, + expected: map[string]float64{}, + }, + { + desc: "One cert", + certs: []string{fooCert}, + expected: map[string]float64{ + "cn,,serial,123624926713171615935660664614975025408,sans,foo.com,foo.org": 3.6e+09, + }, + }, + { + desc: "Two certs", + certs: []string{fooCert, barCert}, + expected: map[string]float64{ + "cn,,serial,123624926713171615935660664614975025408,sans,foo.com,foo.org": 3.6e+09, + "cn,,serial,152706022658490889223053211416725817058,sans,bar.com,bar.org": 3.6e+07, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + gauge := &gaugeMock{ + metrics: map[string]float64{}, + } + + for _, cert := range test.certs { + block, _ := pem.Decode([]byte(cert)) + parsedCert, err := x509.ParseCertificate(block.Bytes) + require.NoError(t, err) + + appendCertMetric(gauge, parsedCert) + } + + assert.Equal(t, test.expected, gauge.metrics) + }) + } +} diff --git a/docs/content/providers/docker.md b/docs/content/providers/docker.md index 1a7fff417..439e53a0e 100644 --- a/docs/content/providers/docker.md +++ b/docs/content/providers/docker.md @@ -98,8 +98,8 @@ See the list of labels in the dedicated [routing](../routing/providers/docker.md By default, Traefik watches for [container level labels](https://docs.docker.com/config/labels-custom-metadata/) on a standalone Docker Engine. When using Docker Compose, labels are specified by the directive -[`labels`](https://docs.docker.com/compose/compose-file/#labels) from the -["services" objects](https://docs.docker.com/compose/compose-file/#service-configuration-reference). +[`labels`](https://docs.docker.com/compose/compose-file/compose-file-v3/#labels) from the +["services" objects](https://docs.docker.com/compose/compose-file/compose-file-v3/#service-configuration-reference). !!! tip "Not Only Docker" Please note that any tool like Nomad, Terraform, Ansible, etc. @@ -186,7 +186,7 @@ set the [`swarmMode`](#swarmmode) directive to `true`. While in Swarm Mode, Traefik uses labels found on services, not on individual containers. Therefore, if you use a compose file with Swarm Mode, labels should be defined in the -[`deploy`](https://docs.docker.com/compose/compose-file/#labels-1) part of your service. +[`deploy`](https://docs.docker.com/compose/compose-file/compose-file-v3/#labels-1) part of your service. This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file)). diff --git a/docs/content/routing/providers/docker.md b/docs/content/routing/providers/docker.md index 2a58fa806..21f986a56 100644 --- a/docs/content/routing/providers/docker.md +++ b/docs/content/routing/providers/docker.md @@ -124,7 +124,7 @@ Attach labels to your containers and let Traefik do the rest! !!! important "Labels in Docker Swarm Mode" While in Swarm Mode, Traefik uses labels found on services, not on individual containers. Therefore, if you use a compose file with Swarm Mode, labels should be defined in the `deploy` part of your service. - This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/#labels-1)). + This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/compose-file-v3/#labels-1)). ## Routing Configuration diff --git a/pkg/metrics/datadog.go b/pkg/metrics/datadog.go index 605426689..e7f28d65d 100644 --- a/pkg/metrics/datadog.go +++ b/pkg/metrics/datadog.go @@ -20,18 +20,19 @@ var datadogTicker *time.Ticker // Metric names consistent with https://github.com/DataDog/integrations-extras/pull/64 const ( - ddMetricsServiceReqsName = "service.request.total" - ddMetricsServiceLatencyName = "service.request.duration" - ddRetriesTotalName = "service.retries.total" - ddConfigReloadsName = "config.reload.total" - ddConfigReloadsFailureTagName = "failure" - ddLastConfigReloadSuccessName = "config.reload.lastSuccessTimestamp" - ddLastConfigReloadFailureName = "config.reload.lastFailureTimestamp" - ddEntryPointReqsName = "entrypoint.request.total" - ddEntryPointReqDurationName = "entrypoint.request.duration" - ddEntryPointOpenConnsName = "entrypoint.connections.open" - ddOpenConnsName = "service.connections.open" - ddServerUpName = "service.server.up" + ddMetricsServiceReqsName = "service.request.total" + ddMetricsServiceLatencyName = "service.request.duration" + ddRetriesTotalName = "service.retries.total" + ddConfigReloadsName = "config.reload.total" + ddConfigReloadsFailureTagName = "failure" + ddLastConfigReloadSuccessName = "config.reload.lastSuccessTimestamp" + ddLastConfigReloadFailureName = "config.reload.lastFailureTimestamp" + ddEntryPointReqsName = "entrypoint.request.total" + ddEntryPointReqDurationName = "entrypoint.request.duration" + ddEntryPointOpenConnsName = "entrypoint.connections.open" + ddOpenConnsName = "service.connections.open" + ddServerUpName = "service.server.up" + ddTLSCertsNotAfterTimestampName = "tls.certs.notAfterTimestamp" ) // RegisterDatadog registers the metrics pusher if this didn't happen yet and creates a datadog Registry instance. @@ -41,10 +42,11 @@ func RegisterDatadog(ctx context.Context, config *types.Datadog) Registry { } registry := &standardRegistry{ - configReloadsCounter: datadogClient.NewCounter(ddConfigReloadsName, 1.0), - configReloadsFailureCounter: datadogClient.NewCounter(ddConfigReloadsName, 1.0).With(ddConfigReloadsFailureTagName, "true"), - lastConfigReloadSuccessGauge: datadogClient.NewGauge(ddLastConfigReloadSuccessName), - lastConfigReloadFailureGauge: datadogClient.NewGauge(ddLastConfigReloadFailureName), + configReloadsCounter: datadogClient.NewCounter(ddConfigReloadsName, 1.0), + configReloadsFailureCounter: datadogClient.NewCounter(ddConfigReloadsName, 1.0).With(ddConfigReloadsFailureTagName, "true"), + lastConfigReloadSuccessGauge: datadogClient.NewGauge(ddLastConfigReloadSuccessName), + lastConfigReloadFailureGauge: datadogClient.NewGauge(ddLastConfigReloadFailureName), + tlsCertsNotAfterTimestampGauge: datadogClient.NewGauge(ddTLSCertsNotAfterTimestampName), } if config.AddEntryPointsLabels { diff --git a/pkg/metrics/datadog_test.go b/pkg/metrics/datadog_test.go index e10626e75..af086f725 100644 --- a/pkg/metrics/datadog_test.go +++ b/pkg/metrics/datadog_test.go @@ -36,6 +36,7 @@ func TestDatadog(t *testing.T) { "traefik.entrypoint.request.duration:10000.000000|h|#entrypoint:test\n", "traefik.entrypoint.connections.open:1.000000|g|#entrypoint:test\n", "traefik.service.server.up:1.000000|g|#service:test,url:http://127.0.0.1,one:two\n", + "traefik.tls.certs.notAfterTimestamp:1.000000|g|#key:value\n", } udp.ShouldReceiveAll(t, expected, func() { @@ -50,5 +51,6 @@ func TestDatadog(t *testing.T) { datadogRegistry.EntryPointReqDurationHistogram().With("entrypoint", "test").Observe(10000) datadogRegistry.EntryPointOpenConnsGauge().With("entrypoint", "test").Set(1) datadogRegistry.ServiceServerUpGauge().With("service", "test", "url", "http://127.0.0.1", "one", "two").Set(1) + datadogRegistry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1) }) } diff --git a/pkg/metrics/influxdb.go b/pkg/metrics/influxdb.go index 6cf33433e..8c6b5b03f 100644 --- a/pkg/metrics/influxdb.go +++ b/pkg/metrics/influxdb.go @@ -26,18 +26,19 @@ type influxDBWriter struct { var influxDBTicker *time.Ticker const ( - influxDBMetricsServiceReqsName = "traefik.service.requests.total" - influxDBMetricsServiceLatencyName = "traefik.service.request.duration" - influxDBRetriesTotalName = "traefik.service.retries.total" - influxDBConfigReloadsName = "traefik.config.reload.total" - influxDBConfigReloadsFailureName = influxDBConfigReloadsName + ".failure" - influxDBLastConfigReloadSuccessName = "traefik.config.reload.lastSuccessTimestamp" - influxDBLastConfigReloadFailureName = "traefik.config.reload.lastFailureTimestamp" - influxDBEntryPointReqsName = "traefik.entrypoint.requests.total" - influxDBEntryPointReqDurationName = "traefik.entrypoint.request.duration" - influxDBEntryPointOpenConnsName = "traefik.entrypoint.connections.open" - influxDBOpenConnsName = "traefik.service.connections.open" - influxDBServerUpName = "traefik.service.server.up" + influxDBMetricsServiceReqsName = "traefik.service.requests.total" + influxDBMetricsServiceLatencyName = "traefik.service.request.duration" + influxDBRetriesTotalName = "traefik.service.retries.total" + influxDBConfigReloadsName = "traefik.config.reload.total" + influxDBConfigReloadsFailureName = influxDBConfigReloadsName + ".failure" + influxDBLastConfigReloadSuccessName = "traefik.config.reload.lastSuccessTimestamp" + influxDBLastConfigReloadFailureName = "traefik.config.reload.lastFailureTimestamp" + influxDBEntryPointReqsName = "traefik.entrypoint.requests.total" + influxDBEntryPointReqDurationName = "traefik.entrypoint.request.duration" + influxDBEntryPointOpenConnsName = "traefik.entrypoint.connections.open" + influxDBOpenConnsName = "traefik.service.connections.open" + influxDBServerUpName = "traefik.service.server.up" + influxDBTLSCertsNotAfterTimestampName = "traefik.tls.certs.notAfterTimestamp" ) const ( @@ -55,10 +56,11 @@ func RegisterInfluxDB(ctx context.Context, config *types.InfluxDB) Registry { } registry := &standardRegistry{ - configReloadsCounter: influxDBClient.NewCounter(influxDBConfigReloadsName), - configReloadsFailureCounter: influxDBClient.NewCounter(influxDBConfigReloadsFailureName), - lastConfigReloadSuccessGauge: influxDBClient.NewGauge(influxDBLastConfigReloadSuccessName), - lastConfigReloadFailureGauge: influxDBClient.NewGauge(influxDBLastConfigReloadFailureName), + configReloadsCounter: influxDBClient.NewCounter(influxDBConfigReloadsName), + configReloadsFailureCounter: influxDBClient.NewCounter(influxDBConfigReloadsFailureName), + lastConfigReloadSuccessGauge: influxDBClient.NewGauge(influxDBLastConfigReloadSuccessName), + lastConfigReloadFailureGauge: influxDBClient.NewGauge(influxDBLastConfigReloadFailureName), + tlsCertsNotAfterTimestampGauge: influxDBClient.NewGauge(influxDBTLSCertsNotAfterTimestampName), } if config.AddEntryPointsLabels { diff --git a/pkg/metrics/influxdb_test.go b/pkg/metrics/influxdb_test.go index 9e0bd1735..ba3db81bd 100644 --- a/pkg/metrics/influxdb_test.go +++ b/pkg/metrics/influxdb_test.go @@ -64,6 +64,16 @@ func TestInfluxDB(t *testing.T) { }) assertMessage(t, msgEntrypoint, expectedEntrypoint) + + expectedTLS := []string{ + `(traefik\.tls\.certs\.notAfterTimestamp,key=value value=1) [\d]{19}`, + } + + msgTLS := udp.ReceiveString(t, func() { + influxDBRegistry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1) + }) + + assertMessage(t, msgTLS, expectedTLS) } func TestInfluxDBHTTP(t *testing.T) { @@ -121,6 +131,15 @@ func TestInfluxDBHTTP(t *testing.T) { msgEntrypoint := <-c assertMessage(t, *msgEntrypoint, expectedEntrypoint) + + expectedTLS := []string{ + `(traefik\.tls\.certs\.notAfterTimestamp,key=value value=1) [\d]{19}`, + } + + influxDBRegistry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1) + msgTLS := <-c + + assertMessage(t, *msgTLS, expectedTLS) } func assertMessage(t *testing.T, msg string, patterns []string) { diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index fe48f4555..a5c28571d 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -21,6 +21,9 @@ type Registry interface { LastConfigReloadSuccessGauge() metrics.Gauge LastConfigReloadFailureGauge() metrics.Gauge + // TLS + TLSCertsNotAfterTimestampGauge() metrics.Gauge + // entry point metrics EntryPointReqsCounter() metrics.Counter EntryPointReqsTLSCounter() metrics.Counter @@ -50,6 +53,7 @@ func NewMultiRegistry(registries []Registry) Registry { var configReloadsFailureCounter []metrics.Counter var lastConfigReloadSuccessGauge []metrics.Gauge var lastConfigReloadFailureGauge []metrics.Gauge + var tlsCertsNotAfterTimestampGauge []metrics.Gauge var entryPointReqsCounter []metrics.Counter var entryPointReqsTLSCounter []metrics.Counter var entryPointReqDurationHistogram []ScalableHistogram @@ -74,6 +78,9 @@ func NewMultiRegistry(registries []Registry) Registry { if r.LastConfigReloadFailureGauge() != nil { lastConfigReloadFailureGauge = append(lastConfigReloadFailureGauge, r.LastConfigReloadFailureGauge()) } + if r.TLSCertsNotAfterTimestampGauge() != nil { + tlsCertsNotAfterTimestampGauge = append(tlsCertsNotAfterTimestampGauge, r.TLSCertsNotAfterTimestampGauge()) + } if r.EntryPointReqsCounter() != nil { entryPointReqsCounter = append(entryPointReqsCounter, r.EntryPointReqsCounter()) } @@ -113,6 +120,7 @@ func NewMultiRegistry(registries []Registry) Registry { configReloadsFailureCounter: multi.NewCounter(configReloadsFailureCounter...), lastConfigReloadSuccessGauge: multi.NewGauge(lastConfigReloadSuccessGauge...), lastConfigReloadFailureGauge: multi.NewGauge(lastConfigReloadFailureGauge...), + tlsCertsNotAfterTimestampGauge: multi.NewGauge(tlsCertsNotAfterTimestampGauge...), entryPointReqsCounter: multi.NewCounter(entryPointReqsCounter...), entryPointReqsTLSCounter: multi.NewCounter(entryPointReqsTLSCounter...), entryPointReqDurationHistogram: NewMultiHistogram(entryPointReqDurationHistogram...), @@ -133,6 +141,7 @@ type standardRegistry struct { configReloadsFailureCounter metrics.Counter lastConfigReloadSuccessGauge metrics.Gauge lastConfigReloadFailureGauge metrics.Gauge + tlsCertsNotAfterTimestampGauge metrics.Gauge entryPointReqsCounter metrics.Counter entryPointReqsTLSCounter metrics.Counter entryPointReqDurationHistogram ScalableHistogram @@ -169,6 +178,10 @@ func (r *standardRegistry) LastConfigReloadFailureGauge() metrics.Gauge { return r.lastConfigReloadFailureGauge } +func (r *standardRegistry) TLSCertsNotAfterTimestampGauge() metrics.Gauge { + return r.tlsCertsNotAfterTimestampGauge +} + func (r *standardRegistry) EntryPointReqsCounter() metrics.Counter { return r.entryPointReqsCounter } diff --git a/pkg/metrics/prometheus.go b/pkg/metrics/prometheus.go index b627b50b4..b042ea850 100644 --- a/pkg/metrics/prometheus.go +++ b/pkg/metrics/prometheus.go @@ -29,6 +29,10 @@ const ( configLastReloadSuccessName = metricConfigPrefix + "last_reload_success" configLastReloadFailureName = metricConfigPrefix + "last_reload_failure" + // TLS. + metricsTLSPrefix = MetricNamePrefix + "tls_" + tlsCertsNotAfterTimestamp = metricsTLSPrefix + "certs_not_after" + // entry point. metricEntryPointPrefix = MetricNamePrefix + "entrypoint_" entryPointReqsTotalName = metricEntryPointPrefix + "requests_total" @@ -121,21 +125,27 @@ func initStandardRegistry(config *types.Prometheus) Registry { Name: configLastReloadFailureName, Help: "Last config reload failure", }, []string{}) + tlsCertsNotAfterTimesptamp := newGaugeFrom(promState.collectors, stdprometheus.GaugeOpts{ + Name: tlsCertsNotAfterTimestamp, + Help: "Certificate expiration timestamp", + }, []string{"cn", "serial", "sans"}) promState.describers = []func(chan<- *stdprometheus.Desc){ configReloads.cv.Describe, configReloadsFailures.cv.Describe, lastConfigReloadSuccess.gv.Describe, lastConfigReloadFailure.gv.Describe, + tlsCertsNotAfterTimesptamp.gv.Describe, } reg := &standardRegistry{ - epEnabled: config.AddEntryPointsLabels, - svcEnabled: config.AddServicesLabels, - configReloadsCounter: configReloads, - configReloadsFailureCounter: configReloadsFailures, - lastConfigReloadSuccessGauge: lastConfigReloadSuccess, - lastConfigReloadFailureGauge: lastConfigReloadFailure, + epEnabled: config.AddEntryPointsLabels, + svcEnabled: config.AddServicesLabels, + configReloadsCounter: configReloads, + configReloadsFailureCounter: configReloadsFailures, + lastConfigReloadSuccessGauge: lastConfigReloadSuccess, + lastConfigReloadFailureGauge: lastConfigReloadFailure, + tlsCertsNotAfterTimestampGauge: tlsCertsNotAfterTimesptamp, } if config.AddEntryPointsLabels { @@ -163,11 +173,13 @@ func initStandardRegistry(config *types.Prometheus) Registry { entryPointReqDurations.hv.Describe, entryPointOpenConns.gv.Describe, }...) + reg.entryPointReqsCounter = entryPointReqs reg.entryPointReqsTLSCounter = entryPointReqsTLS reg.entryPointReqDurationHistogram, _ = NewHistogramWithScale(entryPointReqDurations, time.Second) reg.entryPointOpenConnsGauge = entryPointOpenConns } + if config.AddServicesLabels { serviceReqs := newCounterFrom(promState.collectors, stdprometheus.CounterOpts{ Name: serviceReqsTotalName, diff --git a/pkg/metrics/prometheus_test.go b/pkg/metrics/prometheus_test.go index 87fc1b650..5d5a5e34d 100644 --- a/pkg/metrics/prometheus_test.go +++ b/pkg/metrics/prometheus_test.go @@ -116,6 +116,11 @@ func TestPrometheus(t *testing.T) { prometheusRegistry.LastConfigReloadSuccessGauge().Set(float64(time.Now().Unix())) prometheusRegistry.LastConfigReloadFailureGauge().Set(float64(time.Now().Unix())) + prometheusRegistry. + TLSCertsNotAfterTimestampGauge(). + With("cn", "value", "serial", "value", "sans", "value"). + Set(float64(time.Now().Unix())) + prometheusRegistry. EntryPointReqsCounter(). With("code", strconv.Itoa(http.StatusOK), "method", http.MethodGet, "protocol", "http", "entrypoint", "http"). @@ -175,6 +180,15 @@ func TestPrometheus(t *testing.T) { name: configLastReloadFailureName, assert: buildTimestampAssert(t, configLastReloadFailureName), }, + { + name: tlsCertsNotAfterTimestamp, + labels: map[string]string{ + "cn": "value", + "serial": "value", + "sans": "value", + }, + assert: buildTimestampAssert(t, tlsCertsNotAfterTimestamp), + }, { name: entryPointReqsTotalName, labels: map[string]string{ diff --git a/pkg/metrics/statsd.go b/pkg/metrics/statsd.go index 552741f36..3ef6a72ac 100644 --- a/pkg/metrics/statsd.go +++ b/pkg/metrics/statsd.go @@ -17,18 +17,19 @@ var ( ) const ( - statsdMetricsServiceReqsName = "service.request.total" - statsdMetricsServiceLatencyName = "service.request.duration" - statsdRetriesTotalName = "service.retries.total" - statsdConfigReloadsName = "config.reload.total" - statsdConfigReloadsFailureName = statsdConfigReloadsName + ".failure" - statsdLastConfigReloadSuccessName = "config.reload.lastSuccessTimestamp" - statsdLastConfigReloadFailureName = "config.reload.lastFailureTimestamp" - statsdEntryPointReqsName = "entrypoint.request.total" - statsdEntryPointReqDurationName = "entrypoint.request.duration" - statsdEntryPointOpenConnsName = "entrypoint.connections.open" - statsdOpenConnsName = "service.connections.open" - statsdServerUpName = "service.server.up" + statsdMetricsServiceReqsName = "service.request.total" + statsdMetricsServiceLatencyName = "service.request.duration" + statsdRetriesTotalName = "service.retries.total" + statsdConfigReloadsName = "config.reload.total" + statsdConfigReloadsFailureName = statsdConfigReloadsName + ".failure" + statsdLastConfigReloadSuccessName = "config.reload.lastSuccessTimestamp" + statsdLastConfigReloadFailureName = "config.reload.lastFailureTimestamp" + statsdEntryPointReqsName = "entrypoint.request.total" + statsdEntryPointReqDurationName = "entrypoint.request.duration" + statsdEntryPointOpenConnsName = "entrypoint.connections.open" + statsdOpenConnsName = "service.connections.open" + statsdServerUpName = "service.server.up" + statsdTLSCertsNotAfterTimestampName = "tls.certs.notAfterTimestamp" ) // RegisterStatsd registers the metrics pusher if this didn't happen yet and creates a statsd Registry instance. @@ -48,10 +49,11 @@ func RegisterStatsd(ctx context.Context, config *types.Statsd) Registry { } registry := &standardRegistry{ - configReloadsCounter: statsdClient.NewCounter(statsdConfigReloadsName, 1.0), - configReloadsFailureCounter: statsdClient.NewCounter(statsdConfigReloadsFailureName, 1.0), - lastConfigReloadSuccessGauge: statsdClient.NewGauge(statsdLastConfigReloadSuccessName), - lastConfigReloadFailureGauge: statsdClient.NewGauge(statsdLastConfigReloadFailureName), + configReloadsCounter: statsdClient.NewCounter(statsdConfigReloadsName, 1.0), + configReloadsFailureCounter: statsdClient.NewCounter(statsdConfigReloadsFailureName, 1.0), + lastConfigReloadSuccessGauge: statsdClient.NewGauge(statsdLastConfigReloadSuccessName), + lastConfigReloadFailureGauge: statsdClient.NewGauge(statsdLastConfigReloadFailureName), + tlsCertsNotAfterTimestampGauge: statsdClient.NewGauge(statsdTLSCertsNotAfterTimestampName), } if config.AddEntryPointsLabels { diff --git a/pkg/metrics/statsd_test.go b/pkg/metrics/statsd_test.go index eb3af9e5d..3b8deca06 100644 --- a/pkg/metrics/statsd_test.go +++ b/pkg/metrics/statsd_test.go @@ -35,6 +35,7 @@ func TestStatsD(t *testing.T) { "traefik.entrypoint.request.duration:10000.000000|ms", "traefik.entrypoint.connections.open:1.000000|g\n", "traefik.service.server.up:1.000000|g\n", + "tls.certs.notAfterTimestamp:1.000000|g\n", } udp.ShouldReceiveAll(t, expected, func() { @@ -49,6 +50,7 @@ func TestStatsD(t *testing.T) { statsdRegistry.EntryPointReqDurationHistogram().With("entrypoint", "test").Observe(10000) statsdRegistry.EntryPointOpenConnsGauge().With("entrypoint", "test").Set(1) statsdRegistry.ServiceServerUpGauge().With("service:test", "url", "http://127.0.0.1").Set(1) + statsdRegistry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1) }) } @@ -75,6 +77,7 @@ func TestStatsDWithPrefix(t *testing.T) { "testPrefix.entrypoint.request.duration:10000.000000|ms", "testPrefix.entrypoint.connections.open:1.000000|g\n", "testPrefix.service.server.up:1.000000|g\n", + "tls.certs.notAfterTimestamp:1.000000|g\n", } udp.ShouldReceiveAll(t, expected, func() { @@ -89,5 +92,6 @@ func TestStatsDWithPrefix(t *testing.T) { statsdRegistry.EntryPointReqDurationHistogram().With("entrypoint", "test").Observe(10000) statsdRegistry.EntryPointOpenConnsGauge().With("entrypoint", "test").Set(1) statsdRegistry.ServiceServerUpGauge().With("service:test", "url", "http://127.0.0.1").Set(1) + statsdRegistry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1) }) } diff --git a/pkg/tls/certificate_store.go b/pkg/tls/certificate_store.go index 2db8afd1c..91bd00ef5 100644 --- a/pkg/tls/certificate_store.go +++ b/pkg/tls/certificate_store.go @@ -56,15 +56,16 @@ func (c CertificateStore) getDefaultCertificateDomains() []string { // GetAllDomains return a slice with all the certificate domain. func (c CertificateStore) GetAllDomains() []string { - allCerts := c.getDefaultCertificateDomains() + allDomains := c.getDefaultCertificateDomains() // Get dynamic certificates if c.DynamicCerts != nil && c.DynamicCerts.Get() != nil { - for domains := range c.DynamicCerts.Get().(map[string]*tls.Certificate) { - allCerts = append(allCerts, domains) + for domain := range c.DynamicCerts.Get().(map[string]*tls.Certificate) { + allDomains = append(allDomains, domain) } } - return allCerts + + return allDomains } // GetBestCertificate returns the best match certificate, and caches the response. diff --git a/pkg/tls/tlsmanager.go b/pkg/tls/tlsmanager.go index 1beebfc64..88fae8357 100644 --- a/pkg/tls/tlsmanager.go +++ b/pkg/tls/tlsmanager.go @@ -131,6 +131,27 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) { return tlsConfig, err } +// GetCertificates returns all stored certificates. +func (m *Manager) GetCertificates() []*x509.Certificate { + var certificates []*x509.Certificate + + // We iterate over all the certificates. + for _, store := range m.stores { + if store.DynamicCerts != nil && store.DynamicCerts.Get() != nil { + for _, cert := range store.DynamicCerts.Get().(map[string]*tls.Certificate) { + x509Cert, err := x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + continue + } + + certificates = append(certificates, x509Cert) + } + } + } + + return certificates +} + func (m *Manager) getStore(storeName string) *CertificateStore { _, ok := m.stores[storeName] if !ok { From 759d17547a11dd62cdf96189c61ffacedae1b0da Mon Sep 17 00:00:00 2001 From: Gian Ortiz Date: Wed, 6 Jan 2021 13:08:03 -0300 Subject: [PATCH 02/18] Use Datadog tracer environment variables to setup default config --- pkg/tracing/datadog/datadog.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/pkg/tracing/datadog/datadog.go b/pkg/tracing/datadog/datadog.go index a64f81b4a..12c86c018 100644 --- a/pkg/tracing/datadog/datadog.go +++ b/pkg/tracing/datadog/datadog.go @@ -2,6 +2,8 @@ package datadog import ( "io" + "net" + "os" "strings" "github.com/opentracing/opentracing-go" @@ -27,10 +29,17 @@ type Config struct { // SetDefaults sets the default values. func (c *Config) SetDefaults() { - c.LocalAgentHostPort = "localhost:8126" - c.GlobalTag = "" - c.Debug = false - c.PrioritySampling = false + host, ok := os.LookupEnv("DD_AGENT_HOST") + if !ok { + host = "localhost" + } + + port, ok := os.LookupEnv("DD_TRACE_AGENT_PORT") + if !ok { + port = "8126" + } + + c.LocalAgentHostPort = net.JoinHostPort(host, port) } // Setup sets up the tracer. From e5a01c7cc8d17323be71b61d0609f4ce90b626fa Mon Sep 17 00:00:00 2001 From: Julien Salleyron Date: Thu, 7 Jan 2021 14:48:04 +0100 Subject: [PATCH 03/18] Add HTTP3 support (experimental) Co-authored-by: Ludovic Fernandez --- .../reference/static-configuration/cli-ref.md | 6 + .../reference/static-configuration/env-ref.md | 6 + .../reference/static-configuration/file.toml | 2 + .../reference/static-configuration/file.yaml | 2 + docs/content/routing/entrypoints.md | 40 ++++++ go.mod | 4 +- go.sum | 114 ++++++++++++++++-- pkg/config/static/entrypoints.go | 1 + pkg/config/static/experimental.go | 1 + pkg/config/static/static_config.go | 6 + pkg/server/server_entrypoint_tcp.go | 39 ++++-- pkg/server/server_entrypoint_tcp_http3.go | 87 +++++++++++++ pkg/tcp/router.go | 10 ++ 13 files changed, 298 insertions(+), 20 deletions(-) create mode 100644 pkg/server/server_entrypoint_tcp_http3.go diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index c01e9cbb4..7d3e2f922 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -102,6 +102,9 @@ Entry points definition. (Default: ```false```) `--entrypoints..address`: Entry point address. +`--entrypoints..enablehttp3`: +Enable HTTP3. (Default: ```false```) + `--entrypoints..forwardedheaders.insecure`: Trust all forwarded headers. (Default: ```false```) @@ -174,6 +177,9 @@ plugin's GOPATH. `--experimental.devplugin.modulename`: plugin's module name. +`--experimental.http3`: +Enable HTTP3. (Default: ```false```) + `--experimental.kubernetesgateway`: Allow the Kubernetes gateway api provider usage. (Default: ```false```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index a0fa23028..da2dbf08a 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -102,6 +102,9 @@ Entry points definition. (Default: ```false```) `TRAEFIK_ENTRYPOINTS__ADDRESS`: Entry point address. +`TRAEFIK_ENTRYPOINTS__ENABLEHTTP3`: +Enable HTTP3. (Default: ```false```) + `TRAEFIK_ENTRYPOINTS__FORWARDEDHEADERS_INSECURE`: Trust all forwarded headers. (Default: ```false```) @@ -174,6 +177,9 @@ plugin's GOPATH. `TRAEFIK_EXPERIMENTAL_DEVPLUGIN_MODULENAME`: plugin's module name. +`TRAEFIK_EXPERIMENTAL_HTTP3`: +Enable HTTP3. (Default: ```false```) + `TRAEFIK_EXPERIMENTAL_KUBERNETESGATEWAY`: Allow the Kubernetes gateway api provider usage. (Default: ```false```) diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index ceacd0198..4d89cc216 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -14,6 +14,7 @@ [entryPoints] [entryPoints.EntryPoint0] address = "foobar" + enableHTTP3 = true [entryPoints.EntryPoint0.transport] [entryPoints.EntryPoint0.transport.lifeCycle] requestAcceptGraceTimeout = 42 @@ -390,4 +391,5 @@ [experimental.devPlugin] goPath = "foobar" moduleName = "foobar" + http3 = true kubernetesGateway = true diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index e2011bc1a..19a180143 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -32,6 +32,7 @@ entryPoints: trustedIPs: - foobar - foobar + enableHTTP3: true http: redirections: entryPoint: @@ -410,5 +411,6 @@ experimental: devPlugin: goPath: foobar moduleName: foobar + http3: true kubernetesGateway: true diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index 444b2f57a..0e71a962f 100644 --- a/docs/content/routing/entrypoints.md +++ b/docs/content/routing/entrypoints.md @@ -100,6 +100,7 @@ They can be defined by using a file (TOML or YAML) or CLI arguments. [entryPoints] [entryPoints.name] address = ":8888" # same as ":8888/tcp" + enableHTTP3 = true [entryPoints.name.transport] [entryPoints.name.transport.lifeCycle] requestAcceptGraceTimeout = 42 @@ -121,6 +122,7 @@ They can be defined by using a file (TOML or YAML) or CLI arguments. entryPoints: name: address: ":8888" # same as ":8888/tcp" + enableHTTP3: true transport: lifeCycle: requestAcceptGraceTimeout: 42 @@ -144,6 +146,7 @@ They can be defined by using a file (TOML or YAML) or CLI arguments. ```bash tab="CLI" ## Static configuration --entryPoints.name.address=:8888 # same as :8888/tcp + --entryPoints.name.http3=true --entryPoints.name.transport.lifeCycle.requestAcceptGraceTimeout=42 --entryPoints.name.transport.lifeCycle.graceTimeOut=42 --entryPoints.name.transport.respondingTimeouts.readTimeout=42 @@ -218,6 +221,43 @@ If both TCP and UDP are wanted for the same port, two entryPoints definitions ar Full details for how to specify `address` can be found in [net.Listen](https://golang.org/pkg/net/#Listen) (and [net.Dial](https://golang.org/pkg/net/#Dial)) of the doc for go. +### EnableHTTP3 + +`enableHTTP3` defines that you want to enable HTTP3 on this `address`. +You can only enable HTTP3 on a TCP entrypoint. +Enabling HTTP3 will automatically add the correct headers for the connection upgrade to HTTP3. + +??? info "HTTP3 uses UDP+TLS" + + As HTTP3 uses UDP, you can't have a TCP entrypoint with HTTP3 on the same port as a UDP entrypoint. + Since HTTP3 requires the use of TLS, only routers with TLS enabled will be usable with HTTP3. + +!!! warning "Enabling Experimental HTTP3" + + As the HTTP3 spec is still in draft, HTTP3 support in Traefik is an experimental feature and needs to be activated + in the experimental section of the static configuration. + + ```toml tab="File (TOML)" + [experimental] + http3 = true + + [entryPoints.name] + enableHTTP3 = true + ``` + + ```yaml tab="File (YAML)" + experimental: + http3: true + + entryPoints: + name: + enableHTTP3: true + ``` + + ```bash tab="CLI" + --experimental.http3=true --entrypoints.name.enablehttp3=true + ``` + ### Forwarded Headers You can configure Traefik to trust the forwarded headers information (`X-Forwarded-*`). diff --git a/go.mod b/go.mod index 00ae45762..78e3d531a 100644 --- a/go.mod +++ b/go.mod @@ -30,12 +30,11 @@ require ( github.com/eapache/channels v1.1.0 github.com/elazarl/go-bindata-assetfs v1.0.0 github.com/fatih/structs v1.1.0 - github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2 github.com/go-acme/lego/v4 v4.1.3 github.com/go-check/check v0.0.0-00010101000000-000000000000 github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea - github.com/golang/protobuf v1.4.2 + github.com/golang/protobuf v1.4.3 github.com/google/go-github/v28 v28.1.1 github.com/gorilla/mux v1.7.3 github.com/gorilla/websocket v1.4.2 @@ -47,6 +46,7 @@ require ( github.com/libkermit/compose v0.0.0-20171122111507-c04e39c026ad github.com/libkermit/docker v0.0.0-20171122101128-e6674d32b807 github.com/libkermit/docker-check v0.0.0-20171122104347-1113af38e591 + github.com/lucas-clemente/quic-go v0.19.3 github.com/magiconair/properties v1.8.1 // indirect github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f github.com/miekg/dns v1.1.31 diff --git a/go.sum b/go.sum index 1d09a784a..dda631568 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= @@ -23,20 +25,23 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= +dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= +dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/Azure/azure-sdk-for-go v32.4.0+incompatible h1:1JP8SKfroEakYiQU2ZyPDosh8w2Tg9UopKt88VyQPt4= github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg= github.com/Azure/go-autorest/autorest v0.5.0/go.mod h1:9HLKlQjVBH6U3oDfsXOeVc56THsLPw1L03yban4xThw= -github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest v0.9.6 h1:5YWtOnckcudzIw8lPPBcWOnmIFWMtHci1ZWAZulMSx0= github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E= github.com/Azure/go-autorest/autorest/adal v0.2.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E= -github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.2 h1:O1X4oexUxnZCaEUGsvMnr8ZGj8HI37tNezwY4npRqA0= github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= @@ -44,12 +49,10 @@ github.com/Azure/go-autorest/autorest/azure/auth v0.1.0 h1:YgO/vSnJEc76NLw2ecIXv github.com/Azure/go-autorest/autorest/azure/auth v0.1.0/go.mod h1:Gf7/i2FUpyb/sGBLIFxTBzrNzBo7aPXXE3ZVeDRwdpM= github.com/Azure/go-autorest/autorest/azure/cli v0.1.0 h1:YTtBrcb6mhA+PoSW8WxFDoIIyjp13XqJeX80ssQtri4= github.com/Azure/go-autorest/autorest/azure/cli v0.1.0/go.mod h1:Dk8CUAt/b/PzkfeRsWzVG9Yj3ps8mS8ECztu43rdU8U= -github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM= github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtOjOmKaui4EOpFI6zZc= github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= @@ -122,6 +125,7 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/aliyun/alibaba-cloud-sdk-go v1.61.458 h1:UdFGeD4Eg6gZFQ7tLWdguNLpBTevJwBa97S0YunGy1k= github.com/aliyun/alibaba-cloud-sdk-go v1.61.458/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -150,18 +154,21 @@ github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.0.2 h1:JIufpQLbh4DkbQoii76ItQIUFzevQSqOLZca4eamEDs= github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= -github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -207,11 +214,11 @@ github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= @@ -298,6 +305,7 @@ github.com/felixge/httpsnoop v1.0.0 h1:gh8fMGz0rlOv/1WmRZm7OgncIOTsAj21iNJot48om github.com/felixge/httpsnoop v1.0.0/go.mod h1:3+D9sFq0ahK/JeJPhCBUV1xlf4/eIYrUQaxulT0VzX8= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -308,6 +316,7 @@ github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2/go.mod h1:GLy github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-acme/lego/v4 v4.1.3 h1:D8nnzrijQFUAqdNPwnbvm6tJ3AJAzQAlnROeecUNG/4= @@ -400,14 +409,18 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -420,8 +433,9 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -433,6 +447,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= @@ -450,6 +466,9 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -480,6 +499,7 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= @@ -552,6 +572,7 @@ github.com/instana/go-sensor v1.5.1 h1:GLxYsYiDWD15RSXDHS70VvTVU/CbwUimWrK6/e4eB github.com/instana/go-sensor v1.5.1/go.mod h1:5dEieTqu59XZr2/X53xF2Px4v83aSRRZa/47VbxAVa4= github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03 h1:FUwcHNlEqkqLjLBdCp5PRlCFijNjvcYANOZXzCfXwCM= github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= @@ -577,13 +598,13 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b h1:DzHy0GlWeF0KAglaTMY7Q+khIFoG8toHP+wLFBVBQJc= github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -605,6 +626,9 @@ github.com/liquidweb/liquidweb-go v1.6.1 h1:O51RbJo3ZEWFkZFfP32zIF6MCoZzwuuybuXs github.com/liquidweb/liquidweb-go v1.6.1/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ= github.com/looplab/fsm v0.1.0 h1:Qte7Zdn/5hBNbXzP7yxVU4OIFHWXBovyTT2LaBTyC20= github.com/looplab/fsm v0.1.0/go.mod h1:m2VaOfDHxqXBBMgc26m6yUOwkFn8H2AlJDE+jd/uafI= +github.com/lucas-clemente/quic-go v0.19.3 h1:eCDQqvGBB+kCTkA0XrAFtNe81FMa0/fn4QSoeAbmiF4= +github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= @@ -620,6 +644,12 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs= +github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= +github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc= +github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= +github.com/marten-seemann/qtls-go1-15 v0.1.1 h1:LIH6K34bPVttyXnUWixk0bzH6/N07VxbSabxn5A5gZQ= +github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -630,6 +660,7 @@ github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo= github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= @@ -668,7 +699,6 @@ github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAA github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2 h1:i2Ly0B+1+rzNZHHWtD4ZwKi+OU5l+uQo1iDHZ2PmiIc= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= @@ -676,6 +706,8 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/nrdcg/auroradns v1.0.1 h1:m/kBq83Xvy3cU261MOknd8BdnOk12q4lAWM+kOdsC2Y= github.com/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI= github.com/nrdcg/desec v0.5.0 h1:foL7hqivYOMlv0qDhHXJtuuEXkqf0wW9EQMqyrt228g= @@ -699,8 +731,9 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -729,6 +762,7 @@ github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsq github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI= @@ -766,6 +800,7 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= @@ -781,6 +816,7 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -789,6 +825,7 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -822,10 +859,31 @@ github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= +github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= +github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= +github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= +github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= +github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= +github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= +github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -838,6 +896,8 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -871,6 +931,7 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 h1:XGopsea1Dw7ecQ8JscCNQXDGYAKDiWjDeXnpN/+BY9g= github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tinylib/msgp v1.0.2 h1:DfdQrzQa7Yh2es9SuLkixqxuXS2SxsdYn0KbdrOGWD8= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= @@ -903,6 +964,8 @@ github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/vdemeester/shakers v0.1.0 h1:K+n9sSyUCg2ywmZkv+3c7vsYZfivcfKhMh8kRxCrONM= github.com/vdemeester/shakers v0.1.0/go.mod h1:IZ1HHynUOQt32iQ3rvAeVddXLd19h/6LWiKsh9RZtAQ= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= +github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vulcand/oxy v1.1.0 h1:DbBijGo1+6cFqR9jarkMxasdj0lgWwrrFtue6ijek4Q= github.com/vulcand/oxy v1.1.0/go.mod h1:ADiMYHi8gkGl2987yQIzDRoXZilANF4WtKaQ92OppKY= github.com/vulcand/predicate v1.1.0 h1:Gq/uWopa4rx/tnZu2opOSBqHK63Yqlou/SzrbwdJiNg= @@ -941,6 +1004,7 @@ go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSF go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -962,12 +1026,16 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= @@ -980,6 +1048,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= @@ -996,6 +1065,7 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1022,6 +1092,8 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1029,6 +1101,7 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1057,11 +1130,14 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1077,6 +1153,7 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1085,6 +1162,7 @@ golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1139,6 +1217,7 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1191,6 +1270,9 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -1205,12 +1287,17 @@ google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1231,6 +1318,8 @@ google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1311,6 +1400,7 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclp gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1387,3 +1477,5 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go index f55bfc3dc..aabed680f 100644 --- a/pkg/config/static/entrypoints.go +++ b/pkg/config/static/entrypoints.go @@ -15,6 +15,7 @@ type EntryPoint struct { ProxyProtocol *ProxyProtocol `description:"Proxy-Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` ForwardedHeaders *ForwardedHeaders `description:"Trust client forwarding headers." json:"forwardedHeaders,omitempty" toml:"forwardedHeaders,omitempty" yaml:"forwardedHeaders,omitempty" export:"true"` HTTP HTTPConfig `description:"HTTP configuration." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"` + EnableHTTP3 bool `description:"Enable HTTP3." json:"enableHTTP3,omitempty" toml:"enableHTTP3,omitempty" yaml:"enableHTTP3,omitempty" export:"true"` } // GetAddress strips any potential protocol part of the address field of the diff --git a/pkg/config/static/experimental.go b/pkg/config/static/experimental.go index fbb91fa9f..e6be2525c 100644 --- a/pkg/config/static/experimental.go +++ b/pkg/config/static/experimental.go @@ -7,4 +7,5 @@ type Experimental struct { Plugins map[string]plugins.Descriptor `description:"Plugins configuration." json:"plugins,omitempty" toml:"plugins,omitempty" yaml:"plugins,omitempty" export:"true"` DevPlugin *plugins.DevPlugin `description:"Dev plugin configuration." json:"devPlugin,omitempty" toml:"devPlugin,omitempty" yaml:"devPlugin,omitempty" export:"true"` KubernetesGateway bool `description:"Allow the Kubernetes gateway api provider usage." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true"` + HTTP3 bool `description:"Enable HTTP3." json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty" export:"true"` } diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index 048b87ec7..0c4e7d123 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -236,6 +236,12 @@ func (c *Configuration) SetEffectiveConfiguration() { c.Providers.KubernetesGateway = nil } + if c.Experimental == nil || !c.Experimental.HTTP3 { + for _, ep := range c.EntryPoints { + ep.EnableHTTP3 = false + } + } + // Configure Gateway API provider if c.Providers.KubernetesGateway != nil { log.WithoutContext().Debugf("Experimental Kubernetes Gateway provider has been activated") diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index 1f001c9c8..34729901e 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -125,6 +125,8 @@ type TCPEntryPoint struct { tracker *connectionTracker httpServer *httpServer httpsServer *httpServer + + http3Server *http3server } // NewTCPEntryPoint creates a new TCPEntryPoint. @@ -136,24 +138,29 @@ func NewTCPEntryPoint(ctx context.Context, configuration *static.EntryPoint) (*T return nil, fmt.Errorf("error preparing server: %w", err) } - router := &tcp.Router{} + rt := &tcp.Router{} httpServer, err := createHTTPServer(ctx, listener, configuration, true) if err != nil { return nil, fmt.Errorf("error preparing httpServer: %w", err) } - router.HTTPForwarder(httpServer.Forwarder) + rt.HTTPForwarder(httpServer.Forwarder) httpsServer, err := createHTTPServer(ctx, listener, configuration, false) if err != nil { return nil, fmt.Errorf("error preparing httpsServer: %w", err) } - router.HTTPSForwarder(httpsServer.Forwarder) + h3server, err := newHTTP3Server(ctx, configuration, httpsServer) + if err != nil { + return nil, err + } + + rt.HTTPSForwarder(httpsServer.Forwarder) tcpSwitcher := &tcp.HandlerSwitcher{} - tcpSwitcher.Switch(router) + tcpSwitcher.Switch(rt) return &TCPEntryPoint{ listener: listener, @@ -162,6 +169,7 @@ func NewTCPEntryPoint(ctx context.Context, configuration *static.EntryPoint) (*T tracker: tracker, httpServer: httpServer, httpsServer: httpsServer, + http3Server: h3server, }, nil } @@ -170,6 +178,10 @@ func (e *TCPEntryPoint) Start(ctx context.Context) { logger := log.FromContext(ctx) logger.Debugf("Start TCP Server") + if e.http3Server != nil { + go func() { _ = e.http3Server.Start() }() + } + for { conn, err := e.listener.Accept() if err != nil { @@ -230,7 +242,7 @@ func (e *TCPEntryPoint) Shutdown(ctx context.Context) { var wg sync.WaitGroup - shutdownServer := func(server stoppableServer) { + shutdownServer := func(server stoppable) { defer wg.Done() err := server.Shutdown(ctx) if err == nil { @@ -257,6 +269,11 @@ func (e *TCPEntryPoint) Shutdown(ctx context.Context) { if e.httpsServer.Server != nil { wg.Add(1) go shutdownServer(e.httpsServer.Server) + + if e.http3Server != nil { + wg.Add(1) + go shutdownServer(e.http3Server) + } } if e.tracker != nil { @@ -299,6 +316,10 @@ func (e *TCPEntryPoint) SwitchRouter(rt *tcp.Router) { e.httpsServer.Switcher.UpdateHandler(httpsHandler) e.switcher.Switch(rt) + + if e.http3Server != nil { + e.http3Server.Switch(rt) + } } // writeCloserWrapper wraps together a connection, and the concrete underlying @@ -463,9 +484,13 @@ func (c *connectionTracker) Close() { } } -type stoppableServer interface { +type stoppable interface { Shutdown(context.Context) error Close() error +} + +type stoppableServer interface { + stoppable Serve(listener net.Listener) error } @@ -504,7 +529,7 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati listener := newHTTPForwarder(ln) go func() { err := serverHTTP.Serve(listener) - if err != nil { + if err != nil && !errors.Is(err, http.ErrServerClosed) { log.FromContext(ctx).Errorf("Error while starting server: %v", err) } }() diff --git a/pkg/server/server_entrypoint_tcp_http3.go b/pkg/server/server_entrypoint_tcp_http3.go new file mode 100644 index 000000000..2c4a28fab --- /dev/null +++ b/pkg/server/server_entrypoint_tcp_http3.go @@ -0,0 +1,87 @@ +package server + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net" + "net/http" + "sync" + "time" + + "github.com/lucas-clemente/quic-go/http3" + "github.com/traefik/traefik/v2/pkg/config/static" + "github.com/traefik/traefik/v2/pkg/log" + "github.com/traefik/traefik/v2/pkg/tcp" +) + +type http3server struct { + *http3.Server + + http3conn net.PacketConn + + lock sync.RWMutex + getter func(info *tls.ClientHelloInfo) (*tls.Config, error) +} + +func newHTTP3Server(ctx context.Context, configuration *static.EntryPoint, httpsServer *httpServer) (*http3server, error) { + if !configuration.EnableHTTP3 { + return nil, nil + } + + conn, err := net.ListenPacket("udp", configuration.GetAddress()) + if err != nil { + return nil, fmt.Errorf("error while starting http3 listener: %w", err) + } + + h3 := &http3server{ + http3conn: conn, + getter: func(info *tls.ClientHelloInfo) (*tls.Config, error) { + return nil, errors.New("no tls config") + }, + } + + h3.Server = &http3.Server{ + Server: &http.Server{ + Addr: configuration.GetAddress(), + Handler: httpsServer.Server.(*http.Server).Handler, + ErrorLog: httpServerLogger, + ReadTimeout: time.Duration(configuration.Transport.RespondingTimeouts.ReadTimeout), + WriteTimeout: time.Duration(configuration.Transport.RespondingTimeouts.WriteTimeout), + IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout), + TLSConfig: &tls.Config{GetConfigForClient: h3.getGetConfigForClient}, + }, + } + + previousHandler := httpsServer.Server.(*http.Server).Handler + + httpsServer.Server.(*http.Server).Handler = http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + err := h3.Server.SetQuicHeaders(rw.Header()) + if err != nil { + log.FromContext(ctx).Errorf("failed to set HTTP3 headers: %v", err) + } + + previousHandler.ServeHTTP(rw, req) + }) + + return h3, nil +} + +func (e *http3server) Start() error { + return e.Serve(e.http3conn) +} + +func (e *http3server) Switch(rt *tcp.Router) { + e.lock.Lock() + defer e.lock.Unlock() + + e.getter = rt.GetTLSGetClientInfo() +} + +func (e *http3server) getGetConfigForClient(info *tls.ClientHelloInfo) (*tls.Config, error) { + e.lock.RLock() + defer e.lock.RUnlock() + + return e.getter(info) +} diff --git a/pkg/tcp/router.go b/pkg/tcp/router.go index ea0f406e7..9bbfa29b9 100644 --- a/pkg/tcp/router.go +++ b/pkg/tcp/router.go @@ -27,6 +27,16 @@ type Router struct { hostHTTPTLSConfig map[string]*tls.Config // TLS configs keyed by SNI } +// GetTLSGetClientInfo is called after a ClientHello is received from a client. +func (r *Router) GetTLSGetClientInfo() func(info *tls.ClientHelloInfo) (*tls.Config, error) { + return func(info *tls.ClientHelloInfo) (*tls.Config, error) { + if tlsConfig, ok := r.hostHTTPTLSConfig[info.ServerName]; ok { + return tlsConfig, nil + } + return r.httpsTLSConfig, nil + } +} + // ServeTCP forwards the connection to the right TCP/HTTP handler. func (r *Router) ServeTCP(conn WriteCloser) { // FIXME -- Check if ProxyProtocol changes the first bytes of the request From fc7ec1790563ebb4a5fc1544033fb3762fd6d321 Mon Sep 17 00:00:00 2001 From: Linden Krouse Date: Thu, 7 Jan 2021 11:16:03 -0500 Subject: [PATCH 04/18] Feature: add udp timeout configuration --- .../reference/static-configuration/cli-ref.md | 3 ++ .../reference/static-configuration/env-ref.md | 3 ++ .../reference/static-configuration/file.toml | 2 ++ .../reference/static-configuration/file.yaml | 2 ++ docs/content/routing/entrypoints.md | 32 +++++++++++++++++++ docs/content/routing/routers/index.md | 5 +-- pkg/config/static/entrypoints.go | 14 ++++++++ pkg/config/static/static_config.go | 4 +++ pkg/server/server_entrypoint_udp.go | 3 +- pkg/server/server_entrypoint_udp_test.go | 7 ++-- pkg/udp/conn.go | 25 ++++++++------- pkg/udp/conn_test.go | 18 ++++++++--- pkg/udp/proxy_test.go | 2 +- 13 files changed, 98 insertions(+), 22 deletions(-) diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 7d3e2f922..dbf41e15a 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -171,6 +171,9 @@ ReadTimeout is the maximum duration for reading the entire request, including th `--entrypoints..transport.respondingtimeouts.writetimeout`: WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```) +`--entrypoints..udp.timeout`: +Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```) + `--experimental.devplugin.gopath`: plugin's GOPATH. diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index da2dbf08a..d1d9f6ff6 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -171,6 +171,9 @@ ReadTimeout is the maximum duration for reading the entire request, including th `TRAEFIK_ENTRYPOINTS__TRANSPORT_RESPONDINGTIMEOUTS_WRITETIMEOUT`: WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```) +`TRAEFIK_ENTRYPOINTS__UDP_TIMEOUT`: +Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```) + `TRAEFIK_EXPERIMENTAL_DEVPLUGIN_GOPATH`: plugin's GOPATH. diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 4d89cc216..edc01257d 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -29,6 +29,8 @@ [entryPoints.EntryPoint0.forwardedHeaders] insecure = true trustedIPs = ["foobar", "foobar"] + [entryPoints.EntryPoint0.udp] + timeout = 42 [entryPoints.EntryPoint0.http] middlewares = ["foobar", "foobar"] [entryPoints.EntryPoint0.http.redirections] diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index 19a180143..46b9f3c06 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -33,6 +33,8 @@ entryPoints: - foobar - foobar enableHTTP3: true + udp: + timeout: 42 http: redirections: entryPoint: diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index 0e71a962f..36db2cc9f 100644 --- a/docs/content/routing/entrypoints.md +++ b/docs/content/routing/entrypoints.md @@ -864,3 +864,35 @@ entryPoints: --entrypoints.websecure.address=:443 --entrypoints.websecure.http.tls.certResolver=leresolver ``` + +## UDP Options + +This whole section is dedicated to options, keyed by entry point, that will apply only to UDP routing. + +### Timeout + +_Optional, Default=3s_ + +Timeout defines how long to wait on an idle session before releasing the related resources. +The Timeout value must be greater than zero. + +```toml tab="File (TOML)" +[entryPoints.foo] + address = ":8000/udp" + + [entryPoints.foo.udp] + timeout = "10s" +``` + +```yaml tab="File (YAML)" +entryPoints: + foo: + address: ':8000/udp' + udp: + timeout: 10s +``` + +```bash tab="CLI" +entrypoints.foo.address=:8000/udp +entrypoints.foo.udp.timeout=10s +``` diff --git a/docs/content/routing/routers/index.md b/docs/content/routing/routers/index.md index d97e079a7..7216d2cb7 100644 --- a/docs/content/routing/routers/index.md +++ b/docs/content/routing/routers/index.md @@ -982,8 +982,9 @@ So UDP "routers" at this time are pretty much only load-balancers in one form or It basically means that some state is kept about an ongoing communication between a client and a backend, notably so that the proxy knows where to forward a response packet from a backend. As expected, a `timeout` is associated to each of these sessions, - so that they get cleaned out if they go through a period of inactivity longer than a given duration (that is hardcoded to 3 seconds for now). - Making this timeout configurable will be considered later if we get more usage feedback on this matter. + so that they get cleaned out if they go through a period of inactivity longer than a given duration. + Timeout can be configured using the `entryPoints.name.udp.timeout` option as described + under [entry points](../entrypoints/#udp-options). ### EntryPoints diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go index aabed680f..ddb5d6c38 100644 --- a/pkg/config/static/entrypoints.go +++ b/pkg/config/static/entrypoints.go @@ -5,6 +5,7 @@ import ( "math" "strings" + ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v2/pkg/types" ) @@ -16,6 +17,7 @@ type EntryPoint struct { ForwardedHeaders *ForwardedHeaders `description:"Trust client forwarding headers." json:"forwardedHeaders,omitempty" toml:"forwardedHeaders,omitempty" yaml:"forwardedHeaders,omitempty" export:"true"` HTTP HTTPConfig `description:"HTTP configuration." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"` EnableHTTP3 bool `description:"Enable HTTP3." json:"enableHTTP3,omitempty" toml:"enableHTTP3,omitempty" yaml:"enableHTTP3,omitempty" export:"true"` + UDP *UDPConfig `description:"UDP configuration." json:"udp,omitempty" toml:"udp,omitempty" yaml:"udp,omitempty"` } // GetAddress strips any potential protocol part of the address field of the @@ -46,6 +48,8 @@ func (ep *EntryPoint) SetDefaults() { ep.Transport = &EntryPointsTransport{} ep.Transport.SetDefaults() ep.ForwardedHeaders = &ForwardedHeaders{} + ep.UDP = &UDPConfig{} + ep.UDP.SetDefaults() } // HTTPConfig is the HTTP configuration of an entry point. @@ -110,3 +114,13 @@ func (t *EntryPointsTransport) SetDefaults() { t.RespondingTimeouts = &RespondingTimeouts{} t.RespondingTimeouts.SetDefaults() } + +// UDPConfig is the UDP configuration of an entry point. +type UDPConfig struct { + Timeout ptypes.Duration `description:"Timeout defines how long to wait on an idle session before releasing the related resources." json:"timeout,omitempty" toml:"timeout,omitempty" yaml:"timeout,omitempty"` +} + +// SetDefaults sets the default values. +func (u *UDPConfig) SetDefaults() { + u.Timeout = ptypes.Duration(DefaultUDPTimeout) +} diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index 0c4e7d123..a4623d78b 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -51,6 +51,10 @@ const ( // DefaultAcmeCAServer is the default ACME API endpoint. DefaultAcmeCAServer = "https://acme-v02.api.letsencrypt.org/directory" + + // DefaultUDPTimeout defines how long to wait by default on an idle session, + // before releasing all resources related to that session. + DefaultUDPTimeout = 3 * time.Second ) // Configuration is the static configuration. diff --git a/pkg/server/server_entrypoint_udp.go b/pkg/server/server_entrypoint_udp.go index 32a225096..163a719af 100644 --- a/pkg/server/server_entrypoint_udp.go +++ b/pkg/server/server_entrypoint_udp.go @@ -89,7 +89,8 @@ func NewUDPEntryPoint(cfg *static.EntryPoint) (*UDPEntryPoint, error) { if err != nil { return nil, err } - listener, err := udp.Listen("udp", addr) + + listener, err := udp.Listen("udp", addr, time.Duration(cfg.UDP.Timeout)) if err != nil { return nil, err } diff --git a/pkg/server/server_entrypoint_udp_test.go b/pkg/server/server_entrypoint_udp_test.go index 2aa5e70d8..f219cd98f 100644 --- a/pkg/server/server_entrypoint_udp_test.go +++ b/pkg/server/server_entrypoint_udp_test.go @@ -14,14 +14,17 @@ import ( ) func TestShutdownUDPConn(t *testing.T) { - entryPoint, err := NewUDPEntryPoint(&static.EntryPoint{ + ep := static.EntryPoint{ Address: ":0", Transport: &static.EntryPointsTransport{ LifeCycle: &static.LifeCycle{ GraceTimeOut: ptypes.Duration(5 * time.Second), }, }, - }) + } + ep.SetDefaults() + + entryPoint, err := NewUDPEntryPoint(&ep) require.NoError(t, err) go entryPoint.Start(context.Background()) diff --git a/pkg/udp/conn.go b/pkg/udp/conn.go index 9aef31f2a..d8b38f0e9 100644 --- a/pkg/udp/conn.go +++ b/pkg/udp/conn.go @@ -12,12 +12,6 @@ const receiveMTU = 8192 const closeRetryInterval = 500 * time.Millisecond -// connTimeout determines how long to wait on an idle session, -// before releasing all resources related to that session. -const connTimeout = 3 * time.Second - -var timeoutTicker = connTimeout / 10 - var errClosedListener = errors.New("udp: listener closed") // Listener augments a session-oriented Listener over a UDP PacketConn. @@ -31,10 +25,18 @@ type Listener struct { accepting bool acceptCh chan *Conn // no need for a Once, already indirectly guarded by accepting. + + // timeout defines how long to wait on an idle session, + // before releasing its related resources. + timeout time.Duration } // Listen creates a new listener. -func Listen(network string, laddr *net.UDPAddr) (*Listener, error) { +func Listen(network string, laddr *net.UDPAddr, timeout time.Duration) (*Listener, error) { + if timeout <= 0 { + return nil, errors.New("timeout should be greater than zero") + } + conn, err := net.ListenUDP(network, laddr) if err != nil { return nil, err @@ -45,6 +47,7 @@ func Listen(network string, laddr *net.UDPAddr) (*Listener, error) { acceptCh: make(chan *Conn), conns: make(map[string]*Conn), accepting: true, + timeout: timeout, } go l.readLoop() @@ -179,7 +182,7 @@ func (l *Listener) newConn(rAddr net.Addr) *Conn { readCh: make(chan []byte), sizeCh: make(chan int), doneCh: make(chan struct{}), - timeout: timeoutTicker, + timeout: l.timeout, } } @@ -206,7 +209,7 @@ type Conn struct { // that is to say it waits on readCh to receive the slice of bytes that the Read operation wants to read onto. // The Read operation receives the signal that the data has been written to the slice of bytes through the sizeCh. func (c *Conn) readLoop() { - ticker := time.NewTicker(c.timeout) + ticker := time.NewTicker(c.timeout / 10) defer ticker.Stop() for { @@ -216,7 +219,7 @@ func (c *Conn) readLoop() { c.msgs = append(c.msgs, msg) case <-ticker.C: c.muActivity.RLock() - deadline := c.lastActivity.Add(connTimeout) + deadline := c.lastActivity.Add(c.timeout) c.muActivity.RUnlock() if time.Now().After(deadline) { c.Close() @@ -236,7 +239,7 @@ func (c *Conn) readLoop() { c.msgs = append(c.msgs, msg) case <-ticker.C: c.muActivity.RLock() - deadline := c.lastActivity.Add(connTimeout) + deadline := c.lastActivity.Add(c.timeout) c.muActivity.RUnlock() if time.Now().After(deadline) { c.Close() diff --git a/pkg/udp/conn_test.go b/pkg/udp/conn_test.go index 3f0116e07..dce924bc9 100644 --- a/pkg/udp/conn_test.go +++ b/pkg/udp/conn_test.go @@ -15,7 +15,7 @@ func TestConsecutiveWrites(t *testing.T) { addr, err := net.ResolveUDPAddr("udp", ":0") require.NoError(t, err) - ln, err := Listen("udp", addr) + ln, err := Listen("udp", addr, 3*time.Second) require.NoError(t, err) defer func() { err := ln.Close() @@ -77,7 +77,7 @@ func TestListenNotBlocking(t *testing.T) { require.NoError(t, err) - ln, err := Listen("udp", addr) + ln, err := Listen("udp", addr, 3*time.Second) require.NoError(t, err) defer func() { err := ln.Close() @@ -162,6 +162,14 @@ func TestListenNotBlocking(t *testing.T) { } } +func TestListenWithZeroTimeout(t *testing.T) { + addr, err := net.ResolveUDPAddr("udp", ":0") + require.NoError(t, err) + + _, err = Listen("udp", addr, 0) + assert.Error(t, err) +} + func TestTimeoutWithRead(t *testing.T) { testTimeout(t, true) } @@ -176,7 +184,7 @@ func testTimeout(t *testing.T, withRead bool) { addr, err := net.ResolveUDPAddr("udp", ":0") require.NoError(t, err) - ln, err := Listen("udp", addr) + ln, err := Listen("udp", addr, 3*time.Second) require.NoError(t, err) defer func() { err := ln.Close() @@ -212,7 +220,7 @@ func testTimeout(t *testing.T, withRead bool) { assert.Equal(t, 10, len(ln.conns)) - time.Sleep(4 * time.Second) + time.Sleep(ln.timeout + time.Second) assert.Equal(t, 0, len(ln.conns)) } @@ -220,7 +228,7 @@ func TestShutdown(t *testing.T) { addr, err := net.ResolveUDPAddr("udp", ":0") require.NoError(t, err) - l, err := Listen("udp", addr) + l, err := Listen("udp", addr, 3*time.Second) require.NoError(t, err) go func() { diff --git a/pkg/udp/proxy_test.go b/pkg/udp/proxy_test.go index e2995846d..120cbc457 100644 --- a/pkg/udp/proxy_test.go +++ b/pkg/udp/proxy_test.go @@ -46,7 +46,7 @@ func newServer(t *testing.T, addr string, handler Handler) { addrL, err := net.ResolveUDPAddr("udp", addr) require.NoError(t, err) - listener, err := Listen("udp", addrL) + listener, err := Listen("udp", addrL, 3*time.Second) require.NoError(t, err) for { From bbee63fcf37c84940c248e5978f79e886698e4b1 Mon Sep 17 00:00:00 2001 From: Cirrith Date: Fri, 15 Jan 2021 06:54:04 -0800 Subject: [PATCH 05/18] Add named port support to Kubernetes IngressRoute CRDs --- .../routing/providers/kubernetes-crd.md | 65 ++++++------ pkg/provider/kubernetes/crd/kubernetes.go | 13 +-- .../kubernetes/crd/kubernetes_http.go | 9 +- pkg/provider/kubernetes/crd/kubernetes_tcp.go | 2 +- .../kubernetes/crd/kubernetes_test.go | 99 +++++++++++++++++-- pkg/provider/kubernetes/crd/kubernetes_udp.go | 5 +- .../crd/traefik/v1alpha1/ingressroute.go | 3 +- .../crd/traefik/v1alpha1/ingressroutetcp.go | 3 +- .../crd/traefik/v1alpha1/ingressrouteudp.go | 9 +- .../traefik/v1alpha1/zz_generated.deepcopy.go | 3 + 10 files changed, 154 insertions(+), 57 deletions(-) diff --git a/docs/content/routing/providers/kubernetes-crd.md b/docs/content/routing/providers/kubernetes-crd.md index 302a28020..0e4ec863d 100644 --- a/docs/content/routing/providers/kubernetes-crd.md +++ b/docs/content/routing/providers/kubernetes-crd.md @@ -145,7 +145,7 @@ The Kubernetes Ingress Controller, The Custom Resource Way. spec: entryPoints: - - fooudp + - udpep routes: - kind: Rule services: @@ -331,7 +331,7 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne name: foo namespace: default passHostHeader: true - port: 80 + port: 80 # [9] responseForwarding: flushInterval: 1ms scheme: https @@ -343,38 +343,39 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne sameSite: none strategy: RoundRobin weight: 10 - tls: # [9] - secretName: supersecret # [10] - options: # [11] - name: opt # [12] - namespace: default # [13] - certResolver: foo # [14] - domains: # [15] - - main: example.net # [16] - sans: # [17] + tls: # [10] + secretName: supersecret # [11] + options: # [12] + name: opt # [13] + namespace: default # [14] + certResolver: foo # [15] + domains: # [16] + - main: example.net # [17] + sans: # [18] - a.example.net - b.example.net ``` -| Ref | Attribute | Purpose | -|------|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [1] | `entryPoints` | List of [entry points](../routers/index.md#entrypoints) names | -| [2] | `routes` | List of routes | -| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule) corresponding to an underlying router. | -| [4] | `routes[n].priority` | [Disambiguate](../routers/index.md#priority) rules of the same length, for route matching | -| [5] | `routes[n].middlewares` | List of reference to [Middleware](#kind-middleware) | -| [6] | `middlewares[n].name` | Defines the [Middleware](#kind-middleware) name | -| [7] | `middlewares[n].namespace` | Defines the [Middleware](#kind-middleware) namespace | -| [8] | `routes[n].services` | List of any combination of [TraefikService](#kind-traefikservice) and reference to a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) (See below for `ExternalName Service` setup) | -| [9] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration | -| [10] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) | -| [11] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) | -| [12] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name | -| [13] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace | -| [14] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) | -| [15] | `tls.domains` | List of [domains](../routers/index.md#domains) | -| [16] | `domains[n].main` | Defines the main domain name | -| [17] | `domains[n].sans` | List of SANs (alternative domains) | +| Ref | Attribute | Purpose | +|------|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [1] | `entryPoints` | List of [entry points](../routers/index.md#entrypoints) names | +| [2] | `routes` | List of routes | +| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule) corresponding to an underlying router. | +| [4] | `routes[n].priority` | [Disambiguate](../routers/index.md#priority) rules of the same length, for route matching | +| [5] | `routes[n].middlewares` | List of reference to [Middleware](#kind-middleware) | +| [6] | `middlewares[n].name` | Defines the [Middleware](#kind-middleware) name | +| [7] | `middlewares[n].namespace` | Defines the [Middleware](#kind-middleware) namespace | +| [8] | `routes[n].services` | List of any combination of [TraefikService](#kind-traefikservice) and reference to a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) (See below for `ExternalName Service` setup) | +| [9] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. | +| [10] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration | +| [11] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) | +| [12] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) | +| [13] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name | +| [14] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace | +| [15] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) | +| [16] | `tls.domains` | List of [domains](../routers/index.md#domains) | +| [17] | `domains[n].main` | Defines the main domain name | +| [18] | `domains[n].sans` | List of SANs (alternative domains) | ??? example "Declaring an IngressRoute" @@ -1113,7 +1114,7 @@ Register the `IngressRouteTCP` [kind](../../reference/dynamic-configuration/kube | [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule_1) corresponding to an underlying router | | [4] | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions (See below for `ExternalName Service` setup) | | [5] | `services[n].name` | Defines the name of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) | -| [6] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) | +| [6] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. | | [7] | `services[n].weight` | Defines the weight to apply to the server load balancing | | [8] | `services[n].terminationDelay` | corresponds to the deadline that the proxy sets, after one of its connected peers indicates it has closed the writing capability of its connection, to close the reading capability as well, hence fully terminating the connection. It is a duration in milliseconds, defaulting to 100. A negative value means an infinite deadline (i.e. the reading capability is never closed). | | [9] | `proxyProtocol` | Defines the [PROXY protocol](../services/index.md#proxy-protocol) configuration | @@ -1323,7 +1324,7 @@ Register the `IngressRouteUDP` [kind](../../reference/dynamic-configuration/kube | [2] | `routes` | List of routes | | [3] | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions | | [4] | `services[n].name` | Defines the name of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) | -| [6] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) | +| [6] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. | | [7] | `services[n].weight` | Defines the weight to apply to the server load balancing | ??? example "Declaring an IngressRouteUDP" diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index ccda4a173..4b8417632 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -24,6 +24,7 @@ import ( "github.com/traefik/traefik/v2/pkg/tls" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/intstr" ) const ( @@ -323,18 +324,18 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) return conf } -func getServicePort(svc *corev1.Service, port int32) (*corev1.ServicePort, error) { +func getServicePort(svc *corev1.Service, port intstr.IntOrString) (*corev1.ServicePort, error) { if svc == nil { return nil, errors.New("service is not defined") } - if port == 0 { + if (port.Type == intstr.Int && port.IntVal == 0) || (port.Type == intstr.String && port.StrVal == "") { return nil, errors.New("ingressRoute service port not defined") } hasValidPort := false for _, p := range svc.Spec.Ports { - if p.Port == port { + if (port.Type == intstr.Int && port.IntVal == p.Port) || (port.Type == intstr.String && port.StrVal == p.Name) { return &p, nil } @@ -343,8 +344,8 @@ func getServicePort(svc *corev1.Service, port int32) (*corev1.ServicePort, error } } - if svc.Spec.Type != corev1.ServiceTypeExternalName { - return nil, fmt.Errorf("service port not found: %d", port) + if svc.Spec.Type != corev1.ServiceTypeExternalName || port.Type == intstr.String { + return nil, fmt.Errorf("service port not found: %s", &port) } if hasValidPort { @@ -352,7 +353,7 @@ func getServicePort(svc *corev1.Service, port int32) (*corev1.ServicePort, error Warning("The port %d from IngressRoute doesn't match with ports defined in the ExternalName service %s/%s.", port, svc.Namespace, svc.Name) } - return &corev1.ServicePort{Port: port}, nil + return &corev1.ServicePort{Port: port.IntVal}, nil } func (p *Provider) createErrorPageMiddleware(client Client, namespace string, errorPage *v1alpha1.ErrorPage) (*dynamic.ErrorPage, *dynamic.Service, error) { diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index 57a16cab5..79f848035 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -14,6 +14,7 @@ import ( "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" "github.com/traefik/traefik/v2/pkg/tls" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/intstr" ) const ( @@ -399,7 +400,7 @@ func (c configBuilder) nameAndService(ctx context.Context, parentNamespace strin return fullName, serversLB, nil case service.Kind == "TraefikService": - return fullServiceName(svcCtx, namespace, service, 0), nil, nil + return fullServiceName(svcCtx, namespace, service, intstr.FromInt(0)), nil, nil default: return "", nil, fmt.Errorf("unsupported service kind %s", service.Kind) } @@ -414,9 +415,9 @@ func splitSvcNameProvider(name string) (string, string) { return svc, pvd } -func fullServiceName(ctx context.Context, namespace string, service v1alpha1.LoadBalancerSpec, port int32) string { - if port != 0 { - return provider.Normalize(fmt.Sprintf("%s-%s-%d", namespace, service.Name, port)) +func fullServiceName(ctx context.Context, namespace string, service v1alpha1.LoadBalancerSpec, port intstr.IntOrString) string { + if (port.Type == intstr.Int && port.IntVal != 0) || (port.Type == intstr.String && port.StrVal != "") { + return provider.Normalize(fmt.Sprintf("%s-%s-%s", namespace, service.Name, &port)) } if !strings.Contains(service.Name, providerNamespaceSeparator) { diff --git a/pkg/provider/kubernetes/crd/kubernetes_tcp.go b/pkg/provider/kubernetes/crd/kubernetes_tcp.go index fe84dffbe..115522032 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_tcp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_tcp.go @@ -71,7 +71,7 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client break } - serviceKey := fmt.Sprintf("%s-%s-%d", serviceName, service.Name, service.Port) + serviceKey := fmt.Sprintf("%s-%s-%s", serviceName, service.Name, &service.Port) conf.Services[serviceKey] = balancerServerTCP srv := dynamic.TCPWRRService{Name: serviceKey} diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index 43e612dc8..2c2e84e6d 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -18,6 +18,7 @@ import ( "github.com/traefik/traefik/v2/pkg/tls" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" kubefake "k8s.io/client-go/kubernetes/fake" ) @@ -3738,7 +3739,7 @@ func TestGetServicePort(t *testing.T) { testCases := []struct { desc string svc *corev1.Service - port int32 + port intstr.IntOrString expected *corev1.ServicePort expectError bool }{ @@ -3757,7 +3758,7 @@ func TestGetServicePort(t *testing.T) { }, }, }, - port: 80, + port: intstr.FromInt(80), expected: &corev1.ServicePort{ Port: 80, }, @@ -3785,12 +3786,57 @@ func TestGetServicePort(t *testing.T) { }, expectError: true, }, + { + desc: "Matching named port", + svc: &corev1.Service{ + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "http", + Port: 80, + }, + }, + }, + }, + port: intstr.FromString("http"), + expected: &corev1.ServicePort{ + Name: "http", + Port: 80, + }, + }, + { + desc: "Matching named port (with external name)", + svc: &corev1.Service{ + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeExternalName, + Ports: []corev1.ServicePort{ + { + Name: "http", + Port: 80, + }, + }, + }, + }, + port: intstr.FromString("http"), + expected: &corev1.ServicePort{ + Name: "http", + Port: 80, + }, + }, { desc: "Mismatching, only port(Ingress) defined", svc: &corev1.Service{ Spec: corev1.ServiceSpec{}, }, - port: 80, + port: intstr.FromInt(80), + expectError: true, + }, + { + desc: "Mismatching, only named port(Ingress) defined", + svc: &corev1.Service{ + Spec: corev1.ServiceSpec{}, + }, + port: intstr.FromString("http"), expectError: true, }, { @@ -3800,11 +3846,21 @@ func TestGetServicePort(t *testing.T) { Type: corev1.ServiceTypeExternalName, }, }, - port: 80, + port: intstr.FromInt(80), expected: &corev1.ServicePort{ Port: 80, }, }, + { + desc: "Mismatching, only named port(Ingress) defined with external name", + svc: &corev1.Service{ + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeExternalName, + }, + }, + port: intstr.FromString("http"), + expectError: true, + }, { desc: "Mismatching, only Service port defined", svc: &corev1.Service{ @@ -3843,7 +3899,22 @@ func TestGetServicePort(t *testing.T) { }, }, }, - port: 443, + port: intstr.FromInt(443), + expectError: true, + }, + { + desc: "Two different named ports defined", + svc: &corev1.Service{ + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "foo", + Port: 80, + }, + }, + }, + }, + port: intstr.FromString("bar"), expectError: true, }, { @@ -3858,11 +3929,27 @@ func TestGetServicePort(t *testing.T) { }, }, }, - port: 443, + port: intstr.FromInt(443), expected: &corev1.ServicePort{ Port: 443, }, }, + { + desc: "Two different named ports defined (with external name)", + svc: &corev1.Service{ + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeExternalName, + Ports: []corev1.ServicePort{ + { + Name: "foo", + Port: 80, + }, + }, + }, + }, + port: intstr.FromString("bar"), + expectError: true, + }, } for _, test := range testCases { test := test diff --git a/pkg/provider/kubernetes/crd/kubernetes_udp.go b/pkg/provider/kubernetes/crd/kubernetes_udp.go index 0346084a2..35825f741 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_udp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_udp.go @@ -11,6 +11,7 @@ import ( "github.com/traefik/traefik/v2/pkg/log" "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/intstr" ) func (p *Provider) loadIngressRouteUDPConfiguration(ctx context.Context, client Client) *dynamic.UDPConfiguration { @@ -52,7 +53,7 @@ func (p *Provider) loadIngressRouteUDPConfiguration(ctx context.Context, client break } - serviceKey := fmt.Sprintf("%s-%s-%d", serviceName, service.Name, service.Port) + serviceKey := fmt.Sprintf("%s-%s-%s", serviceName, service.Name, &service.Port) conf.Services[serviceKey] = balancerServerUDP srv := dynamic.UDPWRRService{Name: serviceKey} @@ -114,7 +115,7 @@ func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([ var portSpec *corev1.ServicePort for _, p := range service.Spec.Ports { p := p - if svc.Port == p.Port { + if (svc.Port.Type == intstr.Int && svc.Port.IntVal == p.Port) || (svc.Port.Type == intstr.String && svc.Port.StrVal == p.Name) { portSpec = &p break } diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go index 670c295e6..b48bd747b 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go @@ -4,6 +4,7 @@ import ( "github.com/traefik/traefik/v2/pkg/config/dynamic" "github.com/traefik/traefik/v2/pkg/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" ) // IngressRouteSpec is a specification for a IngressRouteSpec resource. @@ -67,7 +68,7 @@ type LoadBalancerSpec struct { // Port and all the fields below are related to a servers load-balancer, // and therefore should only be specified when Name references a Kubernetes Service. - Port int32 `json:"port"` + Port intstr.IntOrString `json:"port"` Scheme string `json:"scheme,omitempty"` Strategy string `json:"strategy,omitempty"` PassHostHeader *bool `json:"passHostHeader,omitempty"` diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go index ab38c16ea..5c11256fd 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go @@ -4,6 +4,7 @@ import ( "github.com/traefik/traefik/v2/pkg/config/dynamic" "github.com/traefik/traefik/v2/pkg/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" ) // IngressRouteTCPSpec is a specification for a IngressRouteTCPSpec resource. @@ -56,7 +57,7 @@ type TLSStoreTCPRef struct { type ServiceTCP struct { Name string `json:"name"` Namespace string `json:"namespace"` - Port int32 `json:"port"` + Port intstr.IntOrString `json:"port"` Weight *int `json:"weight,omitempty"` TerminationDelay *int `json:"terminationDelay,omitempty"` ProxyProtocol *dynamic.ProxyProtocol `json:"proxyProtocol,omitempty"` diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressrouteudp.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressrouteudp.go index 509de5b3c..be6140e13 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressrouteudp.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressrouteudp.go @@ -2,6 +2,7 @@ package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" ) // IngressRouteUDPSpec is a specification for a IngressRouteUDPSpec resource. @@ -23,10 +24,10 @@ type TLSOptionUDPRef struct { // ServiceUDP defines an upstream to proxy traffic. type ServiceUDP struct { - Name string `json:"name"` - Namespace string `json:"namespace"` - Port int32 `json:"port"` - Weight *int `json:"weight,omitempty"` + Name string `json:"name"` + Namespace string `json:"namespace"` + Port intstr.IntOrString `json:"port"` + Weight *int `json:"weight,omitempty"` } // +genclient diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go index 987fffe09..2e6856095 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go @@ -507,6 +507,7 @@ func (in *LoadBalancerSpec) DeepCopyInto(out *LoadBalancerSpec) { *out = new(dynamic.Sticky) (*in).DeepCopyInto(*out) } + out.Port = in.Port if in.PassHostHeader != nil { in, out := &in.PassHostHeader, &out.PassHostHeader *out = new(bool) @@ -1001,6 +1002,7 @@ func (in *ServiceSpec) DeepCopy() *ServiceSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceTCP) DeepCopyInto(out *ServiceTCP) { *out = *in + out.Port = in.Port if in.Weight != nil { in, out := &in.Weight, &out.Weight *out = new(int) @@ -1032,6 +1034,7 @@ func (in *ServiceTCP) DeepCopy() *ServiceTCP { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceUDP) DeepCopyInto(out *ServiceUDP) { *out = *in + out.Port = in.Port if in.Weight != nil { in, out := &in.Weight, &out.Weight *out = new(int) From 41d22ef17e28a6cc0b01e6263668b590c910a1bc Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Tue, 19 Jan 2021 09:30:05 +0100 Subject: [PATCH 06/18] Improve kubernetes external name service support for UDP --- .../routing/providers/kubernetes-crd.md | 101 ++++++++++++- .../fixtures/k8s/05-ingressrouteudp.yml | 2 + integration/testdata/rawdata-crd.json | 27 +++- .../kubernetes/crd/fixtures/udp/services.yml | 37 +++++ .../crd/fixtures/udp/with_externalname.yml | 14 ++ .../udp/with_externalname_with_port.yml | 14 ++ .../udp/with_externalname_without_ports.yml | 13 ++ .../kubernetes/crd/kubernetes_test.go | 140 ++++++++++++------ pkg/provider/kubernetes/crd/kubernetes_udp.go | 19 +-- 9 files changed, 309 insertions(+), 58 deletions(-) create mode 100644 pkg/provider/kubernetes/crd/fixtures/udp/with_externalname.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_with_port.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_without_ports.yml diff --git a/docs/content/routing/providers/kubernetes-crd.md b/docs/content/routing/providers/kubernetes-crd.md index 0e4ec863d..5afac6097 100644 --- a/docs/content/routing/providers/kubernetes-crd.md +++ b/docs/content/routing/providers/kubernetes-crd.md @@ -1322,7 +1322,7 @@ Register the `IngressRouteUDP` [kind](../../reference/dynamic-configuration/kube |------|--------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [1] | `entryPoints` | List of [entrypoints](../routers/index.md#entrypoints_1) names | | [2] | `routes` | List of routes | -| [3] | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions | +| [3] | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions (See below for `ExternalName Service` setup) | | [4] | `services[n].name` | Defines the name of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) | | [6] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. | | [7] | `services[n].weight` | Defines the weight to apply to the server load balancing | @@ -1348,6 +1348,105 @@ Register the `IngressRouteUDP` [kind](../../reference/dynamic-configuration/kube weight: 10 ``` +!!! important "Using Kubernetes ExternalName Service" + + Traefik backends creation needs a port to be set, however Kubernetes [ExternalName Service](https://kubernetes.io/fr/docs/concepts/services-networking/service/#externalname) could be defined without any port. + Accordingly, Traefik supports defining a port in two ways: + + - only on `IngressRouteUDP` service + - on both sides, you'll be warned if the ports don't match, and the `IngressRouteUDP` service port is used + + Thus, in case of two sides port definition, Traefik expects a match between ports. + + ??? example "Examples" + + ```yaml tab="IngressRouteUDP" + --- + apiVersion: traefik.containo.us/v1alpha1 + kind: IngressRouteUDP + metadata: + name: test.route + namespace: default + + spec: + entryPoints: + - foo + + routes: + - services: + - name: external-svc + port: 80 + + --- + apiVersion: v1 + kind: Service + metadata: + name: external-svc + namespace: default + spec: + externalName: external.domain + type: ExternalName + ``` + + ```yaml tab="ExternalName Service" + --- + apiVersion: traefik.containo.us/v1alpha1 + kind: IngressRouteUDP + metadata: + name: test.route + namespace: default + + spec: + entryPoints: + - foo + + routes: + - services: + - name: external-svc + + --- + apiVersion: v1 + kind: Service + metadata: + name: external-svc + namespace: default + spec: + externalName: external.domain + type: ExternalName + ports: + - port: 80 + ``` + + ```yaml tab="Both sides" + --- + apiVersion: traefik.containo.us/v1alpha1 + kind: IngressRouteUDP + metadata: + name: test.route + namespace: default + + spec: + entryPoints: + - foo + + routes: + - services: + - name: external-svc + port: 80 + + --- + apiVersion: v1 + kind: Service + metadata: + name: external-svc + namespace: default + spec: + externalName: external.domain + type: ExternalName + ports: + - port: 80 + ``` + ### Kind: `TLSOption` `TLSOption` is the CRD implementation of a [Traefik "TLS Option"](../../https/tls.md#tls-options). diff --git a/integration/fixtures/k8s/05-ingressrouteudp.yml b/integration/fixtures/k8s/05-ingressrouteudp.yml index 535a9e4d4..358559c23 100644 --- a/integration/fixtures/k8s/05-ingressrouteudp.yml +++ b/integration/fixtures/k8s/05-ingressrouteudp.yml @@ -12,3 +12,5 @@ spec: - name: whoamiudp namespace: default port: 8090 + - name: externalname-svc + port: 9090 diff --git a/integration/testdata/rawdata-crd.json b/integration/testdata/rawdata-crd.json index ea43e5b5d..8b7eaad6e 100644 --- a/integration/testdata/rawdata-crd.json +++ b/integration/testdata/rawdata-crd.json @@ -317,7 +317,17 @@ } }, "udpServices": { - "default-test3.route-0@kubernetescrd": { + "default-test3.route-0-externalname-svc-9090@kubernetescrd": { + "loadBalancer": { + "servers": [ + { + "address": "domain.com:9090" + } + ] + }, + "status": "enabled" + }, + "default-test3.route-0-whoamiudp-8090@kubernetescrd": { "loadBalancer": { "servers": [ { @@ -328,6 +338,21 @@ } ] }, + "status": "enabled" + }, + "default-test3.route-0@kubernetescrd": { + "weighted": { + "services": [ + { + "name": "default-test3.route-0-whoamiudp-8090", + "weight": 1 + }, + { + "name": "default-test3.route-0-externalname-svc-9090", + "weight": 1 + } + ] + }, "status": "enabled", "usedBy": [ "default-test3.route-0@kubernetescrd" diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/services.yml b/pkg/provider/kubernetes/crd/fixtures/udp/services.yml index 887ddc385..daf6dd2ad 100644 --- a/pkg/provider/kubernetes/crd/fixtures/udp/services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/udp/services.yml @@ -146,6 +146,43 @@ spec: app: traefiklabs task: whoamiudp +--- +apiVersion: v1 +kind: Service +metadata: + name: external-svc + namespace: default +spec: + externalName: external.domain + type: ExternalName + +--- +apiVersion: v1 +kind: Service +metadata: + name: external.service.with.port + namespace: default +spec: + externalName: external.domain + type: ExternalName + ports: + - name: http + protocol: TCP + port: 80 + +--- +apiVersion: v1 +kind: Service +metadata: + name: external.service.without.port + namespace: default +spec: + externalName: external.domain + type: ExternalName + ports: + - name: http + protocol: TCP + --- kind: Endpoints apiVersion: v1 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname.yml new file mode 100644 index 000000000..14c3ab658 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname.yml @@ -0,0 +1,14 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteUDP +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - foo + + routes: + - services: + - name: external-svc + port: 8000 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_with_port.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_with_port.yml new file mode 100644 index 000000000..962178ed5 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_with_port.yml @@ -0,0 +1,14 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteUDP +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - foo + + routes: + - services: + - name: external.service.with.port + port: 80 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_without_ports.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_without_ports.yml new file mode 100644 index 000000000..6478e6aa4 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_without_ports.yml @@ -0,0 +1,13 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteUDP +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - foo + + routes: + - services: + - name: external-svc diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index 2c2e84e6d..fdffa31b3 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -82,11 +82,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -123,11 +121,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -137,11 +133,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -179,11 +173,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -423,11 +415,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -467,11 +457,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -532,11 +520,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -595,11 +581,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -657,11 +641,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -708,11 +690,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -759,11 +739,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -801,11 +779,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -844,11 +820,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, TerminationDelay: Int(500), @@ -893,11 +867,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -938,7 +910,6 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "external.domain:8000", - Port: "", }, }, }, @@ -976,7 +947,6 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "external.domain:80", - Port: "", }, }, }, @@ -3036,11 +3006,9 @@ func TestLoadIngressRoutes(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, ProxyProtocol: &dynamic.ProxyProtocol{Version: 2}, @@ -3393,11 +3361,9 @@ func TestLoadIngressRouteUDPs(t *testing.T) { Servers: []dynamic.UDPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -3438,11 +3404,9 @@ func TestLoadIngressRouteUDPs(t *testing.T) { Servers: []dynamic.UDPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -3452,11 +3416,9 @@ func TestLoadIngressRouteUDPs(t *testing.T) { Servers: []dynamic.UDPServer{ { Address: "10.10.0.3:8080", - Port: "", }, { Address: "10.10.0.4:8080", - Port: "", }, }, }, @@ -3622,6 +3584,104 @@ func TestLoadIngressRouteUDPs(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "Simple Ingress Route, with externalName service", + paths: []string{"udp/services.yml", "udp/with_externalname.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{ + "default-test.route-0": { + EntryPoints: []string{"foo"}, + Service: "default-test.route-0", + }, + }, + Services: map[string]*dynamic.UDPService{ + "default-test.route-0": { + LoadBalancer: &dynamic.UDPServersLoadBalancer{ + Servers: []dynamic.UDPServer{ + { + Address: "external.domain:8000", + }, + }, + }, + }, + }, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Ingress Route, externalName service with port", + paths: []string{"udp/services.yml", "udp/with_externalname_with_port.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{ + "default-test.route-0": { + EntryPoints: []string{"foo"}, + Service: "default-test.route-0", + }, + }, + Services: map[string]*dynamic.UDPService{ + "default-test.route-0": { + LoadBalancer: &dynamic.UDPServersLoadBalancer{ + Servers: []dynamic.UDPServer{ + { + Address: "external.domain:80", + }, + }, + }, + }, + }, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Ingress Route, externalName service without port", + paths: []string{"udp/services.yml", "udp/with_externalname_without_ports.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{ + "default-test.route-0": { + EntryPoints: []string{"foo"}, + Service: "default-test.route-0", + }, + }, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, { desc: "Ingress class does not match", paths: []string{"udp/services.yml", "udp/simple.yml"}, @@ -4359,11 +4419,9 @@ func TestCrossNamespace(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -4419,11 +4477,9 @@ func TestCrossNamespace(t *testing.T) { Servers: []dynamic.UDPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, diff --git a/pkg/provider/kubernetes/crd/kubernetes_udp.go b/pkg/provider/kubernetes/crd/kubernetes_udp.go index 35825f741..808b6bff0 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_udp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_udp.go @@ -11,7 +11,6 @@ import ( "github.com/traefik/traefik/v2/pkg/log" "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/intstr" ) func (p *Provider) loadIngressRouteUDPConfiguration(ctx context.Context, client Client) *dynamic.UDPConfiguration { @@ -112,23 +111,15 @@ func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([ return nil, errors.New("service not found") } - var portSpec *corev1.ServicePort - for _, p := range service.Spec.Ports { - p := p - if (svc.Port.Type == intstr.Int && svc.Port.IntVal == p.Port) || (svc.Port.Type == intstr.String && svc.Port.StrVal == p.Name) { - portSpec = &p - break - } - } - - if portSpec == nil { - return nil, errors.New("service port not found") + svcPort, err := getServicePort(service, svc.Port) + if err != nil { + return nil, err } var servers []dynamic.UDPServer if service.Spec.Type == corev1.ServiceTypeExternalName { servers = append(servers, dynamic.UDPServer{ - Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(portSpec.Port))), + Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))), }) } else { endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name) @@ -147,7 +138,7 @@ func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([ var port int32 for _, subset := range endpoints.Subsets { for _, p := range subset.Ports { - if portSpec.Name == p.Name { + if svcPort.Name == p.Name { port = p.Port break } From b05a5c818d33395d00a839f4d1548dd36341cf82 Mon Sep 17 00:00:00 2001 From: na4ma4 <25967676+na4ma4@users.noreply.github.com> Date: Tue, 19 Jan 2021 18:52:06 +1000 Subject: [PATCH 07/18] Add TLS version and cipher to the accessLog --- docs/content/observability/access-logs.md | 6 ++++-- pkg/middlewares/accesslog/logdata.go | 7 +++++++ pkg/middlewares/accesslog/logger.go | 19 +++++++++++++++++++ pkg/middlewares/accesslog/logger_test.go | 11 ++++++++--- pkg/middlewares/metrics/metrics.go | 16 ++++------------ pkg/tls/version.go | 12 ++++++++++++ 6 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 pkg/tls/version.go diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index 829be2a4f..51859c4e1 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -209,7 +209,7 @@ accessLog: | `RequestScheme` | The HTTP scheme requested `http` or `https`. | | `RequestLine` | `RequestMethod` + `RequestPath` + `RequestProtocol` | | `RequestContentSize` | The number of bytes in the request entity (a.k.a. body) sent by the client. | - | `OriginDuration` | The time taken (in nanoseconds) by the origin server ('upstream') to return its response. | + | `OriginDuration` | The time taken (in nanoseconds) by the origin server ('upstream') to return its response. | | `OriginContentSize` | The content length specified by the origin server, or 0 if unspecified. | | `OriginStatus` | The HTTP status code returned by the origin server. If the request was handled by this Traefik instance (e.g. with a redirect), then this value will be absent. | | `OriginStatusLine` | `OriginStatus` + Status code explanation | @@ -218,8 +218,10 @@ accessLog: | `DownstreamContentSize` | The number of bytes in the response entity returned to the client. This is in addition to the "Content-Length" header, which may be present in the origin response. | | `RequestCount` | The number of requests received since the Traefik instance started. | | `GzipRatio` | The response body compression ratio achieved. | - | `Overhead` | The processing time overhead (in nanoseconds) caused by Traefik. | + | `Overhead` | The processing time overhead (in nanoseconds) caused by Traefik. | | `RetryAttempts` | The amount of attempts the request was retried. | + | `TLSVersion` | The TLS version used by the connection (e.g. `1.2`) (if connection is TLS). | + | `TLSCipher` | The TLS cipher used by the connection (e.g. `TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`) (if connection is TLS) | ## Log Rotation diff --git a/pkg/middlewares/accesslog/logdata.go b/pkg/middlewares/accesslog/logdata.go index 37f415ed5..1db168446 100644 --- a/pkg/middlewares/accesslog/logdata.go +++ b/pkg/middlewares/accesslog/logdata.go @@ -70,6 +70,11 @@ const ( Overhead = "Overhead" // RetryAttempts is the map key used for the amount of attempts the request was retried. RetryAttempts = "RetryAttempts" + + // TLSVersion is the version of TLS used in the request. + TLSVersion = "TLSVersion" + // TLSCipher is the cipher used in the request. + TLSCipher = "TLSCipher" ) // These are written out in the default case when no config is provided to specify keys of interest. @@ -111,6 +116,8 @@ func init() { allCoreKeys[StartLocal] = struct{}{} allCoreKeys[Overhead] = struct{}{} allCoreKeys[RetryAttempts] = struct{}{} + allCoreKeys[TLSVersion] = struct{}{} + allCoreKeys[TLSCipher] = struct{}{} } // CoreLogData holds the fields computed from the request/response. diff --git a/pkg/middlewares/accesslog/logger.go b/pkg/middlewares/accesslog/logger.go index b0e273a1f..8698852dd 100644 --- a/pkg/middlewares/accesslog/logger.go +++ b/pkg/middlewares/accesslog/logger.go @@ -18,6 +18,7 @@ import ( "github.com/sirupsen/logrus" ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v2/pkg/log" + traefiktls "github.com/traefik/traefik/v2/pkg/tls" "github.com/traefik/traefik/v2/pkg/types" ) @@ -209,6 +210,8 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http core[RequestScheme] = "http" if req.TLS != nil { core[RequestScheme] = "https" + core[TLSVersion] = getRequestTLSVersion(req) + core[TLSCipher] = getRequestTLSCipher(req) } core[ClientAddr] = req.RemoteAddr @@ -382,3 +385,19 @@ var requestCounter uint64 // Request ID func nextRequestCount() uint64 { return atomic.AddUint64(&requestCounter, 1) } + +func getRequestTLSVersion(req *http.Request) string { + if version, ok := traefiktls.VersionsReversed[req.TLS.Version]; ok { + return version + } + + return "unknown" +} + +func getRequestTLSCipher(req *http.Request) string { + if cypher, ok := traefiktls.CipherSuitesReversed[req.TLS.CipherSuite]; ok { + return cypher + } + + return "unknown" +} diff --git a/pkg/middlewares/accesslog/logger_test.go b/pkg/middlewares/accesslog/logger_test.go index aaa145e5b..d3d680d8f 100644 --- a/pkg/middlewares/accesslog/logger_test.go +++ b/pkg/middlewares/accesslog/logger_test.go @@ -355,9 +355,11 @@ func TestLoggerJSON(t *testing.T) { Duration: assertFloat64NotZero(), Overhead: assertFloat64NotZero(), RetryAttempts: assertFloat64(float64(testRetryAttempts)), + TLSVersion: assertString("1.3"), + TLSCipher: assertString("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"), "time": assertNotEmpty(), - "StartLocal": assertNotEmpty(), - "StartUTC": assertNotEmpty(), + StartLocal: assertNotEmpty(), + StartUTC: assertNotEmpty(), }, }, { @@ -772,7 +774,10 @@ func doLoggingTLSOpt(t *testing.T, config *types.AccessLog, enableTLS bool) { }, } if enableTLS { - req.TLS = &tls.ConnectionState{} + req.TLS = &tls.ConnectionState{ + Version: tls.VersionTLS13, + CipherSuite: tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + } } logger.ServeHTTP(httptest.NewRecorder(), req, http.HandlerFunc(logWriterTestHandlerFunc)) diff --git a/pkg/middlewares/metrics/metrics.go b/pkg/middlewares/metrics/metrics.go index c824b5749..6d9bd2966 100644 --- a/pkg/middlewares/metrics/metrics.go +++ b/pkg/middlewares/metrics/metrics.go @@ -2,7 +2,6 @@ package metrics import ( "context" - "crypto/tls" "net/http" "strconv" "strings" @@ -148,18 +147,11 @@ func getMethod(r *http.Request) string { } func getRequestTLSVersion(req *http.Request) string { - switch req.TLS.Version { - case tls.VersionTLS10: - return "1.0" - case tls.VersionTLS11: - return "1.1" - case tls.VersionTLS12: - return "1.2" - case tls.VersionTLS13: - return "1.3" - default: - return "unknown" + if version, ok := traefiktls.VersionsReversed[req.TLS.Version]; ok { + return version } + + return "unknown" } func getRequestTLSCipher(req *http.Request) string { diff --git a/pkg/tls/version.go b/pkg/tls/version.go new file mode 100644 index 000000000..5ff14cff3 --- /dev/null +++ b/pkg/tls/version.go @@ -0,0 +1,12 @@ +package tls + +import "crypto/tls" + +// VersionsReversed Map of TLS versions from crypto/tls +// Available TLS versions defined at https://golang.org/pkg/crypto/tls/#pkg-constants +var VersionsReversed = map[uint16]string{ + tls.VersionTLS10: "1.0", + tls.VersionTLS11: "1.1", + tls.VersionTLS12: "1.2", + tls.VersionTLS13: "1.3", +} From 4b370930b5581dd91866f83bbc992cc2d4e337cb Mon Sep 17 00:00:00 2001 From: Romain Date: Wed, 20 Jan 2021 04:08:03 +0100 Subject: [PATCH 08/18] Mutualize TLS version and cipher code --- pkg/middlewares/accesslog/logger.go | 20 ++------------------ pkg/middlewares/metrics/metrics.go | 18 +----------------- pkg/tls/cipher.go | 10 ++++++++++ pkg/tls/version.go | 20 ++++++++++++++------ 4 files changed, 27 insertions(+), 41 deletions(-) diff --git a/pkg/middlewares/accesslog/logger.go b/pkg/middlewares/accesslog/logger.go index 8698852dd..b3657b67a 100644 --- a/pkg/middlewares/accesslog/logger.go +++ b/pkg/middlewares/accesslog/logger.go @@ -210,8 +210,8 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http core[RequestScheme] = "http" if req.TLS != nil { core[RequestScheme] = "https" - core[TLSVersion] = getRequestTLSVersion(req) - core[TLSCipher] = getRequestTLSCipher(req) + core[TLSVersion] = traefiktls.GetVersion(req.TLS) + core[TLSCipher] = traefiktls.GetCipherName(req.TLS) } core[ClientAddr] = req.RemoteAddr @@ -385,19 +385,3 @@ var requestCounter uint64 // Request ID func nextRequestCount() uint64 { return atomic.AddUint64(&requestCounter, 1) } - -func getRequestTLSVersion(req *http.Request) string { - if version, ok := traefiktls.VersionsReversed[req.TLS.Version]; ok { - return version - } - - return "unknown" -} - -func getRequestTLSCipher(req *http.Request) string { - if cypher, ok := traefiktls.CipherSuitesReversed[req.TLS.CipherSuite]; ok { - return cypher - } - - return "unknown" -} diff --git a/pkg/middlewares/metrics/metrics.go b/pkg/middlewares/metrics/metrics.go index 6d9bd2966..116afa850 100644 --- a/pkg/middlewares/metrics/metrics.go +++ b/pkg/middlewares/metrics/metrics.go @@ -89,7 +89,7 @@ func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) if req.TLS != nil { var tlsLabels []string tlsLabels = append(tlsLabels, m.baseLabels...) - tlsLabels = append(tlsLabels, "tls_version", getRequestTLSVersion(req), "tls_cipher", getRequestTLSCipher(req)) + tlsLabels = append(tlsLabels, "tls_version", traefiktls.GetVersion(req.TLS), "tls_cipher", traefiktls.GetCipherName(req.TLS)) m.reqsTLSCounter.With(tlsLabels...).Add(1) } @@ -146,22 +146,6 @@ func getMethod(r *http.Request) string { return r.Method } -func getRequestTLSVersion(req *http.Request) string { - if version, ok := traefiktls.VersionsReversed[req.TLS.Version]; ok { - return version - } - - return "unknown" -} - -func getRequestTLSCipher(req *http.Request) string { - if version, ok := traefiktls.CipherSuitesReversed[req.TLS.CipherSuite]; ok { - return version - } - - return "unknown" -} - type retryMetrics interface { ServiceRetriesCounter() gokitmetrics.Counter } diff --git a/pkg/tls/cipher.go b/pkg/tls/cipher.go index 07cbfd7d6..8925cd534 100644 --- a/pkg/tls/cipher.go +++ b/pkg/tls/cipher.go @@ -69,3 +69,13 @@ var ( tls.TLS_FALLBACK_SCSV: `TLS_FALLBACK_SCSV`, } ) + +// GetCipherName returns the Cipher suite name. +// Available CipherSuites defined at https://golang.org/pkg/crypto/tls/#pkg-constants +func GetCipherName(connState *tls.ConnectionState) string { + if cipher, ok := CipherSuitesReversed[connState.CipherSuite]; ok { + return cipher + } + + return "unknown" +} diff --git a/pkg/tls/version.go b/pkg/tls/version.go index 5ff14cff3..8e6b5cfe0 100644 --- a/pkg/tls/version.go +++ b/pkg/tls/version.go @@ -2,11 +2,19 @@ package tls import "crypto/tls" -// VersionsReversed Map of TLS versions from crypto/tls +// GetVersion returns the normalized TLS version. // Available TLS versions defined at https://golang.org/pkg/crypto/tls/#pkg-constants -var VersionsReversed = map[uint16]string{ - tls.VersionTLS10: "1.0", - tls.VersionTLS11: "1.1", - tls.VersionTLS12: "1.2", - tls.VersionTLS13: "1.3", +func GetVersion(connState *tls.ConnectionState) string { + switch connState.Version { + case tls.VersionTLS10: + return "1.0" + case tls.VersionTLS11: + return "1.1" + case tls.VersionTLS12: + return "1.2" + case tls.VersionTLS13: + return "1.3" + } + + return "unknown" } From 2bbb6fc427cbc8354f15405ffa5715738b5cfabf Mon Sep 17 00:00:00 2001 From: Sune Keller Date: Wed, 20 Jan 2021 15:10:04 +0100 Subject: [PATCH 09/18] Update sprig to v3.2.0 --- go.mod | 3 +-- go.sum | 12 ++++++++---- pkg/provider/configuration.go | 2 +- pkg/provider/file/file.go | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 78e3d531a..59f955485 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,7 @@ go 1.15 require ( github.com/BurntSushi/toml v0.3.1 github.com/ExpediaDotCom/haystack-client-go v0.0.0-20190315171017-e7edbdf53a61 - github.com/Masterminds/semver v1.4.2 // indirect - github.com/Masterminds/sprig v2.22.0+incompatible + github.com/Masterminds/sprig/v3 v3.2.0 github.com/Microsoft/hcsshim v0.8.7 // indirect github.com/NYTimes/gziphandler v1.1.1 github.com/Shopify/sarama v1.23.1 // indirect diff --git a/go.sum b/go.sum index dda631568..ca20b300b 100644 --- a/go.sum +++ b/go.sum @@ -79,14 +79,14 @@ github.com/ExpediaDotCom/haystack-client-go v0.0.0-20190315171017-e7edbdf53a61/g github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= -github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk= github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= -github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TNDYD9Y= github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA= +github.com/Masterminds/sprig/v3 v3.2.0 h1:P1ekkbuU73Ui/wS0nK1HOM37hh4xdfZo485UPf8rc+Y= +github.com/Masterminds/sprig/v3 v3.2.0/go.mod h1:tWhwTbUTndesPNeF0C900vKoq283u6zp4APT9vaF3SI= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/hcsshim v0.8.7 h1:ptnOoufxGSzauVTsdE+wMYnCWA301PdoN4xg5oRdZpg= @@ -565,6 +565,8 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= @@ -859,6 +861,8 @@ github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= diff --git a/pkg/provider/configuration.go b/pkg/provider/configuration.go index 972111f91..a708c72af 100644 --- a/pkg/provider/configuration.go +++ b/pkg/provider/configuration.go @@ -9,7 +9,7 @@ import ( "text/template" "unicode" - "github.com/Masterminds/sprig" + "github.com/Masterminds/sprig/v3" "github.com/traefik/traefik/v2/pkg/config/dynamic" "github.com/traefik/traefik/v2/pkg/log" ) diff --git a/pkg/provider/file/file.go b/pkg/provider/file/file.go index e7781d1b8..632d383c1 100644 --- a/pkg/provider/file/file.go +++ b/pkg/provider/file/file.go @@ -11,7 +11,7 @@ import ( "strings" "text/template" - "github.com/Masterminds/sprig" + "github.com/Masterminds/sprig/v3" "github.com/traefik/paerser/file" "github.com/traefik/traefik/v2/pkg/config/dynamic" "github.com/traefik/traefik/v2/pkg/log" From 7996a42f76505c08d36982fa9c3829ff85d145ac Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Doumenjou <925513+jbdoumenjou@users.noreply.github.com> Date: Tue, 2 Feb 2021 19:36:04 +0100 Subject: [PATCH 10/18] Allow crossprovider service reference Co-authored-by: Harold Ozouf --- .../kubernetes-gateway-resource.yml | 11 +++ .../routing/providers/kubernetes-gateway.md | 70 ++++++++------- .../fixtures/simple_cross_provider.yml | 52 +++++++++++ .../fixtures/simple_to_api_internal.yml | 49 +++++++++++ pkg/provider/kubernetes/gateway/kubernetes.go | 81 +++++++++++------ .../kubernetes/gateway/kubernetes_test.go | 86 +++++++++++++++++++ 6 files changed, 293 insertions(+), 56 deletions(-) create mode 100644 pkg/provider/kubernetes/gateway/fixtures/simple_cross_provider.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/simple_to_api_internal.yml diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml b/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml index 821a8fccc..c25130ac5 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml @@ -44,3 +44,14 @@ spec: - serviceName: whoami port: 80 weight: 1 + - matches: + - path: + type: Prefix + value: /foo + forwardTo: + - backendRef: + group: traefik.containo.us + kind: TraefikService + name: myservice@file + weight: 1 + port: 80 diff --git a/docs/content/routing/providers/kubernetes-gateway.md b/docs/content/routing/providers/kubernetes-gateway.md index 03d326760..0db255824 100644 --- a/docs/content/routing/providers/kubernetes-gateway.md +++ b/docs/content/routing/providers/kubernetes-gateway.md @@ -123,39 +123,49 @@ Kubernetes cluster before creating `HTTPRoute` objects. metadata: name: http-app-1 namespace: default - labels: # [1] + labels: # [1] app: foo spec: - hostnames: # [2] + hostnames: # [2] - "whoami" - rules: # [3] - - matches: # [4] - - path: # [5] - type: Exact # [6] - value: /bar # [7] - - headers: # [8] - type: Exact # [9] - values: # [10] + rules: # [3] + - matches: # [4] + - path: # [5] + type: Exact # [6] + value: /bar # [7] + - headers: # [8] + type: Exact # [9] + values: # [10] - foo: bar - forwardTo: # [11] - - serviceName: whoami # [12] - weight: 1 # [13] - port: 80 # [14] + forwardTo: # [11] + - serviceName: whoami # [12] + weight: 1 # [13] + port: 80 # [14] + - backendRef: # [15] + group: traefik.containo.us # [16] + kind: TraefikService # [17] + name: api@internal # [18] + port: 80 + weight: 1 ``` -| Ref | Attribute | Description | -|------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [1] | `labels` | Labels to match with the `Gateway` labelselector. | -| [2] | `hostnames` | A set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. | -| [3] | `rules` | A list of HTTP matchers, filters and actions. | -| [4] | `matches` | Conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. | -| [5] | `path` | An HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. | -| [6] | `type` | Type of match against the path Value (supported types: `Exact`, `Prefix`). | -| [7] | `value` | The value of the HTTP path to match against. | -| [8] | `headers` | Conditions to select a HTTP route by matching HTTP request headers. | -| [9] | `type` | Type of match for the HTTP request header match against the `values` (supported types: `Exact`). | -| [10] | `values` | A map of HTTP Headers to be matched. It MUST contain at least one entry. | -| [11] | `forwardTo` | The upstream target(s) where the request should be sent. | -| [12] | `serviceName` | The name of the referent service. | -| [13] | `weight` | The proportion of traffic forwarded to a targetRef, computed as weight/(sum of all weights in targetRefs). | -| [14] | `port` | The port of the referent service. | +| Ref | Attribute | Description | +|------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [1] | `labels` | Labels to match with the `Gateway` labelselector. | +| [2] | `hostnames` | A set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. | +| [3] | `rules` | A list of HTTP matchers, filters and actions. | +| [4] | `matches` | Conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. | +| [5] | `path` | An HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. | +| [6] | `type` | Type of match against the path Value (supported types: `Exact`, `Prefix`). | +| [7] | `value` | The value of the HTTP path to match against. | +| [8] | `headers` | Conditions to select a HTTP route by matching HTTP request headers. | +| [9] | `type` | Type of match for the HTTP request header match against the `values` (supported types: `Exact`). | +| [10] | `values` | A map of HTTP Headers to be matched. It MUST contain at least one entry. | +| [11] | `forwardTo` | The upstream target(s) where the request should be sent. | +| [12] | `serviceName` | The name of the referent service. | +| [13] | `weight` | The proportion of traffic forwarded to a targetRef, computed as weight/(sum of all weights in targetRefs). | +| [14] | `port` | The port of the referent service. | +| [15] | `backendRef` | The BackendRef is a reference to a backend (API object within a known namespace) to forward matched requests to. If both BackendRef and ServiceName are specified, ServiceName will be given precedence. Only `TraefikService` is supported. | +| [16] | `group` | Group is the group of the referent. Only `traefik.containo.us` value is supported. | +| [17] | `kind` | Kind is kind of the referent. Only `TraefikService` value is supported. | +| [18] | `name` | Name is the name of the referent. | diff --git a/pkg/provider/kubernetes/gateway/fixtures/simple_cross_provider.yml b/pkg/provider/kubernetes/gateway/fixtures/simple_cross_provider.yml new file mode 100644 index 000000000..744599728 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/simple_cross_provider.yml @@ -0,0 +1,52 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTP + port: 80 + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + app: foo + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-1 + namespace: default + labels: + app: foo +spec: + hostnames: + - "foo.com" + rules: + - matches: + - path: + type: Exact + value: /bar + forwardTo: + - weight: 1 + backendRef: + group: traefik.containo.us + kind: TraefikService + name: service@file + port: 80 + - serviceName: whoami + port: 80 + weight: 1 \ No newline at end of file diff --git a/pkg/provider/kubernetes/gateway/fixtures/simple_to_api_internal.yml b/pkg/provider/kubernetes/gateway/fixtures/simple_to_api_internal.yml new file mode 100644 index 000000000..b86f2773f --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/simple_to_api_internal.yml @@ -0,0 +1,49 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTP + port: 80 + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + app: foo + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-1 + namespace: default + labels: + app: foo +spec: + hostnames: + - "foo.com" + rules: + - matches: + - path: + type: Exact + value: /bar + forwardTo: + - weight: 1 + backendRef: + group: traefik.containo.us + kind: TraefikService + name: api@internal + port: 80 diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go index 50490cde7..03793ee14 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes.go +++ b/pkg/provider/kubernetes/gateway/kubernetes.go @@ -28,7 +28,11 @@ import ( "sigs.k8s.io/service-apis/apis/v1alpha1" ) -const providerName = "kubernetesgateway" +const ( + providerName = "kubernetesgateway" + traefikServiceKind = "TraefikService" + traefikServiceGroupName = "traefik.containo.us" +) // Provider holds configurations of the provider. type Provider struct { @@ -463,29 +467,34 @@ func (p *Provider) fillGatewayConf(client Client, gateway *v1alpha1.Gateway, con } if routeRule.ForwardTo != nil { - wrrService, subServices, err := loadServices(client, gateway.Namespace, routeRule.ForwardTo) - if err != nil { - // update "ResolvedRefs" status true with "DroppedRoutes" reason - listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ - Type: string(v1alpha1.ListenerConditionResolvedRefs), - Status: metav1.ConditionFalse, - LastTransitionTime: metav1.Now(), - Reason: string(v1alpha1.ListenerReasonDegradedRoutes), - Message: fmt.Sprintf("Cannot load service from HTTPRoute %s/%s : %v", gateway.Namespace, httpRoute.Name, err), - }) + // Traefik internal service can be used only if there is only one ForwardTo service reference. + if len(routeRule.ForwardTo) == 1 && isInternalService(routeRule.ForwardTo[0]) { + router.Service = routeRule.ForwardTo[0].BackendRef.Name + } else { + wrrService, subServices, err := loadServices(client, gateway.Namespace, routeRule.ForwardTo) + if err != nil { + // update "ResolvedRefs" status true with "DroppedRoutes" reason + listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonDegradedRoutes), + Message: fmt.Sprintf("Cannot load service from HTTPRoute %s/%s : %v", gateway.Namespace, httpRoute.Name, err), + }) - // TODO update the RouteStatus condition / deduplicate conditions on listener - continue + // TODO update the RouteStatus condition / deduplicate conditions on listener + continue + } + + for svcName, svc := range subServices { + conf.HTTP.Services[svcName] = svc + } + + serviceName := provider.Normalize(routerKey + "-wrr") + conf.HTTP.Services[serviceName] = wrrService + + router.Service = serviceName } - - for svcName, svc := range subServices { - conf.HTTP.Services[svcName] = svc - } - - serviceName := provider.Normalize(routerKey + "-wrr") - conf.HTTP.Services[serviceName] = wrrService - - router.Service = serviceName } if router.Service != "" { @@ -765,6 +774,21 @@ func loadServices(client Client, namespace string, targets []v1alpha1.HTTPRouteF } for _, forwardTo := range targets { + weight := int(forwardTo.Weight) + + if forwardTo.ServiceName == nil && forwardTo.BackendRef != nil { + if !(forwardTo.BackendRef.Group == traefikServiceGroupName && forwardTo.BackendRef.Kind == traefikServiceKind) { + continue + } + + if strings.HasSuffix(forwardTo.BackendRef.Name, "@internal") { + return nil, nil, fmt.Errorf("traefik internal service %s is not allowed in a WRR loadbalancer", forwardTo.BackendRef.Name) + } + + wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.WRRService{Name: forwardTo.BackendRef.Name, Weight: &weight}) + continue + } + if forwardTo.ServiceName == nil { continue } @@ -775,8 +799,6 @@ func loadServices(client Client, namespace string, targets []v1alpha1.HTTPRouteF }, } - // TODO Handle BackendRefs - service, exists, err := client.GetService(namespace, *forwardTo.ServiceName) if err != nil { return nil, nil, err @@ -855,11 +877,10 @@ func loadServices(client Client, namespace string, targets []v1alpha1.HTTPRouteF serviceName := provider.Normalize(makeID(service.Namespace, service.Name) + "-" + portStr) services[serviceName] = &svc - weight := int(forwardTo.Weight) wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.WRRService{Name: serviceName, Weight: &weight}) } - if len(services) == 0 { + if len(wrrSvc.Weighted.Services) == 0 { return nil, nil, errors.New("no service has been created") } @@ -904,3 +925,11 @@ func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *s return eventsChanBuffered } + +func isInternalService(forwardTo v1alpha1.HTTPRouteForwardTo) bool { + return forwardTo.ServiceName == nil && + forwardTo.BackendRef != nil && + forwardTo.BackendRef.Kind == traefikServiceKind && + forwardTo.BackendRef.Group == traefikServiceGroupName && + strings.HasSuffix(forwardTo.BackendRef.Name, "@internal") +} diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go index 25a6300c5..362e47595 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes_test.go +++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go @@ -234,6 +234,92 @@ func TestLoadHTTPRoutes(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "Simple HTTPRoute, with api@internal service", + paths: []string{"services.yml", "simple_to_api_internal.yml"}, + entryPoints: map[string]Entrypoint{"web": { + Address: ":80", + }}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06": { + EntryPoints: []string{"web"}, + Service: "api@internal", + Rule: "Host(`foo.com`) && Path(`/bar`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Simple HTTPRoute, with myservice@file service", + paths: []string{"services.yml", "simple_cross_provider.yml"}, + entryPoints: map[string]Entrypoint{"web": { + Address: ":80", + }}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06": { + EntryPoints: []string{"web"}, + Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr", + Rule: "Host(`foo.com`) && Path(`/bar`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "service@file", + Weight: func(i int) *int { return &i }(1), + }, + { + Name: "default-whoami-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, { desc: "Simple HTTPRoute with protocol HTTPS", paths: []string{"services.yml", "with_protocol_https.yml"}, From e658712d537ef3d341313e9345f833479d16390c Mon Sep 17 00:00:00 2001 From: Tom Moulard Date: Tue, 2 Mar 2021 21:34:03 +0100 Subject: [PATCH 11/18] Filter ingress class resources by name Co-authored-by: SantoDE --- .../reference/static-configuration/cli-ref.md | 2 +- .../reference/static-configuration/env-ref.md | 2 +- integration/fixtures/k8s/08-ingressclass.yml | 70 ++++++++++++ integration/fixtures/k8s_ingressclass.toml | 16 +++ integration/k8s_test.go | 11 ++ integration/resources/compose/k8s.yml | 4 +- integration/testdata/rawdata-ingress.json | 44 ++++++++ .../testdata/rawdata-ingressclass.json | 106 ++++++++++++++++++ pkg/provider/kubernetes/ingress/client.go | 13 +++ ...ss-with-ingressClasses-filter_endpoint.yml | 11 ++ ...ess-with-ingressClasses-filter_ingress.yml | 30 +++++ ...ith-ingressClasses-filter_ingressclass.yml | 14 +++ ...ess-with-ingressClasses-filter_service.yml | 10 ++ pkg/provider/kubernetes/ingress/kubernetes.go | 8 +- .../kubernetes/ingress/kubernetes_test.go | 29 +++++ 15 files changed, 364 insertions(+), 6 deletions(-) create mode 100644 integration/fixtures/k8s/08-ingressclass.yml create mode 100644 integration/fixtures/k8s_ingressclass.toml create mode 100644 integration/testdata/rawdata-ingressclass.json create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_endpoint.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_ingress.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_ingressclass.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_service.yml diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index ae6826b56..056e49b6f 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -616,7 +616,7 @@ Kubernetes certificate authority file path (not needed for in-cluster client). Kubernetes server endpoint (required for external cluster client). `--providers.kubernetesingress.ingressclass`: -Value of kubernetes.io/ingress.class annotation to watch for. +Value of kubernetes.io/ingress.class annotation or IngressClass name to watch for. `--providers.kubernetesingress.ingressendpoint.hostname`: Hostname used for Kubernetes Ingress endpoints. diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 9f37ea02e..1eb97890f 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -616,7 +616,7 @@ Kubernetes certificate authority file path (not needed for in-cluster client). Kubernetes server endpoint (required for external cluster client). `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_INGRESSCLASS`: -Value of kubernetes.io/ingress.class annotation to watch for. +Value of kubernetes.io/ingress.class annotation or IngressClass name to watch for. `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_INGRESSENDPOINT_HOSTNAME`: Hostname used for Kubernetes Ingress endpoints. diff --git a/integration/fixtures/k8s/08-ingressclass.yml b/integration/fixtures/k8s/08-ingressclass.yml new file mode 100644 index 000000000..7ae47a208 --- /dev/null +++ b/integration/fixtures/k8s/08-ingressclass.yml @@ -0,0 +1,70 @@ +apiVersion: networking.k8s.io/v1beta1 +kind: IngressClass +metadata: + name: traefik-keep +spec: + controller: traefik.io/ingress-controller + +--- +kind: Ingress +apiVersion: networking.k8s.io/v1beta1 +metadata: + name: "whoami-keep-route" +spec: + ingressClassName: "traefik-keep" + rules: + - host: "whoami.test.keep" + http: + paths: + - path: "/keep" + backend: + serviceName: "whoami" + servicePort: 80 + +--- +apiVersion: networking.k8s.io/v1beta1 +kind: IngressClass +metadata: + name: traefik-drop +spec: + controller: traefik.io/ingress-controller + +--- +kind: Ingress +apiVersion: networking.k8s.io/v1beta1 +metadata: + name: "whoami-drop-route" +spec: + ingressClassName: "traefik-drop" + rules: + - host: "whoami.test.drop" + http: + paths: + - path: "/drop" + backend: + serviceName: "whoami" + servicePort: 80 + +--- +apiVersion: networking.k8s.io/v1beta1 +kind: IngressClass +metadata: + name: traefik-not-ingress-controller +spec: + controller: not.tr43phic.io/ingress-controller + +--- +kind: Ingress +apiVersion: networking.k8s.io/v1beta1 +metadata: + name: "whoami-drop-ingress" +spec: + ingressClassName: "traefik-not-ingress-controller" + rules: + - host: "whoami.test.not.ingress" + http: + paths: + - path: "/ingress" + backend: + serviceName: "whoami" + servicePort: 80 diff --git a/integration/fixtures/k8s_ingressclass.toml b/integration/fixtures/k8s_ingressclass.toml new file mode 100644 index 000000000..51848e026 --- /dev/null +++ b/integration/fixtures/k8s_ingressclass.toml @@ -0,0 +1,16 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + +[api] + insecure = true + +[entryPoints] + [entryPoints.web] + address = ":8000" + +[providers.kubernetesIngress] + ingressClass = "traefik-keep" diff --git a/integration/k8s_test.go b/integration/k8s_test.go index b5f2b2093..e83f30ce4 100644 --- a/integration/k8s_test.go +++ b/integration/k8s_test.go @@ -119,6 +119,17 @@ func (s *K8sSuite) TestGatewayConfiguration(c *check.C) { testConfiguration(c, "testdata/rawdata-gateway.json", "8080") } +func (s *K8sSuite) TestIngressclass(c *check.C) { + cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_ingressclass.toml")) + defer display(c) + + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer s.killCmd(cmd) + + testConfiguration(c, "testdata/rawdata-ingressclass.json", "8080") +} + func testConfiguration(c *check.C, path, apiPort string) { err := try.GetRequest("http://127.0.0.1:"+apiPort+"/api/entrypoints", 20*time.Second, try.BodyContains(`"name":"web"`)) c.Assert(err, checker.IsNil) diff --git a/integration/resources/compose/k8s.yml b/integration/resources/compose/k8s.yml index 83f313c29..01316d95c 100644 --- a/integration/resources/compose/k8s.yml +++ b/integration/resources/compose/k8s.yml @@ -1,5 +1,5 @@ server: - image: rancher/k3s:v1.17.2-k3s1 + image: rancher/k3s:v1.18.15-k3s1 command: server --disable-agent --no-deploy coredns --no-deploy servicelb --no-deploy traefik --no-deploy local-storage --no-deploy metrics-server --log /output/k3s.log environment: - K3S_CLUSTER_SECRET=somethingtotallyrandom @@ -12,7 +12,7 @@ server: - 6443:6443 node: - image: rancher/k3s:v1.17.2-k3s1 + image: rancher/k3s:v1.18.15-k3s1 privileged: true links: - server diff --git a/integration/testdata/rawdata-ingress.json b/integration/testdata/rawdata-ingress.json index ffa2315ec..920154fcc 100644 --- a/integration/testdata/rawdata-ingress.json +++ b/integration/testdata/rawdata-ingress.json @@ -49,6 +49,28 @@ "using": [ "web" ] + }, + "whoami-drop-route-default-whoami-test-drop-drop@kubernetes": { + "entryPoints": [ + "web" + ], + "service": "default-whoami-80", + "rule": "Host(`whoami.test.drop`) \u0026\u0026 PathPrefix(`/drop`)", + "status": "enabled", + "using": [ + "web" + ] + }, + "whoami-keep-route-default-whoami-test-keep-keep@kubernetes": { + "entryPoints": [ + "web" + ], + "service": "default-whoami-80", + "rule": "Host(`whoami.test.keep`) \u0026\u0026 PathPrefix(`/keep`)", + "status": "enabled", + "using": [ + "web" + ] } }, "middlewares": { @@ -89,6 +111,28 @@ "dashboard@internal" ] }, + "default-whoami-80@kubernetes": { + "loadBalancer": { + "servers": [ + { + "url": "XXXX" + }, + { + "url": "XXXX" + } + ], + "passHostHeader": true + }, + "status": "enabled", + "usedBy": [ + "whoami-drop-route-default-whoami-test-drop-drop@kubernetes", + "whoami-keep-route-default-whoami-test-keep-keep@kubernetes" + ], + "serverStatus": { + "http://XXXX": "UP", + "http://XXXX": "UP" + } + }, "default-whoami-http@kubernetes": { "loadBalancer": { "servers": [ diff --git a/integration/testdata/rawdata-ingressclass.json b/integration/testdata/rawdata-ingressclass.json new file mode 100644 index 000000000..53db99cef --- /dev/null +++ b/integration/testdata/rawdata-ingressclass.json @@ -0,0 +1,106 @@ +{ + "routers": { + "api@internal": { + "entryPoints": [ + "traefik" + ], + "service": "api@internal", + "rule": "PathPrefix(`/api`)", + "priority": 2147483646, + "status": "enabled", + "using": [ + "traefik" + ] + }, + "dashboard@internal": { + "entryPoints": [ + "traefik" + ], + "middlewares": [ + "dashboard_redirect@internal", + "dashboard_stripprefix@internal" + ], + "service": "dashboard@internal", + "rule": "PathPrefix(`/`)", + "priority": 2147483645, + "status": "enabled", + "using": [ + "traefik" + ] + }, + "whoami-keep-route-default-whoami-test-keep-keep@kubernetes": { + "entryPoints": [ + "web" + ], + "service": "default-whoami-80", + "rule": "Host(`whoami.test.keep`) \u0026\u0026 PathPrefix(`/keep`)", + "status": "enabled", + "using": [ + "web" + ] + } + }, + "middlewares": { + "dashboard_redirect@internal": { + "redirectRegex": { + "regex": "^(http:\\/\\/(\\[[\\w:.]+\\]|[\\w\\._-]+)(:\\d+)?)\\/$", + "replacement": "${1}/dashboard/", + "permanent": true + }, + "status": "enabled", + "usedBy": [ + "dashboard@internal" + ] + }, + "dashboard_stripprefix@internal": { + "stripPrefix": { + "prefixes": [ + "/dashboard/", + "/dashboard" + ] + }, + "status": "enabled", + "usedBy": [ + "dashboard@internal" + ] + } + }, + "services": { + "api@internal": { + "status": "enabled", + "usedBy": [ + "api@internal" + ] + }, + "dashboard@internal": { + "status": "enabled", + "usedBy": [ + "dashboard@internal" + ] + }, + "default-whoami-80@kubernetes": { + "loadBalancer": { + "servers": [ + { + "url": "http://10.42.0.4:80" + }, + { + "url": "http://10.42.0.5:80" + } + ], + "passHostHeader": true + }, + "status": "enabled", + "usedBy": [ + "whoami-keep-route-default-whoami-test-keep-keep@kubernetes" + ], + "serverStatus": { + "http://10.42.0.4:80": "UP", + "http://10.42.0.5:80": "UP" + } + }, + "noop@internal": { + "status": "enabled" + } + } +} \ No newline at end of file diff --git a/pkg/provider/kubernetes/ingress/client.go b/pkg/provider/kubernetes/ingress/client.go index f05a85372..4a3018e2f 100644 --- a/pkg/provider/kubernetes/ingress/client.go +++ b/pkg/provider/kubernetes/ingress/client.go @@ -476,3 +476,16 @@ func supportsIngressClass(serverVersion *version.Version) bool { return ingressClassVersion.LessThanOrEqual(serverVersion) } + +// filterIngressClassByName return a slice containing ingressclasses with the correct name. +func filterIngressClassByName(ingressClassName string, ics []*networkingv1beta1.IngressClass) []*networkingv1beta1.IngressClass { + var ingressClasses []*networkingv1beta1.IngressClass + + for _, ic := range ics { + if ic.Name == ingressClassName { + ingressClasses = append(ingressClasses, ic) + } + } + + return ingressClasses +} diff --git a/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_ingress.yml new file mode 100644 index 000000000..fb4eec709 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_ingress.yml @@ -0,0 +1,30 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1beta1 +metadata: + name: "" + namespace: testing +spec: + ingressClassName: traefik-lb + rules: + - http: + paths: + - path: /bar + backend: + serviceName: service1 + servicePort: 80 + +--- +kind: Ingress +apiVersion: networking.k8s.io/v1beta1 +metadata: + name: "" + namespace: testing +spec: + ingressClassName: traefik-lb2 + rules: + - http: + paths: + - path: /foo + backend: + serviceName: service1 + servicePort: 80 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_ingressclass.yml b/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_ingressclass.yml new file mode 100644 index 000000000..c0dd6d23a --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_ingressclass.yml @@ -0,0 +1,14 @@ +apiVersion: networking.k8s.io/v1beta1 +kind: IngressClass +metadata: + name: traefik-lb2 +spec: + controller: traefik.io/ingress-controller + +--- +apiVersion: networking.k8s.io/v1beta1 +kind: IngressClass +metadata: + name: traefik-lb +spec: + controller: traefik.io/ingress-controller diff --git a/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v18-Ingress-with-ingressClasses-filter_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index 4c6e82e6d..5cb6da5ca 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -42,7 +42,7 @@ type Provider struct { CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"` Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"` LabelSelector string `description:"Kubernetes Ingress label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"` - IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"` + IngressClass string `description:"Value of kubernetes.io/ingress.class annotation or IngressClass name to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"` IngressEndpoint *EndpointIngress `description:"Kubernetes Ingress Endpoint." json:"ingressEndpoint,omitempty" toml:"ingressEndpoint,omitempty" yaml:"ingressEndpoint,omitempty" export:"true"` ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` lastConfiguration safe.Safe @@ -198,7 +198,11 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl log.FromContext(ctx).Warnf("Failed to list ingress classes: %v", err) } - ingressClasses = ics + if p.IngressClass != "" { + ingressClasses = filterIngressClassByName(p.IngressClass, ics) + } else { + ingressClasses = ics + } } ingresses := client.GetIngresses() diff --git a/pkg/provider/kubernetes/ingress/kubernetes_test.go b/pkg/provider/kubernetes/ingress/kubernetes_test.go index 16b7b3a61..4483c6b32 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes_test.go +++ b/pkg/provider/kubernetes/ingress/kubernetes_test.go @@ -1273,6 +1273,35 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { }, }, }, + { + desc: "v18 Ingress with ingressClasses filter", + serverVersion: "v1.18", + ingressClass: "traefik-lb2", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-foo": { + Rule: "PathPrefix(`/foo`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, } for _, test := range testCases { From 992d4c1b94ff6666467806099db996525165f1ff Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Doumenjou <925513+jbdoumenjou@users.noreply.github.com> Date: Wed, 3 Mar 2021 15:32:04 +0100 Subject: [PATCH 12/18] Upgrade the CRD version from apiextensions.k8s.io/v1beta1 to apiextensions.k8s.io/v1 Co-authored-by: kevinpollet --- docs/content/middlewares/overview.md | 14 - docs/content/migration/v2.md | 12 + docs/content/providers/kubernetes-crd.md | 8 +- .../kubernetes-crd-definition-v1.yml | 8 + ... => kubernetes-crd-definition-v1beta1.yml} | 0 .../kubernetes-crd-resource.yml | 13 +- .../dynamic-configuration/kubernetes-crd.md | 8 +- .../traefik.containo.us_ingressroutes.yaml | 179 +++ .../traefik.containo.us_ingressroutetcps.yaml | 134 ++ .../traefik.containo.us_ingressrouteudps.yaml | 79 + .../traefik.containo.us_middlewares.yaml | 521 +++++++ ...traefik.containo.us_serverstransports.yaml | 88 ++ .../traefik.containo.us_tlsoptions.yaml | 80 + .../traefik.containo.us_tlsstores.yaml | 58 + .../traefik.containo.us_traefikservices.yaml | 238 +++ .../routing/providers/kubernetes-crd.md | 2 +- docs/content/user-guides/crd-acme/index.md | 2 +- go.mod | 2 + go.sum | 8 + integration/fixtures/k8s/01-crd.yml | 119 -- integration/fixtures/k8s/01-traefik-crd.yml | 1377 +++++++++++++++++ pkg/provider/kubernetes/crd/kubernetes.go | 82 +- .../crd/traefik/v1alpha1/ingressroute.go | 30 +- .../crd/traefik/v1alpha1/ingressroutetcp.go | 19 +- .../crd/traefik/v1alpha1/ingressrouteudp.go | 7 +- .../crd/traefik/v1alpha1/middleware.go | 67 +- .../crd/traefik/v1alpha1/serverstransport.go | 29 +- .../crd/traefik/v1alpha1/service.go | 9 +- .../crd/traefik/v1alpha1/tlsoption.go | 10 +- .../crd/traefik/v1alpha1/tlsstore.go | 6 +- .../traefik/v1alpha1/zz_generated.deepcopy.go | 55 +- script/update-generated-crd-code.sh | 20 +- tools.go | 1 + 33 files changed, 3064 insertions(+), 221 deletions(-) create mode 100644 docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml rename docs/content/reference/dynamic-configuration/{kubernetes-crd-definition.yml => kubernetes-crd-definition-v1beta1.yml} (100%) create mode 100644 docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutes.yaml create mode 100644 docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutetcps.yaml create mode 100644 docs/content/reference/dynamic-configuration/traefik.containo.us_ingressrouteudps.yaml create mode 100644 docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml create mode 100644 docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml create mode 100644 docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml create mode 100644 docs/content/reference/dynamic-configuration/traefik.containo.us_tlsstores.yaml create mode 100644 docs/content/reference/dynamic-configuration/traefik.containo.us_traefikservices.yaml delete mode 100644 integration/fixtures/k8s/01-crd.yml create mode 100644 integration/fixtures/k8s/01-traefik-crd.yml diff --git a/docs/content/middlewares/overview.md b/docs/content/middlewares/overview.md index 86fe2808c..69e7d8000 100644 --- a/docs/content/middlewares/overview.md +++ b/docs/content/middlewares/overview.md @@ -31,20 +31,6 @@ whoami: ``` ```yaml tab="Kubernetes IngressRoute" -# As a Kubernetes Traefik IngressRoute -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: middlewares.traefik.containo.us -spec: - group: traefik.containo.us - version: v1alpha1 - names: - kind: Middleware - plural: middlewares - singular: middleware - scope: Namespaced - --- apiVersion: traefik.containo.us/v1alpha1 kind: Middleware diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index 3d936cb94..fc99f99f3 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -336,3 +336,15 @@ The file parser has been changed, since v2.3 the unknown options/fields in a dyn In `v2.3`, the support of `IngressClass`, which is available since Kubernetes version `1.18`, has been introduced. In order to be able to use this new resource the [Kubernetes RBAC](../reference/dynamic-configuration/kubernetes-crd.md#rbac) must be updated. + +## v2.4 to v2.5 + +### Kubernetes CRD + +In `v2.5`, the [Traefik CRDs](../reference/dynamic-configuration/kubernetes-crd.md#definitions) have been updated to support the new API version `apiextensions.k8s.io/v1`. +As required by `apiextensions.k8s.io/v1`, we have included the OpenAPI validation schema. + +After deploying the new [Traefik CRDs](../reference/dynamic-configuration/kubernetes-crd.md#definitions), the resources will be validated only on creation or update. + +Please note that the unknown fields will not be pruned when migrating from `apiextensions.k8s.io/v1beta1` to `apiextensions.k8s.io/v1` CRDs. +For more details check out the official [documentation](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema). diff --git a/docs/content/providers/kubernetes-crd.md b/docs/content/providers/kubernetes-crd.md index 4465b66f2..54c7f512a 100644 --- a/docs/content/providers/kubernetes-crd.md +++ b/docs/content/providers/kubernetes-crd.md @@ -20,11 +20,17 @@ the Traefik engineering team developed a [Custom Resource Definition](https://ku * Apply the needed kubernetesCRD provider [configuration](#provider-configuration) * Add all necessary Traefik custom [resources](../reference/dynamic-configuration/kubernetes-crd.md#resources) +!!! warning "Deprecated apiextensions.k8s.io/v1beta1 CRD" + + The `apiextensions.k8s.io/v1beta1` CustomResourceDefinition is deprecated in Kubernetes `v1.16+` and will be removed in `v1.22+`. + + For Kubernetes `v1.16+`, please use the Traefik `apiextensions.k8s.io/v1` CRDs instead. + ??? example "Initializing Resource Definition and RBAC" ```yaml tab="Traefik Resource Definition" # All resources definition must be declared - --8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition.yml" + --8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml" ``` ```yaml tab="RBAC for Traefik CRD" diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml new file mode 100644 index 000000000..3ec900d5f --- /dev/null +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -0,0 +1,8 @@ +--8<-- "content/reference/dynamic-configuration/traefik.containo.us_ingressroutes.yaml" +--8<-- "content/reference/dynamic-configuration/traefik.containo.us_ingressroutetcps.yaml" +--8<-- "content/reference/dynamic-configuration/traefik.containo.us_ingressrouteudps.yaml" +--8<-- "content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml" +--8<-- "content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml" +--8<-- "content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml" +--8<-- "content/reference/dynamic-configuration/traefik.containo.us_tlsstores.yaml" +--8<-- "content/reference/dynamic-configuration/traefik.containo.us_traefikservices.yaml" diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1beta1.yml similarity index 100% rename from docs/content/reference/dynamic-configuration/kubernetes-crd-definition.yml rename to docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1beta1.yml diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml index cbe4b3e34..332853401 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml @@ -63,10 +63,10 @@ spec: mirroring: name: wrr2 kind: TraefikService + # Optional + maxBodySize: 2000000000 mirrors: - name: s2 - # Optional - maxBodySize: 2000000000 # Optional, as it is the default value kind: Service percent: 20 @@ -77,6 +77,7 @@ apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: ingressroute + spec: entryPoints: - web @@ -98,6 +99,7 @@ spec: port: 433 serversTransport: mytransport - match: PathPrefix(`/misc`) + kind: Rule services: - name: s3 port: 80 @@ -105,6 +107,7 @@ spec: - name: stripprefix - name: addprefix - match: PathPrefix(`/misc`) + kind: Rule services: - name: s3 # Optional, as it is the default value @@ -113,10 +116,12 @@ spec: # scheme allow to override the scheme for the service. (ex: https or h2c) scheme: https - match: PathPrefix(`/lb`) + kind: Rule services: - name: wrr1 kind: TraefikService - match: PathPrefix(`/mirrored`) + kind: Rule services: - name: mirror1 kind: TraefikService @@ -181,10 +186,10 @@ spec: - foobar - foobar clientAuth: - caFiles: + secretNames: - foobar - foobar - clientAuthType: foobar + clientAuthType: RequireAndVerifyClientCert sniStrict: true preferServerCipherSuites: true diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd.md b/docs/content/reference/dynamic-configuration/kubernetes-crd.md index 477b082f9..ccda41843 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd.md +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd.md @@ -5,8 +5,12 @@ Dynamic configuration with Kubernetes Custom Resource ## Definitions -```yaml ---8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition.yml" +```yaml tab="apiextensions.k8s.io/v1" +--8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml" +``` + +```yaml tab="apiextensions.k8s.io/v1beta1" +--8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition-v1beta1.yml" ``` ## Resources diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutes.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutes.yaml new file mode 100644 index 000000000..726341ba8 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutes.yaml @@ -0,0 +1,179 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: ingressroutes.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: IngressRoute + listKind: IngressRouteList + plural: ingressroutes + singular: ingressroute + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: IngressRoute is an Ingress CRD specification. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: IngressRouteSpec is a specification for a IngressRouteSpec resource. + properties: + entryPoints: + items: + type: string + type: array + routes: + items: + description: Route contains the set of routes. + properties: + kind: + enum: + - Rule + type: string + match: + type: string + middlewares: + items: + description: MiddlewareRef is a ref to the Middleware resources. + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + type: array + priority: + type: integer + services: + items: + description: Service defines an upstream to proxy traffic. + properties: + kind: + enum: + - Service + - TraefikService + type: string + name: + description: Name is a reference to a Kubernetes Service object (for a load-balancer of servers), or to a TraefikService object (service load-balancer, mirroring, etc). The differentiation between the two is specified in the Kind field. + type: string + namespace: + type: string + passHostHeader: + type: boolean + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding holds configuration for the forward of the response. + properties: + flushInterval: + type: string + type: object + scheme: + type: string + serversTransport: + type: string + sticky: + description: Sticky holds the sticky configuration. + properties: + cookie: + description: Cookie holds the sticky configuration based on cookie. + properties: + httpOnly: + type: boolean + name: + type: string + sameSite: + type: string + secure: + type: boolean + type: object + type: object + strategy: + type: string + weight: + description: Weight should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + type: array + required: + - kind + - match + type: object + type: array + tls: + description: "TLS contains the TLS certificates configuration of the routes. To enable Let's Encrypt, use an empty TLS struct, e.g. in YAML: \n \t tls: {} # inline format \n \t tls: \t secretName: # block format" + properties: + certResolver: + type: string + domains: + items: + description: Domain holds a domain name with SANs. + properties: + main: + type: string + sans: + items: + type: string + type: array + type: object + type: array + options: + description: Options is a reference to a TLSOption, that specifies the parameters of the TLS connection. + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + secretName: + description: SecretName is the name of the referenced Kubernetes Secret to specify the certificate details. + type: string + store: + description: Store is a reference to a TLSStore, that specifies the parameters of the TLS store. + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + type: object + required: + - routes + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutetcps.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutetcps.yaml new file mode 100644 index 000000000..1347fa776 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutetcps.yaml @@ -0,0 +1,134 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: ingressroutetcps.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: IngressRouteTCP + listKind: IngressRouteTCPList + plural: ingressroutetcps + singular: ingressroutetcp + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: IngressRouteTCP is an Ingress CRD specification. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: IngressRouteTCPSpec is a specification for a IngressRouteTCPSpec resource. + properties: + entryPoints: + items: + type: string + type: array + routes: + items: + description: RouteTCP contains the set of routes. + properties: + match: + type: string + services: + items: + description: ServiceTCP defines an upstream to proxy traffic. + properties: + name: + type: string + namespace: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + proxyProtocol: + description: ProxyProtocol holds the ProxyProtocol configuration. + properties: + version: + type: integer + type: object + terminationDelay: + type: integer + weight: + type: integer + required: + - name + - port + type: object + type: array + required: + - match + type: object + type: array + tls: + description: "TLSTCP contains the TLS certificates configuration of the routes. To enable Let's Encrypt, use an empty TLS struct, e.g. in YAML: \n \t tls: {} # inline format \n \t tls: \t secretName: # block format" + properties: + certResolver: + type: string + domains: + items: + description: Domain holds a domain name with SANs. + properties: + main: + type: string + sans: + items: + type: string + type: array + type: object + type: array + options: + description: Options is a reference to a TLSOption, that specifies the parameters of the TLS connection. + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + passthrough: + type: boolean + secretName: + description: SecretName is the name of the referenced Kubernetes Secret to specify the certificate details. + type: string + store: + description: Store is a reference to a TLSStore, that specifies the parameters of the TLS store. + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + type: object + required: + - routes + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressrouteudps.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressrouteudps.yaml new file mode 100644 index 000000000..86b045463 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressrouteudps.yaml @@ -0,0 +1,79 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: ingressrouteudps.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: IngressRouteUDP + listKind: IngressRouteUDPList + plural: ingressrouteudps + singular: ingressrouteudp + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: IngressRouteUDP is an Ingress CRD specification. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: IngressRouteUDPSpec is a specification for a IngressRouteUDPSpec resource. + properties: + entryPoints: + items: + type: string + type: array + routes: + items: + description: RouteUDP contains the set of routes. + properties: + services: + items: + description: ServiceUDP defines an upstream to proxy traffic. + properties: + name: + type: string + namespace: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + weight: + type: integer + required: + - name + - port + type: object + type: array + type: object + type: array + required: + - routes + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml new file mode 100644 index 000000000..a5b8029d9 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml @@ -0,0 +1,521 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: middlewares.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: Middleware + listKind: MiddlewareList + plural: middlewares + singular: middleware + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Middleware is a specification for a Middleware resource. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: MiddlewareSpec holds the Middleware configuration. + properties: + addPrefix: + description: AddPrefix holds the AddPrefix configuration. + properties: + prefix: + type: string + type: object + basicAuth: + description: BasicAuth holds the HTTP basic authentication configuration. + properties: + headerField: + type: string + realm: + type: string + removeHeader: + type: boolean + secret: + type: string + type: object + buffering: + description: Buffering holds the request/response buffering configuration. + properties: + maxRequestBodyBytes: + format: int64 + type: integer + maxResponseBodyBytes: + format: int64 + type: integer + memRequestBodyBytes: + format: int64 + type: integer + memResponseBodyBytes: + format: int64 + type: integer + retryExpression: + type: string + type: object + chain: + description: Chain holds a chain of middlewares. + properties: + middlewares: + items: + description: MiddlewareRef is a ref to the Middleware resources. + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + type: array + type: object + circuitBreaker: + description: CircuitBreaker holds the circuit breaker configuration. + properties: + expression: + type: string + type: object + compress: + description: Compress holds the compress configuration. + properties: + excludedContentTypes: + items: + type: string + type: array + type: object + contentType: + description: ContentType middleware - or rather its unique `autoDetect` option - specifies whether to let the `Content-Type` header, if it has not been set by the backend, be automatically set to a value derived from the contents of the response. As a proxy, the default behavior should be to leave the header alone, regardless of what the backend did with it. However, the historic default was to always auto-detect and set the header if it was nil, and it is going to be kept that way in order to support users currently relying on it. This middleware exists to enable the correct behavior until at least the default one can be changed in a future version. + properties: + autoDetect: + type: boolean + type: object + digestAuth: + description: DigestAuth holds the Digest HTTP authentication configuration. + properties: + headerField: + type: string + realm: + type: string + removeHeader: + type: boolean + secret: + type: string + type: object + errors: + description: ErrorPage holds the custom error page configuration. + properties: + query: + type: string + service: + description: Service defines an upstream to proxy traffic. + properties: + kind: + enum: + - Service + - TraefikService + type: string + name: + description: Name is a reference to a Kubernetes Service object (for a load-balancer of servers), or to a TraefikService object (service load-balancer, mirroring, etc). The differentiation between the two is specified in the Kind field. + type: string + namespace: + type: string + passHostHeader: + type: boolean + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding holds configuration for the forward of the response. + properties: + flushInterval: + type: string + type: object + scheme: + type: string + serversTransport: + type: string + sticky: + description: Sticky holds the sticky configuration. + properties: + cookie: + description: Cookie holds the sticky configuration based on cookie. + properties: + httpOnly: + type: boolean + name: + type: string + sameSite: + type: string + secure: + type: boolean + type: object + type: object + strategy: + type: string + weight: + description: Weight should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + status: + items: + type: string + type: array + type: object + forwardAuth: + description: ForwardAuth holds the http forward authentication configuration. + properties: + address: + type: string + authRequestHeaders: + items: + type: string + type: array + authResponseHeaders: + items: + type: string + type: array + authResponseHeadersRegex: + type: string + tls: + description: ClientTLS holds TLS specific configurations as client. + properties: + caOptional: + type: boolean + caSecret: + type: string + certSecret: + type: string + insecureSkipVerify: + type: boolean + type: object + trustForwardHeader: + type: boolean + type: object + headers: + description: Headers holds the custom header configuration. + properties: + accessControlAllowCredentials: + description: AccessControlAllowCredentials is only valid if true. false is ignored. + type: boolean + accessControlAllowHeaders: + description: AccessControlAllowHeaders must be used in response to a preflight request with Access-Control-Request-Headers set. + items: + type: string + type: array + accessControlAllowMethods: + description: AccessControlAllowMethods must be used in response to a preflight request with Access-Control-Request-Method set. + items: + type: string + type: array + accessControlAllowOrigin: + description: AccessControlAllowOrigin Can be "origin-list-or-null" or "*". From (https://www.w3.org/TR/cors/#access-control-allow-origin-response-header) + type: string + accessControlAllowOriginList: + description: AccessControlAllowOriginList is a list of allowable origins. Can also be a wildcard origin "*". + items: + type: string + type: array + accessControlAllowOriginListRegex: + description: AccessControlAllowOriginListRegex is a list of allowable origins written following the Regular Expression syntax (https://golang.org/pkg/regexp/). + items: + type: string + type: array + accessControlExposeHeaders: + description: AccessControlExposeHeaders sets valid headers for the response. + items: + type: string + type: array + accessControlMaxAge: + description: AccessControlMaxAge sets the time that a preflight request may be cached. + format: int64 + type: integer + addVaryHeader: + description: AddVaryHeader controls if the Vary header is automatically added/updated when the AccessControlAllowOrigin is set. + type: boolean + allowedHosts: + items: + type: string + type: array + browserXssFilter: + type: boolean + contentSecurityPolicy: + type: string + contentTypeNosniff: + type: boolean + customBrowserXSSValue: + type: string + customFrameOptionsValue: + type: string + customRequestHeaders: + additionalProperties: + type: string + type: object + customResponseHeaders: + additionalProperties: + type: string + type: object + featurePolicy: + type: string + forceSTSHeader: + type: boolean + frameDeny: + type: boolean + hostsProxyHeaders: + items: + type: string + type: array + isDevelopment: + type: boolean + publicKey: + type: string + referrerPolicy: + type: string + sslForceHost: + type: boolean + sslHost: + type: string + sslProxyHeaders: + additionalProperties: + type: string + type: object + sslRedirect: + type: boolean + sslTemporaryRedirect: + type: boolean + stsIncludeSubdomains: + type: boolean + stsPreload: + type: boolean + stsSeconds: + format: int64 + type: integer + type: object + inFlightReq: + description: InFlightReq limits the number of requests being processed and served concurrently. + properties: + amount: + format: int64 + type: integer + sourceCriterion: + description: SourceCriterion defines what criterion is used to group requests as originating from a common source. If none are set, the default is to use the request's remote address field. All fields are mutually exclusive. + properties: + ipStrategy: + description: IPStrategy holds the ip strategy configuration. + properties: + depth: + type: integer + excludedIPs: + items: + type: string + type: array + type: object + requestHeaderName: + type: string + requestHost: + type: boolean + type: object + type: object + ipWhiteList: + description: IPWhiteList holds the ip white list configuration. + properties: + ipStrategy: + description: IPStrategy holds the ip strategy configuration. + properties: + depth: + type: integer + excludedIPs: + items: + type: string + type: array + type: object + sourceRange: + items: + type: string + type: array + type: object + passTLSClientCert: + description: PassTLSClientCert holds the TLS client cert headers configuration. + properties: + info: + description: TLSClientCertificateInfo holds the client TLS certificate info configuration. + properties: + issuer: + description: TLSCLientCertificateDNInfo holds the client TLS certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739 + properties: + commonName: + type: boolean + country: + type: boolean + domainComponent: + type: boolean + locality: + type: boolean + organization: + type: boolean + province: + type: boolean + serialNumber: + type: boolean + type: object + notAfter: + type: boolean + notBefore: + type: boolean + sans: + type: boolean + serialNumber: + type: boolean + subject: + description: TLSCLientCertificateDNInfo holds the client TLS certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739 + properties: + commonName: + type: boolean + country: + type: boolean + domainComponent: + type: boolean + locality: + type: boolean + organization: + type: boolean + province: + type: boolean + serialNumber: + type: boolean + type: object + type: object + pem: + type: boolean + type: object + plugin: + additionalProperties: + x-kubernetes-preserve-unknown-fields: true + type: object + rateLimit: + description: RateLimit holds the rate limiting configuration for a given router. + properties: + average: + format: int64 + type: integer + burst: + format: int64 + type: integer + period: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + sourceCriterion: + description: SourceCriterion defines what criterion is used to group requests as originating from a common source. If none are set, the default is to use the request's remote address field. All fields are mutually exclusive. + properties: + ipStrategy: + description: IPStrategy holds the ip strategy configuration. + properties: + depth: + type: integer + excludedIPs: + items: + type: string + type: array + type: object + requestHeaderName: + type: string + requestHost: + type: boolean + type: object + type: object + redirectRegex: + description: RedirectRegex holds the redirection configuration. + properties: + permanent: + type: boolean + regex: + type: string + replacement: + type: string + type: object + redirectScheme: + description: RedirectScheme holds the scheme redirection configuration. + properties: + permanent: + type: boolean + port: + type: string + scheme: + type: string + type: object + replacePath: + description: ReplacePath holds the ReplacePath configuration. + properties: + path: + type: string + type: object + replacePathRegex: + description: ReplacePathRegex holds the ReplacePathRegex configuration. + properties: + regex: + type: string + replacement: + type: string + type: object + retry: + description: Retry holds the retry configuration. + properties: + attempts: + type: integer + initialInterval: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + stripPrefix: + description: StripPrefix holds the StripPrefix configuration. + properties: + forceSlash: + type: boolean + prefixes: + items: + type: string + type: array + type: object + stripPrefixRegex: + description: StripPrefixRegex holds the StripPrefixRegex configuration. + properties: + regex: + items: + type: string + type: array + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml new file mode 100644 index 000000000..43c9e6db9 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml @@ -0,0 +1,88 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: serverstransports.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: ServersTransport + listKind: ServersTransportList + plural: serverstransports + singular: serverstransport + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ServersTransport is a specification for a ServersTransport resource. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ServersTransportSpec options to configure communication between Traefik and the servers. + properties: + certificatesSecrets: + description: Certificates for mTLS. + items: + type: string + type: array + forwardingTimeouts: + description: Timeouts for requests forwarded to the backend servers. + properties: + dialTimeout: + anyOf: + - type: integer + - type: string + description: The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists. + x-kubernetes-int-or-string: true + idleConnTimeout: + anyOf: + - type: integer + - type: string + description: The maximum period for which an idle HTTP keep-alive connection will remain open before closing itself. + x-kubernetes-int-or-string: true + responseHeaderTimeout: + anyOf: + - type: integer + - type: string + description: The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists. + x-kubernetes-int-or-string: true + type: object + insecureSkipVerify: + description: Disable SSL certificate verification. + type: boolean + maxIdleConnsPerHost: + description: If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used. + type: integer + rootCAsSecrets: + description: Add cert file for self-signed certificate. + items: + type: string + type: array + serverName: + description: ServerName used to contact the server. + type: string + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml new file mode 100644 index 000000000..3b9bdc2f2 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml @@ -0,0 +1,80 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: tlsoptions.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: TLSOption + listKind: TLSOptionList + plural: tlsoptions + singular: tlsoption + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: TLSOption is a specification for a TLSOption resource. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TLSOptionSpec configures TLS for an entry point. + properties: + cipherSuites: + items: + type: string + type: array + clientAuth: + description: ClientAuth defines the parameters of the client authentication part of the TLS connection, if any. + properties: + clientAuthType: + description: ClientAuthType defines the client authentication type to apply. + enum: + - NoClientCert + - RequestClientCert + - VerifyClientCertIfGiven + - RequireAndVerifyClientCert + type: string + secretNames: + description: SecretName is the name of the referenced Kubernetes Secret to specify the certificate details. + items: + type: string + type: array + type: object + curvePreferences: + items: + type: string + type: array + maxVersion: + type: string + minVersion: + type: string + preferServerCipherSuites: + type: boolean + sniStrict: + type: boolean + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsstores.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsstores.yaml new file mode 100644 index 000000000..8bc871801 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsstores.yaml @@ -0,0 +1,58 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: tlsstores.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: TLSStore + listKind: TLSStoreList + plural: tlsstores + singular: tlsstore + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: TLSStore is a specification for a TLSStore resource. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TLSStoreSpec configures a TLSStore resource. + properties: + defaultCertificate: + description: DefaultCertificate holds a secret name for the TLSOption resource. + properties: + secretName: + description: SecretName is the name of the referenced Kubernetes Secret to specify the certificate details. + type: string + required: + - secretName + type: object + required: + - defaultCertificate + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_traefikservices.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_traefikservices.yaml new file mode 100644 index 000000000..a0792d77c --- /dev/null +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_traefikservices.yaml @@ -0,0 +1,238 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: traefikservices.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: TraefikService + listKind: TraefikServiceList + plural: traefikservices + singular: traefikservice + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: TraefikService is the specification for a service (that an IngressRoute refers to) that is usually not a terminal service (i.e. not a pod of servers), as opposed to a Kubernetes Service. That is to say, it usually refers to other (children) services, which themselves can be TraefikServices or Services. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ServiceSpec defines whether a TraefikService is a load-balancer of services or a mirroring service. + properties: + mirroring: + description: Mirroring defines a mirroring service, which is composed of a main load-balancer, and a list of mirrors. + properties: + kind: + enum: + - Service + - TraefikService + type: string + maxBodySize: + format: int64 + type: integer + mirrors: + items: + description: MirrorService defines one of the mirrors of a Mirroring service. + properties: + kind: + enum: + - Service + - TraefikService + type: string + name: + description: Name is a reference to a Kubernetes Service object (for a load-balancer of servers), or to a TraefikService object (service load-balancer, mirroring, etc). The differentiation between the two is specified in the Kind field. + type: string + namespace: + type: string + passHostHeader: + type: boolean + percent: + type: integer + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding holds configuration for the forward of the response. + properties: + flushInterval: + type: string + type: object + scheme: + type: string + serversTransport: + type: string + sticky: + description: Sticky holds the sticky configuration. + properties: + cookie: + description: Cookie holds the sticky configuration based on cookie. + properties: + httpOnly: + type: boolean + name: + type: string + sameSite: + type: string + secure: + type: boolean + type: object + type: object + strategy: + type: string + weight: + description: Weight should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + type: array + name: + description: Name is a reference to a Kubernetes Service object (for a load-balancer of servers), or to a TraefikService object (service load-balancer, mirroring, etc). The differentiation between the two is specified in the Kind field. + type: string + namespace: + type: string + passHostHeader: + type: boolean + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding holds configuration for the forward of the response. + properties: + flushInterval: + type: string + type: object + scheme: + type: string + serversTransport: + type: string + sticky: + description: Sticky holds the sticky configuration. + properties: + cookie: + description: Cookie holds the sticky configuration based on cookie. + properties: + httpOnly: + type: boolean + name: + type: string + sameSite: + type: string + secure: + type: boolean + type: object + type: object + strategy: + type: string + weight: + description: Weight should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + weighted: + description: WeightedRoundRobin defines a load-balancer of services. + properties: + services: + items: + description: Service defines an upstream to proxy traffic. + properties: + kind: + enum: + - Service + - TraefikService + type: string + name: + description: Name is a reference to a Kubernetes Service object (for a load-balancer of servers), or to a TraefikService object (service load-balancer, mirroring, etc). The differentiation between the two is specified in the Kind field. + type: string + namespace: + type: string + passHostHeader: + type: boolean + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding holds configuration for the forward of the response. + properties: + flushInterval: + type: string + type: object + scheme: + type: string + serversTransport: + type: string + sticky: + description: Sticky holds the sticky configuration. + properties: + cookie: + description: Cookie holds the sticky configuration based on cookie. + properties: + httpOnly: + type: boolean + name: + type: string + sameSite: + type: string + secure: + type: boolean + type: object + type: object + strategy: + type: string + weight: + description: Weight should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + type: array + sticky: + description: Sticky holds the sticky configuration. + properties: + cookie: + description: Cookie holds the sticky configuration based on cookie. + properties: + httpOnly: + type: boolean + name: + type: string + sameSite: + type: string + secure: + type: boolean + type: object + type: object + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/docs/content/routing/providers/kubernetes-crd.md b/docs/content/routing/providers/kubernetes-crd.md index 573a26cb2..6a26e0e06 100644 --- a/docs/content/routing/providers/kubernetes-crd.md +++ b/docs/content/routing/providers/kubernetes-crd.md @@ -9,7 +9,7 @@ The Kubernetes Ingress Controller, The Custom Resource Way. ```yaml tab="Resource Definition" # All resources definition must be declared - --8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition.yml" + --8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml" ``` ```yaml tab="RBAC" diff --git a/docs/content/user-guides/crd-acme/index.md b/docs/content/user-guides/crd-acme/index.md index 100f63b89..04e63807b 100644 --- a/docs/content/user-guides/crd-acme/index.md +++ b/docs/content/user-guides/crd-acme/index.md @@ -43,7 +43,7 @@ First, the definition of the `IngressRoute` and the `Middleware` kinds. Also note the RBAC authorization resources; they'll be referenced through the `serviceAccountName` of the deployment, later on. ```yaml ---8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition.yml" +--8<-- "content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml" --- --8<-- "content/reference/dynamic-configuration/kubernetes-crd-rbac.yml" diff --git a/go.mod b/go.mod index b6111678b..f38fe1745 100644 --- a/go.mod +++ b/go.mod @@ -90,10 +90,12 @@ require ( gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 k8s.io/api v0.19.2 + k8s.io/apiextensions-apiserver v0.18.6 k8s.io/apimachinery v0.19.2 k8s.io/client-go v0.19.2 k8s.io/code-generator v0.19.2 mvdan.cc/xurls/v2 v2.1.0 + sigs.k8s.io/controller-tools v0.4.0 sigs.k8s.io/service-apis v0.1.0 ) diff --git a/go.sum b/go.sum index b8f5a1eaf..c4e81e53e 100644 --- a/go.sum +++ b/go.sum @@ -296,6 +296,7 @@ github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQo github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exoscale/egoscale v0.23.0 h1:hoUDzrO8yNoobNdnrRvlRFjfg3Ng0vQTrv6bXRJu6z0= github.com/exoscale/egoscale v0.23.0/go.mod h1:hRo78jkjkCDKpivQdRBEpNYF5+cVpCJCPDg2/r45KaY= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= @@ -391,6 +392,7 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/flect v0.2.0 h1:EWCvMGGxOjsgwlWaP+f4+Hh6yrrte7JeFL2S6b+0hdM= github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= @@ -565,6 +567,7 @@ github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= @@ -651,9 +654,11 @@ github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0a github.com/marten-seemann/qtls-go1-15 v0.1.1 h1:LIH6K34bPVttyXnUWixk0bzH6/N07VxbSabxn5A5gZQ= github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -908,6 +913,7 @@ github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -1422,6 +1428,7 @@ k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms= k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY= +k8s.io/apiextensions-apiserver v0.18.6 h1:vDlk7cyFsDyfwn2rNAO2DbmUbvXy5yT5GE3rrqOzaMo= k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M= k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= @@ -1472,6 +1479,7 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= sigs.k8s.io/controller-runtime v0.6.2/go.mod h1:vhcq/rlnENJ09SIRp3EveTaZ0yqH526hjf9iJdbUJ/E= +sigs.k8s.io/controller-tools v0.4.0 h1:9zIdrc6q9RKke8+DnVPVBVZ+cfF9L0TwM01cxNnklYo= sigs.k8s.io/controller-tools v0.4.0/go.mod h1:G9rHdZMVlBDocIxGkK3jHLWqcTMNvveypYJwrvYKjWU= sigs.k8s.io/service-apis v0.1.0 h1:yImgpgLrxSD5tMdLqpIDEzroFaUzqwZbrg6/H3VpkYM= sigs.k8s.io/service-apis v0.1.0/go.mod h1:QkiV/PnK7YbN5zqYqXnh5wByTTT1LYJ5scwdIs62qWs= diff --git a/integration/fixtures/k8s/01-crd.yml b/integration/fixtures/k8s/01-crd.yml deleted file mode 100644 index 2bf53b37a..000000000 --- a/integration/fixtures/k8s/01-crd.yml +++ /dev/null @@ -1,119 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: ingressroutes.traefik.containo.us - -spec: - group: traefik.containo.us - version: v1alpha1 - names: - kind: IngressRoute - plural: ingressroutes - singular: ingressroute - scope: Namespaced - ---- -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: middlewares.traefik.containo.us - -spec: - group: traefik.containo.us - version: v1alpha1 - names: - kind: Middleware - plural: middlewares - singular: middleware - scope: Namespaced - ---- -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: ingressroutetcps.traefik.containo.us - -spec: - group: traefik.containo.us - version: v1alpha1 - names: - kind: IngressRouteTCP - plural: ingressroutetcps - singular: ingressroutetcp - scope: Namespaced - ---- -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: ingressrouteudps.traefik.containo.us - -spec: - group: traefik.containo.us - version: v1alpha1 - names: - kind: IngressRouteUDP - plural: ingressrouteudps - singular: ingressrouteudp - scope: Namespaced - ---- -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: tlsoptions.traefik.containo.us - -spec: - group: traefik.containo.us - version: v1alpha1 - names: - kind: TLSOption - plural: tlsoptions - singular: tlsoption - scope: Namespaced - ---- -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: serverstransports.traefik.containo.us - -spec: - group: traefik.containo.us - version: v1alpha1 - names: - kind: ServersTransport - plural: serverstransports - singular: serverstransport - scope: Namespaced - ---- -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: tlsstores.traefik.containo.us - -spec: - group: traefik.containo.us - version: v1alpha1 - names: - kind: TLSStore - plural: tlsstores - singular: tlsstore - scope: Namespaced - - ---- -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: traefikservices.traefik.containo.us - -spec: - group: traefik.containo.us - version: v1alpha1 - names: - kind: TraefikService - plural: traefikservices - singular: traefikservice - scope: Namespaced diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml new file mode 100644 index 000000000..53b7a921c --- /dev/null +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -0,0 +1,1377 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: ingressroutes.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: IngressRoute + listKind: IngressRouteList + plural: ingressroutes + singular: ingressroute + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: IngressRoute is an Ingress CRD specification. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: IngressRouteSpec is a specification for a IngressRouteSpec resource. + properties: + entryPoints: + items: + type: string + type: array + routes: + items: + description: Route contains the set of routes. + properties: + kind: + enum: + - Rule + type: string + match: + type: string + middlewares: + items: + description: MiddlewareRef is a ref to the Middleware resources. + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + type: array + priority: + type: integer + services: + items: + description: Service defines an upstream to proxy traffic. + properties: + kind: + enum: + - Service + - TraefikService + type: string + name: + description: Name is a reference to a Kubernetes Service object (for a load-balancer of servers), or to a TraefikService object (service load-balancer, mirroring, etc). The differentiation between the two is specified in the Kind field. + type: string + namespace: + type: string + passHostHeader: + type: boolean + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding holds configuration for the forward of the response. + properties: + flushInterval: + type: string + type: object + scheme: + type: string + serversTransport: + type: string + sticky: + description: Sticky holds the sticky configuration. + properties: + cookie: + description: Cookie holds the sticky configuration based on cookie. + properties: + httpOnly: + type: boolean + name: + type: string + sameSite: + type: string + secure: + type: boolean + type: object + type: object + strategy: + type: string + weight: + description: Weight should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + type: array + required: + - kind + - match + type: object + type: array + tls: + description: "TLS contains the TLS certificates configuration of the routes. To enable Let's Encrypt, use an empty TLS struct, e.g. in YAML: \n \t tls: {} # inline format \n \t tls: \t secretName: # block format" + properties: + certResolver: + type: string + domains: + items: + description: Domain holds a domain name with SANs. + properties: + main: + type: string + sans: + items: + type: string + type: array + type: object + type: array + options: + description: Options is a reference to a TLSOption, that specifies the parameters of the TLS connection. + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + secretName: + description: SecretName is the name of the referenced Kubernetes Secret to specify the certificate details. + type: string + store: + description: Store is a reference to a TLSStore, that specifies the parameters of the TLS store. + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + type: object + required: + - routes + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: ingressroutetcps.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: IngressRouteTCP + listKind: IngressRouteTCPList + plural: ingressroutetcps + singular: ingressroutetcp + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: IngressRouteTCP is an Ingress CRD specification. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: IngressRouteTCPSpec is a specification for a IngressRouteTCPSpec resource. + properties: + entryPoints: + items: + type: string + type: array + routes: + items: + description: RouteTCP contains the set of routes. + properties: + match: + type: string + services: + items: + description: ServiceTCP defines an upstream to proxy traffic. + properties: + name: + type: string + namespace: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + proxyProtocol: + description: ProxyProtocol holds the ProxyProtocol configuration. + properties: + version: + type: integer + type: object + terminationDelay: + type: integer + weight: + type: integer + required: + - name + - port + type: object + type: array + required: + - match + type: object + type: array + tls: + description: "TLSTCP contains the TLS certificates configuration of the routes. To enable Let's Encrypt, use an empty TLS struct, e.g. in YAML: \n \t tls: {} # inline format \n \t tls: \t secretName: # block format" + properties: + certResolver: + type: string + domains: + items: + description: Domain holds a domain name with SANs. + properties: + main: + type: string + sans: + items: + type: string + type: array + type: object + type: array + options: + description: Options is a reference to a TLSOption, that specifies the parameters of the TLS connection. + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + passthrough: + type: boolean + secretName: + description: SecretName is the name of the referenced Kubernetes Secret to specify the certificate details. + type: string + store: + description: Store is a reference to a TLSStore, that specifies the parameters of the TLS store. + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + type: object + required: + - routes + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: ingressrouteudps.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: IngressRouteUDP + listKind: IngressRouteUDPList + plural: ingressrouteudps + singular: ingressrouteudp + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: IngressRouteUDP is an Ingress CRD specification. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: IngressRouteUDPSpec is a specification for a IngressRouteUDPSpec resource. + properties: + entryPoints: + items: + type: string + type: array + routes: + items: + description: RouteUDP contains the set of routes. + properties: + services: + items: + description: ServiceUDP defines an upstream to proxy traffic. + properties: + name: + type: string + namespace: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + weight: + type: integer + required: + - name + - port + type: object + type: array + type: object + type: array + required: + - routes + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: middlewares.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: Middleware + listKind: MiddlewareList + plural: middlewares + singular: middleware + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Middleware is a specification for a Middleware resource. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: MiddlewareSpec holds the Middleware configuration. + properties: + addPrefix: + description: AddPrefix holds the AddPrefix configuration. + properties: + prefix: + type: string + type: object + basicAuth: + description: BasicAuth holds the HTTP basic authentication configuration. + properties: + headerField: + type: string + realm: + type: string + removeHeader: + type: boolean + secret: + type: string + type: object + buffering: + description: Buffering holds the request/response buffering configuration. + properties: + maxRequestBodyBytes: + format: int64 + type: integer + maxResponseBodyBytes: + format: int64 + type: integer + memRequestBodyBytes: + format: int64 + type: integer + memResponseBodyBytes: + format: int64 + type: integer + retryExpression: + type: string + type: object + chain: + description: Chain holds a chain of middlewares. + properties: + middlewares: + items: + description: MiddlewareRef is a ref to the Middleware resources. + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + type: array + type: object + circuitBreaker: + description: CircuitBreaker holds the circuit breaker configuration. + properties: + expression: + type: string + type: object + compress: + description: Compress holds the compress configuration. + properties: + excludedContentTypes: + items: + type: string + type: array + type: object + contentType: + description: ContentType middleware - or rather its unique `autoDetect` option - specifies whether to let the `Content-Type` header, if it has not been set by the backend, be automatically set to a value derived from the contents of the response. As a proxy, the default behavior should be to leave the header alone, regardless of what the backend did with it. However, the historic default was to always auto-detect and set the header if it was nil, and it is going to be kept that way in order to support users currently relying on it. This middleware exists to enable the correct behavior until at least the default one can be changed in a future version. + properties: + autoDetect: + type: boolean + type: object + digestAuth: + description: DigestAuth holds the Digest HTTP authentication configuration. + properties: + headerField: + type: string + realm: + type: string + removeHeader: + type: boolean + secret: + type: string + type: object + errors: + description: ErrorPage holds the custom error page configuration. + properties: + query: + type: string + service: + description: Service defines an upstream to proxy traffic. + properties: + kind: + enum: + - Service + - TraefikService + type: string + name: + description: Name is a reference to a Kubernetes Service object (for a load-balancer of servers), or to a TraefikService object (service load-balancer, mirroring, etc). The differentiation between the two is specified in the Kind field. + type: string + namespace: + type: string + passHostHeader: + type: boolean + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding holds configuration for the forward of the response. + properties: + flushInterval: + type: string + type: object + scheme: + type: string + serversTransport: + type: string + sticky: + description: Sticky holds the sticky configuration. + properties: + cookie: + description: Cookie holds the sticky configuration based on cookie. + properties: + httpOnly: + type: boolean + name: + type: string + sameSite: + type: string + secure: + type: boolean + type: object + type: object + strategy: + type: string + weight: + description: Weight should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + status: + items: + type: string + type: array + type: object + forwardAuth: + description: ForwardAuth holds the http forward authentication configuration. + properties: + address: + type: string + authRequestHeaders: + items: + type: string + type: array + authResponseHeaders: + items: + type: string + type: array + authResponseHeadersRegex: + type: string + tls: + description: ClientTLS holds TLS specific configurations as client. + properties: + caOptional: + type: boolean + caSecret: + type: string + certSecret: + type: string + insecureSkipVerify: + type: boolean + type: object + trustForwardHeader: + type: boolean + type: object + headers: + description: Headers holds the custom header configuration. + properties: + accessControlAllowCredentials: + description: AccessControlAllowCredentials is only valid if true. false is ignored. + type: boolean + accessControlAllowHeaders: + description: AccessControlAllowHeaders must be used in response to a preflight request with Access-Control-Request-Headers set. + items: + type: string + type: array + accessControlAllowMethods: + description: AccessControlAllowMethods must be used in response to a preflight request with Access-Control-Request-Method set. + items: + type: string + type: array + accessControlAllowOrigin: + description: AccessControlAllowOrigin Can be "origin-list-or-null" or "*". From (https://www.w3.org/TR/cors/#access-control-allow-origin-response-header) + type: string + accessControlAllowOriginList: + description: AccessControlAllowOriginList is a list of allowable origins. Can also be a wildcard origin "*". + items: + type: string + type: array + accessControlAllowOriginListRegex: + description: AccessControlAllowOriginListRegex is a list of allowable origins written following the Regular Expression syntax (https://golang.org/pkg/regexp/). + items: + type: string + type: array + accessControlExposeHeaders: + description: AccessControlExposeHeaders sets valid headers for the response. + items: + type: string + type: array + accessControlMaxAge: + description: AccessControlMaxAge sets the time that a preflight request may be cached. + format: int64 + type: integer + addVaryHeader: + description: AddVaryHeader controls if the Vary header is automatically added/updated when the AccessControlAllowOrigin is set. + type: boolean + allowedHosts: + items: + type: string + type: array + browserXssFilter: + type: boolean + contentSecurityPolicy: + type: string + contentTypeNosniff: + type: boolean + customBrowserXSSValue: + type: string + customFrameOptionsValue: + type: string + customRequestHeaders: + additionalProperties: + type: string + type: object + customResponseHeaders: + additionalProperties: + type: string + type: object + featurePolicy: + type: string + forceSTSHeader: + type: boolean + frameDeny: + type: boolean + hostsProxyHeaders: + items: + type: string + type: array + isDevelopment: + type: boolean + publicKey: + type: string + referrerPolicy: + type: string + sslForceHost: + type: boolean + sslHost: + type: string + sslProxyHeaders: + additionalProperties: + type: string + type: object + sslRedirect: + type: boolean + sslTemporaryRedirect: + type: boolean + stsIncludeSubdomains: + type: boolean + stsPreload: + type: boolean + stsSeconds: + format: int64 + type: integer + type: object + inFlightReq: + description: InFlightReq limits the number of requests being processed and served concurrently. + properties: + amount: + format: int64 + type: integer + sourceCriterion: + description: SourceCriterion defines what criterion is used to group requests as originating from a common source. If none are set, the default is to use the request's remote address field. All fields are mutually exclusive. + properties: + ipStrategy: + description: IPStrategy holds the ip strategy configuration. + properties: + depth: + type: integer + excludedIPs: + items: + type: string + type: array + type: object + requestHeaderName: + type: string + requestHost: + type: boolean + type: object + type: object + ipWhiteList: + description: IPWhiteList holds the ip white list configuration. + properties: + ipStrategy: + description: IPStrategy holds the ip strategy configuration. + properties: + depth: + type: integer + excludedIPs: + items: + type: string + type: array + type: object + sourceRange: + items: + type: string + type: array + type: object + passTLSClientCert: + description: PassTLSClientCert holds the TLS client cert headers configuration. + properties: + info: + description: TLSClientCertificateInfo holds the client TLS certificate info configuration. + properties: + issuer: + description: TLSCLientCertificateDNInfo holds the client TLS certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739 + properties: + commonName: + type: boolean + country: + type: boolean + domainComponent: + type: boolean + locality: + type: boolean + organization: + type: boolean + province: + type: boolean + serialNumber: + type: boolean + type: object + notAfter: + type: boolean + notBefore: + type: boolean + sans: + type: boolean + serialNumber: + type: boolean + subject: + description: TLSCLientCertificateDNInfo holds the client TLS certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739 + properties: + commonName: + type: boolean + country: + type: boolean + domainComponent: + type: boolean + locality: + type: boolean + organization: + type: boolean + province: + type: boolean + serialNumber: + type: boolean + type: object + type: object + pem: + type: boolean + type: object + plugin: + additionalProperties: + x-kubernetes-preserve-unknown-fields: true + type: object + rateLimit: + description: RateLimit holds the rate limiting configuration for a given router. + properties: + average: + format: int64 + type: integer + burst: + format: int64 + type: integer + period: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + sourceCriterion: + description: SourceCriterion defines what criterion is used to group requests as originating from a common source. If none are set, the default is to use the request's remote address field. All fields are mutually exclusive. + properties: + ipStrategy: + description: IPStrategy holds the ip strategy configuration. + properties: + depth: + type: integer + excludedIPs: + items: + type: string + type: array + type: object + requestHeaderName: + type: string + requestHost: + type: boolean + type: object + type: object + redirectRegex: + description: RedirectRegex holds the redirection configuration. + properties: + permanent: + type: boolean + regex: + type: string + replacement: + type: string + type: object + redirectScheme: + description: RedirectScheme holds the scheme redirection configuration. + properties: + permanent: + type: boolean + port: + type: string + scheme: + type: string + type: object + replacePath: + description: ReplacePath holds the ReplacePath configuration. + properties: + path: + type: string + type: object + replacePathRegex: + description: ReplacePathRegex holds the ReplacePathRegex configuration. + properties: + regex: + type: string + replacement: + type: string + type: object + retry: + description: Retry holds the retry configuration. + properties: + attempts: + type: integer + initialInterval: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + stripPrefix: + description: StripPrefix holds the StripPrefix configuration. + properties: + forceSlash: + type: boolean + prefixes: + items: + type: string + type: array + type: object + stripPrefixRegex: + description: StripPrefixRegex holds the StripPrefixRegex configuration. + properties: + regex: + items: + type: string + type: array + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: serverstransports.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: ServersTransport + listKind: ServersTransportList + plural: serverstransports + singular: serverstransport + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ServersTransport is a specification for a ServersTransport resource. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ServersTransportSpec options to configure communication between Traefik and the servers. + properties: + certificatesSecrets: + description: Certificates for mTLS. + items: + type: string + type: array + forwardingTimeouts: + description: Timeouts for requests forwarded to the backend servers. + properties: + dialTimeout: + anyOf: + - type: integer + - type: string + description: The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists. + x-kubernetes-int-or-string: true + idleConnTimeout: + anyOf: + - type: integer + - type: string + description: The maximum period for which an idle HTTP keep-alive connection will remain open before closing itself. + x-kubernetes-int-or-string: true + responseHeaderTimeout: + anyOf: + - type: integer + - type: string + description: The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists. + x-kubernetes-int-or-string: true + type: object + insecureSkipVerify: + description: Disable SSL certificate verification. + type: boolean + maxIdleConnsPerHost: + description: If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used. + type: integer + rootCAsSecrets: + description: Add cert file for self-signed certificate. + items: + type: string + type: array + serverName: + description: ServerName used to contact the server. + type: string + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: tlsoptions.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: TLSOption + listKind: TLSOptionList + plural: tlsoptions + singular: tlsoption + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: TLSOption is a specification for a TLSOption resource. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TLSOptionSpec configures TLS for an entry point. + properties: + cipherSuites: + items: + type: string + type: array + clientAuth: + description: ClientAuth defines the parameters of the client authentication part of the TLS connection, if any. + properties: + clientAuthType: + description: ClientAuthType defines the client authentication type to apply. + enum: + - NoClientCert + - RequestClientCert + - VerifyClientCertIfGiven + - RequireAndVerifyClientCert + type: string + secretNames: + description: SecretName is the name of the referenced Kubernetes Secret to specify the certificate details. + items: + type: string + type: array + type: object + curvePreferences: + items: + type: string + type: array + maxVersion: + type: string + minVersion: + type: string + preferServerCipherSuites: + type: boolean + sniStrict: + type: boolean + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: tlsstores.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: TLSStore + listKind: TLSStoreList + plural: tlsstores + singular: tlsstore + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: TLSStore is a specification for a TLSStore resource. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TLSStoreSpec configures a TLSStore resource. + properties: + defaultCertificate: + description: DefaultCertificate holds a secret name for the TLSOption resource. + properties: + secretName: + description: SecretName is the name of the referenced Kubernetes Secret to specify the certificate details. + type: string + required: + - secretName + type: object + required: + - defaultCertificate + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: traefikservices.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: TraefikService + listKind: TraefikServiceList + plural: traefikservices + singular: traefikservice + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: TraefikService is the specification for a service (that an IngressRoute refers to) that is usually not a terminal service (i.e. not a pod of servers), as opposed to a Kubernetes Service. That is to say, it usually refers to other (children) services, which themselves can be TraefikServices or Services. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ServiceSpec defines whether a TraefikService is a load-balancer of services or a mirroring service. + properties: + mirroring: + description: Mirroring defines a mirroring service, which is composed of a main load-balancer, and a list of mirrors. + properties: + kind: + enum: + - Service + - TraefikService + type: string + maxBodySize: + format: int64 + type: integer + mirrors: + items: + description: MirrorService defines one of the mirrors of a Mirroring service. + properties: + kind: + enum: + - Service + - TraefikService + type: string + name: + description: Name is a reference to a Kubernetes Service object (for a load-balancer of servers), or to a TraefikService object (service load-balancer, mirroring, etc). The differentiation between the two is specified in the Kind field. + type: string + namespace: + type: string + passHostHeader: + type: boolean + percent: + type: integer + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding holds configuration for the forward of the response. + properties: + flushInterval: + type: string + type: object + scheme: + type: string + serversTransport: + type: string + sticky: + description: Sticky holds the sticky configuration. + properties: + cookie: + description: Cookie holds the sticky configuration based on cookie. + properties: + httpOnly: + type: boolean + name: + type: string + sameSite: + type: string + secure: + type: boolean + type: object + type: object + strategy: + type: string + weight: + description: Weight should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + type: array + name: + description: Name is a reference to a Kubernetes Service object (for a load-balancer of servers), or to a TraefikService object (service load-balancer, mirroring, etc). The differentiation between the two is specified in the Kind field. + type: string + namespace: + type: string + passHostHeader: + type: boolean + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding holds configuration for the forward of the response. + properties: + flushInterval: + type: string + type: object + scheme: + type: string + serversTransport: + type: string + sticky: + description: Sticky holds the sticky configuration. + properties: + cookie: + description: Cookie holds the sticky configuration based on cookie. + properties: + httpOnly: + type: boolean + name: + type: string + sameSite: + type: string + secure: + type: boolean + type: object + type: object + strategy: + type: string + weight: + description: Weight should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + weighted: + description: WeightedRoundRobin defines a load-balancer of services. + properties: + services: + items: + description: Service defines an upstream to proxy traffic. + properties: + kind: + enum: + - Service + - TraefikService + type: string + name: + description: Name is a reference to a Kubernetes Service object (for a load-balancer of servers), or to a TraefikService object (service load-balancer, mirroring, etc). The differentiation between the two is specified in the Kind field. + type: string + namespace: + type: string + passHostHeader: + type: boolean + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding holds configuration for the forward of the response. + properties: + flushInterval: + type: string + type: object + scheme: + type: string + serversTransport: + type: string + sticky: + description: Sticky holds the sticky configuration. + properties: + cookie: + description: Cookie holds the sticky configuration based on cookie. + properties: + httpOnly: + type: boolean + name: + type: string + sameSite: + type: string + secure: + type: boolean + type: object + type: object + strategy: + type: string + weight: + description: Weight should only be specified when Name references a TraefikService object (and to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + type: array + sticky: + description: Sticky holds the sticky configuration. + properties: + cookie: + description: Cookie holds the sticky configuration based on cookie. + properties: + httpOnly: + type: boolean + name: + type: string + sameSite: + type: string + secure: + type: boolean + type: object + type: object + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 4b8417632..086757fe9 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -5,6 +5,7 @@ import ( "bytes" "context" "crypto/sha256" + "encoding/json" "errors" "fmt" "os" @@ -23,6 +24,7 @@ import ( "github.com/traefik/traefik/v2/pkg/safe" "github.com/traefik/traefik/v2/pkg/tls" corev1 "k8s.io/api/core/v1" + apiextensionv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/intstr" ) @@ -219,6 +221,24 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) conf.HTTP.Services[serviceName] = errorPageService } + plugin, err := createPluginMiddleware(middleware.Spec.Plugin) + if err != nil { + log.FromContext(ctxMid).Errorf("Error while reading plugins middleware: %v", err) + continue + } + + rateLimit, err := createRateLimitMiddleware(middleware.Spec.RateLimit) + if err != nil { + log.FromContext(ctxMid).Errorf("Error while reading rateLimit middleware: %v", err) + continue + } + + retry, err := createRetryMiddleware(middleware.Spec.Retry) + if err != nil { + log.FromContext(ctxMid).Errorf("Error while reading retry middleware: %v", err) + continue + } + conf.HTTP.Middlewares[id] = &dynamic.Middleware{ AddPrefix: middleware.Spec.AddPrefix, StripPrefix: middleware.Spec.StripPrefix, @@ -229,7 +249,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) IPWhiteList: middleware.Spec.IPWhiteList, Headers: middleware.Spec.Headers, Errors: errorPage, - RateLimit: middleware.Spec.RateLimit, + RateLimit: rateLimit, RedirectRegex: middleware.Spec.RedirectRegex, RedirectScheme: middleware.Spec.RedirectScheme, BasicAuth: basicAuth, @@ -240,9 +260,9 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) CircuitBreaker: middleware.Spec.CircuitBreaker, Compress: middleware.Spec.Compress, PassTLSClientCert: middleware.Spec.PassTLSClientCert, - Retry: middleware.Spec.Retry, + Retry: retry, ContentType: middleware.Spec.ContentType, - Plugin: middleware.Spec.Plugin, + Plugin: plugin, } } @@ -356,6 +376,62 @@ func getServicePort(svc *corev1.Service, port intstr.IntOrString) (*corev1.Servi return &corev1.ServicePort{Port: port.IntVal}, nil } +func createPluginMiddleware(plugins map[string]apiextensionv1.JSON) (map[string]dynamic.PluginConf, error) { + if plugins == nil { + return nil, nil + } + + data, err := json.Marshal(plugins) + if err != nil { + return nil, err + } + + pc := map[string]dynamic.PluginConf{} + err = json.Unmarshal(data, &pc) + if err != nil { + return nil, err + } + + return pc, nil +} + +func createRateLimitMiddleware(rateLimit *v1alpha1.RateLimit) (*dynamic.RateLimit, error) { + if rateLimit == nil { + return nil, nil + } + + rl := &dynamic.RateLimit{Average: rateLimit.Average} + rl.SetDefaults() + + if rateLimit.Burst != nil { + rl.Burst = *rateLimit.Burst + } + + if rateLimit.Period != nil { + err := rl.Period.Set(rateLimit.Period.String()) + if err != nil { + return nil, err + } + } + + return rl, nil +} + +func createRetryMiddleware(retry *v1alpha1.Retry) (*dynamic.Retry, error) { + if retry == nil { + return nil, nil + } + + r := &dynamic.Retry{Attempts: retry.Attempts} + + err := r.InitialInterval.Set(retry.InitialInterval.String()) + if err != nil { + return nil, err + } + + return r, nil +} + func (p *Provider) createErrorPageMiddleware(client Client, namespace string, errorPage *v1alpha1.ErrorPage) (*dynamic.ErrorPage, *dynamic.Service, error) { if errorPage == nil { return nil, nil, nil diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go index b48bd747b..0de26e8fa 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go @@ -10,17 +10,18 @@ import ( // IngressRouteSpec is a specification for a IngressRouteSpec resource. type IngressRouteSpec struct { Routes []Route `json:"routes"` - EntryPoints []string `json:"entryPoints"` + EntryPoints []string `json:"entryPoints,omitempty"` TLS *TLS `json:"tls,omitempty"` } // Route contains the set of routes. type Route struct { - Match string `json:"match"` + Match string `json:"match"` + // +kubebuilder:validation:Enum=Rule Kind string `json:"kind"` - Priority int `json:"priority"` + Priority int `json:"priority,omitempty"` Services []Service `json:"services,omitempty"` - Middlewares []MiddlewareRef `json:"middlewares"` + Middlewares []MiddlewareRef `json:"middlewares,omitempty"` } // TLS contains the TLS certificates configuration of the routes. @@ -34,7 +35,7 @@ type Route struct { type TLS struct { // SecretName is the name of the referenced Kubernetes Secret to specify the // certificate details. - SecretName string `json:"secretName"` + SecretName string `json:"secretName,omitempty"` // Options is a reference to a TLSOption, that specifies the parameters of the TLS connection. Options *TLSOptionRef `json:"options,omitempty"` // Store is a reference to a TLSStore, that specifies the parameters of the TLS store. @@ -46,13 +47,13 @@ type TLS struct { // TLSOptionRef is a ref to the TLSOption resources. type TLSOptionRef struct { Name string `json:"name"` - Namespace string `json:"namespace"` + Namespace string `json:"namespace,omitempty"` } // TLSStoreRef is a ref to the TLSStore resource. type TLSStoreRef struct { Name string `json:"name"` - Namespace string `json:"namespace"` + Namespace string `json:"namespace,omitempty"` } // LoadBalancerSpec can reference either a Kubernetes Service object (a load-balancer of servers), @@ -61,14 +62,16 @@ type LoadBalancerSpec struct { // Name is a reference to a Kubernetes Service object (for a load-balancer of servers), // or to a TraefikService object (service load-balancer, mirroring, etc). // The differentiation between the two is specified in the Kind field. - Name string `json:"name"` - Kind string `json:"kind"` - Namespace string `json:"namespace"` + Name string `json:"name"` + // +kubebuilder:validation:Enum=Service;TraefikService + Kind string `json:"kind,omitempty"` + Namespace string `json:"namespace,omitempty"` Sticky *dynamic.Sticky `json:"sticky,omitempty"` // Port and all the fields below are related to a servers load-balancer, // and therefore should only be specified when Name references a Kubernetes Service. - Port intstr.IntOrString `json:"port"` + + Port intstr.IntOrString `json:"port,omitempty"` Scheme string `json:"scheme,omitempty"` Strategy string `json:"strategy,omitempty"` PassHostHeader *bool `json:"passHostHeader,omitempty"` @@ -82,17 +85,18 @@ type LoadBalancerSpec struct { // Service defines an upstream to proxy traffic. type Service struct { - LoadBalancerSpec + LoadBalancerSpec `json:",inline"` } // MiddlewareRef is a ref to the Middleware resources. type MiddlewareRef struct { Name string `json:"name"` - Namespace string `json:"namespace"` + Namespace string `json:"namespace,omitempty"` } // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:storageversion // IngressRoute is an Ingress CRD specification. type IngressRoute struct { diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go index 5c11256fd..a508328e7 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go @@ -10,7 +10,7 @@ import ( // IngressRouteTCPSpec is a specification for a IngressRouteTCPSpec resource. type IngressRouteTCPSpec struct { Routes []RouteTCP `json:"routes"` - EntryPoints []string `json:"entryPoints"` + EntryPoints []string `json:"entryPoints,omitempty"` TLS *TLSTCP `json:"tls,omitempty"` } @@ -31,32 +31,32 @@ type RouteTCP struct { type TLSTCP struct { // SecretName is the name of the referenced Kubernetes Secret to specify the // certificate details. - SecretName string `json:"secretName"` - Passthrough bool `json:"passthrough"` + SecretName string `json:"secretName,omitempty"` + Passthrough bool `json:"passthrough,omitempty"` // Options is a reference to a TLSOption, that specifies the parameters of the TLS connection. - Options *TLSOptionTCPRef `json:"options"` + Options *TLSOptionTCPRef `json:"options,omitempty"` // Store is a reference to a TLSStore, that specifies the parameters of the TLS store. - Store *TLSStoreTCPRef `json:"store"` - CertResolver string `json:"certResolver"` + Store *TLSStoreTCPRef `json:"store,omitempty"` + CertResolver string `json:"certResolver,omitempty"` Domains []types.Domain `json:"domains,omitempty"` } // TLSOptionTCPRef is a ref to the TLSOption resources. type TLSOptionTCPRef struct { Name string `json:"name"` - Namespace string `json:"namespace"` + Namespace string `json:"namespace,omitempty"` } // TLSStoreTCPRef is a ref to the TLSStore resources. type TLSStoreTCPRef struct { Name string `json:"name"` - Namespace string `json:"namespace"` + Namespace string `json:"namespace,omitempty"` } // ServiceTCP defines an upstream to proxy traffic. type ServiceTCP struct { Name string `json:"name"` - Namespace string `json:"namespace"` + Namespace string `json:"namespace,omitempty"` Port intstr.IntOrString `json:"port"` Weight *int `json:"weight,omitempty"` TerminationDelay *int `json:"terminationDelay,omitempty"` @@ -65,6 +65,7 @@ type ServiceTCP struct { // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:storageversion // IngressRouteTCP is an Ingress CRD specification. type IngressRouteTCP struct { diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressrouteudp.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressrouteudp.go index be6140e13..eb44cd64f 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressrouteudp.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressrouteudp.go @@ -8,7 +8,7 @@ import ( // IngressRouteUDPSpec is a specification for a IngressRouteUDPSpec resource. type IngressRouteUDPSpec struct { Routes []RouteUDP `json:"routes"` - EntryPoints []string `json:"entryPoints"` + EntryPoints []string `json:"entryPoints,omitempty"` } // RouteUDP contains the set of routes. @@ -19,19 +19,20 @@ type RouteUDP struct { // TLSOptionUDPRef is a ref to the TLSOption resources. type TLSOptionUDPRef struct { Name string `json:"name"` - Namespace string `json:"namespace"` + Namespace string `json:"namespace,omitempty"` } // ServiceUDP defines an upstream to proxy traffic. type ServiceUDP struct { Name string `json:"name"` - Namespace string `json:"namespace"` + Namespace string `json:"namespace,omitempty"` Port intstr.IntOrString `json:"port"` Weight *int `json:"weight,omitempty"` } // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:storageversion // IngressRouteUDP is an Ingress CRD specification. type IngressRouteUDP struct { diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go index 07e96611f..2fff164a5 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go @@ -2,11 +2,14 @@ package v1alpha1 import ( "github.com/traefik/traefik/v2/pkg/config/dynamic" + apiextensionv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" ) // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:storageversion // Middleware is a specification for a Middleware resource. type Middleware struct { @@ -20,29 +23,29 @@ type Middleware struct { // MiddlewareSpec holds the Middleware configuration. type MiddlewareSpec struct { - AddPrefix *dynamic.AddPrefix `json:"addPrefix,omitempty"` - StripPrefix *dynamic.StripPrefix `json:"stripPrefix,omitempty"` - StripPrefixRegex *dynamic.StripPrefixRegex `json:"stripPrefixRegex,omitempty"` - ReplacePath *dynamic.ReplacePath `json:"replacePath,omitempty"` - ReplacePathRegex *dynamic.ReplacePathRegex `json:"replacePathRegex,omitempty"` - Chain *Chain `json:"chain,omitempty"` - IPWhiteList *dynamic.IPWhiteList `json:"ipWhiteList,omitempty"` - Headers *dynamic.Headers `json:"headers,omitempty"` - Errors *ErrorPage `json:"errors,omitempty"` - RateLimit *dynamic.RateLimit `json:"rateLimit,omitempty"` - RedirectRegex *dynamic.RedirectRegex `json:"redirectRegex,omitempty"` - RedirectScheme *dynamic.RedirectScheme `json:"redirectScheme,omitempty"` - BasicAuth *BasicAuth `json:"basicAuth,omitempty"` - DigestAuth *DigestAuth `json:"digestAuth,omitempty"` - ForwardAuth *ForwardAuth `json:"forwardAuth,omitempty"` - InFlightReq *dynamic.InFlightReq `json:"inFlightReq,omitempty"` - Buffering *dynamic.Buffering `json:"buffering,omitempty"` - CircuitBreaker *dynamic.CircuitBreaker `json:"circuitBreaker,omitempty"` - Compress *dynamic.Compress `json:"compress,omitempty"` - PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty"` - Retry *dynamic.Retry `json:"retry,omitempty"` - ContentType *dynamic.ContentType `json:"contentType,omitempty"` - Plugin map[string]dynamic.PluginConf `json:"plugin,omitempty"` + AddPrefix *dynamic.AddPrefix `json:"addPrefix,omitempty"` + StripPrefix *dynamic.StripPrefix `json:"stripPrefix,omitempty"` + StripPrefixRegex *dynamic.StripPrefixRegex `json:"stripPrefixRegex,omitempty"` + ReplacePath *dynamic.ReplacePath `json:"replacePath,omitempty"` + ReplacePathRegex *dynamic.ReplacePathRegex `json:"replacePathRegex,omitempty"` + Chain *Chain `json:"chain,omitempty"` + IPWhiteList *dynamic.IPWhiteList `json:"ipWhiteList,omitempty"` + Headers *dynamic.Headers `json:"headers,omitempty"` + Errors *ErrorPage `json:"errors,omitempty"` + RateLimit *RateLimit `json:"rateLimit,omitempty"` + RedirectRegex *dynamic.RedirectRegex `json:"redirectRegex,omitempty"` + RedirectScheme *dynamic.RedirectScheme `json:"redirectScheme,omitempty"` + BasicAuth *BasicAuth `json:"basicAuth,omitempty"` + DigestAuth *DigestAuth `json:"digestAuth,omitempty"` + ForwardAuth *ForwardAuth `json:"forwardAuth,omitempty"` + InFlightReq *dynamic.InFlightReq `json:"inFlightReq,omitempty"` + Buffering *dynamic.Buffering `json:"buffering,omitempty"` + CircuitBreaker *dynamic.CircuitBreaker `json:"circuitBreaker,omitempty"` + Compress *dynamic.Compress `json:"compress,omitempty"` + PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty"` + Retry *Retry `json:"retry,omitempty"` + ContentType *dynamic.ContentType `json:"contentType,omitempty"` + Plugin map[string]apiextensionv1.JSON `json:"plugin,omitempty"` } // +k8s:deepcopy-gen=true @@ -110,3 +113,21 @@ type MiddlewareList struct { Items []Middleware `json:"items"` } + +// +k8s:deepcopy-gen=true + +// RateLimit holds the rate limiting configuration for a given router. +type RateLimit struct { + Average int64 `json:"average,omitempty"` + Period *intstr.IntOrString `json:"period,omitempty"` + Burst *int64 `json:"burst,omitempty"` + SourceCriterion *dynamic.SourceCriterion `json:"sourceCriterion,omitempty"` +} + +// +k8s:deepcopy-gen=true + +// Retry holds the retry configuration. +type Retry struct { + Attempts int `json:"attempts,omitempty"` + InitialInterval intstr.IntOrString `json:"initialInterval,omitempty"` +} diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go index 37f211ff1..130598214 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go @@ -7,6 +7,7 @@ import ( // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:storageversion // ServersTransport is a specification for a ServersTransport resource. type ServersTransport struct { @@ -20,21 +21,31 @@ type ServersTransport struct { // ServersTransportSpec options to configure communication between Traefik and the servers. type ServersTransportSpec struct { - ServerName string `description:"ServerName used to contact the server" json:"serverName,omitempty" toml:"serverName,omitempty" yaml:"serverName,omitempty" export:"true"` - InsecureSkipVerify bool `description:"Disable SSL certificate verification." json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"` - RootCAsSecrets []string `description:"Add cert file for self-signed certificate." json:"rootCAsSecrets,omitempty" toml:"rootCAsSecrets,omitempty" yaml:"rootCAsSecrets,omitempty"` - CertificatesSecrets []string `description:"Certificates for mTLS." json:"certificatesSecrets,omitempty" toml:"certificatesSecrets,omitempty" yaml:"certificatesSecrets,omitempty"` - MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" json:"maxIdleConnsPerHost,omitempty" toml:"maxIdleConnsPerHost,omitempty" yaml:"maxIdleConnsPerHost,omitempty" export:"true"` - ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers." json:"forwardingTimeouts,omitempty" toml:"forwardingTimeouts,omitempty" yaml:"forwardingTimeouts,omitempty" export:"true"` + // ServerName used to contact the server. + ServerName string `json:"serverName,omitempty"` + // Disable SSL certificate verification. + InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"` + // Add cert file for self-signed certificate. + RootCAsSecrets []string `json:"rootCAsSecrets,omitempty"` + // Certificates for mTLS. + CertificatesSecrets []string `json:"certificatesSecrets,omitempty"` + // If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used. + MaxIdleConnsPerHost int `json:"maxIdleConnsPerHost,omitempty"` + // Timeouts for requests forwarded to the backend servers. + ForwardingTimeouts *ForwardingTimeouts `json:"forwardingTimeouts,omitempty"` } // +k8s:deepcopy-gen=true // ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers. type ForwardingTimeouts struct { - DialTimeout *intstr.IntOrString `description:"The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists." json:"dialTimeout,omitempty" toml:"dialTimeout,omitempty" yaml:"dialTimeout,omitempty" export:"true"` - ResponseHeaderTimeout *intstr.IntOrString `description:"The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists." json:"responseHeaderTimeout,omitempty" toml:"responseHeaderTimeout,omitempty" yaml:"responseHeaderTimeout,omitempty" export:"true"` - IdleConnTimeout *intstr.IntOrString `description:"The maximum period for which an idle HTTP keep-alive connection will remain open before closing itself" json:"idleConnTimeout,omitempty" toml:"idleConnTimeout,omitempty" yaml:"idleConnTimeout,omitempty" export:"true"` + // The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists. + DialTimeout *intstr.IntOrString `json:"dialTimeout,omitempty"` + // The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). + // If zero, no timeout exists. + ResponseHeaderTimeout *intstr.IntOrString `json:"responseHeaderTimeout,omitempty"` + // The maximum period for which an idle HTTP keep-alive connection will remain open before closing itself. + IdleConnTimeout *intstr.IntOrString `json:"idleConnTimeout,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/service.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/service.go index 2e01f8506..fa70a253b 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/service.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/service.go @@ -7,6 +7,7 @@ import ( // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:storageversion // TraefikService is the specification for a service (that an IngressRoute refers // to) that is usually not a terminal service (i.e. not a pod of servers), as @@ -43,8 +44,9 @@ type ServiceSpec struct { // Mirroring defines a mirroring service, which is composed of a main // load-balancer, and a list of mirrors. type Mirroring struct { - LoadBalancerSpec - MaxBodySize *int64 + LoadBalancerSpec `json:",inline"` + + MaxBodySize *int64 `json:"maxBodySize,omitempty"` Mirrors []MirrorService `json:"mirrors,omitempty"` } @@ -52,7 +54,8 @@ type Mirroring struct { // MirrorService defines one of the mirrors of a Mirroring service. type MirrorService struct { - LoadBalancerSpec + LoadBalancerSpec `json:",inline"` + Percent int `json:"percent,omitempty"` } diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go index 6c6b67ad8..52eb84a48 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go @@ -6,6 +6,7 @@ import ( // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:storageversion // TLSOption is a specification for a TLSOption resource. type TLSOption struct { @@ -32,12 +33,11 @@ type TLSOptionSpec struct { // ClientAuth defines the parameters of the client authentication part of the TLS connection, if any. type ClientAuth struct { - // SecretName is the name of the referenced Kubernetes Secret to specify the - // certificate details. - SecretNames []string `json:"secretNames"` + // SecretName is the name of the referenced Kubernetes Secret to specify the certificate details. + SecretNames []string `json:"secretNames,omitempty"` + // +kubebuilder:validation:Enum=NoClientCert;RequestClientCert;VerifyClientCertIfGiven;RequireAndVerifyClientCert // ClientAuthType defines the client authentication type to apply. - // The available values are: "NoClientCert", "RequestClientCert", "VerifyClientCertIfGiven" and "RequireAndVerifyClientCert". - ClientAuthType string `json:"clientAuthType"` + ClientAuthType string `json:"clientAuthType,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go index 25dee994a..404f07f96 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go @@ -6,6 +6,7 @@ import ( // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:storageversion // TLSStore is a specification for a TLSStore resource. type TLSStore struct { @@ -26,9 +27,8 @@ type TLSStoreSpec struct { // DefaultCertificate holds a secret name for the TLSOption resource. type DefaultCertificate struct { - // SecretName is the name of the referenced Kubernetes Secret to specify the - // certificate details. - SecretName string `json:"secretName,omitempty"` + // SecretName is the name of the referenced Kubernetes Secret to specify the certificate details. + SecretName string `json:"secretName"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go index 2e6856095..eb264ce72 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go @@ -31,6 +31,7 @@ package v1alpha1 import ( dynamic "github.com/traefik/traefik/v2/pkg/config/dynamic" types "github.com/traefik/traefik/v2/pkg/types" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" runtime "k8s.io/apimachinery/pkg/runtime" intstr "k8s.io/apimachinery/pkg/util/intstr" ) @@ -662,7 +663,7 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) { } if in.RateLimit != nil { in, out := &in.RateLimit, &out.RateLimit - *out = new(dynamic.RateLimit) + *out = new(RateLimit) (*in).DeepCopyInto(*out) } if in.RedirectRegex != nil { @@ -717,7 +718,7 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) { } if in.Retry != nil { in, out := &in.Retry, &out.Retry - *out = new(dynamic.Retry) + *out = new(Retry) **out = **in } if in.ContentType != nil { @@ -727,7 +728,7 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) { } if in.Plugin != nil { in, out := &in.Plugin, &out.Plugin - *out = make(map[string]dynamic.PluginConf, len(*in)) + *out = make(map[string]v1.JSON, len(*in)) for key, val := range *in { (*out)[key] = *val.DeepCopy() } @@ -791,6 +792,54 @@ func (in *Mirroring) DeepCopy() *Mirroring { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RateLimit) DeepCopyInto(out *RateLimit) { + *out = *in + if in.Period != nil { + in, out := &in.Period, &out.Period + *out = new(intstr.IntOrString) + **out = **in + } + if in.Burst != nil { + in, out := &in.Burst, &out.Burst + *out = new(int64) + **out = **in + } + if in.SourceCriterion != nil { + in, out := &in.SourceCriterion, &out.SourceCriterion + *out = new(dynamic.SourceCriterion) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimit. +func (in *RateLimit) DeepCopy() *RateLimit { + if in == nil { + return nil + } + out := new(RateLimit) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Retry) DeepCopyInto(out *Retry) { + *out = *in + out.InitialInterval = in.InitialInterval + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Retry. +func (in *Retry) DeepCopy() *Retry { + if in == nil { + return nil + } + out := new(Retry) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Route) DeepCopyInto(out *Route) { *out = *in diff --git a/script/update-generated-crd-code.sh b/script/update-generated-crd-code.sh index 78b70420c..a58af5fe7 100755 --- a/script/update-generated-crd-code.sh +++ b/script/update-generated-crd-code.sh @@ -9,6 +9,7 @@ rm -rf "${REPO_ROOT}"/vendor go mod vendor chmod +x "${REPO_ROOT}"/vendor/k8s.io/code-generator/*.sh +# Generate the crd client "${REPO_ROOT}"/vendor/k8s.io/code-generator/generate-groups.sh \ all \ github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/provider/kubernetes/crd/generated \ @@ -18,12 +19,21 @@ chmod +x "${REPO_ROOT}"/vendor/k8s.io/code-generator/*.sh "$@" deepcopy-gen \ ---input-dirs github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/config/dynamic \ ---input-dirs github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/tls \ ---input-dirs github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/types \ ---output-package github.com/traefik/traefik \ --O zz_generated.deepcopy --go-header-file "${HACK_DIR}"/boilerplate.go.tmpl + --input-dirs github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/config/dynamic \ + --input-dirs github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/tls \ + --input-dirs github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/types \ + --output-package github.com/traefik/traefik \ + -O zz_generated.deepcopy --go-header-file "${HACK_DIR}"/boilerplate.go.tmpl cp -r "${REPO_ROOT}"/"${TRAEFIK_MODULE_VERSION:?}"/* "${REPO_ROOT}"; rm -rf "${REPO_ROOT}"/"${TRAEFIK_MODULE_VERSION:?}" +# Generate the CRD definitions for the documentation +go run "${REPO_ROOT}"/vendor/sigs.k8s.io/controller-tools/cmd/controller-gen \ + crd:crdVersions=v1 \ + paths="${REPO_ROOT}"/pkg/provider/kubernetes/crd/traefik/v1alpha1/... \ + output:dir="${REPO_ROOT}"/docs/content/reference/dynamic-configuration/ + +# Concatenate the CRD definitions for the integration tests +cat "${REPO_ROOT}"/docs/content/reference/dynamic-configuration/traefik.containo.us_*.yaml > "${REPO_ROOT}"/integration/fixtures/k8s/01-traefik-crd.yml + rm -rf "${REPO_ROOT}"/vendor diff --git a/tools.go b/tools.go index 1185af9e3..cb6b3341b 100644 --- a/tools.go +++ b/tools.go @@ -4,4 +4,5 @@ package main import ( _ "k8s.io/code-generator" + _ "sigs.k8s.io/controller-tools/cmd/controller-gen" ) From 606b43dc512fbeceb79d756a6ddcf796676dc884 Mon Sep 17 00:00:00 2001 From: Tom Moulard Date: Thu, 4 Mar 2021 09:24:03 +0100 Subject: [PATCH 13/18] Clarify doc for ingressclass name in k8s 1.18+ --- docs/content/providers/kubernetes-ingress.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/content/providers/kubernetes-ingress.md b/docs/content/providers/kubernetes-ingress.md index 438d16a29..56243c418 100644 --- a/docs/content/providers/kubernetes-ingress.md +++ b/docs/content/providers/kubernetes-ingress.md @@ -224,7 +224,9 @@ Otherwise, Ingresses missing the annotation, having an empty value, or the value If the Kubernetes cluster version is 1.18+, the new `IngressClass` resource can be leveraged to identify Ingress objects that should be processed. - In that case, Traefik will look for an `IngressClass` in the cluster with the controller value equal to *traefik.io/ingress-controller*. + In that case, Traefik will look for an `IngressClass` in the cluster with the controller value equal to *traefik.io/ingress-controller*. + + In addition to the controller value matching mechanism, the property `ingressClass` (if set) will be used to select IngressClasses by applying a strict matching on their name. Please see [this article](https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/) for more information or the example below. From 29908098e47f0458b5d5f50a59fe3583f63874c7 Mon Sep 17 00:00:00 2001 From: Manuel Zapf Date: Mon, 15 Mar 2021 11:16:04 +0100 Subject: [PATCH 14/18] Upgrade Ingress Handling to work with networkingv1/Ingress --- docs/content/migration/v2.md | 10 + docs/content/providers/kubernetes-ingress.md | 69 ++++- .../routing/providers/kubernetes-ingress.md | 129 ++++++++- .../ingress/builder_ingress_test.go | 38 ++- pkg/provider/kubernetes/ingress/client.go | 197 +++++++++---- .../kubernetes/ingress/client_mock_test.go | 15 +- .../kubernetes/ingress/client_test.go | 78 +++++ ...9-Ingress-with-defaultbackend_endpoint.yml | 24 ++ ...19-Ingress-with-defaultbackend_ingress.yml | 12 + ...19-Ingress-with-defaultbackend_service.yml | 22 ++ ...9-Ingress-with-empty-pathType_endpoint.yml | 11 + ...19-Ingress-with-empty-pathType_ingress.yml | 18 ++ ...19-Ingress-with-empty-pathType_service.yml | 10 + ...9-Ingress-with-exact-pathType_endpoint.yml | 11 + ...19-Ingress-with-exact-pathType_ingress.yml | 16 ++ ...19-Ingress-with-exact-pathType_service.yml | 10 + ...plementationSpecific-pathType_endpoint.yml | 11 + ...mplementationSpecific-pathType_ingress.yml | 18 ++ ...mplementationSpecific-pathType_service.yml | 10 + ...gress-with-ingress-annotation_endpoint.yml | 11 + ...ngress-with-ingress-annotation_ingress.yml | 17 ++ ...ngress-with-ingress-annotation_service.yml | 10 + ...v19-Ingress-with-ingressClass_endpoint.yml | 11 + .../v19-Ingress-with-ingressClass_ingress.yml | 16 ++ ...Ingress-with-ingressClass_ingressclass.yml | 6 + .../v19-Ingress-with-ingressClass_service.yml | 10 + ...ess-with-missing-ingressClass_endpoint.yml | 11 + ...ress-with-missing-ingressClass_ingress.yml | 16 ++ ...ress-with-missing-ingressClass_service.yml | 10 + .../v19-Ingress-with-named-port_endpoint.yml | 12 + .../v19-Ingress-with-named-port_ingress.yml | 16 ++ .../v19-Ingress-with-named-port_service.yml | 12 + .../v19-Ingress-with-no-pathType_endpoint.yml | 11 + .../v19-Ingress-with-no-pathType_ingress.yml | 17 ++ .../v19-Ingress-with-no-pathType_service.yml | 10 + ...-Ingress-with-prefix-pathType_endpoint.yml | 11 + ...9-Ingress-with-prefix-pathType_ingress.yml | 16 ++ ...9-Ingress-with-prefix-pathType_service.yml | 10 + pkg/provider/kubernetes/ingress/kubernetes.go | 43 +-- .../kubernetes/ingress/kubernetes_test.go | 269 +++++++++++++++++- 40 files changed, 1141 insertions(+), 113 deletions(-) create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_endpoint.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_ingress.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_service.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_endpoint.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_ingress.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_service.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_endpoint.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_ingress.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_service.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_endpoint.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_ingress.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_service.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_endpoint.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_ingress.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_service.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_endpoint.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_ingress.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_ingressclass.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_service.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_endpoint.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_ingress.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_service.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_endpoint.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_ingress.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_service.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_endpoint.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_ingress.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_service.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_endpoint.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_ingress.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_service.yml diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index fc99f99f3..e88448e32 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -348,3 +348,13 @@ After deploying the new [Traefik CRDs](../reference/dynamic-configuration/kubern Please note that the unknown fields will not be pruned when migrating from `apiextensions.k8s.io/v1beta1` to `apiextensions.k8s.io/v1` CRDs. For more details check out the official [documentation](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema). + +### Kubernetes Ingress + +Traefik v2.5 moves forward for the Ingress provider to support Kubernetes v1.22. + +Traefik now supports only v1.14+ Kubernetes clusters, which means the support of `extensions/v1beta1` API Version ingresses has been dropped. + +The `extensions/v1beta1` API Version should now be replaced either by `networking.k8s.io/v1beta1` or by `networking.k8s.io/v1` (as of Kubernetes v1.19+). + +The support of the `networking.k8s.io/v1beta1` API Version will stop in Kubernetes v1.22. diff --git a/docs/content/providers/kubernetes-ingress.md b/docs/content/providers/kubernetes-ingress.md index 56243c418..ccbde7969 100644 --- a/docs/content/providers/kubernetes-ingress.md +++ b/docs/content/providers/kubernetes-ingress.md @@ -6,6 +6,10 @@ The Kubernetes Ingress Controller. The Traefik Kubernetes Ingress provider is a Kubernetes Ingress controller; that is to say, it manages access to cluster services by supporting the [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) specification. +## Requirements + +Traefik supports `1.14+` Kubernetes clusters. + ## Routing Configuration See the dedicated section in [routing](../routing/providers/kubernetes-ingress.md). @@ -31,9 +35,9 @@ The provider then watches for incoming ingresses events, such as the example bel and derives the corresponding dynamic configuration from it, which in turn creates the resulting routers, services, handlers, etc. -```yaml tab="File (YAML)" +```yaml tab="Ingress" kind: Ingress -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1beta1 metadata: name: "foo" namespace: production @@ -53,6 +57,32 @@ spec: servicePort: 80 ``` +```yaml tab="Ingress Kubernetes v1.19+" +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "foo" + namespace: production + +spec: + rules: + - host: example.net + http: + paths: + - path: /bar + backend: + service: + name: service1 + port: + number: 80 + - path: /foo + backend: + service: + name: service1 + port: + number: 80 +``` + ## LetsEncrypt Support with the Ingress Provider By design, Traefik is a stateless application, @@ -220,7 +250,7 @@ Value of `kubernetes.io/ingress.class` annotation that identifies Ingress object If the parameter is set, only Ingresses containing an annotation with the same value are processed. Otherwise, Ingresses missing the annotation, having an empty value, or the value `traefik` are processed. -!!! info "Kubernetes 1.18+" +??? info "Kubernetes 1.18+" If the Kubernetes cluster version is 1.18+, the new `IngressClass` resource can be leveraged to identify Ingress objects that should be processed. @@ -256,6 +286,39 @@ Otherwise, Ingresses missing the annotation, having an empty value, or the value servicePort: 80 ``` +??? info "Kubernetes 1.19+" + + If the Kubernetes cluster version is 1.19+, + prefer using the `networking.k8s.io/v1` [apiVersion](https://v1-19.docs.kubernetes.io/docs/setup/release/notes/#api-change) of `Ingress` and `IngressClass`. + + ```yaml tab="IngressClass" + apiVersion: networking.k8s.io/v1 + kind: IngressClass + metadata: + name: traefik-lb + spec: + controller: traefik.io/ingress-controller + ``` + + ```yaml tab="Ingress" + apiVersion: "networking.k8s.io/v1" + kind: "Ingress" + metadata: + name: "example-ingress" + spec: + ingressClassName: "traefik-lb" + rules: + - host: "*.example.com" + http: + paths: + - path: "/example" + backend: + service: + name: "example-service" + port: + number: 80 + ``` + ```toml tab="File (TOML)" [providers.kubernetesIngress] ingressClass = "traefik-internal" diff --git a/docs/content/routing/providers/kubernetes-ingress.md b/docs/content/routing/providers/kubernetes-ingress.md index ff4542122..36f6d7dbf 100644 --- a/docs/content/routing/providers/kubernetes-ingress.md +++ b/docs/content/routing/providers/kubernetes-ingress.md @@ -85,6 +85,33 @@ which in turn will create the resulting routers, services, handlers, etc. servicePort: 80 ``` + ```yaml tab="Ingress Kubernetes v1.19+" + kind: Ingress + apiVersion: networking.k8s.io/v1 + metadata: + name: myingress + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: web + + spec: + rules: + - host: example.com + http: + paths: + - path: /bar + backend: + service: + name: whoami + port: + number: 80 + - path: /foo + backend: + service: + name: whoami + port: + number: 80 + ``` + ```yaml tab="Traefik" apiVersion: v1 kind: ServiceAccount @@ -434,6 +461,33 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d servicePort: 80 ``` + ```yaml tab="Ingress Kubernetes v1.19+" + kind: Ingress + apiVersion: networking.k8s.io/v1 + metadata: + name: myingress + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + + spec: + rules: + - host: example.com + http: + paths: + - path: /bar + backend: + service: + name: whoami + port: + number: 80 + - path: /foo + backend: + service: + name: whoami + port: + number: 80 + ``` + ```yaml tab="Traefik" apiVersion: v1 kind: ServiceAccount @@ -613,6 +667,34 @@ For more options, please refer to the available [annotations](#on-ingress). servicePort: 80 ``` + ```yaml tab="Ingress Kubernetes v1.19+" + kind: Ingress + apiVersion: networking.k8s.io/v1 + metadata: + name: myingress + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: true + + spec: + rules: + - host: example.com + http: + paths: + - path: /bar + backend: + service: + name: whoami + port: + number: 80 + - path: /foo + backend: + service: + name: whoami + port: + number: 80 + ``` + ```yaml tab="Traefik" apiVersion: v1 kind: ServiceAccount @@ -732,6 +814,31 @@ For more options, please refer to the available [annotations](#on-ingress). tls: - secretName: supersecret ``` + + ```yaml tab="Ingress Kubernetes v1.19+" + kind: Ingress + apiVersion: networking.k8s.io/v1 + metadata: + name: foo + namespace: production + + spec: + rules: + - host: example.net + http: + paths: + - path: /bar + backend: + service: + name: service1 + port: + number: 80 + # Only selects which certificate(s) should be loaded from the secret, in order to terminate TLS. + # Doesn't enable TLS for that ingress (hence for the underlying router). + # Please see the TLS annotations on ingress made for that purpose. + tls: + - secretName: supersecret + ``` ```yaml tab="Secret" apiVersion: v1 @@ -777,16 +884,30 @@ and will connect via TLS automatically. Ingresses can be created that look like the following: -```yaml +```yaml tab="Ingress" apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: cheese spec: - backend: - serviceName: stilton - servicePort: 80 + defaultBackend: + serviceName: stilton + serverPort: 80 +``` + +```yaml tab="Ingress Kubernetes v1.19+" +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: cheese + +spec: + defaultBackend: + service: + name: stilton + port: + number: 80 ``` This ingress follows the Global Default Backend property of ingresses. diff --git a/pkg/provider/kubernetes/ingress/builder_ingress_test.go b/pkg/provider/kubernetes/ingress/builder_ingress_test.go index f08f325ac..56ba2d286 100644 --- a/pkg/provider/kubernetes/ingress/builder_ingress_test.go +++ b/pkg/provider/kubernetes/ingress/builder_ingress_test.go @@ -1,11 +1,9 @@ package ingress -import ( - "k8s.io/api/networking/v1beta1" -) +import networkingv1 "k8s.io/api/networking/v1" -func buildIngress(opts ...func(*v1beta1.Ingress)) *v1beta1.Ingress { - i := &v1beta1.Ingress{} +func buildIngress(opts ...func(*networkingv1.Ingress)) *networkingv1.Ingress { + i := &networkingv1.Ingress{} i.Kind = "Ingress" for _, opt := range opts { opt(i) @@ -13,15 +11,15 @@ func buildIngress(opts ...func(*v1beta1.Ingress)) *v1beta1.Ingress { return i } -func iNamespace(value string) func(*v1beta1.Ingress) { - return func(i *v1beta1.Ingress) { +func iNamespace(value string) func(*networkingv1.Ingress) { + return func(i *networkingv1.Ingress) { i.Namespace = value } } -func iRules(opts ...func(*v1beta1.IngressSpec)) func(*v1beta1.Ingress) { - return func(i *v1beta1.Ingress) { - s := &v1beta1.IngressSpec{} +func iRules(opts ...func(*networkingv1.IngressSpec)) func(*networkingv1.Ingress) { + return func(i *networkingv1.Ingress) { + s := &networkingv1.IngressSpec{} for _, opt := range opts { opt(s) } @@ -29,9 +27,9 @@ func iRules(opts ...func(*v1beta1.IngressSpec)) func(*v1beta1.Ingress) { } } -func iRule(opts ...func(*v1beta1.IngressRule)) func(*v1beta1.IngressSpec) { - return func(spec *v1beta1.IngressSpec) { - r := &v1beta1.IngressRule{} +func iRule(opts ...func(*networkingv1.IngressRule)) func(*networkingv1.IngressSpec) { + return func(spec *networkingv1.IngressSpec) { + r := &networkingv1.IngressRule{} for _, opt := range opts { opt(r) } @@ -39,24 +37,24 @@ func iRule(opts ...func(*v1beta1.IngressRule)) func(*v1beta1.IngressSpec) { } } -func iHost(name string) func(*v1beta1.IngressRule) { - return func(rule *v1beta1.IngressRule) { +func iHost(name string) func(*networkingv1.IngressRule) { + return func(rule *networkingv1.IngressRule) { rule.Host = name } } -func iTLSes(opts ...func(*v1beta1.IngressTLS)) func(*v1beta1.Ingress) { - return func(i *v1beta1.Ingress) { +func iTLSes(opts ...func(*networkingv1.IngressTLS)) func(*networkingv1.Ingress) { + return func(i *networkingv1.Ingress) { for _, opt := range opts { - iTLS := v1beta1.IngressTLS{} + iTLS := networkingv1.IngressTLS{} opt(&iTLS) i.Spec.TLS = append(i.Spec.TLS, iTLS) } } } -func iTLS(secret string, hosts ...string) func(*v1beta1.IngressTLS) { - return func(i *v1beta1.IngressTLS) { +func iTLS(secret string, hosts ...string) func(*networkingv1.IngressTLS) { + return func(i *networkingv1.IngressTLS) { i.SecretName = secret i.Hosts = hosts } diff --git a/pkg/provider/kubernetes/ingress/client.go b/pkg/provider/kubernetes/ingress/client.go index 743f3f621..87351953a 100644 --- a/pkg/provider/kubernetes/ingress/client.go +++ b/pkg/provider/kubernetes/ingress/client.go @@ -13,11 +13,12 @@ import ( "github.com/traefik/traefik/v2/pkg/log" traefikversion "github.com/traefik/traefik/v2/pkg/version" corev1 "k8s.io/api/core/v1" - extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + networkingv1 "k8s.io/api/networking/v1" networkingv1beta1 "k8s.io/api/networking/v1beta1" kubeerror "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -54,12 +55,12 @@ func (reh *resourceEventHandler) OnDelete(obj interface{}) { // The stores can then be accessed via the Get* functions. type Client interface { WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) - GetIngresses() []*networkingv1beta1.Ingress + GetIngresses() []*networkingv1.Ingress GetIngressClasses() ([]*networkingv1beta1.IngressClass, error) GetService(namespace, name string) (*corev1.Service, bool, error) GetSecret(namespace, name string) (*corev1.Secret, bool, error) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) - UpdateIngressStatus(ing *networkingv1beta1.Ingress, ingStatus []corev1.LoadBalancerIngress) error + UpdateIngressStatus(ing *networkingv1.Ingress, ingStatus []corev1.LoadBalancerIngress) error GetServerVersion() (*version.Version, error) } @@ -167,9 +168,20 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< opts.LabelSelector = c.ingressLabelSelector } + serverVersion, err := c.GetServerVersion() + if err != nil { + return nil, fmt.Errorf("failed to get server version: %w", err) + } + for _, ns := range namespaces { factoryIngress := informers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, informers.WithNamespace(ns), informers.WithTweakListOptions(matchesLabelSelector)) - factoryIngress.Extensions().V1beta1().Ingresses().Informer().AddEventHandler(eventHandler) + + if supportsNetworkingV1Ingress(serverVersion) { + factoryIngress.Networking().V1().Ingresses().Informer().AddEventHandler(eventHandler) + } else { + factoryIngress.Networking().V1beta1().Ingresses().Informer().AddEventHandler(eventHandler) + } + c.factoriesIngress[ns] = factoryIngress factoryKube := informers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, informers.WithNamespace(ns)) @@ -208,12 +220,6 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< } } - serverVersion, err := c.GetServerVersion() - if err != nil { - log.WithoutContext().Errorf("Failed to get server version: %v", err) - return eventCh, nil - } - if supportsIngressClass(serverVersion) { c.clusterFactory = informers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod) c.clusterFactory.Networking().V1beta1().IngressClasses().Informer().AddEventHandler(eventHandler) @@ -230,42 +236,59 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< } // GetIngresses returns all Ingresses for observed namespaces in the cluster. -func (c *clientWrapper) GetIngresses() []*networkingv1beta1.Ingress { - var results []*networkingv1beta1.Ingress +func (c *clientWrapper) GetIngresses() []*networkingv1.Ingress { + var results []*networkingv1.Ingress + + serverVersion, err := c.GetServerVersion() + if err != nil { + log.Errorf("Failed to get server version: %v", err) + return results + } + + isNetworkingV1Supported := supportsNetworkingV1Ingress(serverVersion) for ns, factory := range c.factoriesIngress { - // extensions - ings, err := factory.Extensions().V1beta1().Ingresses().Lister().List(labels.Everything()) - if err != nil { - log.Errorf("Failed to list ingresses in namespace %s: %v", ns, err) - } - - for _, ing := range ings { - n, err := extensionsToNetworking(ing) + if isNetworkingV1Supported { + // networking + listNew, err := factory.Networking().V1().Ingresses().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to convert ingress %s from extensions/v1beta1 to networking/v1beta1: %v", ns, err) + log.WithoutContext().Errorf("Failed to list ingresses in namespace %s: %v", ns, err) continue } - results = append(results, n) + + results = append(results, listNew...) + continue } - // networking + // networking beta list, err := factory.Networking().V1beta1().Ingresses().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list ingresses in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list ingresses in namespace %s: %v", ns, err) + continue + } + + for _, ing := range list { + n, err := toNetworkingV1(ing) + if err != nil { + log.WithoutContext().Errorf("Failed to convert ingress %s from networking/v1beta1 to networking/v1: %v", ns, err) + continue + } + + addServiceFromV1Beta1(n, *ing) + + results = append(results, n) } - results = append(results, list...) } return results } -func extensionsToNetworking(ing marshaler) (*networkingv1beta1.Ingress, error) { +func toNetworkingV1(ing marshaler) (*networkingv1.Ingress, error) { data, err := ing.Marshal() if err != nil { return nil, err } - ni := &networkingv1beta1.Ingress{} + ni := &networkingv1.Ingress{} err = ni.Unmarshal(data) if err != nil { return nil, err @@ -274,16 +297,95 @@ func extensionsToNetworking(ing marshaler) (*networkingv1beta1.Ingress, error) { return ni, nil } +func addServiceFromV1Beta1(ing *networkingv1.Ingress, old networkingv1beta1.Ingress) { + if old.Spec.Backend != nil { + port := networkingv1.ServiceBackendPort{} + if old.Spec.Backend.ServicePort.Type == intstr.Int { + port.Number = old.Spec.Backend.ServicePort.IntVal + } else { + port.Name = old.Spec.Backend.ServicePort.StrVal + } + + if old.Spec.Backend.ServiceName != "" { + ing.Spec.DefaultBackend = &networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: old.Spec.Backend.ServiceName, + Port: port, + }, + } + } + } + + for rc, rule := range ing.Spec.Rules { + if rule.HTTP == nil { + continue + } + for pc, path := range rule.HTTP.Paths { + if path.Backend.Service == nil { + oldBackend := old.Spec.Rules[rc].HTTP.Paths[pc].Backend + + port := networkingv1.ServiceBackendPort{} + if oldBackend.ServicePort.Type == intstr.Int { + port.Number = oldBackend.ServicePort.IntVal + } else { + port.Name = oldBackend.ServicePort.StrVal + } + + svc := networkingv1.IngressServiceBackend{ + Name: oldBackend.ServiceName, + Port: port, + } + + ing.Spec.Rules[rc].HTTP.Paths[pc].Backend.Service = &svc + } + } + } +} + // UpdateIngressStatus updates an Ingress with a provided status. -func (c *clientWrapper) UpdateIngressStatus(src *networkingv1beta1.Ingress, ingStatus []corev1.LoadBalancerIngress) error { +func (c *clientWrapper) UpdateIngressStatus(src *networkingv1.Ingress, ingStatus []corev1.LoadBalancerIngress) error { if !c.isWatchedNamespace(src.Namespace) { return fmt.Errorf("failed to get ingress %s/%s: namespace is not within watched namespaces", src.Namespace, src.Name) } - if src.GetObjectKind().GroupVersionKind().Group != "networking.k8s.io" { + serverVersion, err := c.GetServerVersion() + if err != nil { + log.WithoutContext().Errorf("Failed to get server version: %v", err) + return err + } + + if !supportsNetworkingV1Ingress(serverVersion) { return c.updateIngressStatusOld(src, ingStatus) } + ing, err := c.factoriesIngress[c.lookupNamespace(src.Namespace)].Networking().V1().Ingresses().Lister().Ingresses(src.Namespace).Get(src.Name) + if err != nil { + return fmt.Errorf("failed to get ingress %s/%s: %w", src.Namespace, src.Name, err) + } + + logger := log.WithoutContext().WithField("namespace", ing.Namespace).WithField("ingress", ing.Name) + + if isLoadBalancerIngressEquals(ing.Status.LoadBalancer.Ingress, ingStatus) { + logger.Debug("Skipping ingress status update") + return nil + } + + ingCopy := ing.DeepCopy() + ingCopy.Status = networkingv1.IngressStatus{LoadBalancer: corev1.LoadBalancerStatus{Ingress: ingStatus}} + + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + defer cancel() + + _, err = c.clientset.NetworkingV1().Ingresses(ingCopy.Namespace).UpdateStatus(ctx, ingCopy, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("failed to update ingress status %s/%s: %w", src.Namespace, src.Name, err) + } + + logger.Info("Updated ingress status") + return nil +} + +func (c *clientWrapper) updateIngressStatusOld(src *networkingv1.Ingress, ingStatus []corev1.LoadBalancerIngress) error { ing, err := c.factoriesIngress[c.lookupNamespace(src.Namespace)].Networking().V1beta1().Ingresses().Lister().Ingresses(src.Namespace).Get(src.Name) if err != nil { return fmt.Errorf("failed to get ingress %s/%s: %w", src.Namespace, src.Name, err) @@ -306,35 +408,6 @@ func (c *clientWrapper) UpdateIngressStatus(src *networkingv1beta1.Ingress, ingS if err != nil { return fmt.Errorf("failed to update ingress status %s/%s: %w", src.Namespace, src.Name, err) } - - logger.Info("Updated ingress status") - return nil -} - -func (c *clientWrapper) updateIngressStatusOld(src *networkingv1beta1.Ingress, ingStatus []corev1.LoadBalancerIngress) error { - ing, err := c.factoriesIngress[c.lookupNamespace(src.Namespace)].Extensions().V1beta1().Ingresses().Lister().Ingresses(src.Namespace).Get(src.Name) - if err != nil { - return fmt.Errorf("failed to get ingress %s/%s: %w", src.Namespace, src.Name, err) - } - - logger := log.WithoutContext().WithField("namespace", ing.Namespace).WithField("ingress", ing.Name) - - if isLoadBalancerIngressEquals(ing.Status.LoadBalancer.Ingress, ingStatus) { - logger.Debug("Skipping ingress status update") - return nil - } - - ingCopy := ing.DeepCopy() - ingCopy.Status = extensionsv1beta1.IngressStatus{LoadBalancer: corev1.LoadBalancerStatus{Ingress: ingStatus}} - - ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) - defer cancel() - - _, err = c.clientset.ExtensionsV1beta1().Ingresses(ingCopy.Namespace).UpdateStatus(ctx, ingCopy, metav1.UpdateOptions{}) - if err != nil { - return fmt.Errorf("failed to update ingress status %s/%s: %w", src.Namespace, src.Name, err) - } - logger.Info("Updated ingress status") return nil } @@ -488,3 +561,11 @@ func filterIngressClassByName(ingressClassName string, ics []*networkingv1beta1. return ingressClasses } + +// Ingress in networking.k8s.io/v1 is supported starting 1.19. +// thus, we query it in K8s starting 1.19. +func supportsNetworkingV1Ingress(serverVersion *version.Version) bool { + ingressNetworkingVersion := version.Must(version.NewVersion("1.19")) + + return serverVersion.GreaterThanOrEqual(ingressNetworkingVersion) +} diff --git a/pkg/provider/kubernetes/ingress/client_mock_test.go b/pkg/provider/kubernetes/ingress/client_mock_test.go index 94a282990..493bdf5ec 100644 --- a/pkg/provider/kubernetes/ingress/client_mock_test.go +++ b/pkg/provider/kubernetes/ingress/client_mock_test.go @@ -7,14 +7,14 @@ import ( "github.com/hashicorp/go-version" "github.com/traefik/traefik/v2/pkg/provider/kubernetes/k8s" corev1 "k8s.io/api/core/v1" - extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + networkingv1 "k8s.io/api/networking/v1" networkingv1beta1 "k8s.io/api/networking/v1beta1" ) var _ Client = (*clientMock)(nil) type clientMock struct { - ingresses []*networkingv1beta1.Ingress + ingresses []*networkingv1.Ingress services []*corev1.Service secrets []*corev1.Secret endpoints []*corev1.Endpoints @@ -51,13 +51,14 @@ func newClientMock(serverVersion string, paths ...string) clientMock { case *corev1.Endpoints: c.endpoints = append(c.endpoints, o) case *networkingv1beta1.Ingress: - c.ingresses = append(c.ingresses, o) - case *extensionsv1beta1.Ingress: - ing, err := extensionsToNetworking(o) + ing, err := toNetworkingV1(o) if err != nil { panic(err) } + addServiceFromV1Beta1(ing, *o) c.ingresses = append(c.ingresses, ing) + case *networkingv1.Ingress: + c.ingresses = append(c.ingresses, o) case *networkingv1beta1.IngressClass: c.ingressClasses = append(c.ingressClasses, o) default: @@ -69,7 +70,7 @@ func newClientMock(serverVersion string, paths ...string) clientMock { return c } -func (c clientMock) GetIngresses() []*networkingv1beta1.Ingress { +func (c clientMock) GetIngresses() []*networkingv1.Ingress { return c.ingresses } @@ -125,6 +126,6 @@ func (c clientMock) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-cha return c.watchChan, nil } -func (c clientMock) UpdateIngressStatus(_ *networkingv1beta1.Ingress, _ []corev1.LoadBalancerIngress) error { +func (c clientMock) UpdateIngressStatus(_ *networkingv1.Ingress, _ []corev1.LoadBalancerIngress) error { return c.apiIngressStatusError } diff --git a/pkg/provider/kubernetes/ingress/client_test.go b/pkg/provider/kubernetes/ingress/client_test.go index d65484219..9e32a618b 100644 --- a/pkg/provider/kubernetes/ingress/client_test.go +++ b/pkg/provider/kubernetes/ingress/client_test.go @@ -8,9 +8,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + "k8s.io/api/networking/v1beta1" kubeerror "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/version" + fakediscovery "k8s.io/client-go/discovery/fake" kubefake "k8s.io/client-go/kubernetes/fake" ) @@ -149,6 +153,11 @@ func TestClientIgnoresHelmOwnedSecrets(t *testing.T) { kubeClient := kubefake.NewSimpleClientset(helmSecret, secret) + discovery, _ := kubeClient.Discovery().(*fakediscovery.FakeDiscovery) + discovery.FakedServerVersion = &version.Info{ + GitVersion: "v1.19", + } + client := newClientImpl(kubeClient) stopCh := make(chan struct{}) @@ -180,3 +189,72 @@ func TestClientIgnoresHelmOwnedSecrets(t *testing.T) { require.NoError(t, err) assert.False(t, found) } + +func TestClientUsesCorrectServerVersion(t *testing.T) { + ingressV1Beta := &v1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "ingress-v1beta", + }, + } + + ingressV1 := &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "ingress-v1", + }, + } + + kubeClient := kubefake.NewSimpleClientset(ingressV1Beta, ingressV1) + + discovery, _ := kubeClient.Discovery().(*fakediscovery.FakeDiscovery) + discovery.FakedServerVersion = &version.Info{ + GitVersion: "v1.18.12+foobar", + } + + stopCh := make(chan struct{}) + + client := newClientImpl(kubeClient) + + eventCh, err := client.WatchAll(nil, stopCh) + require.NoError(t, err) + + select { + case event := <-eventCh: + ingress, ok := event.(*v1beta1.Ingress) + require.True(t, ok) + + assert.Equal(t, "ingress-v1beta", ingress.Name) + case <-time.After(50 * time.Millisecond): + assert.Fail(t, "expected to receive event for ingress") + } + + select { + case <-eventCh: + assert.Fail(t, "received more than one event") + case <-time.After(50 * time.Millisecond): + } + + discovery.FakedServerVersion = &version.Info{ + GitVersion: "v1.19", + } + + eventCh, err = client.WatchAll(nil, stopCh) + require.NoError(t, err) + + select { + case event := <-eventCh: + ingress, ok := event.(*networkingv1.Ingress) + require.True(t, ok) + + assert.Equal(t, "ingress-v1", ingress.Name) + case <-time.After(50 * time.Millisecond): + assert.Fail(t, "expected to receive event for ingress") + } + + select { + case <-eventCh: + assert.Fail(t, "received more than one event") + case <-time.After(50 * time.Millisecond): + } +} diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_endpoint.yml new file mode 100644 index 000000000..0e64b4434 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_endpoint.yml @@ -0,0 +1,24 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 80 + +--- +kind: Endpoints +apiVersion: v1 +metadata: + name: defaultservice + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_ingress.yml new file mode 100644 index 000000000..58b7aac63 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_ingress.yml @@ -0,0 +1,12 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: defaultbackend + namespace: testing + +spec: + defaultBackend: + service: + name: defaultservice + port: + number: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_service.yml new file mode 100644 index 000000000..1bca9be2b --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-defaultbackend_service.yml @@ -0,0 +1,22 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 +--- + +kind: Service +apiVersion: v1 +metadata: + name: defaultservice + namespace: testing + +spec: + ports: + - port: 8080 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_ingress.yml new file mode 100644 index 000000000..2ea3bf486 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_ingress.yml @@ -0,0 +1,18 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing + annotations: + traefik.ingress.kubernetes.io/router.pathmatcher: Path +spec: + rules: + - http: + paths: + - path: /bar + pathType: "" + backend: + service: + name: service1 + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-empty-pathType_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_ingress.yml new file mode 100644 index 000000000..ca25ad48e --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_ingress.yml @@ -0,0 +1,16 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing +spec: + rules: + - http: + paths: + - path: /bar + pathType: Exact + backend: + service: + name: service1 + port: + number: 80 \ No newline at end of file diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-exact-pathType_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_ingress.yml new file mode 100644 index 000000000..9804e7eba --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_ingress.yml @@ -0,0 +1,18 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing + annotations: + traefik.ingress.kubernetes.io/router.pathmatcher: Path +spec: + rules: + - http: + paths: + - path: /bar + pathType: ImplementationSpecific + backend: + service: + name: service1 + port: + number: 80 \ No newline at end of file diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-implementationSpecific-pathType_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_ingress.yml new file mode 100644 index 000000000..1e8f60949 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_ingress.yml @@ -0,0 +1,17 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing + annotations: + kubernetes.io/ingress.class: traefik +spec: + rules: + - http: + paths: + - path: /bar + backend: + service: + name: service1 + port: + number: 80 \ No newline at end of file diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingress-annotation_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_ingress.yml new file mode 100644 index 000000000..22c268983 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_ingress.yml @@ -0,0 +1,16 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing +spec: + ingressClassName: traefik-lb + rules: + - http: + paths: + - path: /bar + backend: + service: + name: service1 + port: + number: 80 \ No newline at end of file diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_ingressclass.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_ingressclass.yml new file mode 100644 index 000000000..b96f42518 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_ingressclass.yml @@ -0,0 +1,6 @@ +apiVersion: networking.k8s.io/v1beta1 +kind: IngressClass +metadata: + name: traefik-lb +spec: + controller: traefik.io/ingress-controller diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-ingressClass_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_ingress.yml new file mode 100644 index 000000000..b14b1a4bb --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_ingress.yml @@ -0,0 +1,16 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing +spec: + ingressClassName: traefik-lb + rules: + - http: + paths: + - path: /bar + backend: + service: + name: service1 + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-missing-ingressClass_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_endpoint.yml new file mode 100644 index 000000000..bf2e7526e --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_endpoint.yml @@ -0,0 +1,12 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: + - addresses: + - ip: 10.10.0.1 + ports: + - name: foobar + port: 4711 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_ingress.yml new file mode 100644 index 000000000..12f17bc54 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_ingress.yml @@ -0,0 +1,16 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing +spec: + rules: + - http: + paths: + - path: /bar + pathType: Prefix + backend: + service: + name: service1 + port: + name: foobar diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_service.yml new file mode 100644 index 000000000..c064e5872 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-named-port_service.yml @@ -0,0 +1,12 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - name: foobar + port: 4711 + clusterIP: 10.0.0.1 + diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_ingress.yml new file mode 100644 index 000000000..6327a89a5 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_ingress.yml @@ -0,0 +1,17 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing + annotations: + traefik.ingress.kubernetes.io/router.pathmatcher: Path +spec: + rules: + - http: + paths: + - path: /bar + backend: + service: + name: service1 + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-no-pathType_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_endpoint.yml new file mode 100644 index 000000000..6ed60d79c --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_endpoint.yml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_ingress.yml new file mode 100644 index 000000000..bc88fc058 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_ingress.yml @@ -0,0 +1,16 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing +spec: + rules: + - http: + paths: + - path: /bar + pathType: Prefix + backend: + service: + name: service1 + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_service.yml b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_service.yml new file mode 100644 index 000000000..0ec7e2269 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/v19-Ingress-with-prefix-pathType_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index 5cb6da5ca..a1386a6ef 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -23,9 +23,9 @@ import ( "github.com/traefik/traefik/v2/pkg/safe" "github.com/traefik/traefik/v2/pkg/tls" corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" networkingv1beta1 "k8s.io/api/networking/v1beta1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/intstr" ) const ( @@ -226,17 +226,17 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl log.FromContext(ctx).Errorf("Error configuring TLS: %v", err) } - if len(ingress.Spec.Rules) == 0 && ingress.Spec.Backend != nil { + if len(ingress.Spec.Rules) == 0 && ingress.Spec.DefaultBackend != nil { if _, ok := conf.HTTP.Services["default-backend"]; ok { log.FromContext(ctx).Error("The default backend already exists.") continue } - service, err := loadService(client, ingress.Namespace, *ingress.Spec.Backend) + service, err := loadService(client, ingress.Namespace, *ingress.Spec.DefaultBackend) if err != nil { log.FromContext(ctx). - WithField("serviceName", ingress.Spec.Backend.ServiceName). - WithField("servicePort", ingress.Spec.Backend.ServicePort.String()). + WithField("serviceName", ingress.Spec.DefaultBackend.Service.Name). + WithField("servicePort", ingress.Spec.DefaultBackend.Service.Port.String()). Errorf("Cannot create service: %v", err) continue } @@ -272,13 +272,19 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl service, err := loadService(client, ingress.Namespace, pa.Backend) if err != nil { log.FromContext(ctx). - WithField("serviceName", pa.Backend.ServiceName). - WithField("servicePort", pa.Backend.ServicePort.String()). + WithField("serviceName", pa.Backend.Service.Name). + WithField("servicePort", pa.Backend.Service.Port.String()). Errorf("Cannot create service: %v", err) continue } - serviceName := provider.Normalize(ingress.Namespace + "-" + pa.Backend.ServiceName + "-" + pa.Backend.ServicePort.String()) + portString := pa.Backend.Service.Port.Name + + if len(pa.Backend.Service.Port.Name) == 0 { + portString = fmt.Sprint(pa.Backend.Service.Port.Number) + } + + serviceName := provider.Normalize(ingress.Namespace + "-" + pa.Backend.Service.Name + "-" + portString) conf.HTTP.Services[serviceName] = service routerKey := strings.TrimPrefix(provider.Normalize(ingress.Name+"-"+ingress.Namespace+"-"+rule.Host+pa.Path), "-") @@ -316,7 +322,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl return conf } -func (p *Provider) updateIngressStatus(ing *networkingv1beta1.Ingress, k8sClient Client) error { +func (p *Provider) updateIngressStatus(ing *networkingv1.Ingress, k8sClient Client) error { // Only process if an EndpointIngress has been configured. if p.IngressEndpoint == nil { return nil @@ -355,7 +361,7 @@ func (p *Provider) updateIngressStatus(ing *networkingv1beta1.Ingress, k8sClient return k8sClient.UpdateIngressStatus(ing, service.Status.LoadBalancer.Ingress) } -func (p *Provider) shouldProcessIngress(ingress *networkingv1beta1.Ingress, ingressClasses []*networkingv1beta1.IngressClass) bool { +func (p *Provider) shouldProcessIngress(ingress *networkingv1.Ingress, ingressClasses []*networkingv1beta1.IngressClass) bool { // configuration through the new kubernetes ingressClass if ingress.Spec.IngressClassName != nil { for _, ic := range ingressClasses { @@ -379,7 +385,7 @@ func buildHostRule(host string) string { return "Host(`" + host + "`)" } -func getCertificates(ctx context.Context, ingress *networkingv1beta1.Ingress, k8sClient Client, tlsConfigs map[string]*tls.CertAndStores) error { +func getCertificates(ctx context.Context, ingress *networkingv1.Ingress, k8sClient Client, tlsConfigs map[string]*tls.CertAndStores) error { for _, t := range ingress.Spec.TLS { if t.SecretName == "" { log.FromContext(ctx).Debugf("Skipping TLS sub-section: No secret name provided") @@ -464,8 +470,8 @@ func getTLSConfig(tlsConfigs map[string]*tls.CertAndStores) []*tls.CertAndStores return configs } -func loadService(client Client, namespace string, backend networkingv1beta1.IngressBackend) (*dynamic.Service, error) { - service, exists, err := client.GetService(namespace, backend.ServiceName) +func loadService(client Client, namespace string, backend networkingv1.IngressBackend) (*dynamic.Service, error) { + service, exists, err := client.GetService(namespace, backend.Service.Name) if err != nil { return nil, err } @@ -478,8 +484,7 @@ func loadService(client Client, namespace string, backend networkingv1beta1.Ingr var portSpec corev1.ServicePort var match bool for _, p := range service.Spec.Ports { - if (backend.ServicePort.Type == intstr.Int && backend.ServicePort.IntVal == p.Port) || - (backend.ServicePort.Type == intstr.String && backend.ServicePort.StrVal == p.Name) { + if backend.Service.Port.Number == p.Port || (backend.Service.Port.Name == p.Name && len(p.Name) > 0) { portName = p.Name portSpec = p match = true @@ -520,7 +525,7 @@ func loadService(client Client, namespace string, backend networkingv1beta1.Ingr return svc, nil } - endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, backend.ServiceName) + endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, backend.Service.Name) if endpointsErr != nil { return nil, endpointsErr } @@ -584,7 +589,7 @@ func makeRouterKeyWithHash(key, rule string) (string, error) { return dupKey, nil } -func loadRouter(rule networkingv1beta1.IngressRule, pa networkingv1beta1.HTTPIngressPath, rtConfig *RouterConfig, serviceName string) *dynamic.Router { +func loadRouter(rule networkingv1.IngressRule, pa networkingv1.HTTPIngressPath, rtConfig *RouterConfig, serviceName string) *dynamic.Router { var rules []string if len(rule.Host) > 0 { rules = []string{buildHostRule(rule.Host)} @@ -593,11 +598,11 @@ func loadRouter(rule networkingv1beta1.IngressRule, pa networkingv1beta1.HTTPIng if len(pa.Path) > 0 { matcher := defaultPathMatcher - if pa.PathType == nil || *pa.PathType == "" || *pa.PathType == networkingv1beta1.PathTypeImplementationSpecific { + if pa.PathType == nil || *pa.PathType == "" || *pa.PathType == networkingv1.PathTypeImplementationSpecific { if rtConfig != nil && rtConfig.Router != nil && rtConfig.Router.PathMatcher != "" { matcher = rtConfig.Router.PathMatcher } - } else if *pa.PathType == networkingv1beta1.PathTypeExact { + } else if *pa.PathType == networkingv1.PathTypeExact { matcher = "Path" } diff --git a/pkg/provider/kubernetes/ingress/kubernetes_test.go b/pkg/provider/kubernetes/ingress/kubernetes_test.go index 4483c6b32..4a47fb731 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes_test.go +++ b/pkg/provider/kubernetes/ingress/kubernetes_test.go @@ -14,7 +14,7 @@ import ( "github.com/traefik/traefik/v2/pkg/tls" "github.com/traefik/traefik/v2/pkg/types" corev1 "k8s.io/api/core/v1" - "k8s.io/api/networking/v1beta1" + networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -1302,6 +1302,271 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { }, }, }, + { + desc: "v19 Ingress with prefix pathType", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar": { + Rule: "PathPrefix(`/bar`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "v19 Ingress with no pathType", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar": { + Rule: "Path(`/bar`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "v19 Ingress with empty pathType", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar": { + Rule: "Path(`/bar`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "v19 Ingress with exact pathType", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar": { + Rule: "Path(`/bar`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "v19 Ingress with implementationSpecific pathType", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar": { + Rule: "Path(`/bar`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "v19 Ingress with ingress annotation", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar": { + Rule: "PathPrefix(`/bar`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "v19 Ingress with ingressClass", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar": { + Rule: "PathPrefix(`/bar`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "v19 Ingress with named port", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar": { + Rule: "PathPrefix(`/bar`)", + Service: "testing-service1-foobar", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-foobar": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:4711", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "v19 Ingress with missing ingressClass", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{}, + Services: map[string]*dynamic.Service{}, + }, + }, + }, + { + desc: "v19 Ingress with defaultbackend", + serverVersion: "v1.19", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "default-router": { + Rule: "PathPrefix(`/`)", + Priority: math.MinInt32, + Service: "default-backend", + }, + }, + Services: map[string]*dynamic.Service{ + "default-backend": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, } for _, test := range testCases { @@ -1375,7 +1640,7 @@ func TestGetCertificates(t *testing.T) { testCases := []struct { desc string - ingress *v1beta1.Ingress + ingress *networkingv1.Ingress client Client result map[string]*tls.CertAndStores errResult string From 8947f85ddd791c3113bcc7419a9924d3f3d692c0 Mon Sep 17 00:00:00 2001 From: HMH Date: Tue, 23 Mar 2021 10:24:03 +0000 Subject: [PATCH 15/18] Improve host name resolution for TCP proxy --- pkg/tcp/proxy.go | 30 ++++++++++++---------- pkg/tcp/proxy_test.go | 59 ++++++++++--------------------------------- 2 files changed, 30 insertions(+), 59 deletions(-) diff --git a/pkg/tcp/proxy.go b/pkg/tcp/proxy.go index 552fe0f3b..06dc2884a 100644 --- a/pkg/tcp/proxy.go +++ b/pkg/tcp/proxy.go @@ -31,7 +31,7 @@ func NewProxy(address string, terminationDelay time.Duration, proxyProtocol *dyn return nil, fmt.Errorf("unknown proxyProtocol version: %d", proxyProtocol.Version) } - // enable the refresh of the target only if the address in an IP + // enable the refresh of the target only if the address in not an IP refreshTarget := false if host, _, err := net.SplitHostPort(address); err == nil && net.ParseIP(host) == nil { refreshTarget = true @@ -48,23 +48,14 @@ func NewProxy(address string, terminationDelay time.Duration, proxyProtocol *dyn // ServeTCP forwards the connection to a service. func (p *Proxy) ServeTCP(conn WriteCloser) { - log.Debugf("Handling connection from %s", conn.RemoteAddr()) + log.WithoutContext().Debugf("Handling connection from %s", conn.RemoteAddr()) // needed because of e.g. server.trackedConnection defer conn.Close() - if p.refreshTarget { - tcpAddr, err := net.ResolveTCPAddr("tcp", p.address) - if err != nil { - log.Errorf("Error resolving tcp address: %v", err) - return - } - p.target = tcpAddr - } - - connBackend, err := net.DialTCP("tcp", nil, p.target) + connBackend, err := p.dialBackend() if err != nil { - log.Errorf("Error while connection to backend: %v", err) + log.WithoutContext().Errorf("Error while connecting to backend: %v", err) return } @@ -91,6 +82,19 @@ func (p *Proxy) ServeTCP(conn WriteCloser) { <-errChan } +func (p Proxy) dialBackend() (*net.TCPConn, error) { + if !p.refreshTarget { + return net.DialTCP("tcp", nil, p.target) + } + + conn, err := net.Dial("tcp", p.address) + if err != nil { + return nil, err + } + + return conn.(*net.TCPConn), nil +} + func (p Proxy) connCopy(dst, src WriteCloser, errCh chan error) { _, err := io.Copy(dst, src) errCh <- err diff --git a/pkg/tcp/proxy_test.go b/pkg/tcp/proxy_test.go index 11fdb47c4..e70b36796 100644 --- a/pkg/tcp/proxy_test.go +++ b/pkg/tcp/proxy_test.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "net" - "sync" "testing" "time" @@ -175,19 +174,18 @@ func TestProxyProtocol(t *testing.T) { func TestLookupAddress(t *testing.T) { testCases := []struct { - desc string - address string - expectSame assert.ComparisonAssertionFunc + desc string + address string + expectRefresh bool }{ { - desc: "IP doesn't need refresh", - address: "8.8.4.4:53", - expectSame: assert.Same, + desc: "IP doesn't need refresh", + address: "8.8.4.4:53", }, { - desc: "Hostname needs refresh", - address: "dns.google:53", - expectSame: assert.NotSame, + desc: "Hostname needs refresh", + address: "dns.google:53", + expectRefresh: true, }, } @@ -201,44 +199,13 @@ func TestLookupAddress(t *testing.T) { require.NotNil(t, proxy.target) - proxyListener, err := net.Listen("tcp", ":0") + conn, err := proxy.dialBackend() require.NoError(t, err) - var wg sync.WaitGroup - go func(wg *sync.WaitGroup) { - for { - conn, err := proxyListener.Accept() - require.NoError(t, err) - - proxy.ServeTCP(conn.(*net.TCPConn)) - - wg.Done() - } - }(&wg) - - var lastTarget *net.TCPAddr - - for i := 0; i < 3; i++ { - wg.Add(1) - - conn, err := net.Dial("tcp", proxyListener.Addr().String()) - require.NoError(t, err) - - _, err = conn.Write([]byte("ping\n")) - require.NoError(t, err) - - err = conn.Close() - require.NoError(t, err) - - wg.Wait() - - assert.NotNil(t, proxy.target) - - if lastTarget != nil { - test.expectSame(t, lastTarget, proxy.target) - } - - lastTarget = proxy.target + if test.expectRefresh { + assert.NotEqual(t, test.address, conn.RemoteAddr().String()) + } else { + assert.Equal(t, test.address, conn.RemoteAddr().String()) } }) } From 31a5f3591f278aeeccaee9de530c109c14544ab8 Mon Sep 17 00:00:00 2001 From: Sylvain Rabot Date: Tue, 23 Mar 2021 17:48:04 +0100 Subject: [PATCH 16/18] Allow to define datadogs metrics endpoint with env vars --- pkg/types/metrics.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pkg/types/metrics.go b/pkg/types/metrics.go index f3391fc21..7fd9bc49f 100644 --- a/pkg/types/metrics.go +++ b/pkg/types/metrics.go @@ -1,6 +1,8 @@ package types import ( + "net" + "os" "time" "github.com/traefik/paerser/types" @@ -41,7 +43,16 @@ type Datadog struct { // SetDefaults sets the default values. func (d *Datadog) SetDefaults() { - d.Address = "localhost:8125" + host, ok := os.LookupEnv("DD_AGENT_HOST") + if !ok { + host = "localhost" + } + + port, ok := os.LookupEnv("DD_DOGSTATSD_PORT") + if !ok { + port = "8125" + } + d.Address = net.JoinHostPort(host, port) d.PushInterval = types.Duration(10 * time.Second) d.AddEntryPointsLabels = true d.AddServicesLabels = true From d13d07835197de016506d91f26868ac38f8c11a8 Mon Sep 17 00:00:00 2001 From: jcuzzi <30416082+jcuzzi@users.noreply.github.com> Date: Mon, 29 Mar 2021 05:32:03 -0700 Subject: [PATCH 17/18] Add ability to disable HTTP/2 in dynamic config --- .../reference/dynamic-configuration/file.toml | 2 + .../reference/dynamic-configuration/file.yaml | 2 + .../kubernetes-crd-resource.yml | 1 + ...traefik.containo.us_serverstransports.yaml | 3 + docs/content/routing/services/index.md | 31 +++++++++ integration/fixtures/k8s/01-traefik-crd.yml | 3 + pkg/config/dynamic/http_config.go | 1 + .../crd/traefik/v1alpha1/serverstransport.go | 2 + pkg/server/service/roundtripper.go | 25 ++++--- pkg/server/service/roundtripper_test.go | 66 +++++++++++++++++++ 10 files changed, 126 insertions(+), 10 deletions(-) diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index ea5e29fcf..6f2cd4d52 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -275,6 +275,7 @@ insecureSkipVerify = true rootCAs = ["foobar", "foobar"] maxIdleConnsPerHost = 42 + disableHTTP2 = true [[http.serversTransports.ServersTransport0.certificates]] certFile = "foobar" @@ -292,6 +293,7 @@ insecureSkipVerify = true rootCAs = ["foobar", "foobar"] maxIdleConnsPerHost = 42 + disableHTTP2 = true [[http.serversTransports.ServersTransport1.certificates]] certFile = "foobar" diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index f707c4a03..563eb3cf2 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -327,6 +327,7 @@ http: dialTimeout: 42s responseHeaderTimeout: 42s idleConnTimeout: 42s + disableHTTP2: true ServersTransport1: serverName: foobar insecureSkipVerify: true @@ -343,6 +344,7 @@ http: dialTimeout: 42s responseHeaderTimeout: 42s idleConnTimeout: 42s + disableHTTP2: true tcp: routers: TCPRouter0: diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml index 332853401..56c5e7419 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml @@ -214,3 +214,4 @@ spec: dialTimeout: 42s responseHeaderTimeout: 42s idleConnTimeout: 42s + disableHTTP2: true diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml index 43c9e6db9..0e7efb766 100644 --- a/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml @@ -37,6 +37,9 @@ spec: items: type: string type: array + disableHTTP2: + description: Disable HTTP/2 for connections with backend servers. + type: boolean forwardingTimeouts: description: Timeouts for requests forwarded to the backend servers. properties: diff --git a/docs/content/routing/services/index.md b/docs/content/routing/services/index.md index 2d69d42e6..5add1bbbd 100644 --- a/docs/content/routing/services/index.md +++ b/docs/content/routing/services/index.md @@ -705,6 +705,37 @@ spec: maxIdleConnsPerHost: 7 ``` +#### `disableHTTP2` + +_Optional, Default=false_ + +`disableHTTP2` disables HTTP/2 for connections with backend servers. + +```toml tab="File (TOML)" +## Dynamic configuration +[http.serversTransports.mytransport] + disableHTTP2 = true +``` + +```yaml tab="File (YAML)" +## Dynamic configuration +http: + serversTransports: + mytransport: + disableHTTP2: true +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: ServersTransport +metadata: + name: mytransport + namespace: default + +spec: + disableHTTP2: true +``` + #### `forwardingTimeouts` `forwardingTimeouts` is about a number of timeouts relevant to when forwarding requests to the backend servers. diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index 53b7a921c..956e36f2f 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -950,6 +950,9 @@ spec: items: type: string type: array + disableHTTP2: + description: Disable HTTP/2 for connections with backend servers. + type: boolean forwardingTimeouts: description: Timeouts for requests forwarded to the backend servers. properties: diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index 1d727a2b8..ec359a656 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -208,6 +208,7 @@ type ServersTransport struct { Certificates tls.Certificates `description:"Certificates for mTLS." json:"certificates,omitempty" toml:"certificates,omitempty" yaml:"certificates,omitempty" export:"true"` MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" json:"maxIdleConnsPerHost,omitempty" toml:"maxIdleConnsPerHost,omitempty" yaml:"maxIdleConnsPerHost,omitempty" export:"true"` ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers." json:"forwardingTimeouts,omitempty" toml:"forwardingTimeouts,omitempty" yaml:"forwardingTimeouts,omitempty" export:"true"` + DisableHTTP2 bool `description:"Disable HTTP/2 for connections with backend servers." json:"disableHTTP2,omitempty" toml:"disableHTTP2,omitempty" yaml:"disableHTTP2,omitempty" export:"true"` } // +k8s:deepcopy-gen=true diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go index 130598214..dd8b00ff5 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go @@ -33,6 +33,8 @@ type ServersTransportSpec struct { MaxIdleConnsPerHost int `json:"maxIdleConnsPerHost,omitempty"` // Timeouts for requests forwarded to the backend servers. ForwardingTimeouts *ForwardingTimeouts `json:"forwardingTimeouts,omitempty"` + // Disable HTTP/2 for connections with backend servers. + DisableHTTP2 bool `json:"disableHTTP2,omitempty"` } // +k8s:deepcopy-gen=true diff --git a/pkg/server/service/roundtripper.go b/pkg/server/service/roundtripper.go index 96384c4bc..364cd9e69 100644 --- a/pkg/server/service/roundtripper.go +++ b/pkg/server/service/roundtripper.go @@ -100,7 +100,7 @@ func (r *RoundTripperManager) Get(name string) (http.RoundTripper, error) { // createRoundTripper creates an http.RoundTripper configured with the Transport configuration settings. // For the settings that can't be configured in Traefik it uses the default http.Transport settings. -// An exception to this is the MaxIdleConns setting as we only provide the option MaxIdleConnsPerHostin Traefik at this point in time. +// An exception to this is the MaxIdleConns setting as we only provide the option MaxIdleConnsPerHost in Traefik at this point in time. // Setting this value to the default of 100 could lead to confusing behavior and backwards compatibility issues. func createRoundTripper(cfg *dynamic.ServersTransport) (http.RoundTripper, error) { if cfg == nil { @@ -127,15 +127,6 @@ func createRoundTripper(cfg *dynamic.ServersTransport) (http.RoundTripper, error WriteBufferSize: 64 * 1024, } - transport.RegisterProtocol("h2c", &h2cTransportWrapper{ - Transport: &http2.Transport{ - DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) { - return net.Dial(netw, addr) - }, - AllowHTTP: true, - }, - }) - if cfg.ForwardingTimeouts != nil { transport.ResponseHeaderTimeout = time.Duration(cfg.ForwardingTimeouts.ResponseHeaderTimeout) transport.IdleConnTimeout = time.Duration(cfg.ForwardingTimeouts.IdleConnTimeout) @@ -150,6 +141,20 @@ func createRoundTripper(cfg *dynamic.ServersTransport) (http.RoundTripper, error } } + // Return directly HTTP/1.1 transport when HTTP/2 is disabled + if cfg.DisableHTTP2 { + return transport, nil + } + + transport.RegisterProtocol("h2c", &h2cTransportWrapper{ + Transport: &http2.Transport{ + DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) { + return net.Dial(netw, addr) + }, + AllowHTTP: true, + }, + }) + return newSmartRoundTripper(transport) } diff --git a/pkg/server/service/roundtripper_test.go b/pkg/server/service/roundtripper_test.go index 7049bbf83..b58a26e33 100644 --- a/pkg/server/service/roundtripper_test.go +++ b/pkg/server/service/roundtripper_test.go @@ -227,3 +227,69 @@ func TestMTLS(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) } + +func TestDisableHTTP2(t *testing.T) { + testCases := []struct { + desc string + disableHTTP2 bool + serverHTTP2 bool + expectedProto string + }{ + { + desc: "HTTP1 capable client with HTTP1 server", + disableHTTP2: true, + expectedProto: "HTTP/1.1", + }, + { + desc: "HTTP1 capable client with HTTP2 server", + disableHTTP2: true, + serverHTTP2: true, + expectedProto: "HTTP/1.1", + }, + { + desc: "HTTP2 capable client with HTTP1 server", + expectedProto: "HTTP/1.1", + }, + { + desc: "HTTP2 capable client with HTTP2 server", + serverHTTP2: true, + expectedProto: "HTTP/2.0", + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + srv := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + })) + + srv.EnableHTTP2 = test.serverHTTP2 + srv.StartTLS() + + rtManager := NewRoundTripperManager() + + dynamicConf := map[string]*dynamic.ServersTransport{ + "test": { + DisableHTTP2: test.disableHTTP2, + InsecureSkipVerify: true, + }, + } + + rtManager.Update(dynamicConf) + + tr, err := rtManager.Get("test") + require.NoError(t, err) + + client := http.Client{Transport: tr} + + resp, err := client.Get(srv.URL) + require.NoError(t, err) + + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.Equal(t, test.expectedProto, resp.Proto) + }) + } +} From e28b33b53b203fe5a10d792defafb4c82ab4e9a5 Mon Sep 17 00:00:00 2001 From: Sylvain Rabot Date: Sun, 18 Apr 2021 00:38:03 +0200 Subject: [PATCH 18/18] Upgrade github.com/lucas-clemente/quic-go --- go.mod | 2 +- go.sum | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index a5b91e6ed..6895dfb51 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/libkermit/compose v0.0.0-20171122111507-c04e39c026ad github.com/libkermit/docker v0.0.0-20171122101128-e6674d32b807 github.com/libkermit/docker-check v0.0.0-20171122104347-1113af38e591 - github.com/lucas-clemente/quic-go v0.19.3 + github.com/lucas-clemente/quic-go v0.20.1 github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f github.com/miekg/dns v1.1.40 github.com/mitchellh/copystructure v1.0.0 diff --git a/go.sum b/go.sum index e1121f6ee..05fe9dd31 100644 --- a/go.sum +++ b/go.sum @@ -420,7 +420,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -430,8 +429,9 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -660,8 +660,8 @@ github.com/liquidweb/liquidweb-go v1.6.3 h1:NVHvcnX3eb3BltiIoA+gLYn15nOpkYkdizOE github.com/liquidweb/liquidweb-go v1.6.3/go.mod h1:SuXXp+thr28LnjEw18AYtWwIbWMHSUiajPQs8T9c/Rc= github.com/looplab/fsm v0.1.0 h1:Qte7Zdn/5hBNbXzP7yxVU4OIFHWXBovyTT2LaBTyC20= github.com/looplab/fsm v0.1.0/go.mod h1:m2VaOfDHxqXBBMgc26m6yUOwkFn8H2AlJDE+jd/uafI= -github.com/lucas-clemente/quic-go v0.19.3 h1:eCDQqvGBB+kCTkA0XrAFtNe81FMa0/fn4QSoeAbmiF4= -github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= +github.com/lucas-clemente/quic-go v0.20.1 h1:hb5m76V8QS/8Nw/suHvXqo3BMHAozvIkcnzpJdpanSk= +github.com/lucas-clemente/quic-go v0.20.1/go.mod h1:fZq/HUDIM+mW6X6wtzORjC0E/WDBMKe5Hf9bgjISwLk= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -681,10 +681,10 @@ github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= -github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc= -github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= -github.com/marten-seemann/qtls-go1-15 v0.1.1 h1:LIH6K34bPVttyXnUWixk0bzH6/N07VxbSabxn5A5gZQ= -github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= +github.com/marten-seemann/qtls-go1-15 v0.1.4 h1:RehYMOyRW8hPVEja1KBVsFVNSm35Jj9Mvs5yNoZZ28A= +github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= +github.com/marten-seemann/qtls-go1-16 v0.1.3 h1:XEZ1xGorVy9u+lJq+WXNE+hiqRYLNvJGYmwfwKQN2gU= +github.com/marten-seemann/qtls-go1-16 v0.1.3/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -1289,8 +1289,9 @@ golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201231184435-2d18734c6014 h1:joucsQqXmyBVxViHCPFjG3hx8JzIFSaym3l3MM/Jsdg= +golang.org/x/sys v0.0.0-20201231184435-2d18734c6014/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=