From 5d91c7e15c34a320f9b0e6f287e6a27117c93ac4 Mon Sep 17 00:00:00 2001 From: SALLEYRON Julien Date: Tue, 27 Nov 2018 17:42:04 +0100 Subject: [PATCH] Remove old global config and use new static config --- acme/account.go | 7 +- acme/acme.go | 6 +- acme/acme_test.go | 4 +- acme/localStore.go | 2 +- anonymize/anonymize_config_test.go | 599 ++------------- cmd/bug/bug_test.go | 38 +- cmd/configuration.go | 330 ++++----- cmd/healthcheck/healthcheck.go | 14 +- cmd/storeconfig/storeconfig.go | 44 +- cmd/traefik/traefik.go | 207 +++--- collector/collector.go | 7 +- config/static/entrypoints.go | 169 +++++ .../static}/entrypoints_test.go | 244 +------ config/static/static_config.go | 364 +++++++++- healthcheck/healthcheck.go | 5 +- hostresolver/hostresolver.go | 2 +- integration/fixtures/access_log_config.toml | 17 +- integration/fixtures/acme/acme_base.toml | 6 +- .../fixtures/acme/acme_http01_web_path.toml | 6 +- integration/fixtures/acme/acme_tls.toml | 8 +- .../fixtures/acme/acme_tls_dynamic.toml | 9 +- .../acme/acme_tls_multiple_entrypoints.toml | 4 +- integration/fixtures/consul/simple.toml | 12 +- integration/fixtures/consul/simple_https.toml | 14 +- .../fixtures/consul_catalog/simple.toml | 9 +- integration/fixtures/docker/minimal.toml | 11 +- integration/fixtures/docker/simple.toml | 12 +- integration/fixtures/dynamodb/simple.toml | 14 +- integration/fixtures/error_pages/error.toml | 6 +- integration/fixtures/error_pages/simple.toml | 6 +- integration/fixtures/etcd/simple.toml | 14 +- integration/fixtures/etcd/simple_https.toml | 3 +- integration/fixtures/eureka/simple.toml | 10 +- .../fixtures/file/56-simple-panic.toml | 5 +- integration/fixtures/file/directory.toml | 8 +- integration/fixtures/file/simple.toml | 5 +- integration/fixtures/grpc/config.toml | 9 +- integration/fixtures/grpc/config_h2c.toml | 5 +- .../fixtures/grpc/config_h2c_termination.toml | 7 +- .../fixtures/grpc/config_insecure.toml | 8 +- .../fixtures/grpc/config_with_flush.toml | 7 +- .../healthcheck/multiple-entrypoints-drr.toml | 7 +- .../healthcheck/multiple-entrypoints-wrr.toml | 6 +- .../fixtures/healthcheck/port_overload.toml | 6 +- integration/fixtures/healthcheck/simple.toml | 6 +- .../https/clientca/https_1ca1config.toml | 25 +- .../https/clientca/https_2ca1config.toml | 23 +- .../https/clientca/https_2ca2config.toml | 24 +- .../fixtures/https/dynamic_https_sni.toml | 10 +- .../https/dynamic_https_sni_default_cert.toml | 6 +- .../fixtures/https/https_redirect.toml | 5 +- integration/fixtures/https/https_sni.toml | 22 +- .../https/https_sni_default_cert.toml | 25 +- .../fixtures/https/https_sni_strict.toml | 5 +- integration/fixtures/https/rootcas/https.toml | 6 +- .../https/rootcas/https_with_file.toml | 7 +- integration/fixtures/keep_trailing_slash.toml | 22 +- integration/fixtures/log_rotation_config.toml | 12 +- integration/fixtures/marathon/simple.toml | 12 +- integration/fixtures/mesos/simple.toml | 8 +- integration/fixtures/multiple_provider.toml | 17 +- integration/fixtures/proxy-protocol/with.toml | 5 +- .../fixtures/proxy-protocol/without.toml | 6 +- integration/fixtures/ratelimit/simple.toml | 7 +- integration/fixtures/reqacceptgrace.toml | 14 +- integration/fixtures/retry/simple.toml | 8 +- integration/fixtures/simple_auth.toml | 7 +- integration/fixtures/simple_default.toml | 3 +- integration/fixtures/simple_hostresolver.toml | 11 +- integration/fixtures/simple_stats.toml | 4 +- integration/fixtures/simple_web.toml | 2 +- integration/fixtures/simple_whitelist.toml | 6 +- .../fixtures/timeout/forwarding_timeouts.toml | 14 +- .../fixtures/tlsclientheaders/simple.toml | 15 +- integration/fixtures/tracing/simple.toml | 8 +- integration/fixtures/traefik_log_config.toml | 22 +- integration/fixtures/websocket/config.toml | 6 +- .../fixtures/websocket/config_https.toml | 11 +- integration/simple_test.go | 28 +- integration/websocket_test.go | 20 +- ip/checker.go | 2 +- middlewares/requestdecorator/hostresolver.go | 2 +- old/configuration/configuration.go | 2 +- old/configuration/configuration_test.go | 224 ------ .../static => old/configuration}/convert.go | 84 +-- old/configuration/entrypoints.go | 2 +- .../router/internal_router_test.go | 151 ---- old/log/logger_test.go | 79 -- old/middlewares/redirect/redirect_test.go | 182 ----- old/provider/acme/provider_test.go | 684 ------------------ old/tls/certificate.go | 244 +++++++ old/tls/certificate_store.go | 137 ++++ old/tls/generate/generate.go | 94 +++ old/tls/tls.go | 101 +++ provider/acme/provider.go | 2 +- provider/acme/provider_test.go | 27 +- provider/aggregator/aggregator.go | 14 +- provider/base_provider.go | 3 +- provider/file/file.go | 5 +- provider/provider.go | 3 +- server/roundtripper.go | 26 +- server/router/route_appender_aggregator.go | 1 + .../router/route_appender_aggregator_test.go | 14 +- server/router/router.go | 8 +- server/router/router_test.go | 22 +- server/server.go | 530 ++------------ server/server_configuration.go | 107 +-- server/server_configuration_test.go | 47 +- server/server_entrypoint.go | 426 +++++++++++ server/server_test.go | 77 +- tls/certificate_store.go | 52 +- tls/certificate_store_test.go | 59 +- tls/tls.go | 1 - tracing/zipkin/zipkin.go | 11 +- 114 files changed, 2485 insertions(+), 3646 deletions(-) create mode 100644 config/static/entrypoints.go rename {old/configuration => config/static}/entrypoints_test.go (54%) delete mode 100644 old/configuration/configuration_test.go rename {config/static => old/configuration}/convert.go (66%) delete mode 100644 old/configuration/router/internal_router_test.go delete mode 100644 old/log/logger_test.go delete mode 100644 old/middlewares/redirect/redirect_test.go delete mode 100644 old/provider/acme/provider_test.go create mode 100644 old/tls/certificate.go create mode 100644 old/tls/certificate_store.go create mode 100644 old/tls/generate/generate.go create mode 100644 old/tls/tls.go create mode 100644 server/server_entrypoint.go diff --git a/acme/account.go b/acme/account.go index 97f757665..77bdcc83a 100644 --- a/acme/account.go +++ b/acme/account.go @@ -1,6 +1,7 @@ package acme import ( + "context" "crypto" "crypto/rand" "crypto/rsa" @@ -15,8 +16,8 @@ import ( "time" "github.com/containous/traefik/log" - acmeprovider "github.com/containous/traefik/old/provider/acme" - "github.com/containous/traefik/old/types" + acmeprovider "github.com/containous/traefik/provider/acme" + "github.com/containous/traefik/types" "github.com/xenolf/lego/acme" ) @@ -72,7 +73,7 @@ func (a *Account) Init() error { // NewAccount creates an account func NewAccount(email string, certs []*DomainsCertificate, keyTypeValue string) (*Account, error) { - keyType := acmeprovider.GetKeyType(keyTypeValue) + keyType := acmeprovider.GetKeyType(context.Background(), keyTypeValue) // Create a user. New accounts need an email and private key to start privateKey, err := rsa.GenerateKey(rand.Reader, 4096) diff --git a/acme/acme.go b/acme/acme.go index 3e941fb11..af3da1a17 100644 --- a/acme/acme.go +++ b/acme/acme.go @@ -22,9 +22,9 @@ import ( "github.com/containous/staert" "github.com/containous/traefik/cluster" "github.com/containous/traefik/log" - acmeprovider "github.com/containous/traefik/old/provider/acme" - "github.com/containous/traefik/old/types" + acmeprovider "github.com/containous/traefik/provider/acme" "github.com/containous/traefik/safe" + "github.com/containous/traefik/types" "github.com/containous/traefik/version" "github.com/eapache/channels" "github.com/xenolf/lego/acme" @@ -208,7 +208,7 @@ func (a *ACME) leadershipListener(elected bool) error { needRegister = true } else if len(account.KeyType) == 0 { // Set the KeyType if not already defined in the account - account.KeyType = acmeprovider.GetKeyType(a.KeyType) + account.KeyType = acmeprovider.GetKeyType(context.Background(), a.KeyType) } a.client, err = a.buildACMEClient(account) diff --git a/acme/acme_test.go b/acme/acme_test.go index e3357fe88..aadfa17b6 100644 --- a/acme/acme_test.go +++ b/acme/acme_test.go @@ -11,9 +11,9 @@ import ( "testing" "time" - acmeprovider "github.com/containous/traefik/old/provider/acme" - "github.com/containous/traefik/old/types" + acmeprovider "github.com/containous/traefik/provider/acme" "github.com/containous/traefik/tls/generate" + "github.com/containous/traefik/types" "github.com/stretchr/testify/assert" "github.com/xenolf/lego/acme" ) diff --git a/acme/localStore.go b/acme/localStore.go index ec26f0119..b6883b38a 100644 --- a/acme/localStore.go +++ b/acme/localStore.go @@ -6,7 +6,7 @@ import ( "os" "github.com/containous/traefik/log" - "github.com/containous/traefik/old/provider/acme" + "github.com/containous/traefik/provider/acme" ) // LocalStore is a store using a file as storage diff --git a/anonymize/anonymize_config_test.go b/anonymize/anonymize_config_test.go index 0f0dc7a3b..59133bc8e 100644 --- a/anonymize/anonymize_config_test.go +++ b/anonymize/anonymize_config_test.go @@ -8,163 +8,77 @@ import ( "github.com/containous/flaeg/parse" "github.com/containous/traefik/acme" - "github.com/containous/traefik/old/api" - "github.com/containous/traefik/old/configuration" - "github.com/containous/traefik/old/middlewares" - "github.com/containous/traefik/old/provider" - acmeprovider "github.com/containous/traefik/old/provider/acme" - "github.com/containous/traefik/old/provider/boltdb" - "github.com/containous/traefik/old/provider/consul" - "github.com/containous/traefik/old/provider/consulcatalog" - "github.com/containous/traefik/old/provider/docker" - "github.com/containous/traefik/old/provider/dynamodb" - "github.com/containous/traefik/old/provider/ecs" - "github.com/containous/traefik/old/provider/etcd" - "github.com/containous/traefik/old/provider/eureka" - "github.com/containous/traefik/old/provider/file" - "github.com/containous/traefik/old/provider/kubernetes" - "github.com/containous/traefik/old/provider/kv" - "github.com/containous/traefik/old/provider/marathon" - "github.com/containous/traefik/old/provider/mesos" - "github.com/containous/traefik/old/provider/rancher" - "github.com/containous/traefik/old/provider/zk" - "github.com/containous/traefik/old/types" - "github.com/containous/traefik/safe" + "github.com/containous/traefik/config/static" + "github.com/containous/traefik/provider" + acmeprovider "github.com/containous/traefik/provider/acme" + "github.com/containous/traefik/provider/file" traefiktls "github.com/containous/traefik/tls" + "github.com/containous/traefik/types" "github.com/elazarl/go-bindata-assetfs" - "github.com/thoas/stats" ) func TestDo_globalConfiguration(t *testing.T) { - config := &configuration.GlobalConfiguration{} + config := &static.Configuration{} - config.Debug = true - config.CheckNewVersion = true + config.Global = &static.Global{ + Debug: true, + CheckNewVersion: true, + SendAnonymousUsage: true, + } config.AccessLog = &types.AccessLog{ FilePath: "AccessLog FilePath", Format: "AccessLog Format", } - config.LogLevel = "LogLevel" - config.EntryPoints = configuration.EntryPoints{ + config.Log = &types.TraefikLog{ + LogLevel: "LogLevel", + FilePath: "/foo/path", + Format: "json", + } + config.EntryPoints = static.EntryPoints{ "foo": { Address: "foo Address", + Transport: &static.EntryPointsTransport{ + RespondingTimeouts: &static.RespondingTimeouts{ + ReadTimeout: parse.Duration(111 * time.Second), + WriteTimeout: parse.Duration(111 * time.Second), + IdleTimeout: parse.Duration(111 * time.Second), + }, + }, TLS: &traefiktls.TLS{ MinVersion: "foo MinVersion", CipherSuites: []string{"foo CipherSuites 1", "foo CipherSuites 2", "foo CipherSuites 3"}, - Certificates: traefiktls.Certificates{ - {CertFile: "CertFile 1", KeyFile: "KeyFile 1"}, - {CertFile: "CertFile 2", KeyFile: "KeyFile 2"}, - }, ClientCA: traefiktls.ClientCA{ Files: traefiktls.FilesOrContents{"foo ClientCAFiles 1", "foo ClientCAFiles 2", "foo ClientCAFiles 3"}, Optional: false, }, }, - Redirect: &types.Redirect{ - Replacement: "foo Replacement", - Regex: "foo Regex", - EntryPoint: "foo EntryPoint", - }, - Auth: &types.Auth{ - Basic: &types.Basic{ - UsersFile: "foo Basic UsersFile", - Users: types.Users{"foo Basic Users 1", "foo Basic Users 2", "foo Basic Users 3"}, - }, - Digest: &types.Digest{ - UsersFile: "foo Digest UsersFile", - Users: types.Users{"foo Digest Users 1", "foo Digest Users 2", "foo Digest Users 3"}, - }, - Forward: &types.Forward{ - Address: "foo Address", - TLS: &types.ClientTLS{ - CA: "foo CA", - Cert: "foo Cert", - Key: "foo Key", - InsecureSkipVerify: true, - }, - TrustForwardHeader: true, - }, - }, - WhiteList: &types.WhiteList{ - SourceRange: []string{ - "127.0.0.1/32", - }, - }, - Compress: &configuration.Compress{}, - ProxyProtocol: &configuration.ProxyProtocol{ + ProxyProtocol: &static.ProxyProtocol{ TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"}, }, }, "fii": { Address: "fii Address", + Transport: &static.EntryPointsTransport{ + RespondingTimeouts: &static.RespondingTimeouts{ + ReadTimeout: parse.Duration(111 * time.Second), + WriteTimeout: parse.Duration(111 * time.Second), + IdleTimeout: parse.Duration(111 * time.Second), + }, + }, TLS: &traefiktls.TLS{ MinVersion: "fii MinVersion", CipherSuites: []string{"fii CipherSuites 1", "fii CipherSuites 2", "fii CipherSuites 3"}, - Certificates: traefiktls.Certificates{ - {CertFile: "CertFile 1", KeyFile: "KeyFile 1"}, - {CertFile: "CertFile 2", KeyFile: "KeyFile 2"}, - }, ClientCA: traefiktls.ClientCA{ Files: traefiktls.FilesOrContents{"fii ClientCAFiles 1", "fii ClientCAFiles 2", "fii ClientCAFiles 3"}, Optional: false, }, }, - Redirect: &types.Redirect{ - Replacement: "fii Replacement", - Regex: "fii Regex", - EntryPoint: "fii EntryPoint", - }, - Auth: &types.Auth{ - Basic: &types.Basic{ - UsersFile: "fii Basic UsersFile", - Users: types.Users{"fii Basic Users 1", "fii Basic Users 2", "fii Basic Users 3"}, - }, - Digest: &types.Digest{ - UsersFile: "fii Digest UsersFile", - Users: types.Users{"fii Digest Users 1", "fii Digest Users 2", "fii Digest Users 3"}, - }, - Forward: &types.Forward{ - Address: "fii Address", - TLS: &types.ClientTLS{ - CA: "fii CA", - Cert: "fii Cert", - Key: "fii Key", - InsecureSkipVerify: true, - }, - TrustForwardHeader: true, - }, - }, - WhiteList: &types.WhiteList{ - SourceRange: []string{ - "127.0.0.1/32", - }, - }, - Compress: &configuration.Compress{}, - ProxyProtocol: &configuration.ProxyProtocol{ + ProxyProtocol: &static.ProxyProtocol{ TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"}, }, }, } - config.Cluster = &types.Cluster{ - Node: "Cluster Node", - Store: &types.Store{ - Prefix: "Cluster Store Prefix", - // ... - }, - } - config.Constraints = types.Constraints{ - { - Key: "Constraints Key 1", - Regex: "Constraints Regex 2", - MustMatch: true, - }, - { - Key: "Constraints Key 1", - Regex: "Constraints Regex 2", - MustMatch: true, - }, - } config.ACME = &acme.ACME{ Email: "acme Email", Domains: []types.Domain{ @@ -186,33 +100,26 @@ func TestDo_globalConfiguration(t *testing.T) { // ... }, } - config.DefaultEntryPoints = configuration.DefaultEntryPoints{"DefaultEntryPoints 1", "DefaultEntryPoints 2", "DefaultEntryPoints 3"} - config.ProvidersThrottleDuration = parse.Duration(666 * time.Second) - config.MaxIdleConnsPerHost = 666 - config.InsecureSkipVerify = true - config.RootCAs = traefiktls.FilesOrContents{"RootCAs 1", "RootCAs 2", "RootCAs 3"} - config.Retry = &configuration.Retry{ - Attempts: 666, + config.Providers = &static.Providers{ + ProvidersThrottleDuration: parse.Duration(111 * time.Second), } - config.HealthCheck = &configuration.HealthCheckConfig{ - Interval: parse.Duration(666 * time.Second), + + config.ServersTransport = &static.ServersTransport{ + InsecureSkipVerify: true, + RootCAs: traefiktls.FilesOrContents{"RootCAs 1", "RootCAs 2", "RootCAs 3"}, + MaxIdleConnsPerHost: 111, + ForwardingTimeouts: &static.ForwardingTimeouts{ + DialTimeout: parse.Duration(111 * time.Second), + ResponseHeaderTimeout: parse.Duration(111 * time.Second), + }, } - config.API = &api.Handler{ - EntryPoint: "traefik", - Dashboard: true, - Debug: true, - CurrentConfigurations: &safe.Safe{}, + + config.API = &static.API{ + EntryPoint: "traefik", + Dashboard: true, Statistics: &types.Statistics{ - RecentErrors: 666, + RecentErrors: 111, }, - Stats: &stats.Stats{ - Uptime: time.Now(), - Pid: 666, - ResponseCounts: map[string]int{"foo": 1}, - TotalResponseCounts: map[string]int{"bar": 1}, - TotalResponseTime: time.Now(), - }, - StatsRecorder: &middlewares.StatsRecorder{}, DashboardAssets: &assetfs.AssetFS{ Asset: func(path string) ([]byte, error) { return nil, nil @@ -225,48 +132,10 @@ func TestDo_globalConfiguration(t *testing.T) { }, Prefix: "fii", }, + Middlewares: []string{"first", "second"}, } - config.RespondingTimeouts = &configuration.RespondingTimeouts{ - ReadTimeout: parse.Duration(666 * time.Second), - WriteTimeout: parse.Duration(666 * time.Second), - IdleTimeout: parse.Duration(666 * time.Second), - } - config.ForwardingTimeouts = &configuration.ForwardingTimeouts{ - DialTimeout: parse.Duration(666 * time.Second), - ResponseHeaderTimeout: parse.Duration(666 * time.Second), - } - config.Docker = &docker.Provider{ - BaseProvider: provider.BaseProvider{ - Watch: true, - Filename: "docker Filename", - Constraints: types.Constraints{ - { - Key: "docker Constraints Key 1", - Regex: "docker Constraints Regex 2", - MustMatch: true, - }, - { - Key: "docker Constraints Key 1", - Regex: "docker Constraints Regex 2", - MustMatch: true, - }, - }, - Trace: true, - DebugLogGeneratedTemplate: true, - }, - Endpoint: "docker Endpoint", - Domain: "docker Domain", - TLS: &types.ClientTLS{ - CA: "docker CA", - Cert: "docker Cert", - Key: "docker Key", - InsecureSkipVerify: true, - }, - ExposedByDefault: true, - UseBindPortIP: true, - SwarmMode: true, - } - config.File = &file.Provider{ + + config.Providers.File = &file.Provider{ BaseProvider: provider.BaseProvider{ Watch: true, Filename: "file Filename", @@ -287,368 +156,8 @@ func TestDo_globalConfiguration(t *testing.T) { }, Directory: "file Directory", } - config.Marathon = &marathon.Provider{ - BaseProvider: provider.BaseProvider{ - Watch: true, - Filename: "marathon Filename", - Constraints: types.Constraints{ - { - Key: "marathon Constraints Key 1", - Regex: "marathon Constraints Regex 2", - MustMatch: true, - }, - { - Key: "marathon Constraints Key 1", - Regex: "marathon Constraints Regex 2", - MustMatch: true, - }, - }, - Trace: true, - DebugLogGeneratedTemplate: true, - }, - Endpoint: "", - Domain: "", - ExposedByDefault: true, - GroupsAsSubDomains: true, - DCOSToken: "", - MarathonLBCompatibility: true, - TLS: &types.ClientTLS{ - CA: "marathon CA", - Cert: "marathon Cert", - Key: "marathon Key", - InsecureSkipVerify: true, - }, - DialerTimeout: parse.Duration(666 * time.Second), - KeepAlive: parse.Duration(666 * time.Second), - ForceTaskHostname: true, - Basic: &marathon.Basic{ - HTTPBasicAuthUser: "marathon HTTPBasicAuthUser", - HTTPBasicPassword: "marathon HTTPBasicPassword", - }, - RespectReadinessChecks: true, - } - config.ConsulCatalog = &consulcatalog.Provider{ - BaseProvider: provider.BaseProvider{ - Watch: true, - Filename: "ConsulCatalog Filename", - Constraints: types.Constraints{ - { - Key: "ConsulCatalog Constraints Key 1", - Regex: "ConsulCatalog Constraints Regex 2", - MustMatch: true, - }, - { - Key: "ConsulCatalog Constraints Key 1", - Regex: "ConsulCatalog Constraints Regex 2", - MustMatch: true, - }, - }, - Trace: true, - DebugLogGeneratedTemplate: true, - }, - Endpoint: "ConsulCatalog Endpoint", - Domain: "ConsulCatalog Domain", - ExposedByDefault: true, - Prefix: "ConsulCatalog Prefix", - FrontEndRule: "ConsulCatalog FrontEndRule", - } - config.Kubernetes = &kubernetes.Provider{ - BaseProvider: provider.BaseProvider{ - Watch: true, - Filename: "k8s Filename", - Constraints: types.Constraints{ - { - Key: "k8s Constraints Key 1", - Regex: "k8s Constraints Regex 2", - MustMatch: true, - }, - { - Key: "k8s Constraints Key 1", - Regex: "k8s Constraints Regex 2", - MustMatch: true, - }, - }, - Trace: true, - DebugLogGeneratedTemplate: true, - }, - Endpoint: "k8s Endpoint", - Token: "k8s Token", - CertAuthFilePath: "k8s CertAuthFilePath", - DisablePassHostHeaders: true, - Namespaces: kubernetes.Namespaces{"k8s Namespaces 1", "k8s Namespaces 2", "k8s Namespaces 3"}, - LabelSelector: "k8s LabelSelector", - } - config.Mesos = &mesos.Provider{ - BaseProvider: provider.BaseProvider{ - Watch: true, - Filename: "mesos Filename", - Constraints: types.Constraints{ - { - Key: "mesos Constraints Key 1", - Regex: "mesos Constraints Regex 2", - MustMatch: true, - }, - { - Key: "mesos Constraints Key 1", - Regex: "mesos Constraints Regex 2", - MustMatch: true, - }, - }, - Trace: true, - DebugLogGeneratedTemplate: true, - }, - Endpoint: "mesos Endpoint", - Domain: "mesos Domain", - ExposedByDefault: true, - GroupsAsSubDomains: true, - ZkDetectionTimeout: 666, - RefreshSeconds: 666, - IPSources: "mesos IPSources", - StateTimeoutSecond: 666, - Masters: []string{"mesos Masters 1", "mesos Masters 2", "mesos Masters 3"}, - } - config.Eureka = &eureka.Provider{ - BaseProvider: provider.BaseProvider{ - Watch: true, - Filename: "eureka Filename", - Constraints: types.Constraints{ - { - Key: "eureka Constraints Key 1", - Regex: "eureka Constraints Regex 2", - MustMatch: true, - }, - { - Key: "eureka Constraints Key 1", - Regex: "eureka Constraints Regex 2", - MustMatch: true, - }, - }, - Trace: true, - DebugLogGeneratedTemplate: true, - }, - Endpoint: "eureka Endpoint", - RefreshSeconds: parse.Duration(30 * time.Second), - } - config.ECS = &ecs.Provider{ - BaseProvider: provider.BaseProvider{ - Watch: true, - Filename: "ecs Filename", - Constraints: types.Constraints{ - { - Key: "ecs Constraints Key 1", - Regex: "ecs Constraints Regex 2", - MustMatch: true, - }, - { - Key: "ecs Constraints Key 1", - Regex: "ecs Constraints Regex 2", - MustMatch: true, - }, - }, - Trace: true, - DebugLogGeneratedTemplate: true, - }, - Domain: "ecs Domain", - ExposedByDefault: true, - RefreshSeconds: 666, - Clusters: ecs.Clusters{"ecs Clusters 1", "ecs Clusters 2", "ecs Clusters 3"}, - AutoDiscoverClusters: true, - Region: "ecs Region", - AccessKeyID: "ecs AccessKeyID", - SecretAccessKey: "ecs SecretAccessKey", - } - config.Rancher = &rancher.Provider{ - BaseProvider: provider.BaseProvider{ - Watch: true, - Filename: "rancher Filename", - Constraints: types.Constraints{ - { - Key: "rancher Constraints Key 1", - Regex: "rancher Constraints Regex 2", - MustMatch: true, - }, - { - Key: "rancher Constraints Key 1", - Regex: "rancher Constraints Regex 2", - MustMatch: true, - }, - }, - Trace: true, - DebugLogGeneratedTemplate: true, - }, - APIConfiguration: rancher.APIConfiguration{ - Endpoint: "rancher Endpoint", - AccessKey: "rancher AccessKey", - SecretKey: "rancher SecretKey", - }, - API: &rancher.APIConfiguration{ - Endpoint: "rancher Endpoint", - AccessKey: "rancher AccessKey", - SecretKey: "rancher SecretKey", - }, - Metadata: &rancher.MetadataConfiguration{ - IntervalPoll: true, - Prefix: "rancher Metadata Prefix", - }, - Domain: "rancher Domain", - RefreshSeconds: 666, - ExposedByDefault: true, - EnableServiceHealthFilter: true, - } - config.DynamoDB = &dynamodb.Provider{ - BaseProvider: provider.BaseProvider{ - Watch: true, - Filename: "dynamodb Filename", - Constraints: types.Constraints{ - { - Key: "dynamodb Constraints Key 1", - Regex: "dynamodb Constraints Regex 2", - MustMatch: true, - }, - { - Key: "dynamodb Constraints Key 1", - Regex: "dynamodb Constraints Regex 2", - MustMatch: true, - }, - }, - Trace: true, - DebugLogGeneratedTemplate: true, - }, - AccessKeyID: "dynamodb AccessKeyID", - RefreshSeconds: 666, - Region: "dynamodb Region", - SecretAccessKey: "dynamodb SecretAccessKey", - TableName: "dynamodb TableName", - Endpoint: "dynamodb Endpoint", - } - config.Etcd = &etcd.Provider{ - Provider: kv.Provider{ - BaseProvider: provider.BaseProvider{ - Watch: true, - Filename: "etcd Filename", - Constraints: types.Constraints{ - { - Key: "etcd Constraints Key 1", - Regex: "etcd Constraints Regex 2", - MustMatch: true, - }, - { - Key: "etcd Constraints Key 1", - Regex: "etcd Constraints Regex 2", - MustMatch: true, - }, - }, - Trace: true, - DebugLogGeneratedTemplate: true, - }, - Endpoint: "etcd Endpoint", - Prefix: "etcd Prefix", - TLS: &types.ClientTLS{ - CA: "etcd CA", - Cert: "etcd Cert", - Key: "etcd Key", - InsecureSkipVerify: true, - }, - Username: "etcd Username", - Password: "etcd Password", - }, - } - config.Zookeeper = &zk.Provider{ - Provider: kv.Provider{ - BaseProvider: provider.BaseProvider{ - Watch: true, - Filename: "zk Filename", - Constraints: types.Constraints{ - { - Key: "zk Constraints Key 1", - Regex: "zk Constraints Regex 2", - MustMatch: true, - }, - { - Key: "zk Constraints Key 1", - Regex: "zk Constraints Regex 2", - MustMatch: true, - }, - }, - Trace: true, - DebugLogGeneratedTemplate: true, - }, - Endpoint: "zk Endpoint", - Prefix: "zk Prefix", - TLS: &types.ClientTLS{ - CA: "zk CA", - Cert: "zk Cert", - Key: "zk Key", - InsecureSkipVerify: true, - }, - Username: "zk Username", - Password: "zk Password", - }, - } - config.Boltdb = &boltdb.Provider{ - Provider: kv.Provider{ - BaseProvider: provider.BaseProvider{ - Watch: true, - Filename: "boltdb Filename", - Constraints: types.Constraints{ - { - Key: "boltdb Constraints Key 1", - Regex: "boltdb Constraints Regex 2", - MustMatch: true, - }, - { - Key: "boltdb Constraints Key 1", - Regex: "boltdb Constraints Regex 2", - MustMatch: true, - }, - }, - Trace: true, - DebugLogGeneratedTemplate: true, - }, - Endpoint: "boltdb Endpoint", - Prefix: "boltdb Prefix", - TLS: &types.ClientTLS{ - CA: "boltdb CA", - Cert: "boltdb Cert", - Key: "boltdb Key", - InsecureSkipVerify: true, - }, - Username: "boltdb Username", - Password: "boltdb Password", - }, - } - config.Consul = &consul.Provider{ - Provider: kv.Provider{ - BaseProvider: provider.BaseProvider{ - Watch: true, - Filename: "consul Filename", - Constraints: types.Constraints{ - { - Key: "consul Constraints Key 1", - Regex: "consul Constraints Regex 2", - MustMatch: true, - }, - { - Key: "consul Constraints Key 1", - Regex: "consul Constraints Regex 2", - MustMatch: true, - }, - }, - Trace: true, - DebugLogGeneratedTemplate: true, - }, - Endpoint: "consul Endpoint", - Prefix: "consul Prefix", - TLS: &types.ClientTLS{ - CA: "consul CA", - Cert: "consul Cert", - Key: "consul Key", - InsecureSkipVerify: true, - }, - Username: "consul Username", - Password: "consul Password", - }, - } + + // FIXME Test the other providers once they are migrated cleanJSON, err := Do(config, true) if err != nil { diff --git a/cmd/bug/bug_test.go b/cmd/bug/bug_test.go index e21136bcb..687cd87e4 100644 --- a/cmd/bug/bug_test.go +++ b/cmd/bug/bug_test.go @@ -5,36 +5,19 @@ import ( "github.com/containous/traefik/anonymize" "github.com/containous/traefik/cmd" - "github.com/containous/traefik/old/configuration" - "github.com/containous/traefik/old/provider/file" - "github.com/containous/traefik/old/types" - "github.com/containous/traefik/tls" + "github.com/containous/traefik/config/static" "github.com/stretchr/testify/assert" ) func Test_createReport(t *testing.T) { traefikConfiguration := &cmd.TraefikConfiguration{ ConfigFile: "FOO", - GlobalConfiguration: configuration.GlobalConfiguration{ - EntryPoints: configuration.EntryPoints{ - "goo": &configuration.EntryPoint{ + Configuration: static.Configuration{ + EntryPoints: static.EntryPoints{ + "goo": &static.EntryPoint{ Address: "hoo.bar", - Auth: &types.Auth{ - Basic: &types.Basic{ - UsersFile: "foo Basic UsersFile", - Users: types.Users{"foo Basic Users 1", "foo Basic Users 2", "foo Basic Users 3"}, - }, - Digest: &types.Digest{ - UsersFile: "foo Digest UsersFile", - Users: types.Users{"foo Digest Users 1", "foo Digest Users 2", "foo Digest Users 3"}, - }, - }, }, }, - File: &file.Provider{ - Directory: "BAR", - }, - RootCAs: tls.FilesOrContents{"fllf"}, }, } @@ -42,26 +25,21 @@ func Test_createReport(t *testing.T) { assert.NoError(t, err, report) // exported anonymous configuration - assert.NotContains(t, "web Basic Users ", report) - assert.NotContains(t, "foo Digest Users ", report) assert.NotContains(t, "hoo.bar", report) } func Test_anonymize_traefikConfiguration(t *testing.T) { traefikConfiguration := &cmd.TraefikConfiguration{ ConfigFile: "FOO", - GlobalConfiguration: configuration.GlobalConfiguration{ - EntryPoints: configuration.EntryPoints{ - "goo": &configuration.EntryPoint{ + Configuration: static.Configuration{ + EntryPoints: static.EntryPoints{ + "goo": &static.EntryPoint{ Address: "hoo.bar", }, }, - File: &file.Provider{ - Directory: "BAR", - }, }, } _, err := anonymize.Do(traefikConfiguration, true) assert.NoError(t, err) - assert.Equal(t, "hoo.bar", traefikConfiguration.GlobalConfiguration.EntryPoints["goo"].Address) + assert.Equal(t, "hoo.bar", traefikConfiguration.Configuration.EntryPoints["goo"].Address) } diff --git a/cmd/configuration.go b/cmd/configuration.go index f9a9d95af..1cf25aa5e 100644 --- a/cmd/configuration.go +++ b/cmd/configuration.go @@ -4,14 +4,9 @@ import ( "time" "github.com/containous/flaeg/parse" - "github.com/containous/traefik/old/api" + "github.com/containous/traefik/config/static" "github.com/containous/traefik/old/configuration" "github.com/containous/traefik/old/middlewares/accesslog" - "github.com/containous/traefik/old/middlewares/tracing" - "github.com/containous/traefik/old/middlewares/tracing/datadog" - "github.com/containous/traefik/old/middlewares/tracing/jaeger" - "github.com/containous/traefik/old/middlewares/tracing/zipkin" - "github.com/containous/traefik/old/ping" "github.com/containous/traefik/old/provider/boltdb" "github.com/containous/traefik/old/provider/consul" "github.com/containous/traefik/old/provider/consulcatalog" @@ -20,129 +15,53 @@ import ( "github.com/containous/traefik/old/provider/ecs" "github.com/containous/traefik/old/provider/etcd" "github.com/containous/traefik/old/provider/eureka" - "github.com/containous/traefik/old/provider/file" "github.com/containous/traefik/old/provider/kubernetes" "github.com/containous/traefik/old/provider/marathon" "github.com/containous/traefik/old/provider/mesos" "github.com/containous/traefik/old/provider/rancher" "github.com/containous/traefik/old/provider/rest" "github.com/containous/traefik/old/provider/zk" - "github.com/containous/traefik/old/types" + "github.com/containous/traefik/ping" + "github.com/containous/traefik/provider/file" + "github.com/containous/traefik/tracing/datadog" + "github.com/containous/traefik/tracing/jaeger" + "github.com/containous/traefik/tracing/zipkin" + "github.com/containous/traefik/types" ) // TraefikConfiguration holds GlobalConfiguration and other stuff type TraefikConfiguration struct { - configuration.GlobalConfiguration `mapstructure:",squash" export:"true"` - ConfigFile string `short:"c" description:"Configuration file to use (TOML)." export:"true"` + static.Configuration `mapstructure:",squash" export:"true"` + ConfigFile string `short:"c" description:"Configuration file to use (TOML)." export:"true"` +} + +// NewTraefikConfiguration creates a TraefikConfiguration with default values +func NewTraefikConfiguration() *TraefikConfiguration { + return &TraefikConfiguration{ + Configuration: static.Configuration{ + Global: &static.Global{ + CheckNewVersion: true, + SendAnonymousUsage: false, + }, + EntryPoints: make(static.EntryPoints), + Providers: &static.Providers{ + ProvidersThrottleDuration: parse.Duration(2 * time.Second), + }, + ServersTransport: &static.ServersTransport{ + MaxIdleConnsPerHost: 200, + }, + }, + ConfigFile: "", + } } // NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { - // default Docker - var defaultDocker docker.Provider - defaultDocker.Watch = true - defaultDocker.ExposedByDefault = true - defaultDocker.Endpoint = "unix:///var/run/docker.sock" - defaultDocker.SwarmMode = false - // default File var defaultFile file.Provider defaultFile.Watch = true defaultFile.Filename = "" // needs equivalent to viper.ConfigFileUsed() - // default Rest - var defaultRest rest.Provider - defaultRest.EntryPoint = configuration.DefaultInternalEntryPointName - - // default Marathon - var defaultMarathon marathon.Provider - defaultMarathon.Watch = true - defaultMarathon.Endpoint = "http://127.0.0.1:8080" - defaultMarathon.ExposedByDefault = true - defaultMarathon.Constraints = types.Constraints{} - defaultMarathon.DialerTimeout = parse.Duration(5 * time.Second) - defaultMarathon.ResponseHeaderTimeout = parse.Duration(60 * time.Second) - defaultMarathon.TLSHandshakeTimeout = parse.Duration(5 * time.Second) - defaultMarathon.KeepAlive = parse.Duration(10 * time.Second) - - // default Consul - var defaultConsul consul.Provider - defaultConsul.Watch = true - defaultConsul.Endpoint = "127.0.0.1:8500" - defaultConsul.Prefix = "traefik" - defaultConsul.Constraints = types.Constraints{} - - // default CatalogProvider - var defaultConsulCatalog consulcatalog.Provider - defaultConsulCatalog.Endpoint = "127.0.0.1:8500" - defaultConsulCatalog.ExposedByDefault = true - defaultConsulCatalog.Constraints = types.Constraints{} - defaultConsulCatalog.Prefix = "traefik" - defaultConsulCatalog.FrontEndRule = "Host:{{.ServiceName}}.{{.Domain}}" - defaultConsulCatalog.Stale = false - - // default Etcd - var defaultEtcd etcd.Provider - defaultEtcd.Watch = true - defaultEtcd.Endpoint = "127.0.0.1:2379" - defaultEtcd.Prefix = "/traefik" - defaultEtcd.Constraints = types.Constraints{} - - // default Zookeeper - var defaultZookeeper zk.Provider - defaultZookeeper.Watch = true - defaultZookeeper.Endpoint = "127.0.0.1:2181" - defaultZookeeper.Prefix = "traefik" - defaultZookeeper.Constraints = types.Constraints{} - - // default Boltdb - var defaultBoltDb boltdb.Provider - defaultBoltDb.Watch = true - defaultBoltDb.Endpoint = "127.0.0.1:4001" - defaultBoltDb.Prefix = "/traefik" - defaultBoltDb.Constraints = types.Constraints{} - - // default Kubernetes - var defaultKubernetes kubernetes.Provider - defaultKubernetes.Watch = true - defaultKubernetes.Constraints = types.Constraints{} - - // default Mesos - var defaultMesos mesos.Provider - defaultMesos.Watch = true - defaultMesos.Endpoint = "http://127.0.0.1:5050" - defaultMesos.ExposedByDefault = true - defaultMesos.Constraints = types.Constraints{} - defaultMesos.RefreshSeconds = 30 - defaultMesos.ZkDetectionTimeout = 30 - defaultMesos.StateTimeoutSecond = 30 - - // default ECS - var defaultECS ecs.Provider - defaultECS.Watch = true - defaultECS.ExposedByDefault = true - defaultECS.AutoDiscoverClusters = false - defaultECS.Clusters = ecs.Clusters{"default"} - defaultECS.RefreshSeconds = 15 - defaultECS.Constraints = types.Constraints{} - - // default Rancher - var defaultRancher rancher.Provider - defaultRancher.Watch = true - defaultRancher.ExposedByDefault = true - defaultRancher.RefreshSeconds = 15 - - // default DynamoDB - var defaultDynamoDB dynamodb.Provider - defaultDynamoDB.Constraints = types.Constraints{} - defaultDynamoDB.RefreshSeconds = 15 - defaultDynamoDB.TableName = "traefik" - defaultDynamoDB.Watch = true - - // default Eureka - var defaultEureka eureka.Provider - defaultEureka.RefreshSeconds = parse.Duration(30 * time.Second) - // default Ping var defaultPing = ping.Handler{ EntryPoint: "traefik", @@ -167,24 +86,8 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { }, } - // default HealthCheckConfig - healthCheck := configuration.HealthCheckConfig{ - Interval: parse.Duration(configuration.DefaultHealthCheckInterval), - Timeout: parse.Duration(configuration.DefaultHealthCheckTimeout), - } - - // default RespondingTimeouts - respondingTimeouts := configuration.RespondingTimeouts{ - IdleTimeout: parse.Duration(configuration.DefaultIdleTimeout), - } - - // default ForwardingTimeouts - forwardingTimeouts := configuration.ForwardingTimeouts{ - DialTimeout: parse.Duration(configuration.DefaultDialTimeout), - } - // default Tracing - defaultTracing := tracing.Tracing{ + defaultTracing := static.Tracing{ Backend: "jaeger", ServiceName: "traefik", SpanNameLimit: 0, @@ -210,13 +113,8 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { }, } - // default LifeCycle - defaultLifeCycle := configuration.LifeCycle{ - GraceTimeOut: parse.Duration(configuration.DefaultGraceTimeout), - } - // default ApiConfiguration - defaultAPI := api.Handler{ + defaultAPI := static.API{ EntryPoint: "traefik", Dashboard: true, } @@ -245,65 +143,129 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { }, } - defaultResolver := configuration.HostResolverConfig{ + defaultResolver := static.HostResolverConfig{ CnameFlattening: false, ResolvConfig: "/etc/resolv.conf", ResolvDepth: 5, } - defaultConfiguration := configuration.GlobalConfiguration{ - Docker: &defaultDocker, - File: &defaultFile, - Rest: &defaultRest, - Marathon: &defaultMarathon, - Consul: &defaultConsul, - ConsulCatalog: &defaultConsulCatalog, - Etcd: &defaultEtcd, - Zookeeper: &defaultZookeeper, - Boltdb: &defaultBoltDb, - Kubernetes: &defaultKubernetes, - Mesos: &defaultMesos, - ECS: &defaultECS, - Rancher: &defaultRancher, - Eureka: &defaultEureka, - DynamoDB: &defaultDynamoDB, - Retry: &configuration.Retry{}, - HealthCheck: &healthCheck, - RespondingTimeouts: &respondingTimeouts, - ForwardingTimeouts: &forwardingTimeouts, - TraefikLog: &defaultTraefikLog, - AccessLog: &defaultAccessLog, - LifeCycle: &defaultLifeCycle, - Ping: &defaultPing, - API: &defaultAPI, - Metrics: &defaultMetrics, - Tracing: &defaultTracing, - HostResolver: &defaultResolver, + var defaultDocker docker.Provider + defaultDocker.Watch = true + defaultDocker.ExposedByDefault = true + defaultDocker.Endpoint = "unix:///var/run/docker.sock" + defaultDocker.SwarmMode = false + + // default Rest + var defaultRest rest.Provider + defaultRest.EntryPoint = configuration.DefaultInternalEntryPointName + + // default Marathon + var defaultMarathon marathon.Provider + defaultMarathon.Watch = true + defaultMarathon.Endpoint = "http://127.0.0.1:8080" + defaultMarathon.ExposedByDefault = true + defaultMarathon.DialerTimeout = parse.Duration(5 * time.Second) + defaultMarathon.ResponseHeaderTimeout = parse.Duration(60 * time.Second) + defaultMarathon.TLSHandshakeTimeout = parse.Duration(5 * time.Second) + defaultMarathon.KeepAlive = parse.Duration(10 * time.Second) + + // default Consul + var defaultConsul consul.Provider + defaultConsul.Watch = true + defaultConsul.Endpoint = "127.0.0.1:8500" + defaultConsul.Prefix = "traefik" + + // default CatalogProvider + var defaultConsulCatalog consulcatalog.Provider + defaultConsulCatalog.Endpoint = "127.0.0.1:8500" + defaultConsulCatalog.ExposedByDefault = true + defaultConsulCatalog.Prefix = "traefik" + defaultConsulCatalog.FrontEndRule = "Host:{{.ServiceName}}.{{.Domain}}" + defaultConsulCatalog.Stale = false + + // default Etcd + var defaultEtcd etcd.Provider + defaultEtcd.Watch = true + defaultEtcd.Endpoint = "127.0.0.1:2379" + defaultEtcd.Prefix = "/traefik" + + // default Zookeeper + var defaultZookeeper zk.Provider + defaultZookeeper.Watch = true + defaultZookeeper.Endpoint = "127.0.0.1:2181" + defaultZookeeper.Prefix = "traefik" + + // default Boltdb + var defaultBoltDb boltdb.Provider + defaultBoltDb.Watch = true + defaultBoltDb.Endpoint = "127.0.0.1:4001" + defaultBoltDb.Prefix = "/traefik" + + // default Kubernetes + var defaultKubernetes kubernetes.Provider + defaultKubernetes.Watch = true + + // default Mesos + var defaultMesos mesos.Provider + defaultMesos.Watch = true + defaultMesos.Endpoint = "http://127.0.0.1:5050" + defaultMesos.ExposedByDefault = true + defaultMesos.RefreshSeconds = 30 + defaultMesos.ZkDetectionTimeout = 30 + defaultMesos.StateTimeoutSecond = 30 + + // default ECS + var defaultECS ecs.Provider + defaultECS.Watch = true + defaultECS.ExposedByDefault = true + defaultECS.AutoDiscoverClusters = false + defaultECS.Clusters = ecs.Clusters{"default"} + defaultECS.RefreshSeconds = 15 + + // default Rancher + var defaultRancher rancher.Provider + defaultRancher.Watch = true + defaultRancher.ExposedByDefault = true + defaultRancher.RefreshSeconds = 15 + + // default DynamoDB + var defaultDynamoDB dynamodb.Provider + defaultDynamoDB.RefreshSeconds = 15 + defaultDynamoDB.TableName = "traefik" + defaultDynamoDB.Watch = true + + // default Eureka + var defaultEureka eureka.Provider + defaultEureka.RefreshSeconds = parse.Duration(30 * time.Second) + + defaultProviders := static.Providers{ + File: &defaultFile, + Docker: &defaultDocker, + Rest: &defaultRest, + Marathon: &defaultMarathon, + Consul: &defaultConsul, + ConsulCatalog: &defaultConsulCatalog, + Etcd: &defaultEtcd, + Zookeeper: &defaultZookeeper, + Boltdb: &defaultBoltDb, + Kubernetes: &defaultKubernetes, + Mesos: &defaultMesos, + ECS: &defaultECS, + Rancher: &defaultRancher, + Eureka: &defaultEureka, + DynamoDB: &defaultDynamoDB, } return &TraefikConfiguration{ - GlobalConfiguration: defaultConfiguration, - } -} - -// NewTraefikConfiguration creates a TraefikConfiguration with default values -func NewTraefikConfiguration() *TraefikConfiguration { - return &TraefikConfiguration{ - GlobalConfiguration: configuration.GlobalConfiguration{ - EntryPoints: map[string]*configuration.EntryPoint{}, - Constraints: types.Constraints{}, - DefaultEntryPoints: []string{"http"}, - ProvidersThrottleDuration: parse.Duration(2 * time.Second), - MaxIdleConnsPerHost: 200, - HealthCheck: &configuration.HealthCheckConfig{ - Interval: parse.Duration(configuration.DefaultHealthCheckInterval), - Timeout: parse.Duration(configuration.DefaultHealthCheckTimeout), - }, - LifeCycle: &configuration.LifeCycle{ - GraceTimeOut: parse.Duration(configuration.DefaultGraceTimeout), - }, - CheckNewVersion: true, + Configuration: static.Configuration{ + Providers: &defaultProviders, + Log: &defaultTraefikLog, + AccessLog: &defaultAccessLog, + Ping: &defaultPing, + API: &defaultAPI, + Metrics: &defaultMetrics, + Tracing: &defaultTracing, + HostResolver: &defaultResolver, }, - ConfigFile: "", } } diff --git a/cmd/healthcheck/healthcheck.go b/cmd/healthcheck/healthcheck.go index 8822c76a3..0637e0667 100644 --- a/cmd/healthcheck/healthcheck.go +++ b/cmd/healthcheck/healthcheck.go @@ -10,7 +10,7 @@ import ( "github.com/containous/flaeg" "github.com/containous/traefik/cmd" - "github.com/containous/traefik/old/configuration" + "github.com/containous/traefik/config/static" ) // NewCmd builds a new HealthCheck command @@ -29,9 +29,9 @@ func NewCmd(traefikConfiguration *cmd.TraefikConfiguration, traefikPointersConfi func runCmd(traefikConfiguration *cmd.TraefikConfiguration) func() error { return func() error { - traefikConfiguration.GlobalConfiguration.SetEffectiveConfiguration(traefikConfiguration.ConfigFile) + traefikConfiguration.Configuration.SetEffectiveConfiguration(traefikConfiguration.ConfigFile) - resp, errPing := Do(traefikConfiguration.GlobalConfiguration) + resp, errPing := Do(traefikConfiguration.Configuration) if errPing != nil { fmt.Printf("Error calling healthcheck: %s\n", errPing) os.Exit(1) @@ -47,17 +47,18 @@ func runCmd(traefikConfiguration *cmd.TraefikConfiguration) func() error { } // Do try to do a healthcheck -func Do(globalConfiguration configuration.GlobalConfiguration) (*http.Response, error) { - if globalConfiguration.Ping == nil { +func Do(staticConfiguration static.Configuration) (*http.Response, error) { + if staticConfiguration.Ping == nil { return nil, errors.New("please enable `ping` to use health check") } - pingEntryPoint, ok := globalConfiguration.EntryPoints[globalConfiguration.Ping.EntryPoint] + pingEntryPoint, ok := staticConfiguration.EntryPoints[staticConfiguration.Ping.EntryPoint] if !ok { return nil, errors.New("missing `ping` entrypoint") } client := &http.Client{Timeout: 5 * time.Second} protocol := "http" + if pingEntryPoint.TLS != nil { protocol = "https" tr := &http.Transport{ @@ -65,6 +66,7 @@ func Do(globalConfiguration configuration.GlobalConfiguration) (*http.Response, } client.Transport = tr } + path := "/" return client.Head(protocol + "://" + pingEntryPoint.Address + path + "ping") diff --git a/cmd/storeconfig/storeconfig.go b/cmd/storeconfig/storeconfig.go index 9a79ff139..ea97eb347 100644 --- a/cmd/storeconfig/storeconfig.go +++ b/cmd/storeconfig/storeconfig.go @@ -36,21 +36,21 @@ func Run(kv *staert.KvSource, traefikConfiguration *cmd.TraefikConfiguration) fu return fmt.Errorf("error using command storeconfig, no Key-value store defined") } - fileConfig := traefikConfiguration.GlobalConfiguration.File + fileConfig := traefikConfiguration.Providers.File if fileConfig != nil { - traefikConfiguration.GlobalConfiguration.File = nil + traefikConfiguration.Providers.File = nil if len(fileConfig.Filename) == 0 && len(fileConfig.Directory) == 0 { fileConfig.Filename = traefikConfiguration.ConfigFile } } - jsonConf, err := json.Marshal(traefikConfiguration.GlobalConfiguration) + jsonConf, err := json.Marshal(traefikConfiguration.Configuration) if err != nil { return err } stdlog.Printf("Storing configuration: %s\n", jsonConf) - err = kv.StoreConfig(traefikConfiguration.GlobalConfiguration) + err = kv.StoreConfig(traefikConfiguration.Configuration) if err != nil { return err } @@ -74,24 +74,24 @@ func Run(kv *staert.KvSource, traefikConfiguration *cmd.TraefikConfiguration) fu } } - if traefikConfiguration.GlobalConfiguration.ACME != nil { + if traefikConfiguration.Configuration.ACME != nil { account := &acme.Account{} // Migrate ACME data from file to KV store if needed - if len(traefikConfiguration.GlobalConfiguration.ACME.StorageFile) > 0 { - account, err = migrateACMEData(traefikConfiguration.GlobalConfiguration.ACME.StorageFile) + if len(traefikConfiguration.Configuration.ACME.StorageFile) > 0 { + account, err = migrateACMEData(traefikConfiguration.Configuration.ACME.StorageFile) if err != nil { return err } } - accountInitialized, err := keyExists(kv, traefikConfiguration.GlobalConfiguration.ACME.Storage) + accountInitialized, err := keyExists(kv, traefikConfiguration.Configuration.ACME.Storage) if err != nil && err != store.ErrKeyNotFound { return err } // Check to see if ACME account object is already in kv store - if traefikConfiguration.GlobalConfiguration.ACME.OverrideCertificates || !accountInitialized { + if traefikConfiguration.Configuration.ACME.OverrideCertificates || !accountInitialized { // Store the ACME Account into the KV Store // Certificates in KV Store will be overridden @@ -103,7 +103,7 @@ func Run(kv *staert.KvSource, traefikConfiguration *cmd.TraefikConfiguration) fu source := staert.KvSource{ Store: kv, - Prefix: traefikConfiguration.GlobalConfiguration.ACME.Storage, + Prefix: traefikConfiguration.Configuration.ACME.Storage, } err = source.StoreConfig(meta) @@ -182,29 +182,29 @@ func CreateKvSource(traefikConfiguration *cmd.TraefikConfiguration) (*staert.KvS var err error switch { - case traefikConfiguration.Consul != nil: - kvStore, err = traefikConfiguration.Consul.CreateStore() + case traefikConfiguration.Providers.Consul != nil: + kvStore, err = traefikConfiguration.Providers.Consul.CreateStore() kv = &staert.KvSource{ Store: kvStore, - Prefix: traefikConfiguration.Consul.Prefix, + Prefix: traefikConfiguration.Providers.Consul.Prefix, } - case traefikConfiguration.Etcd != nil: - kvStore, err = traefikConfiguration.Etcd.CreateStore() + case traefikConfiguration.Providers.Etcd != nil: + kvStore, err = traefikConfiguration.Providers.Etcd.CreateStore() kv = &staert.KvSource{ Store: kvStore, - Prefix: traefikConfiguration.Etcd.Prefix, + Prefix: traefikConfiguration.Providers.Etcd.Prefix, } - case traefikConfiguration.Zookeeper != nil: - kvStore, err = traefikConfiguration.Zookeeper.CreateStore() + case traefikConfiguration.Providers.Zookeeper != nil: + kvStore, err = traefikConfiguration.Providers.Zookeeper.CreateStore() kv = &staert.KvSource{ Store: kvStore, - Prefix: traefikConfiguration.Zookeeper.Prefix, + Prefix: traefikConfiguration.Providers.Zookeeper.Prefix, } - case traefikConfiguration.Boltdb != nil: - kvStore, err = traefikConfiguration.Boltdb.CreateStore() + case traefikConfiguration.Providers.Boltdb != nil: + kvStore, err = traefikConfiguration.Providers.Boltdb.CreateStore() kv = &staert.KvSource{ Store: kvStore, - Prefix: traefikConfiguration.Boltdb.Prefix, + Prefix: traefikConfiguration.Providers.Boltdb.Prefix, } } return kv, err diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index 808b795f9..81bb76a15 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/json" + "fmt" fmtlog "log" "net/http" "os" @@ -25,16 +26,15 @@ import ( "github.com/containous/traefik/config/static" "github.com/containous/traefik/job" "github.com/containous/traefik/log" - "github.com/containous/traefik/old/configuration" "github.com/containous/traefik/old/provider/ecs" "github.com/containous/traefik/old/provider/kubernetes" - "github.com/containous/traefik/old/types" + oldtypes "github.com/containous/traefik/old/types" "github.com/containous/traefik/provider/aggregator" "github.com/containous/traefik/safe" "github.com/containous/traefik/server" "github.com/containous/traefik/server/router" - "github.com/containous/traefik/server/uuid" traefiktls "github.com/containous/traefik/tls" + "github.com/containous/traefik/types" "github.com/containous/traefik/version" "github.com/coreos/go-systemd/daemon" "github.com/elazarl/go-bindata-assetfs" @@ -43,6 +43,44 @@ import ( "github.com/vulcand/oxy/roundrobin" ) +// sliceOfStrings is the parser for []string +type sliceOfStrings []string + +// String is the method to format the flag's value, part of the flag.Value interface. +// The String method's output will be used in diagnostics. +func (s *sliceOfStrings) String() string { + return strings.Join(*s, ",") +} + +// Set is the method to set the flag value, part of the flag.Value interface. +// Set's argument is a string to be parsed to set the flag. +// It's a comma-separated list, so we split it. +func (s *sliceOfStrings) Set(value string) error { + strings := strings.Split(value, ",") + if len(strings) == 0 { + return fmt.Errorf("bad []string format: %s", value) + } + for _, entrypoint := range strings { + *s = append(*s, entrypoint) + } + return nil +} + +// Get return the []string +func (s *sliceOfStrings) Get() interface{} { + return *s +} + +// SetValue sets the []string with val +func (s *sliceOfStrings) SetValue(val interface{}) { + *s = val.([]string) +} + +// Type is type of the struct +func (s *sliceOfStrings) Type() string { + return "sliceOfStrings" +} + func main() { // traefik config inits traefikConfiguration := cmd.NewTraefikConfiguration() @@ -56,7 +94,7 @@ Complete documentation is available at https://traefik.io`, Config: traefikConfiguration, DefaultPointersConfig: traefikPointersConfiguration, Run: func() error { - runCmd(&traefikConfiguration.GlobalConfiguration, traefikConfiguration.ConfigFile) + runCmd(&traefikConfiguration.Configuration, traefikConfiguration.ConfigFile) return nil }, } @@ -67,8 +105,9 @@ Complete documentation is available at https://traefik.io`, // init flaeg source f := flaeg.New(traefikCmd, os.Args[1:]) // add custom parsers - f.AddParser(reflect.TypeOf(configuration.EntryPoints{}), &configuration.EntryPoints{}) - f.AddParser(reflect.TypeOf(configuration.DefaultEntryPoints{}), &configuration.DefaultEntryPoints{}) + f.AddParser(reflect.TypeOf(static.EntryPoints{}), &static.EntryPoints{}) + + f.AddParser(reflect.SliceOf(reflect.TypeOf("")), &sliceOfStrings{}) f.AddParser(reflect.TypeOf(traefiktls.FilesOrContents{}), &traefiktls.FilesOrContents{}) f.AddParser(reflect.TypeOf(types.Constraints{}), &types.Constraints{}) f.AddParser(reflect.TypeOf(kubernetes.Namespaces{}), &kubernetes.Namespaces{}) @@ -76,10 +115,16 @@ Complete documentation is available at https://traefik.io`, f.AddParser(reflect.TypeOf([]types.Domain{}), &types.Domains{}) f.AddParser(reflect.TypeOf(types.DNSResolvers{}), &types.DNSResolvers{}) f.AddParser(reflect.TypeOf(types.Buckets{}), &types.Buckets{}) + f.AddParser(reflect.TypeOf(types.StatusCodes{}), &types.StatusCodes{}) f.AddParser(reflect.TypeOf(types.FieldNames{}), &types.FieldNames{}) f.AddParser(reflect.TypeOf(types.FieldHeaderNames{}), &types.FieldHeaderNames{}) + // FIXME Remove with ACME + f.AddParser(reflect.TypeOf([]oldtypes.Domain{}), &oldtypes.Domains{}) + // FIXME Remove with old providers + f.AddParser(reflect.TypeOf(oldtypes.Constraints{}), &oldtypes.Constraints{}) + // add commands f.AddCommand(cmdVersion.NewCmd()) f.AddCommand(bug.NewCmd(traefikConfiguration, traefikPointersConfiguration)) @@ -124,19 +169,13 @@ Complete documentation is available at https://traefik.io`, // if a KV Store is enable and no sub-command called in args if kv != nil && usedCmd == traefikCmd { - if traefikConfiguration.Cluster == nil { - traefikConfiguration.Cluster = &types.Cluster{Node: uuid.Get()} - } - if traefikConfiguration.Cluster.Store == nil { - traefikConfiguration.Cluster.Store = &types.Store{Prefix: kv.Prefix, Store: kv.Store} - } s.AddSource(kv) operation := func() error { _, err := s.LoadConfig() return err } notify := func(err error, time time.Duration) { - log.Errorf("Load config error: %+v, retrying in %s", err, time) + log.WithoutContext().Errorf("Load config error: %+v, retrying in %s", err, time) } err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify) if err != nil { @@ -153,84 +192,85 @@ Complete documentation is available at https://traefik.io`, os.Exit(0) } -func runCmd(globalConfiguration *configuration.GlobalConfiguration, configFile string) { - configureLogging(globalConfiguration) +func runCmd(staticConfiguration *static.Configuration, configFile string) { + configureLogging(staticConfiguration) if len(configFile) > 0 { - log.Infof("Using TOML configuration file %s", configFile) + log.WithoutContext().Infof("Using TOML configuration file %s", configFile) } http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment if err := roundrobin.SetDefaultWeight(0); err != nil { - log.Error(err) + log.WithoutContext().Errorf("Could not set roundrobin default weight: %v", err) } - globalConfiguration.SetEffectiveConfiguration(configFile) - globalConfiguration.ValidateConfiguration() + staticConfiguration.SetEffectiveConfiguration(configFile) + staticConfiguration.ValidateConfiguration() - log.Infof("Traefik version %s built on %s", version.Version, version.BuildDate) + log.WithoutContext().Infof("Traefik version %s built on %s", version.Version, version.BuildDate) - jsonConf, err := json.Marshal(globalConfiguration) + jsonConf, err := json.Marshal(staticConfiguration) if err != nil { - log.Error(err) - log.Debugf("Global configuration loaded [struct] %#v", globalConfiguration) + log.WithoutContext().Errorf("Could not marshal static configuration: %v", err) + log.WithoutContext().Debugf("Static configuration loaded [struct] %#v", staticConfiguration) } else { - log.Debugf("Global configuration loaded %s", string(jsonConf)) + log.WithoutContext().Debugf("Static configuration loaded %s", string(jsonConf)) } - if globalConfiguration.API != nil && globalConfiguration.API.Dashboard { - globalConfiguration.API.DashboardAssets = &assetfs.AssetFS{Asset: genstatic.Asset, AssetInfo: genstatic.AssetInfo, AssetDir: genstatic.AssetDir, Prefix: "static"} + if staticConfiguration.API != nil && staticConfiguration.API.Dashboard { + staticConfiguration.API.DashboardAssets = &assetfs.AssetFS{Asset: genstatic.Asset, AssetInfo: genstatic.AssetInfo, AssetDir: genstatic.AssetDir, Prefix: "static"} } - if globalConfiguration.CheckNewVersion { + if staticConfiguration.Global.CheckNewVersion { checkNewVersion() } - stats(globalConfiguration) + stats(staticConfiguration) - providerAggregator := aggregator.NewProviderAggregator(static.ConvertStaticConf(*globalConfiguration)) + providerAggregator := aggregator.NewProviderAggregator(*staticConfiguration.Providers) - acmeProvider, err := globalConfiguration.InitACMEProvider() + acmeProvider, err := staticConfiguration.InitACMEProvider() if err != nil { - log.Errorf("Unable to initialize ACME provider: %v", err) + log.WithoutContext().Errorf("Unable to initialize ACME provider: %v", err) } else if acmeProvider != nil { if err := providerAggregator.AddProvider(acmeProvider); err != nil { - log.Errorf("Unable to add ACME provider to the providers list: %v", err) + log.WithoutContext().Errorf("Unable to add ACME provider to the providers list: %v", err) acmeProvider = nil } } - entryPoints := map[string]server.EntryPoint{} - staticConf := static.ConvertStaticConf(*globalConfiguration) - for entryPointName, config := range globalConfiguration.EntryPoints { - factory := router.NewRouteAppenderFactory(staticConf, entryPointName, acmeProvider) - entryPoint := server.EntryPoint{ - RouteAppenderFactory: factory, - Configuration: config, + serverEntryPoints := make(server.EntryPoints) + for entryPointName, config := range staticConfiguration.EntryPoints { + ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName)) + logger := log.FromContext(ctx) + + serverEntryPoint, err := server.NewEntryPoint(ctx, config) + if err != nil { + logger.Errorf("Error while building entryPoint: %v", err) + continue } - if acmeProvider != nil { + serverEntryPoint.RouteAppenderFactory = router.NewRouteAppenderFactory(*staticConfiguration, entryPointName, acmeProvider) + + if acmeProvider != nil && entryPointName == acmeProvider.EntryPoint { + logger.Debugf("Setting Acme Certificate store from Entrypoint") + acmeProvider.SetCertificateStore(serverEntryPoint.Certs) + + if acmeProvider.OnDemand { + serverEntryPoint.OnDemandListener = acmeProvider.ListenRequest + } + // TLS ALPN 01 if acmeProvider.TLSChallenge != nil && acmeProvider.HTTPChallenge == nil && acmeProvider.DNSChallenge == nil { - entryPoint.TLSALPNGetter = acmeProvider.GetTLSALPNCertificate - } - - if acmeProvider.OnDemand && entryPointName == acmeProvider.EntryPoint { - entryPoint.OnDemandListener = acmeProvider.ListenRequest - } - - if entryPointName == acmeProvider.EntryPoint { - entryPoint.CertificateStore = traefiktls.NewCertificateStore() - acmeProvider.SetCertificateStore(entryPoint.CertificateStore) - log.Debugf("Setting Acme Certificate store from Entrypoint: %s", entryPointName) + serverEntryPoint.TLSALPNGetter = acmeProvider.GetTLSALPNCertificate } } - entryPoints[entryPointName] = entryPoint + serverEntryPoints[entryPointName] = serverEntryPoint } - svr := server.NewServer(*globalConfiguration, providerAggregator, entryPoints) + svr := server.NewServer(*staticConfiguration, providerAggregator, serverEntryPoints) if acmeProvider != nil && acmeProvider.OnHostRule { acmeProvider.SetConfigListenerChan(make(chan config.Configuration)) @@ -238,46 +278,46 @@ func runCmd(globalConfiguration *configuration.GlobalConfiguration, configFile s } ctx := cmd.ContextWithSignal(context.Background()) - if staticConf.Ping != nil { - staticConf.Ping.WithContext(ctx) + if staticConfiguration.Ping != nil { + staticConfiguration.Ping.WithContext(ctx) } - svr.StartWithContext(ctx) + svr.Start(ctx) defer svr.Close() sent, err := daemon.SdNotify(false, "READY=1") if !sent && err != nil { - log.Error("Fail to notify", err) + log.WithoutContext().Errorf("Failed to notify: %v", err) } t, err := daemon.SdWatchdogEnabled(false) if err != nil { - log.Error("Problem with watchdog", err) + log.WithoutContext().Errorf("Could not enable Watchdog: %v", err) } else if t != 0 { // Send a ping each half time given t = t / 2 - log.Info("Watchdog activated with timer each ", t) + log.WithoutContext().Infof("Watchdog activated with timer duration %s", t) safe.Go(func() { tick := time.Tick(t) for range tick { - _, errHealthCheck := healthcheck.Do(*globalConfiguration) - if globalConfiguration.Ping == nil || errHealthCheck == nil { + _, errHealthCheck := healthcheck.Do(*staticConfiguration) + if staticConfiguration.Ping == nil || errHealthCheck == nil { if ok, _ := daemon.SdNotify(false, "WATCHDOG=1"); !ok { - log.Error("Fail to tick watchdog") + log.WithoutContext().Error("Fail to tick watchdog") } } else { - log.Error(errHealthCheck) + log.WithoutContext().Error(errHealthCheck) } } }) } svr.Wait() - log.Info("Shutting down") + log.WithoutContext().Info("Shutting down") logrus.Exit(0) } -func configureLogging(globalConfiguration *configuration.GlobalConfiguration) { +func configureLogging(staticConfiguration *static.Configuration) { // configure default log flags fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags) @@ -285,27 +325,30 @@ func configureLogging(globalConfiguration *configuration.GlobalConfiguration) { // an explicitly defined log level always has precedence. if none is // given and debug mode is disabled, the default is ERROR, and DEBUG // otherwise. - levelStr := strings.ToLower(globalConfiguration.LogLevel) + var levelStr string + if staticConfiguration.Log != nil { + levelStr = strings.ToLower(staticConfiguration.Log.LogLevel) + } if levelStr == "" { levelStr = "error" - if globalConfiguration.Debug { + if staticConfiguration.Global.Debug { levelStr = "debug" } } level, err := logrus.ParseLevel(levelStr) if err != nil { - log.Error("Error getting level", err) + log.WithoutContext().Errorf("Error getting level: %v", err) } log.SetLevel(level) var logFile string - if globalConfiguration.TraefikLog != nil && len(globalConfiguration.TraefikLog.FilePath) > 0 { - logFile = globalConfiguration.TraefikLog.FilePath + if staticConfiguration.Log != nil && len(staticConfiguration.Log.FilePath) > 0 { + logFile = staticConfiguration.Log.FilePath } // configure log format var formatter logrus.Formatter - if globalConfiguration.TraefikLog != nil && globalConfiguration.TraefikLog.Format == "json" { + if staticConfiguration.Log != nil && staticConfiguration.Log.Format == "json" { formatter = &logrus.JSONFormatter{} } else { disableColors := len(logFile) > 0 @@ -317,17 +360,17 @@ func configureLogging(globalConfiguration *configuration.GlobalConfiguration) { dir := filepath.Dir(logFile) if err := os.MkdirAll(dir, 0755); err != nil { - log.Errorf("Failed to create log path %s: %s", dir, err) + log.WithoutContext().Errorf("Failed to create log path %s: %s", dir, err) } err = log.OpenFile(logFile) logrus.RegisterExitHandler(func() { if err := log.CloseFile(); err != nil { - log.Error("Error closing log", err) + log.WithoutContext().Errorf("Error while closing log: %v", err) } }) if err != nil { - log.Error("Error opening file", err) + log.WithoutContext().Errorf("Error while opening log file %s: %v", logFile, err) } } } @@ -341,17 +384,17 @@ func checkNewVersion() { }) } -func stats(globalConfiguration *configuration.GlobalConfiguration) { - if globalConfiguration.SendAnonymousUsage { - log.Info(` +func stats(staticConfiguration *static.Configuration) { + if staticConfiguration.Global.SendAnonymousUsage { + log.WithoutContext().Info(` Stats collection is enabled. Many thanks for contributing to Traefik's improvement by allowing us to receive anonymous information from your configuration. Help us improve Traefik by leaving this feature on :) More details on: https://docs.traefik.io/basics/#collected-data `) - collect(globalConfiguration) + collect(staticConfiguration) } else { - log.Info(` + log.WithoutContext().Info(` Stats collection is disabled. Help us improve Traefik by turning this feature on :) More details on: https://docs.traefik.io/basics/#collected-data @@ -359,12 +402,12 @@ More details on: https://docs.traefik.io/basics/#collected-data } } -func collect(globalConfiguration *configuration.GlobalConfiguration) { +func collect(staticConfiguration *static.Configuration) { ticker := time.Tick(24 * time.Hour) safe.Go(func() { for time.Sleep(10 * time.Minute); ; <-ticker { - if err := collector.Collect(globalConfiguration); err != nil { - log.Debug(err) + if err := collector.Collect(staticConfiguration); err != nil { + log.WithoutContext().Debug(err) } } }) diff --git a/collector/collector.go b/collector/collector.go index 5a48112dd..1171c8c27 100644 --- a/collector/collector.go +++ b/collector/collector.go @@ -10,6 +10,7 @@ import ( "time" "github.com/containous/traefik/anonymize" + "github.com/containous/traefik/config/static" "github.com/containous/traefik/log" "github.com/containous/traefik/old/configuration" "github.com/containous/traefik/version" @@ -29,15 +30,15 @@ type data struct { } // Collect anonymous data. -func Collect(globalConfiguration *configuration.GlobalConfiguration) error { - anonConfig, err := anonymize.Do(globalConfiguration, false) +func Collect(staticConfiguration *static.Configuration) error { + anonConfig, err := anonymize.Do(staticConfiguration, false) if err != nil { return err } log.Infof("Anonymous stats sent to %s: %s", collectorURL, anonConfig) - hashConf, err := hashstructure.Hash(globalConfiguration, nil) + hashConf, err := hashstructure.Hash(staticConfiguration, nil) if err != nil { return err } diff --git a/config/static/entrypoints.go b/config/static/entrypoints.go new file mode 100644 index 000000000..b9d4b772b --- /dev/null +++ b/config/static/entrypoints.go @@ -0,0 +1,169 @@ +package static + +import ( + "fmt" + "strings" + + "github.com/containous/traefik/log" + "github.com/containous/traefik/tls" +) + +// EntryPoint holds the entry point configuration. +type EntryPoint struct { + Address string + Transport *EntryPointsTransport + TLS *tls.TLS + ProxyProtocol *ProxyProtocol +} + +// ProxyProtocol contains Proxy-Protocol configuration. +type ProxyProtocol struct { + Insecure bool `export:"true"` + TrustedIPs []string +} + +// EntryPoints holds the HTTP entry point list. +type EntryPoints map[string]*EntryPoint + +// EntryPointsTransport configures communication between clients and Traefik. +type EntryPointsTransport struct { + LifeCycle *LifeCycle `description:"Timeouts influencing the server life cycle" export:"true"` + RespondingTimeouts *RespondingTimeouts `description:"Timeouts for incoming requests to the Traefik instance" export:"true"` +} + +// String is the method to format the flag's value, part of the flag.Value interface. +// The String method's output will be used in diagnostics. +func (ep EntryPoints) String() string { + return fmt.Sprintf("%+v", map[string]*EntryPoint(ep)) +} + +// Get return the EntryPoints map. +func (ep *EntryPoints) Get() interface{} { + return *ep +} + +// SetValue sets the EntryPoints map with val. +func (ep *EntryPoints) SetValue(val interface{}) { + *ep = val.(EntryPoints) +} + +// Type is type of the struct. +func (ep *EntryPoints) Type() string { + return "entrypoints" +} + +// Set is the method to set the flag value, part of the flag.Value interface. +// Set's argument is a string to be parsed to set the flag. +// It's a comma-separated list, so we split it. +func (ep *EntryPoints) Set(value string) error { + result := parseEntryPointsConfiguration(value) + + configTLS, err := makeEntryPointTLS(result) + if err != nil { + return err + } + + (*ep)[result["name"]] = &EntryPoint{ + Address: result["address"], + TLS: configTLS, + ProxyProtocol: makeEntryPointProxyProtocol(result), + } + + return nil +} + +func makeEntryPointProxyProtocol(result map[string]string) *ProxyProtocol { + var proxyProtocol *ProxyProtocol + + ppTrustedIPs := result["proxyprotocol_trustedips"] + if len(result["proxyprotocol_insecure"]) > 0 || len(ppTrustedIPs) > 0 { + proxyProtocol = &ProxyProtocol{ + Insecure: toBool(result, "proxyprotocol_insecure"), + } + if len(ppTrustedIPs) > 0 { + proxyProtocol.TrustedIPs = strings.Split(ppTrustedIPs, ",") + } + } + + if proxyProtocol != nil && proxyProtocol.Insecure { + log.Warn("ProxyProtocol.insecure:true is dangerous. Please use 'ProxyProtocol.TrustedIPs:IPs' and remove 'ProxyProtocol.insecure:true'") + } + + return proxyProtocol +} + +func makeEntryPointTLS(result map[string]string) (*tls.TLS, error) { + var configTLS *tls.TLS + + if len(result["tls"]) > 0 { + certs := tls.Certificates{} + if err := certs.Set(result["tls"]); err != nil { + return nil, err + } + configTLS = &tls.TLS{} + } else if len(result["tls_acme"]) > 0 { + configTLS = &tls.TLS{} + } + + if configTLS != nil { + if len(result["ca"]) > 0 { + files := tls.FilesOrContents{} + files.Set(result["ca"]) + optional := toBool(result, "ca_optional") + configTLS.ClientCA = tls.ClientCA{ + Files: files, + Optional: optional, + } + } + + if len(result["tls_minversion"]) > 0 { + configTLS.MinVersion = result["tls_minversion"] + } + + if len(result["tls_ciphersuites"]) > 0 { + configTLS.CipherSuites = strings.Split(result["tls_ciphersuites"], ",") + } + + if len(result["tls_snistrict"]) > 0 { + configTLS.SniStrict = toBool(result, "tls_snistrict") + } + + if len(result["tls_defaultcertificate_cert"]) > 0 && len(result["tls_defaultcertificate_key"]) > 0 { + configTLS.DefaultCertificate = &tls.Certificate{ + CertFile: tls.FileOrContent(result["tls_defaultcertificate_cert"]), + KeyFile: tls.FileOrContent(result["tls_defaultcertificate_key"]), + } + } + } + + return configTLS, nil +} + +func parseEntryPointsConfiguration(raw string) map[string]string { + sections := strings.Fields(raw) + + config := make(map[string]string) + for _, part := range sections { + field := strings.SplitN(part, ":", 2) + name := strings.ToLower(strings.Replace(field[0], ".", "_", -1)) + if len(field) > 1 { + config[name] = field[1] + } else { + if strings.EqualFold(name, "TLS") { + config["tls_acme"] = "TLS" + } else { + config[name] = "" + } + } + } + return config +} + +func toBool(conf map[string]string, key string) bool { + if val, ok := conf[key]; ok { + return strings.EqualFold(val, "true") || + strings.EqualFold(val, "enable") || + strings.EqualFold(val, "on") + } + return false +} diff --git a/old/configuration/entrypoints_test.go b/config/static/entrypoints_test.go similarity index 54% rename from old/configuration/entrypoints_test.go rename to config/static/entrypoints_test.go index cf4e74fd0..8efd4f480 100644 --- a/old/configuration/entrypoints_test.go +++ b/config/static/entrypoints_test.go @@ -1,9 +1,8 @@ -package configuration +package static import ( "testing" - "github.com/containous/traefik/old/types" "github.com/containous/traefik/tls" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -186,277 +185,70 @@ func TestEntryPoints_Set(t *testing.T) { name: "all parameters camelcase", expression: "Name:foo " + "Address::8000 " + - "TLS:goo,gii;foo,fii " + "TLS " + "TLS.MinVersion:VersionTLS11 " + "TLS.CipherSuites:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA " + "CA:car " + "CA.Optional:true " + - "Redirect.EntryPoint:https " + - "Redirect.Regex:http://localhost/(.*) " + - "Redirect.Replacement:http://mydomain/$1 " + - "Redirect.Permanent:true " + - "Compress:true " + - "ProxyProtocol.TrustedIPs:192.168.0.1 " + - "ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " + - "Auth.Basic.Realm:myRealm " + - "Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " + - "Auth.Basic.RemoveHeader:true " + - "Auth.Digest.Users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e " + - "Auth.Digest.RemoveHeader:true " + - "Auth.HeaderField:X-WebAuth-User " + - "Auth.Forward.Address:https://authserver.com/auth " + - "Auth.Forward.AuthResponseHeaders:X-Auth,X-Test,X-Secret " + - "Auth.Forward.TrustForwardHeader:true " + - "Auth.Forward.TLS.CA:path/to/local.crt " + - "Auth.Forward.TLS.CAOptional:true " + - "Auth.Forward.TLS.Cert:path/to/foo.cert " + - "Auth.Forward.TLS.Key:path/to/foo.key " + - "Auth.Forward.TLS.InsecureSkipVerify:true " + - "WhiteList.SourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " + - "WhiteList.IPStrategy.depth:3 " + - "WhiteList.IPStrategy.ExcludedIPs:10.0.0.3/24,20.0.0.3/24 " + - "ClientIPStrategy.depth:3 " + - "ClientIPStrategy.ExcludedIPs:10.0.0.3/24,20.0.0.3/24 ", + "ProxyProtocol.TrustedIPs:192.168.0.1 ", expectedEntryPointName: "foo", expectedEntryPoint: &EntryPoint{ Address: ":8000", TLS: &tls.TLS{ MinVersion: "VersionTLS11", CipherSuites: []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"}, - Certificates: tls.Certificates{ - { - CertFile: tls.FileOrContent("goo"), - KeyFile: tls.FileOrContent("gii"), - }, - { - CertFile: tls.FileOrContent("foo"), - KeyFile: tls.FileOrContent("fii"), - }, - }, ClientCA: tls.ClientCA{ Files: tls.FilesOrContents{"car"}, Optional: true, }, }, - Redirect: &types.Redirect{ - EntryPoint: "https", - Regex: "http://localhost/(.*)", - Replacement: "http://mydomain/$1", - Permanent: true, - }, - Auth: &types.Auth{ - Basic: &types.Basic{ - Realm: "myRealm", - RemoveHeader: true, - Users: types.Users{ - "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - }, - }, - Digest: &types.Digest{ - RemoveHeader: true, - Users: types.Users{ - "test:traefik:a2688e031edb4be6a3797f3882655c05", - "test2:traefik:518845800f9e2bfb1f1f740ec24f074e", - }, - }, - Forward: &types.Forward{ - Address: "https://authserver.com/auth", - AuthResponseHeaders: []string{"X-Auth", "X-Test", "X-Secret"}, - TLS: &types.ClientTLS{ - CA: "path/to/local.crt", - CAOptional: true, - Cert: "path/to/foo.cert", - Key: "path/to/foo.key", - InsecureSkipVerify: true, - }, - TrustForwardHeader: true, - }, - HeaderField: "X-WebAuth-User", - }, - WhiteList: &types.WhiteList{ - SourceRange: []string{ - "10.42.0.0/16", - "152.89.1.33/32", - "afed:be44::/16", - }, - IPStrategy: &types.IPStrategy{ - Depth: 3, - ExcludedIPs: []string{ - "10.0.0.3/24", - "20.0.0.3/24", - }, - }, - }, - Compress: &Compress{}, ProxyProtocol: &ProxyProtocol{ Insecure: false, TrustedIPs: []string{"192.168.0.1"}, }, - ForwardedHeaders: &ForwardedHeaders{ - Insecure: false, - TrustedIPs: []string{ - "10.0.0.3/24", - "20.0.0.3/24", - }, - }, - ClientIPStrategy: &types.IPStrategy{ - Depth: 3, - ExcludedIPs: []string{ - "10.0.0.3/24", - "20.0.0.3/24", - }, - }, + // FIXME Test ServersTransport }, }, { name: "all parameters lowercase", expression: "Name:foo " + "address::8000 " + - "tls:goo,gii;foo,fii " + "tls " + "tls.minversion:VersionTLS11 " + "tls.ciphersuites:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA " + "ca:car " + "ca.Optional:true " + - "redirect.entryPoint:https " + - "redirect.regex:http://localhost/(.*) " + - "redirect.replacement:http://mydomain/$1 " + - "redirect.permanent:true " + - "compress:true " + - "whiteList.sourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " + - "proxyProtocol.TrustedIPs:192.168.0.1 " + - "forwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " + - "auth.basic.realm:myRealm " + - "auth.basic.users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " + - "auth.digest.users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e " + - "auth.headerField:X-WebAuth-User " + - "auth.forward.address:https://authserver.com/auth " + - "auth.forward.authResponseHeaders:X-Auth,X-Test,X-Secret " + - "auth.forward.trustForwardHeader:true " + - "auth.forward.tls.ca:path/to/local.crt " + - "auth.forward.tls.caOptional:true " + - "auth.forward.tls.cert:path/to/foo.cert " + - "auth.forward.tls.key:path/to/foo.key " + - "auth.forward.tls.insecureSkipVerify:true ", + "proxyProtocol.TrustedIPs:192.168.0.1 ", expectedEntryPointName: "foo", expectedEntryPoint: &EntryPoint{ Address: ":8000", TLS: &tls.TLS{ MinVersion: "VersionTLS11", CipherSuites: []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"}, - Certificates: tls.Certificates{ - { - CertFile: tls.FileOrContent("goo"), - KeyFile: tls.FileOrContent("gii"), - }, - { - CertFile: tls.FileOrContent("foo"), - KeyFile: tls.FileOrContent("fii"), - }, - }, ClientCA: tls.ClientCA{ Files: tls.FilesOrContents{"car"}, Optional: true, }, }, - Redirect: &types.Redirect{ - EntryPoint: "https", - Regex: "http://localhost/(.*)", - Replacement: "http://mydomain/$1", - Permanent: true, - }, - Auth: &types.Auth{ - Basic: &types.Basic{ - Realm: "myRealm", - Users: types.Users{ - "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - }, - }, - Digest: &types.Digest{ - Users: types.Users{ - "test:traefik:a2688e031edb4be6a3797f3882655c05", - "test2:traefik:518845800f9e2bfb1f1f740ec24f074e", - }, - }, - Forward: &types.Forward{ - Address: "https://authserver.com/auth", - AuthResponseHeaders: []string{"X-Auth", "X-Test", "X-Secret"}, - TLS: &types.ClientTLS{ - CA: "path/to/local.crt", - CAOptional: true, - Cert: "path/to/foo.cert", - Key: "path/to/foo.key", - InsecureSkipVerify: true, - }, - TrustForwardHeader: true, - }, - HeaderField: "X-WebAuth-User", - }, - WhiteList: &types.WhiteList{ - SourceRange: []string{ - "10.42.0.0/16", - "152.89.1.33/32", - "afed:be44::/16", - }, - }, - Compress: &Compress{}, ProxyProtocol: &ProxyProtocol{ Insecure: false, TrustedIPs: []string{"192.168.0.1"}, }, - ForwardedHeaders: &ForwardedHeaders{ - Insecure: false, - TrustedIPs: []string{ - "10.0.0.3/24", - "20.0.0.3/24", - }, - }, + // FIXME Test ServersTransport }, }, { name: "default", expression: "Name:foo", expectedEntryPointName: "foo", - expectedEntryPoint: &EntryPoint{ - ForwardedHeaders: &ForwardedHeaders{Insecure: false}, - }, - }, - { - name: "ForwardedHeaders insecure true", - expression: "Name:foo ForwardedHeaders.insecure:true", - expectedEntryPointName: "foo", - expectedEntryPoint: &EntryPoint{ - ForwardedHeaders: &ForwardedHeaders{Insecure: true}, - }, - }, - { - name: "ForwardedHeaders insecure false", - expression: "Name:foo ForwardedHeaders.insecure:false", - expectedEntryPointName: "foo", - expectedEntryPoint: &EntryPoint{ - ForwardedHeaders: &ForwardedHeaders{Insecure: false}, - }, - }, - { - name: "ForwardedHeaders TrustedIPs", - expression: "Name:foo ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24", - expectedEntryPointName: "foo", - expectedEntryPoint: &EntryPoint{ - ForwardedHeaders: &ForwardedHeaders{ - TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"}, - }, - }, + expectedEntryPoint: &EntryPoint{}, }, { name: "ProxyProtocol insecure true", expression: "Name:foo ProxyProtocol.insecure:true", expectedEntryPointName: "foo", expectedEntryPoint: &EntryPoint{ - ForwardedHeaders: &ForwardedHeaders{}, - ProxyProtocol: &ProxyProtocol{Insecure: true}, + ProxyProtocol: &ProxyProtocol{Insecure: true}, }, }, { @@ -464,8 +256,7 @@ func TestEntryPoints_Set(t *testing.T) { expression: "Name:foo ProxyProtocol.insecure:false", expectedEntryPointName: "foo", expectedEntryPoint: &EntryPoint{ - ForwardedHeaders: &ForwardedHeaders{}, - ProxyProtocol: &ProxyProtocol{}, + ProxyProtocol: &ProxyProtocol{}, }, }, { @@ -473,30 +264,11 @@ func TestEntryPoints_Set(t *testing.T) { expression: "Name:foo ProxyProtocol.TrustedIPs:10.0.0.3/24,20.0.0.3/24", expectedEntryPointName: "foo", expectedEntryPoint: &EntryPoint{ - ForwardedHeaders: &ForwardedHeaders{}, ProxyProtocol: &ProxyProtocol{ TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"}, }, }, }, - { - name: "compress on", - expression: "Name:foo Compress:on", - expectedEntryPointName: "foo", - expectedEntryPoint: &EntryPoint{ - Compress: &Compress{}, - ForwardedHeaders: &ForwardedHeaders{}, - }, - }, - { - name: "compress true", - expression: "Name:foo Compress:true", - expectedEntryPointName: "foo", - expectedEntryPoint: &EntryPoint{ - Compress: &Compress{}, - ForwardedHeaders: &ForwardedHeaders{}, - }, - }, } for _, test := range testCases { diff --git a/config/static/static_config.go b/config/static/static_config.go index 61ceb366b..ee8f1a420 100644 --- a/config/static/static_config.go +++ b/config/static/static_config.go @@ -1,8 +1,29 @@ package static import ( + "errors" + "strings" + "time" + "github.com/containous/flaeg/parse" + "github.com/containous/traefik/acme" + "github.com/containous/traefik/log" + "github.com/containous/traefik/old/provider/boltdb" + "github.com/containous/traefik/old/provider/consul" + "github.com/containous/traefik/old/provider/consulcatalog" + "github.com/containous/traefik/old/provider/docker" + "github.com/containous/traefik/old/provider/dynamodb" + "github.com/containous/traefik/old/provider/ecs" + "github.com/containous/traefik/old/provider/etcd" + "github.com/containous/traefik/old/provider/eureka" + "github.com/containous/traefik/old/provider/kubernetes" + "github.com/containous/traefik/old/provider/marathon" + "github.com/containous/traefik/old/provider/mesos" + "github.com/containous/traefik/old/provider/rancher" + "github.com/containous/traefik/old/provider/rest" + "github.com/containous/traefik/old/provider/zk" "github.com/containous/traefik/ping" + acmeprovider "github.com/containous/traefik/provider/acme" "github.com/containous/traefik/provider/file" "github.com/containous/traefik/tls" "github.com/containous/traefik/tracing/datadog" @@ -10,12 +31,31 @@ import ( "github.com/containous/traefik/tracing/zipkin" "github.com/containous/traefik/types" "github.com/elazarl/go-bindata-assetfs" + lego "github.com/xenolf/lego/acme" ) -// Configuration FIXME temp static configuration +const ( + // DefaultInternalEntryPointName the name of the default internal entry point + DefaultInternalEntryPointName = "traefik" + + // DefaultGraceTimeout controls how long Traefik serves pending requests + // prior to shutting down. + DefaultGraceTimeout = 10 * time.Second + + // DefaultIdleTimeout before closing an idle connection. + DefaultIdleTimeout = 180 * time.Second + + // DefaultAcmeCAServer is the default ACME API endpoint + DefaultAcmeCAServer = "https://acme-v02.api.letsencrypt.org/directory" +) + +// Configuration is the static configuration type Configuration struct { - Global *Global - EntryPoints *EntryPoints + Global *Global `description:"Global configuration options" export:"true"` + + ServersTransport *ServersTransport `description:"Servers default transport" export:"true"` + EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key;prod/traefik.crt,prod/traefik.key'" export:"true"` + Providers *Providers `description:"Providers configuration" export:"true"` API *API `description:"Enable api/dashboard" export:"true"` Metrics *types.Metrics `description:"Enable a metrics exporter" export:"true"` @@ -26,31 +66,24 @@ type Configuration struct { AccessLog *types.AccessLog `description:"Access log settings" export:"true"` Tracing *Tracing `description:"OpenTracing configuration" export:"true"` - File *file.Provider `description:"Enable File backend with default settings" export:"true"` - Constraints types.Constraints `description:"Filter services by constraint, matching with service tags" export:"true"` - HostResolver *HostResolverConfig `description:"Enable CNAME Flattening" export:"true"` - // TODO - // ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL" export:"true"` - // Retry *Retry `description:"Enable retry sending request if network error" export:"true"` - // HealthCheck *HealthCheckConfig `description:"Health check parameters" export:"true"` - // - + ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL" export:"true"` } // Global holds the global configuration. type Global struct { - Debug bool `short:"d" description:"Enable debug mode" export:"true"` - CheckNewVersion bool `description:"Periodically check if a new version has been released" export:"true"` - SendAnonymousUsage bool `description:"send periodically anonymous usage statistics" export:"true"` - InsecureSkipVerify bool `description:"Disable SSL certificate verification" export:"true"` - RootCAs tls.FilesOrContents `description:"Add cert file for self-signed certificate"` - ProvidersThrottleDuration parse.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time." export:"true"` - LifeCycle *LifeCycle `description:"Timeouts influencing the server life cycle" export:"true"` - RespondingTimeouts *RespondingTimeouts `description:"Timeouts for incoming requests to the Traefik instance" export:"true"` - ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers" export:"true"` - MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" export:"true"` + Debug bool `short:"d" description:"Enable debug mode" export:"true"` + CheckNewVersion bool `description:"Periodically check if a new version has been released" export:"true"` + SendAnonymousUsage bool `description:"send periodically anonymous usage statistics" export:"true"` +} + +// ServersTransport options to configure communication between Traefik and the servers +type ServersTransport struct { + InsecureSkipVerify bool `description:"Disable SSL certificate verification" export:"true"` + RootCAs tls.FilesOrContents `description:"Add cert file for self-signed certificate"` + MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" export:"true"` + ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers" export:"true"` } // API holds the API configuration @@ -81,20 +114,6 @@ type LifeCycle struct { GraceTimeOut parse.Duration `description:"Duration to give active requests a chance to finish before Traefik stops"` } -// EntryPoint holds the entry point configuration -type EntryPoint struct { - Address string -} - -// EntryPointList holds the HTTP entry point list type. -type EntryPointList map[string]EntryPoint - -// EntryPoints holds the entry points configuration. -type EntryPoints struct { - EntryPointList - Defaults []string -} - // Tracing holds the tracing configuration. type Tracing struct { Backend string `description:"Selects the tracking backend ('jaeger','zipkin', 'datadog')." export:"true"` @@ -111,3 +130,276 @@ type HostResolverConfig struct { ResolvConfig string `description:"resolv.conf used for DNS resolving" export:"true"` ResolvDepth int `description:"The maximal depth of DNS recursive resolving" export:"true"` } + +// Providers contains providers configuration +type Providers struct { + ProvidersThrottleDuration parse.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time." export:"true"` + Docker *docker.Provider `description:"Enable Docker backend with default settings" export:"true"` + File *file.Provider `description:"Enable File backend with default settings" export:"true"` + Marathon *marathon.Provider `description:"Enable Marathon backend with default settings" export:"true"` + Consul *consul.Provider `description:"Enable Consul backend with default settings" export:"true"` + ConsulCatalog *consulcatalog.Provider `description:"Enable Consul catalog backend with default settings" export:"true"` + Etcd *etcd.Provider `description:"Enable Etcd backend with default settings" export:"true"` + Zookeeper *zk.Provider `description:"Enable Zookeeper backend with default settings" export:"true"` + Boltdb *boltdb.Provider `description:"Enable Boltdb backend with default settings" export:"true"` + Kubernetes *kubernetes.Provider `description:"Enable Kubernetes backend with default settings" export:"true"` + Mesos *mesos.Provider `description:"Enable Mesos backend with default settings" export:"true"` + Eureka *eureka.Provider `description:"Enable Eureka backend with default settings" export:"true"` + ECS *ecs.Provider `description:"Enable ECS backend with default settings" export:"true"` + Rancher *rancher.Provider `description:"Enable Rancher backend with default settings" export:"true"` + DynamoDB *dynamodb.Provider `description:"Enable DynamoDB backend with default settings" export:"true"` + Rest *rest.Provider `description:"Enable Rest backend with default settings" export:"true"` +} + +// SetEffectiveConfiguration adds missing configuration parameters derived from existing ones. +// It also takes care of maintaining backwards compatibility. +func (c *Configuration) SetEffectiveConfiguration(configFile string) { + if len(c.EntryPoints) == 0 { + c.EntryPoints = EntryPoints{ + "http": &EntryPoint{ + Address: ":80", + }, + } + } + + if (c.API != nil && c.API.EntryPoint == DefaultInternalEntryPointName) || + (c.Ping != nil && c.Ping.EntryPoint == DefaultInternalEntryPointName) || + (c.Metrics != nil && c.Metrics.Prometheus != nil && c.Metrics.Prometheus.EntryPoint == DefaultInternalEntryPointName) || + (c.Providers.Rest != nil && c.Providers.Rest.EntryPoint == DefaultInternalEntryPointName) { + if _, ok := c.EntryPoints[DefaultInternalEntryPointName]; !ok { + c.EntryPoints[DefaultInternalEntryPointName] = &EntryPoint{Address: ":8080"} + } + } + + for _, entryPoint := range c.EntryPoints { + if entryPoint.Transport == nil { + entryPoint.Transport = &EntryPointsTransport{} + } + + // Make sure LifeCycle isn't nil to spare nil checks elsewhere. + if entryPoint.Transport.LifeCycle == nil { + entryPoint.Transport.LifeCycle = &LifeCycle{ + GraceTimeOut: parse.Duration(DefaultGraceTimeout), + } + entryPoint.Transport.RespondingTimeouts = &RespondingTimeouts{ + IdleTimeout: parse.Duration(DefaultIdleTimeout), + } + + } + } + + if c.Providers.Rancher != nil { + // Ensure backwards compatibility for now + if len(c.Providers.Rancher.AccessKey) > 0 || + len(c.Providers.Rancher.Endpoint) > 0 || + len(c.Providers.Rancher.SecretKey) > 0 { + + if c.Providers.Rancher.API == nil { + c.Providers.Rancher.API = &rancher.APIConfiguration{ + AccessKey: c.Providers.Rancher.AccessKey, + SecretKey: c.Providers.Rancher.SecretKey, + Endpoint: c.Providers.Rancher.Endpoint, + } + } + log.Warn("Deprecated configuration found: rancher.[accesskey|secretkey|endpoint]. " + + "Please use rancher.api.[accesskey|secretkey|endpoint] instead.") + } + + if c.Providers.Rancher.Metadata != nil && len(c.Providers.Rancher.Metadata.Prefix) == 0 { + c.Providers.Rancher.Metadata.Prefix = "latest" + } + } + + if c.Providers.File != nil { + c.Providers.File.TraefikFile = configFile + } + + c.initACMEProvider() + c.initTracing() +} + +func (c *Configuration) initTracing() { + if c.Tracing != nil { + switch c.Tracing.Backend { + case jaeger.Name: + if c.Tracing.Jaeger == nil { + c.Tracing.Jaeger = &jaeger.Config{ + SamplingServerURL: "http://localhost:5778/sampling", + SamplingType: "const", + SamplingParam: 1.0, + LocalAgentHostPort: "127.0.0.1:6831", + Propagation: "jaeger", + Gen128Bit: false, + } + } + if c.Tracing.Zipkin != nil { + log.Warn("Zipkin configuration will be ignored") + c.Tracing.Zipkin = nil + } + if c.Tracing.DataDog != nil { + log.Warn("DataDog configuration will be ignored") + c.Tracing.DataDog = nil + } + case zipkin.Name: + if c.Tracing.Zipkin == nil { + c.Tracing.Zipkin = &zipkin.Config{ + HTTPEndpoint: "http://localhost:9411/api/v1/spans", + SameSpan: false, + ID128Bit: true, + Debug: false, + SampleRate: 1.0, + } + } + if c.Tracing.Jaeger != nil { + log.Warn("Jaeger configuration will be ignored") + c.Tracing.Jaeger = nil + } + if c.Tracing.DataDog != nil { + log.Warn("DataDog configuration will be ignored") + c.Tracing.DataDog = nil + } + case datadog.Name: + if c.Tracing.DataDog == nil { + c.Tracing.DataDog = &datadog.Config{ + LocalAgentHostPort: "localhost:8126", + GlobalTag: "", + Debug: false, + } + } + if c.Tracing.Zipkin != nil { + log.Warn("Zipkin configuration will be ignored") + c.Tracing.Zipkin = nil + } + if c.Tracing.Jaeger != nil { + log.Warn("Jaeger configuration will be ignored") + c.Tracing.Jaeger = nil + } + default: + log.Warnf("Unknown tracer %q", c.Tracing.Backend) + return + } + } +} + +// FIXME handle on new configuration ACME struct +func (c *Configuration) initACMEProvider() { + if c.ACME != nil { + c.ACME.CAServer = getSafeACMECAServer(c.ACME.CAServer) + + if c.ACME.DNSChallenge != nil && c.ACME.HTTPChallenge != nil { + log.Warn("Unable to use DNS challenge and HTTP challenge at the same time. Fallback to DNS challenge.") + c.ACME.HTTPChallenge = nil + } + + if c.ACME.DNSChallenge != nil && c.ACME.TLSChallenge != nil { + log.Warn("Unable to use DNS challenge and TLS challenge at the same time. Fallback to DNS challenge.") + c.ACME.TLSChallenge = nil + } + + if c.ACME.HTTPChallenge != nil && c.ACME.TLSChallenge != nil { + log.Warn("Unable to use HTTP challenge and TLS challenge at the same time. Fallback to TLS challenge.") + c.ACME.HTTPChallenge = nil + } + + if c.ACME.OnDemand { + log.Warn("ACME.OnDemand is deprecated") + } + } +} + +// InitACMEProvider create an acme provider from the ACME part of globalConfiguration +func (c *Configuration) InitACMEProvider() (*acmeprovider.Provider, error) { + if c.ACME != nil { + if len(c.ACME.Storage) == 0 { + // Delete the ACME configuration to avoid starting ACME in cluster mode + c.ACME = nil + return nil, errors.New("unable to initialize ACME provider with no storage location for the certificates") + } + provider := &acmeprovider.Provider{} + provider.Configuration = convertACMEChallenge(c.ACME) + + store := acmeprovider.NewLocalStore(provider.Storage) + provider.Store = store + acme.ConvertToNewFormat(provider.Storage) + c.ACME = nil + return provider, nil + } + return nil, nil +} + +// ValidateConfiguration validate that configuration is coherent +func (c *Configuration) ValidateConfiguration() { + if c.ACME != nil { + if _, ok := c.EntryPoints[c.ACME.EntryPoint]; !ok { + log.Fatalf("Unknown entrypoint %q for ACME configuration", c.ACME.EntryPoint) + } else { + if c.EntryPoints[c.ACME.EntryPoint].TLS == nil { + log.Fatalf("Entrypoint %q has no TLS configuration for ACME configuration", c.ACME.EntryPoint) + } + } + } +} + +func getSafeACMECAServer(caServerSrc string) string { + if len(caServerSrc) == 0 { + return DefaultAcmeCAServer + } + + if strings.HasPrefix(caServerSrc, "https://acme-v01.api.letsencrypt.org") { + caServer := strings.Replace(caServerSrc, "v01", "v02", 1) + log.Warnf("The CA server %[1]q refers to a v01 endpoint of the ACME API, please change to %[2]q. Fallback to %[2]q.", caServerSrc, caServer) + return caServer + } + + if strings.HasPrefix(caServerSrc, "https://acme-staging.api.letsencrypt.org") { + caServer := strings.Replace(caServerSrc, "https://acme-staging.api.letsencrypt.org", "https://acme-staging-v02.api.letsencrypt.org", 1) + log.Warnf("The CA server %[1]q refers to a v01 endpoint of the ACME API, please change to %[2]q. Fallback to %[2]q.", caServerSrc, caServer) + return caServer + } + + return caServerSrc +} + +// Deprecated +func convertACMEChallenge(oldACMEChallenge *acme.ACME) *acmeprovider.Configuration { + conf := &acmeprovider.Configuration{ + KeyType: oldACMEChallenge.KeyType, + OnHostRule: oldACMEChallenge.OnHostRule, + OnDemand: oldACMEChallenge.OnDemand, + Email: oldACMEChallenge.Email, + Storage: oldACMEChallenge.Storage, + ACMELogging: oldACMEChallenge.ACMELogging, + CAServer: oldACMEChallenge.CAServer, + EntryPoint: oldACMEChallenge.EntryPoint, + } + + for _, domain := range oldACMEChallenge.Domains { + if domain.Main != lego.UnFqdn(domain.Main) { + log.Warnf("FQDN detected, please remove the trailing dot: %s", domain.Main) + } + for _, san := range domain.SANs { + if san != lego.UnFqdn(san) { + log.Warnf("FQDN detected, please remove the trailing dot: %s", san) + } + } + conf.Domains = append(conf.Domains, domain) + } + if oldACMEChallenge.HTTPChallenge != nil { + conf.HTTPChallenge = &acmeprovider.HTTPChallenge{ + EntryPoint: oldACMEChallenge.HTTPChallenge.EntryPoint, + } + } + + if oldACMEChallenge.DNSChallenge != nil { + conf.DNSChallenge = &acmeprovider.DNSChallenge{ + Provider: oldACMEChallenge.DNSChallenge.Provider, + DelayBeforeCheck: oldACMEChallenge.DNSChallenge.DelayBeforeCheck, + } + } + + if oldACMEChallenge.TLSChallenge != nil { + conf.TLSChallenge = &acmeprovider.TLSChallenge{} + } + + return conf +} diff --git a/healthcheck/healthcheck.go b/healthcheck/healthcheck.go index 05574fa24..542f0c8e2 100644 --- a/healthcheck/healthcheck.go +++ b/healthcheck/healthcheck.go @@ -53,9 +53,8 @@ func (opt Options) String() string { // BackendConfig HealthCheck configuration for a backend type BackendConfig struct { Options - name string - disabledURLs []*url.URL - requestTimeout time.Duration + name string + disabledURLs []*url.URL } func (b *BackendConfig) newRequest(serverURL *url.URL) (*http.Request, error) { diff --git a/hostresolver/hostresolver.go b/hostresolver/hostresolver.go index 30dab4080..4b8cbdfaa 100644 --- a/hostresolver/hostresolver.go +++ b/hostresolver/hostresolver.go @@ -93,7 +93,7 @@ func cnameResolve(host string, resolvPath string) (*cnameResolv, error) { result = append(result, tempRecord) } - if len(result) <= 0 { + if len(result) == 0 { return nil, nil } diff --git a/integration/fixtures/access_log_config.toml b/integration/fixtures/access_log_config.toml index 4be76e703..845e4de78 100644 --- a/integration/fixtures/access_log_config.toml +++ b/integration/fixtures/access_log_config.toml @@ -1,9 +1,9 @@ +[log] logLevel = "ERROR" -defaultEntryPoints = ["http"] -checkNewVersion = false +filePath = "traefik.log" -[traefikLog] - filePath = "traefik.log" +[global] +checkNewVersion = false [accessLog] filePath = "access.log" @@ -36,7 +36,8 @@ checkNewVersion = false [api] -[docker] - exposedByDefault = false - domain = "docker.local" - watch = true +[providers] + [providers.docker] + exposedByDefault = false + domain = "docker.local" + watch = true diff --git a/integration/fixtures/acme/acme_base.toml b/integration/fixtures/acme/acme_base.toml index 1a6659ea8..dd0185a19 100644 --- a/integration/fixtures/acme/acme_base.toml +++ b/integration/fixtures/acme/acme_base.toml @@ -1,6 +1,7 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["http", "https"] + [entryPoints] [entryPoints.http] @@ -38,7 +39,8 @@ defaultEntryPoints = ["http", "https"] [api] -[file] +[providers] + [providers.file] [services] [services.test.loadbalancer] diff --git a/integration/fixtures/acme/acme_http01_web_path.toml b/integration/fixtures/acme/acme_http01_web_path.toml index 5d80d3293..cd6c5bfbd 100644 --- a/integration/fixtures/acme/acme_http01_web_path.toml +++ b/integration/fixtures/acme/acme_http01_web_path.toml @@ -1,6 +1,7 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["http", "https"] + [entryPoints] [entryPoints.http] @@ -35,7 +36,8 @@ defaultEntryPoints = ["http", "https"] [web] path="/traefik" -[file] +[providers] + [providers.file] [services] [services.test.loadbalancer] diff --git a/integration/fixtures/acme/acme_tls.toml b/integration/fixtures/acme/acme_tls.toml index 85386a3cf..f139dea4a 100644 --- a/integration/fixtures/acme/acme_tls.toml +++ b/integration/fixtures/acme/acme_tls.toml @@ -1,14 +1,13 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["http", "https"] - [entryPoints] [entryPoints.http] address = "{{ .PortHTTP }}" [entryPoints.https] address = "{{ .PortHTTPS }}" [entryPoints.https.tls] - [[entryPoints.https.tls.certificates]] + [entryPoints.https.tls.DefaultCertificate] certFile = "fixtures/acme/ssl/wildcard.crt" keyFile = "fixtures/acme/ssl/wildcard.key" @@ -41,7 +40,8 @@ defaultEntryPoints = ["http", "https"] [api] -[file] +[providers] + [providers.file] [services] [services.test.loadbalancer] diff --git a/integration/fixtures/acme/acme_tls_dynamic.toml b/integration/fixtures/acme/acme_tls_dynamic.toml index bc2ed9477..f97e25077 100644 --- a/integration/fixtures/acme/acme_tls_dynamic.toml +++ b/integration/fixtures/acme/acme_tls_dynamic.toml @@ -1,6 +1,6 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["http", "https"] [entryPoints] [entryPoints.http] @@ -34,6 +34,7 @@ defaultEntryPoints = ["http", "https"] [api] -[file] -filename = "fixtures/acme/certificates.toml" -watch = true +[providers] + [providers.file] + filename = "fixtures/acme/certificates.toml" + watch = true diff --git a/integration/fixtures/acme/acme_tls_multiple_entrypoints.toml b/integration/fixtures/acme/acme_tls_multiple_entrypoints.toml index 588343dcb..27d62e212 100644 --- a/integration/fixtures/acme/acme_tls_multiple_entrypoints.toml +++ b/integration/fixtures/acme/acme_tls_multiple_entrypoints.toml @@ -1,6 +1,6 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["http", "https"] [entryPoints] [entryPoints.http] @@ -11,7 +11,7 @@ defaultEntryPoints = ["http", "https"] [entryPoints.traefik] address = ":9000" [entryPoints.traefik.tls] - [[entryPoints.traefik.tls.certificates]] + [entryPoints.traefik.tls.DefaultCertificate] certFile = "fixtures/acme/ssl/wildcard.crt" keyFile = "fixtures/acme/ssl/wildcard.key" diff --git a/integration/fixtures/consul/simple.toml b/integration/fixtures/consul/simple.toml index e6db89985..c3bdcc247 100644 --- a/integration/fixtures/consul/simple.toml +++ b/integration/fixtures/consul/simple.toml @@ -1,5 +1,4 @@ -defaultEntryPoints = ["http"] - +[log] logLevel = "DEBUG" [entryPoints] @@ -9,10 +8,11 @@ logLevel = "DEBUG" address = ":8081" -[consul] - endpoint = "{{.ConsulHost}}:8500" - watch = true - prefix = "traefik" +[providers] + [providers.consul] + endpoint = "{{.ConsulHost}}:8500" + watch = true + prefix = "traefik" [api] entryPoint = "api" diff --git a/integration/fixtures/consul/simple_https.toml b/integration/fixtures/consul/simple_https.toml index bc4b235cd..6588446e0 100644 --- a/integration/fixtures/consul/simple_https.toml +++ b/integration/fixtures/consul/simple_https.toml @@ -1,5 +1,4 @@ -defaultEntryPoints = ["http","https"] - +[log] logLevel = "DEBUG" [entryPoints] @@ -11,12 +10,11 @@ logLevel = "DEBUG" address = ":4443" [entryPoints.https.tls] - - -[consul] - endpoint = "{{.ConsulHost}}:8500" - prefix = "traefik" - watch = true +[providers] + [providers.consul] + endpoint = "{{.ConsulHost}}:8500" + prefix = "traefik" + watch = true [api] entryPoint = "api" diff --git a/integration/fixtures/consul_catalog/simple.toml b/integration/fixtures/consul_catalog/simple.toml index 2912f2595..79311ea00 100644 --- a/integration/fixtures/consul_catalog/simple.toml +++ b/integration/fixtures/consul_catalog/simple.toml @@ -1,4 +1,4 @@ -defaultEntryPoints = ["http"] +[log] logLevel = "DEBUG" [api] @@ -7,6 +7,7 @@ logLevel = "DEBUG" [entryPoints.http] address = ":8000" -[consulCatalog] -domain = "consul.localhost" -frontEndRule = "Host:{{.ServiceName}}.{{.Domain}}" +[providers] + [providers.consulCatalog] + domain = "consul.localhost" + frontEndRule = "Host:{{.ServiceName}}.{{.Domain}}" diff --git a/integration/fixtures/docker/minimal.toml b/integration/fixtures/docker/minimal.toml index 7a8cd9ffa..1131541fc 100644 --- a/integration/fixtures/docker/minimal.toml +++ b/integration/fixtures/docker/minimal.toml @@ -1,5 +1,5 @@ -defaultEntryPoints = ["http"] +[log] logLevel = "DEBUG" [entryPoints] @@ -8,7 +8,8 @@ logLevel = "DEBUG" [api] -[docker] -endpoint = "{{.DockerHost}}" -domain = "docker.localhost" -exposedByDefault = false +[providers] + [providers.docker] + endpoint = "{{.DockerHost}}" + domain = "docker.localhost" + exposedByDefault = false diff --git a/integration/fixtures/docker/simple.toml b/integration/fixtures/docker/simple.toml index f51e491b9..ab3177f4f 100644 --- a/integration/fixtures/docker/simple.toml +++ b/integration/fixtures/docker/simple.toml @@ -1,5 +1,4 @@ -defaultEntryPoints = ["http"] - +[log] logLevel = "DEBUG" [entryPoints] @@ -8,7 +7,8 @@ logLevel = "DEBUG" [api] -[docker] -endpoint = "{{.DockerHost}}" -domain = "docker.localhost" -exposedByDefault = true +[providers] + [providers.docker] + endpoint = "{{.DockerHost}}" + domain = "docker.localhost" + exposedByDefault = true diff --git a/integration/fixtures/dynamodb/simple.toml b/integration/fixtures/dynamodb/simple.toml index a9b440c0d..1389c635e 100644 --- a/integration/fixtures/dynamodb/simple.toml +++ b/integration/fixtures/dynamodb/simple.toml @@ -1,5 +1,4 @@ -defaultEntryPoints = ["http"] - +[log] logLevel = "DEBUG" [entryPoints] @@ -8,11 +7,12 @@ logLevel = "DEBUG" [entryPoints.api] address = ":8081" -[dynamodb] - accessKeyID = "key" - secretAccessKey = "secret" - endpoint = "{{.DynamoURL}}" - region = "us-east-1" +[providers] + [providers.dynamodb] + accessKeyID = "key" + secretAccessKey = "secret" + endpoint = "{{.DynamoURL}}" + region = "us-east-1" [api] entryPoint = "api" diff --git a/integration/fixtures/error_pages/error.toml b/integration/fixtures/error_pages/error.toml index 1bc39829e..6989048c4 100644 --- a/integration/fixtures/error_pages/error.toml +++ b/integration/fixtures/error_pages/error.toml @@ -1,12 +1,12 @@ -defaultEntryPoints = ["http"] - +[log] logLevel = "DEBUG" [entryPoints] [entryPoints.http] address = ":8080" -[file] +[providers] + [providers.file] [routers] [routers.router1] diff --git a/integration/fixtures/error_pages/simple.toml b/integration/fixtures/error_pages/simple.toml index 5652deb0b..fbabe4273 100644 --- a/integration/fixtures/error_pages/simple.toml +++ b/integration/fixtures/error_pages/simple.toml @@ -1,12 +1,12 @@ -defaultEntryPoints = ["http"] - +[log] logLevel = "DEBUG" [entryPoints] [entryPoints.http] address = ":8080" -[file] +[providers] + [providers.file] [routers] [routers.router1] diff --git a/integration/fixtures/etcd/simple.toml b/integration/fixtures/etcd/simple.toml index 1f4e3908b..bad23a344 100644 --- a/integration/fixtures/etcd/simple.toml +++ b/integration/fixtures/etcd/simple.toml @@ -1,5 +1,4 @@ -defaultEntryPoints = ["http"] - +[log] logLevel = "DEBUG" [entryPoints] @@ -9,10 +8,11 @@ logLevel = "DEBUG" address = ":8081" -[etcd] - endpoint = "{{.EtcdHost}}:2379" - prefix = "/traefik" - watch = true +[providers] + [providers.etcd] + endpoint = "{{.EtcdHost}}:2379" + prefix = "/traefik" + watch = true [api] - entryPoint = "api" \ No newline at end of file + entryPoint = "api" diff --git a/integration/fixtures/etcd/simple_https.toml b/integration/fixtures/etcd/simple_https.toml index c964da3d9..859ed370d 100644 --- a/integration/fixtures/etcd/simple_https.toml +++ b/integration/fixtures/etcd/simple_https.toml @@ -1,5 +1,4 @@ -defaultEntryPoints = ["http","https"] - +[log] logLevel = "DEBUG" [entryPoints] diff --git a/integration/fixtures/eureka/simple.toml b/integration/fixtures/eureka/simple.toml index e026549ce..440b2dfbd 100644 --- a/integration/fixtures/eureka/simple.toml +++ b/integration/fixtures/eureka/simple.toml @@ -1,13 +1,13 @@ -defaultEntryPoints = ["http"] - +[log] logLevel = "DEBUG" [entryPoints] [entryPoints.http] address = ":8000" +[providers] + [providers.eureka] + endpoint = "http://{{.EurekaHost}}:8761/eureka" + delay = "1s" -[eureka] - endpoint = "http://{{.EurekaHost}}:8761/eureka" - delay = "1s" [api] diff --git a/integration/fixtures/file/56-simple-panic.toml b/integration/fixtures/file/56-simple-panic.toml index 278b9cb9c..9a9519b4f 100644 --- a/integration/fixtures/file/56-simple-panic.toml +++ b/integration/fixtures/file/56-simple-panic.toml @@ -1,9 +1,8 @@ -defaultEntryPoints = ["http"] - [entryPoints] [entryPoints.http] address = ":8000" logLevel = "DEBUG" -[file] +[providers] + [providers.file] diff --git a/integration/fixtures/file/directory.toml b/integration/fixtures/file/directory.toml index 15308c560..a3d17ed59 100644 --- a/integration/fixtures/file/directory.toml +++ b/integration/fixtures/file/directory.toml @@ -1,10 +1,10 @@ -defaultEntryPoints = ["http"] - +[log] logLevel = "DEBUG" [entryPoints] [entryPoints.http] address = ":8000" -[file] - directory = "fixtures/file/dir/" +[providers] + [providers.file] + directory = "fixtures/file/dir/" diff --git a/integration/fixtures/file/simple.toml b/integration/fixtures/file/simple.toml index bbc095a3d..238aef85e 100644 --- a/integration/fixtures/file/simple.toml +++ b/integration/fixtures/file/simple.toml @@ -1,12 +1,13 @@ -defaultEntryPoints = ["http"] [entryPoints] [entryPoints.http] address = ":8000" +[log] logLevel = "DEBUG" -[file] +[providers] + [providers.file] [routers] [routers.router1] diff --git a/integration/fixtures/grpc/config.toml b/integration/fixtures/grpc/config.toml index c9217a90b..e7095b35d 100644 --- a/integration/fixtures/grpc/config.toml +++ b/integration/fixtures/grpc/config.toml @@ -1,20 +1,21 @@ -defaultEntryPoints = ["https"] - +[serversTransport] rootCAs = [ """{{ .CertContent }}""" ] +[global] debug = true [entryPoints] [entryPoints.https] address = ":4443" [entryPoints.https.tls] - [[entryPoints.https.tls.certificates]] + [entryPoints.https.tls.DefaultCertificate] certFile = """{{ .CertContent }}""" keyFile = """{{ .KeyContent }}""" [api] -[file] +[providers] + [providers.file] [routers] [routers.router1] diff --git a/integration/fixtures/grpc/config_h2c.toml b/integration/fixtures/grpc/config_h2c.toml index f33ec3ce7..271703df3 100644 --- a/integration/fixtures/grpc/config_h2c.toml +++ b/integration/fixtures/grpc/config_h2c.toml @@ -1,5 +1,5 @@ -defaultEntryPoints = ["http"] +[global] debug = true [entryPoints] @@ -8,7 +8,8 @@ debug = true [api] -[file] +[providers] + [providers.file] [routers] [routers.router1] diff --git a/integration/fixtures/grpc/config_h2c_termination.toml b/integration/fixtures/grpc/config_h2c_termination.toml index 4e3b8ef52..7e2ffa1a3 100644 --- a/integration/fixtures/grpc/config_h2c_termination.toml +++ b/integration/fixtures/grpc/config_h2c_termination.toml @@ -1,18 +1,19 @@ -defaultEntryPoints = ["https"] +[global] debug = true [entryPoints] [entryPoints.https] address = ":4443" [entryPoints.https.tls] - [[entryPoints.https.tls.certificates]] + [entryPoints.https.tls.DefaultCertificate] certFile = """{{ .CertContent }}""" keyFile = """{{ .KeyContent }}""" [api] -[file] +[providers] + [providers.file] [routers] [routers.router1] diff --git a/integration/fixtures/grpc/config_insecure.toml b/integration/fixtures/grpc/config_insecure.toml index 87bc9ef46..e8e61fd2d 100644 --- a/integration/fixtures/grpc/config_insecure.toml +++ b/integration/fixtures/grpc/config_insecure.toml @@ -1,21 +1,23 @@ -defaultEntryPoints = ["https"] +[serversTransport] insecureSkipVerify = true +[global] debug = true [entryPoints] [entryPoints.https] address = ":4443" [entryPoints.https.tls] - [[entryPoints.https.tls.certificates]] + [entryPoints.https.tls.DefaultCertificate] certFile = """{{ .CertContent }}""" keyFile = """{{ .KeyContent }}""" [api] -[file] +[providers] + [providers.file] [routers] [routers.router1] diff --git a/integration/fixtures/grpc/config_with_flush.toml b/integration/fixtures/grpc/config_with_flush.toml index 82e1ec286..4f662a465 100644 --- a/integration/fixtures/grpc/config_with_flush.toml +++ b/integration/fixtures/grpc/config_with_flush.toml @@ -1,19 +1,20 @@ -defaultEntryPoints = ["https"] +[serversTransport] rootCAs = [ """{{ .CertContent }}""" ] [entryPoints] [entryPoints.https] address = ":4443" [entryPoints.https.tls] - [[entryPoints.https.tls.certificates]] + [entryPoints.https.tls.DefaultCertificate] certFile = """{{ .CertContent }}""" keyFile = """{{ .KeyContent }}""" [api] -[file] +[providers] + [providers.file] [routers] [routers.router1] diff --git a/integration/fixtures/healthcheck/multiple-entrypoints-drr.toml b/integration/fixtures/healthcheck/multiple-entrypoints-drr.toml index 3a8af810d..1f17d2b5c 100644 --- a/integration/fixtures/healthcheck/multiple-entrypoints-drr.toml +++ b/integration/fixtures/healthcheck/multiple-entrypoints-drr.toml @@ -1,5 +1,4 @@ -defaultEntryPoints = ["http1", "http2"] - +[log] logLevel = "DEBUG" [entryPoints] @@ -10,7 +9,9 @@ logLevel = "DEBUG" [api] -[file] +[providers] + [providers.file] + [routers] [routers.router1] service = "service1" diff --git a/integration/fixtures/healthcheck/multiple-entrypoints-wrr.toml b/integration/fixtures/healthcheck/multiple-entrypoints-wrr.toml index 0fa828a1d..84c376c54 100644 --- a/integration/fixtures/healthcheck/multiple-entrypoints-wrr.toml +++ b/integration/fixtures/healthcheck/multiple-entrypoints-wrr.toml @@ -1,5 +1,4 @@ -defaultEntryPoints = ["http1", "http2"] - +[log] logLevel = "DEBUG" [entryPoints] @@ -10,7 +9,8 @@ logLevel = "DEBUG" [api] -[file] +[providers] + [providers.file] [routers] [routers.router1] diff --git a/integration/fixtures/healthcheck/port_overload.toml b/integration/fixtures/healthcheck/port_overload.toml index 8aa104afd..bfa3624a4 100644 --- a/integration/fixtures/healthcheck/port_overload.toml +++ b/integration/fixtures/healthcheck/port_overload.toml @@ -1,5 +1,4 @@ -defaultEntryPoints = ["http"] - +[log] logLevel = "DEBUG" [entryPoints] @@ -8,7 +7,8 @@ logLevel = "DEBUG" [api] -[file] +[providers] + [providers.file] [routers] [routers.router1] diff --git a/integration/fixtures/healthcheck/simple.toml b/integration/fixtures/healthcheck/simple.toml index 77108efb5..6fd00ee13 100644 --- a/integration/fixtures/healthcheck/simple.toml +++ b/integration/fixtures/healthcheck/simple.toml @@ -1,5 +1,4 @@ -defaultEntryPoints = ["http"] - +[log] logLevel = "DEBUG" [entryPoints] @@ -8,7 +7,8 @@ logLevel = "DEBUG" [api] -[file] +[providers] + [providers.file] [routers] [routers.router1] diff --git a/integration/fixtures/https/clientca/https_1ca1config.toml b/integration/fixtures/https/clientca/https_1ca1config.toml index b5215e302..77b5f4838 100644 --- a/integration/fixtures/https/clientca/https_1ca1config.toml +++ b/integration/fixtures/https/clientca/https_1ca1config.toml @@ -1,7 +1,6 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["https"] - [entryPoints] [entryPoints.https] address = ":4443" @@ -9,16 +8,11 @@ defaultEntryPoints = ["https"] [entryPoints.https.tls.ClientCA] files = ["fixtures/https/clientca/ca1.crt"] optional = true - [[entryPoints.https.tls.certificates]] - certFile = "fixtures/https/snitest.com.cert" - keyFile = "fixtures/https/snitest.com.key" - [[entryPoints.https.tls.certificates]] - certFile = "fixtures/https/snitest.org.cert" - keyFile = "fixtures/https/snitest.org.key" [api] -[file] +[providers] + [providers.file] [Routers] [Routers.router1] @@ -41,3 +35,16 @@ defaultEntryPoints = ["https"] [[Services.service2.LoadBalancer.Servers]] URL = "http://127.0.0.1:9020" Weight = 1 + +[[tls]] + entryPoints = ["https"] + [tls.certificate] + certFile = "fixtures/https/snitest.com.cert" + keyFile = "fixtures/https/snitest.com.key" + +[[tls]] + entryPoints = ["https"] + [tls.certificate] + certFile = "fixtures/https/snitest.org.cert" + keyFile = "fixtures/https/snitest.org.key" + diff --git a/integration/fixtures/https/clientca/https_2ca1config.toml b/integration/fixtures/https/clientca/https_2ca1config.toml index 4331463e6..84db043cc 100644 --- a/integration/fixtures/https/clientca/https_2ca1config.toml +++ b/integration/fixtures/https/clientca/https_2ca1config.toml @@ -1,23 +1,17 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["https"] - [entryPoints] [entryPoints.https] address = ":4443" [entryPoints.https.tls] [entryPoints.https.tls.ClientCA] files = ["fixtures/https/clientca/ca1and2.crt"] - [[entryPoints.https.tls.certificates]] - certFile = "fixtures/https/snitest.com.cert" - keyFile = "fixtures/https/snitest.com.key" - [[entryPoints.https.tls.certificates]] - certFile = "fixtures/https/snitest.org.cert" - keyFile = "fixtures/https/snitest.org.key" [api] -[file] +[providers] + [providers.file] [Routers] [Routers.router1] @@ -40,3 +34,14 @@ defaultEntryPoints = ["https"] [[Services.service2.LoadBalancer.Servers]] URL = "http://127.0.0.1:9020" Weight = 1 + +[[tls]] + entryPoints = ["https"] + [tls.certificate] + certFile = "fixtures/https/snitest.com.cert" + keyFile = "fixtures/https/snitest.com.key" +[[tls]] + entryPoints = ["https"] + [tls.certificate] + certFile = "fixtures/https/snitest.org.cert" + keyFile = "fixtures/https/snitest.org.key" diff --git a/integration/fixtures/https/clientca/https_2ca2config.toml b/integration/fixtures/https/clientca/https_2ca2config.toml index af654c056..5e2372a71 100644 --- a/integration/fixtures/https/clientca/https_2ca2config.toml +++ b/integration/fixtures/https/clientca/https_2ca2config.toml @@ -1,6 +1,6 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["https"] [entryPoints] [entryPoints.https] @@ -9,16 +9,12 @@ defaultEntryPoints = ["https"] [entryPoints.https.tls.ClientCA] files = ["fixtures/https/clientca/ca1.crt", "fixtures/https/clientca/ca2.crt"] optional = false - [[entryPoints.https.tls.certificates]] - certFile = "fixtures/https/snitest.com.cert" - keyFile = "fixtures/https/snitest.com.key" - [[entryPoints.https.tls.certificates]] - certFile = "fixtures/https/snitest.org.cert" - keyFile = "fixtures/https/snitest.org.key" [api] -[file] +[providers] + [providers.file] + [Routers] [Routers.router1] Service = "service1" @@ -40,3 +36,15 @@ defaultEntryPoints = ["https"] [[Services.service2.LoadBalancer.Servers]] URL = "http://127.0.0.1:9020" Weight = 1 + +[[tls]] + entryPoints = ["https"] + [tls.certificate] + certFile = "fixtures/https/snitest.com.cert" + keyFile = "fixtures/https/snitest.com.key" + +[[tls]] + entryPoints = ["https"] + [tls.certificate] + certFile = "fixtures/https/snitest.org.cert" + keyFile = "fixtures/https/snitest.org.key" diff --git a/integration/fixtures/https/dynamic_https_sni.toml b/integration/fixtures/https/dynamic_https_sni.toml index 9a91e80d0..591dc2ae9 100644 --- a/integration/fixtures/https/dynamic_https_sni.toml +++ b/integration/fixtures/https/dynamic_https_sni.toml @@ -1,6 +1,6 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["https"] [entryPoints] [entryPoints.https] @@ -13,7 +13,7 @@ defaultEntryPoints = ["https"] [api] -[file] - -fileName = "{{.DynamicConfFileName}}" -watch = true +[providers] + [providers.file] + fileName = "{{.DynamicConfFileName}}" + watch = true diff --git a/integration/fixtures/https/dynamic_https_sni_default_cert.toml b/integration/fixtures/https/dynamic_https_sni_default_cert.toml index 1ccf4d969..1c906d3c2 100644 --- a/integration/fixtures/https/dynamic_https_sni_default_cert.toml +++ b/integration/fixtures/https/dynamic_https_sni_default_cert.toml @@ -1,7 +1,6 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["https"] - [entryPoints] [entryPoints.https] address = ":4443" @@ -12,7 +11,8 @@ defaultEntryPoints = ["https"] [api] -[file] +[providers] + [providers.file] [Routers] [Routers.router1] diff --git a/integration/fixtures/https/https_redirect.toml b/integration/fixtures/https/https_redirect.toml index e9ab70847..491d0d916 100644 --- a/integration/fixtures/https/https_redirect.toml +++ b/integration/fixtures/https/https_redirect.toml @@ -1,6 +1,6 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["http", "https"] [entryPoints] [entryPoints.http] @@ -15,7 +15,8 @@ defaultEntryPoints = ["http", "https"] [api] -[file] +[providers] + [providers.file] [Routers] [Routers.router1] diff --git a/integration/fixtures/https/https_sni.toml b/integration/fixtures/https/https_sni.toml index 0b4108955..4e5cdd04b 100644 --- a/integration/fixtures/https/https_sni.toml +++ b/integration/fixtures/https/https_sni.toml @@ -1,21 +1,16 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["https"] [entryPoints] [entryPoints.https] address = ":4443" [entryPoints.https.tls] - [[entryPoints.https.tls.certificates]] - certFile = "fixtures/https/snitest.com.cert" - keyFile = "fixtures/https/snitest.com.key" - [[entryPoints.https.tls.certificates]] - certFile = "fixtures/https/snitest.org.cert" - keyFile = "fixtures/https/snitest.org.key" [api] -[file] +[providers] + [providers.file] [Routers] [Routers.router1] @@ -38,3 +33,14 @@ defaultEntryPoints = ["https"] [[Services.service2.LoadBalancer.Servers]] URL = "http://127.0.0.1:9020" Weight = 1 + +[[tls]] + entryPoints = ["https"] + [tls.certificate] + certFile = "fixtures/https/snitest.com.cert" + keyFile = "fixtures/https/snitest.com.key" +[[tls]] + entryPoints = ["https"] + [tls.certificate] + certFile = "fixtures/https/snitest.org.cert" + keyFile = "fixtures/https/snitest.org.key" diff --git a/integration/fixtures/https/https_sni_default_cert.toml b/integration/fixtures/https/https_sni_default_cert.toml index df5c24202..5dd6aae6f 100644 --- a/integration/fixtures/https/https_sni_default_cert.toml +++ b/integration/fixtures/https/https_sni_default_cert.toml @@ -1,24 +1,19 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["https"] [entryPoints] [entryPoints.https] address = ":4443" [entryPoints.https.tls] - [entryPoints.https.tls.defaultCertificate] + [entryPoints.https.tls.DefaultCertificate] certFile = "fixtures/https/snitest.com.cert" keyFile = "fixtures/https/snitest.com.key" - [[entryPoints.https.tls.certificates]] - certFile = "fixtures/https/wildcard.snitest.com.cert" - keyFile = "fixtures/https/wildcard.snitest.com.key" - [[entryPoints.https.tls.certificates]] - certFile = "fixtures/https/www.snitest.com.cert" - keyFile = "fixtures/https/www.snitest.com.key" [api] -[file] +[providers] + [providers.file] [Routers] [Routers.router1] @@ -35,3 +30,15 @@ defaultEntryPoints = ["https"] [[Services.service1.LoadBalancer.Servers]] URL = "http://127.0.0.1:9010" Weight = 1 + +[[tls]] + entryPoints = ["https"] + [tls.certificate] + certFile = "fixtures/https/wildcard.snitest.com.cert" + keyFile = "fixtures/https/wildcard.snitest.com.key" +[[tls]] + entryPoints = ["https"] + [tls.certificate] + certFile = "fixtures/https/www.snitest.com.cert" + keyFile = "fixtures/https/www.snitest.com.key" + diff --git a/integration/fixtures/https/https_sni_strict.toml b/integration/fixtures/https/https_sni_strict.toml index 5aba7ac3b..c9aafc7b9 100644 --- a/integration/fixtures/https/https_sni_strict.toml +++ b/integration/fixtures/https/https_sni_strict.toml @@ -1,6 +1,6 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["https"] [entryPoints] [entryPoints.https] @@ -13,7 +13,8 @@ defaultEntryPoints = ["https"] [api] -[file] +[providers] + [providers.file] [Routers] [Routers.router1] diff --git a/integration/fixtures/https/rootcas/https.toml b/integration/fixtures/https/rootcas/https.toml index 630240596..8cb38a98a 100644 --- a/integration/fixtures/https/rootcas/https.toml +++ b/integration/fixtures/https/rootcas/https.toml @@ -1,7 +1,8 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["http"] +[serversTransport] # Use certificate in net/internal/testcert.go rootCAs = [ """ -----BEGIN CERTIFICATE----- @@ -26,7 +27,8 @@ fblo6RBxUQ== [api] -[file] +[providers] + [providers.file] [Routers] [Routers.router1] diff --git a/integration/fixtures/https/rootcas/https_with_file.toml b/integration/fixtures/https/rootcas/https_with_file.toml index 80bfa3d01..277f7d1c6 100644 --- a/integration/fixtures/https/rootcas/https_with_file.toml +++ b/integration/fixtures/https/rootcas/https_with_file.toml @@ -1,7 +1,7 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["http"] - +[serversTransport] # Use certificate in net/internal/testcert.go rootCAs = [ "fixtures/https/rootcas/local.crt"] @@ -11,7 +11,8 @@ rootCAs = [ "fixtures/https/rootcas/local.crt"] [api] -[file] +[providers] + [providers.file] [Routers] [Routers.router1] diff --git a/integration/fixtures/keep_trailing_slash.toml b/integration/fixtures/keep_trailing_slash.toml index 9d9079814..69739f1a6 100644 --- a/integration/fixtures/keep_trailing_slash.toml +++ b/integration/fixtures/keep_trailing_slash.toml @@ -1,23 +1,25 @@ -defaultEntryPoints = ["http"] - keepTrailingSlash = {{ .KeepTrailingSlash }} + +[log] +logLevel = "DEBUG" + + [entryPoints] [entryPoints.http] address = ":8000" -logLevel = "DEBUG" - -[file] +[providers] + [providers.file] # rules [backends] [backends.backend1] [backends.backend1.servers.server1] - url = "http://172.17.0.2:80" - weight = 1 + url = "http://172.17.0.2:80" + weight = 1 [frontends] [frontends.frontend1] - backend = "backend1" - [frontends.frontend1.routes.test_1] - rule = "Path:/test/foo" + backend = "backend1" + [frontends.frontend1.routes.test_1] + rule = "Path:/test/foo" diff --git a/integration/fixtures/log_rotation_config.toml b/integration/fixtures/log_rotation_config.toml index e68421ddc..571a064e2 100644 --- a/integration/fixtures/log_rotation_config.toml +++ b/integration/fixtures/log_rotation_config.toml @@ -1,10 +1,13 @@ ################################################################ # Global configuration ################################################################ -traefikLogsFile = "traefik.log" -accessLogsFile = "access.log" +[accessLog] +filePath = "access.log" + +[log] +filePath = "traefik.log" logLevel = "ERROR" -defaultEntryPoints = ["http"] + [entryPoints] [entryPoints.http] address = ":8000" @@ -22,7 +25,8 @@ entryPoint = "api" ################################################################ # File configuration backend ################################################################ -[file] +[providers] + [providers.file] ################################################################ # rules diff --git a/integration/fixtures/marathon/simple.toml b/integration/fixtures/marathon/simple.toml index f737e7d38..4182aa7fc 100644 --- a/integration/fixtures/marathon/simple.toml +++ b/integration/fixtures/marathon/simple.toml @@ -1,5 +1,4 @@ -defaultEntryPoints = ["http"] - +[log] logLevel = "DEBUG" [entryPoints] @@ -11,7 +10,8 @@ logLevel = "DEBUG" [api] entryPoint = "api" -[marathon] - endpoint = "{{.MarathonURL}}" - watch = true - exposedByDefault = true +[providers] + [providers.marathon] + endpoint = "{{.MarathonURL}}" + watch = true + exposedByDefault = true diff --git a/integration/fixtures/mesos/simple.toml b/integration/fixtures/mesos/simple.toml index 0b330b62a..638c5962f 100644 --- a/integration/fixtures/mesos/simple.toml +++ b/integration/fixtures/mesos/simple.toml @@ -1,9 +1,9 @@ -defaultEntryPoints = ["http"] +[log] +logLevel = "DEBUG" [entryPoints] [entryPoints.http] address = ":8000" -logLevel = "DEBUG" - -[mesos] +[providers] + [providers.mesos] diff --git a/integration/fixtures/multiple_provider.toml b/integration/fixtures/multiple_provider.toml index 0950114fd..ccade3841 100644 --- a/integration/fixtures/multiple_provider.toml +++ b/integration/fixtures/multiple_provider.toml @@ -1,19 +1,20 @@ -defaultEntryPoints = ["http"] - +[global] debug=true [entryPoints] [entryPoints.http] - address = ":8000" + address = ":8000" [api] -[docker] -endpoint = "unix:///var/run/docker.sock" -watch = true -exposedByDefault = false +[providers] + [providers.docker] + endpoint = "unix:///var/run/docker.sock" + watch = true + exposedByDefault = false + + [providers.file] -[file] [Routers] [Routers.router-1] Service = "service-test" diff --git a/integration/fixtures/proxy-protocol/with.toml b/integration/fixtures/proxy-protocol/with.toml index ed0b43204..69ed09c2a 100644 --- a/integration/fixtures/proxy-protocol/with.toml +++ b/integration/fixtures/proxy-protocol/with.toml @@ -1,5 +1,5 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["http"] [entryPoints] [entryPoints.http] @@ -9,7 +9,8 @@ defaultEntryPoints = ["http"] [api] -[file] +[providers] + [providers.file] [Routers] [Routers.router1] diff --git a/integration/fixtures/proxy-protocol/without.toml b/integration/fixtures/proxy-protocol/without.toml index ebbe2277b..b1871c5ca 100644 --- a/integration/fixtures/proxy-protocol/without.toml +++ b/integration/fixtures/proxy-protocol/without.toml @@ -1,5 +1,5 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["http"] [entryPoints] [entryPoints.http] @@ -9,7 +9,9 @@ defaultEntryPoints = ["http"] [api] -[file] +[providers] + [providers.file] + [Routers] [Routers.router1] Service = "service1" diff --git a/integration/fixtures/ratelimit/simple.toml b/integration/fixtures/ratelimit/simple.toml index 8d367ccf9..d0b8ca842 100644 --- a/integration/fixtures/ratelimit/simple.toml +++ b/integration/fixtures/ratelimit/simple.toml @@ -1,13 +1,12 @@ -defaultEntryPoints = ["http"] - +[log] logLevel = "DEBUG" [entryPoints] [entryPoints.http] address = ":80" -[file] - +[providers] + [providers.file] [Routers] [Routers.router1] diff --git a/integration/fixtures/reqacceptgrace.toml b/integration/fixtures/reqacceptgrace.toml index 985e00a5c..92622ae11 100644 --- a/integration/fixtures/reqacceptgrace.toml +++ b/integration/fixtures/reqacceptgrace.toml @@ -1,18 +1,22 @@ -defaultEntryPoints = ["http"] - +[log] logLevel = "DEBUG" [entryPoints] [entryPoints.http] address = ":8000" + [entryPoints.http.transport.lifeCycle] + RequestAcceptGraceTimeout = "10s" + [entryPoints.traefik] address = ":8001" +[entryPoints.traefik.transport.lifeCycle] + RequestAcceptGraceTimeout = "10s" -[lifeCycle] - requestAcceptGraceTimeout = "10s" -[file] +[providers] + [providers.file] + [Routers] [Routers.router] Service = "service" diff --git a/integration/fixtures/retry/simple.toml b/integration/fixtures/retry/simple.toml index c09e66a6a..8ce9931f1 100644 --- a/integration/fixtures/retry/simple.toml +++ b/integration/fixtures/retry/simple.toml @@ -1,5 +1,4 @@ -defaultEntryPoints = ["http"] - +[log] logLevel = "DEBUG" [entryPoints] @@ -8,7 +7,8 @@ logLevel = "DEBUG" [api] -[file] +[providers] + [providers.file] [Routers] [Routers.router1] @@ -17,7 +17,7 @@ logLevel = "DEBUG" Rule = "PathPrefix:/" [Middlewares.retry.Retry] -Attempts = 3 + Attempts = 3 [Services] [Services.service1] diff --git a/integration/fixtures/simple_auth.toml b/integration/fixtures/simple_auth.toml index ec8ce8661..2549f76c9 100644 --- a/integration/fixtures/simple_auth.toml +++ b/integration/fixtures/simple_auth.toml @@ -1,5 +1,5 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["http"] [entryPoints] [entryPoints.http] @@ -12,7 +12,8 @@ defaultEntryPoints = ["http"] [api] middlewares = ["authentication"] -[middleware.authentication.basic-auth] - users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"] +[middlewares] + [middlewares.authentication.basic-auth] + users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"] [ping] diff --git a/integration/fixtures/simple_default.toml b/integration/fixtures/simple_default.toml index ff256c8a2..145164b9f 100644 --- a/integration/fixtures/simple_default.toml +++ b/integration/fixtures/simple_default.toml @@ -1,5 +1,4 @@ -defaultEntryPoints = ["http"] [entryPoints] [entryPoints.http] - address = ":8000" + address = ":8000" diff --git a/integration/fixtures/simple_hostresolver.toml b/integration/fixtures/simple_hostresolver.toml index f94d1622f..57d029a84 100644 --- a/integration/fixtures/simple_hostresolver.toml +++ b/integration/fixtures/simple_hostresolver.toml @@ -1,5 +1,5 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["http"] [entryPoints] [entryPoints.http] @@ -7,10 +7,11 @@ defaultEntryPoints = ["http"] [api] -[docker] -exposedByDefault = false -domain = "docker.local" -watch = true +[providers] + [providers.docker] + exposedByDefault = false + domain = "docker.local" + watch = true [hostResolver] cnameFlattening = true diff --git a/integration/fixtures/simple_stats.toml b/integration/fixtures/simple_stats.toml index c003f4cfb..dd0d483fc 100644 --- a/integration/fixtures/simple_stats.toml +++ b/integration/fixtures/simple_stats.toml @@ -1,3 +1,4 @@ +[global] debug=true [entryPoints] @@ -6,7 +7,8 @@ debug=true [api] -[file] +[providers] + [providers.file] [Routers] [Routers.router1] diff --git a/integration/fixtures/simple_web.toml b/integration/fixtures/simple_web.toml index 55b88480e..d4f9d9bda 100644 --- a/integration/fixtures/simple_web.toml +++ b/integration/fixtures/simple_web.toml @@ -1,5 +1,5 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["http"] [entryPoints] [entryPoints.http] diff --git a/integration/fixtures/simple_whitelist.toml b/integration/fixtures/simple_whitelist.toml index dc3010e30..83cc8eeff 100644 --- a/integration/fixtures/simple_whitelist.toml +++ b/integration/fixtures/simple_whitelist.toml @@ -1,5 +1,5 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["http"] [entryPoints] [entryPoints.http] @@ -10,4 +10,6 @@ defaultEntryPoints = ["http"] depth=2 [api] -[docker] + +[providers] + [providers.docker] diff --git a/integration/fixtures/timeout/forwarding_timeouts.toml b/integration/fixtures/timeout/forwarding_timeouts.toml index 768bde1d3..774b5bb3a 100644 --- a/integration/fixtures/timeout/forwarding_timeouts.toml +++ b/integration/fixtures/timeout/forwarding_timeouts.toml @@ -1,5 +1,11 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["http"] + + +[serversTransport.forwardingTimeouts] + dialTimeout = "300ms" + responseHeaderTimeout = "300ms" + [entryPoints] [entryPoints.http] @@ -10,11 +16,9 @@ defaultEntryPoints = ["http"] [api] -[forwardingTimeouts] - dialTimeout = "300ms" - responseHeaderTimeout = "300ms" -[file] +[providers] + [providers.file] [Routers] [Routers.router1] diff --git a/integration/fixtures/tlsclientheaders/simple.toml b/integration/fixtures/tlsclientheaders/simple.toml index 40809f315..6fe89506d 100644 --- a/integration/fixtures/tlsclientheaders/simple.toml +++ b/integration/fixtures/tlsclientheaders/simple.toml @@ -1,6 +1,10 @@ +[log] logLevel = "DEBUG" -defaultEntryPoints = ["https"] + +[global] debug = true + +[serversTransport] rootCAs = [ """{{ .RootCertContent }}""" ] [entryPoints] @@ -13,12 +17,13 @@ rootCAs = [ """{{ .RootCertContent }}""" ] files = [ """{{ .RootCertContent }}""" ] optional = false - [[entryPoints.https.tls.certificates]] + [entryPoints.https.tls.DefaultCertificate] certFile = """{{ .ServerCertContent }}""" keyFile = """{{ .ServerKeyContent }}""" [api] -[docker] -endpoint = "unix:///var/run/docker.sock" -watch = true +[providers] + [providers.docker] + endpoint = "unix:///var/run/docker.sock" + watch = true diff --git a/integration/fixtures/tracing/simple.toml b/integration/fixtures/tracing/simple.toml index ebf5a1f96..36b72924d 100644 --- a/integration/fixtures/tracing/simple.toml +++ b/integration/fixtures/tracing/simple.toml @@ -1,6 +1,7 @@ -defaultEntryPoints = ["http"] - +[log] logLevel = "DEBUG" + +[global] debug = true [api] @@ -19,7 +20,8 @@ debug = true samplingType = "const" samplingParam = 1.0 -[file] +[providers] + [providers.file] [Routers] [Routers.router1] diff --git a/integration/fixtures/traefik_log_config.toml b/integration/fixtures/traefik_log_config.toml index 7d7f90c0f..764687b44 100644 --- a/integration/fixtures/traefik_log_config.toml +++ b/integration/fixtures/traefik_log_config.toml @@ -1,17 +1,16 @@ ################################################################ # Global configuration ################################################################ -[traefikLog] - filePath = "traefik.log" +[log] +logLevel = "DEBUG" +filePath = "traefik.log" + +[global] +checkNewVersion = false [accessLog] filePath = "access.log" -logLevel = "DEBUG" -defaultEntryPoints = ["http"] - -checkNewVersion = false - [entryPoints] [entryPoints.http] address = ":8000" @@ -19,7 +18,8 @@ checkNewVersion = false [api] dashboard = false -[docker] - exposedByDefault = false - domain = "docker.local" - watch = true +[providers] + [providers.docker] + exposedByDefault = false + domain = "docker.local" + watch = true diff --git a/integration/fixtures/websocket/config.toml b/integration/fixtures/websocket/config.toml index bb643d609..91d3b2a53 100644 --- a/integration/fixtures/websocket/config.toml +++ b/integration/fixtures/websocket/config.toml @@ -1,5 +1,4 @@ -defaultEntryPoints = ["http"] - +[log] logLevel = "DEBUG" [entryPoints] @@ -8,7 +7,8 @@ logLevel = "DEBUG" [api] -[file] +[providers] + [providers.file] [Routers] [Routers.router1] diff --git a/integration/fixtures/websocket/config_https.toml b/integration/fixtures/websocket/config_https.toml index e02599a1d..283c4224f 100644 --- a/integration/fixtures/websocket/config_https.toml +++ b/integration/fixtures/websocket/config_https.toml @@ -1,19 +1,22 @@ -defaultEntryPoints = ["wss"] - +[log] logLevel = "DEBUG" + +[serversTransport] insecureSkipVerify=true [entryPoints] [entryPoints.wss] address = ":8000" [entryPoints.wss.tls] - [[entryPoints.wss.tls.certificates]] + [entryPoints.wss.tls.DefaultCertificate] certFile = "resources/tls/local.cert" keyFile = "resources/tls/local.key" [api] -[file] + +[providers] + [providers.file] [Routers] [Routers.router1] diff --git a/integration/simple_test.go b/integration/simple_test.go index 4ceaf814b..abdd5284e 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -61,26 +61,6 @@ func (s *SimpleSuite) TestWithWebConfig(c *check.C) { c.Assert(err, checker.IsNil) } -func (s *SimpleSuite) TestDefaultEntryPoints(c *check.C) { - cmd, output := s.cmdTraefik("--debug") - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer cmd.Process.Kill() - - err = try.Do(500*time.Millisecond, func() error { - expected := `\"DefaultEntryPoints\":[\"http\"]` - actual := output.String() - - if !strings.Contains(actual, expected) { - return fmt.Errorf("got %s, wanted %s", actual, expected) - } - - return nil - }) - c.Assert(err, checker.IsNil) -} - func (s *SimpleSuite) TestPrintHelp(c *check.C) { cmd, output := s.cmdTraefik("--help") @@ -180,7 +160,7 @@ func (s *SimpleSuite) TestApiOnSameEntryPoint(c *check.C) { s.createComposeProject(c, "base") s.composeProject.Start(c) - cmd, output := s.traefikCmd("--defaultEntryPoints=http", "--entryPoints=Name:http Address::8000", "--api.entryPoint=http", "--debug", "--docker") + cmd, output := s.traefikCmd("--entryPoints=Name:http Address::8000", "--api.entryPoint=http", "--global.debug", "--providers.docker") defer output(c) err := cmd.Start() @@ -264,7 +244,7 @@ func (s *SimpleSuite) TestDefaultEntrypointHTTP(c *check.C) { s.createComposeProject(c, "base") s.composeProject.Start(c) - cmd, output := s.traefikCmd("--entryPoints=Name:http Address::8000", "--debug", "--docker", "--api") + cmd, output := s.traefikCmd("--entryPoints=Name:http Address::8000", "--global.debug", "--providers.docker", "--api") defer output(c) err := cmd.Start() @@ -284,7 +264,7 @@ func (s *SimpleSuite) TestWithUnexistingEntrypoint(c *check.C) { s.createComposeProject(c, "base") s.composeProject.Start(c) - cmd, output := s.traefikCmd("--defaultEntryPoints=https,http", "--entryPoints=Name:http Address::8000", "--debug", "--docker", "--api") + cmd, output := s.traefikCmd("--entryPoints=Name:http Address::8000", "--global.debug", "--providers.docker", "--api") defer output(c) err := cmd.Start() @@ -304,7 +284,7 @@ func (s *SimpleSuite) TestMetricsPrometheusDefaultEntrypoint(c *check.C) { s.createComposeProject(c, "base") s.composeProject.Start(c) - cmd, output := s.traefikCmd("--defaultEntryPoints=http", "--entryPoints=Name:http Address::8000", "--api", "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0", "--docker", "--debug") + cmd, output := s.traefikCmd("--entryPoints=Name:http Address::8000", "--api", "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0", "--docker", "--global.debug") defer output(c) err := cmd.Start() diff --git a/integration/websocket_test.go b/integration/websocket_test.go index e423ae6d8..a117ce01d 100644 --- a/integration/websocket_test.go +++ b/integration/websocket_test.go @@ -49,7 +49,7 @@ func (s *WebsocketSuite) TestBase(c *check.C) { }) defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--debug") + cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug") defer display(c) err := cmd.Start() @@ -99,7 +99,7 @@ func (s *WebsocketSuite) TestWrongOrigin(c *check.C) { }) defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--debug") + cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug") defer display(c) err := cmd.Start() @@ -149,7 +149,7 @@ func (s *WebsocketSuite) TestOrigin(c *check.C) { }) defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--debug") + cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug") defer display(c) err := cmd.Start() @@ -210,7 +210,7 @@ func (s *WebsocketSuite) TestWrongOriginIgnoredByServer(c *check.C) { }) defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--debug") + cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug") defer display(c) err := cmd.Start() @@ -268,7 +268,7 @@ func (s *WebsocketSuite) TestSSLTermination(c *check.C) { }) defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--debug") + cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug") defer display(c) err := cmd.Start() @@ -331,7 +331,7 @@ func (s *WebsocketSuite) TestBasicAuth(c *check.C) { }) defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--debug") + cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug") defer display(c) err := cmd.Start() @@ -375,7 +375,7 @@ func (s *WebsocketSuite) TestSpecificResponseFromBackend(c *check.C) { }) defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--debug") + cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug") defer display(c) err := cmd.Start() @@ -421,7 +421,7 @@ func (s *WebsocketSuite) TestURLWithURLEncodedChar(c *check.C) { }) defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--debug") + cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug") defer display(c) err := cmd.Start() @@ -476,7 +476,7 @@ func (s *WebsocketSuite) TestSSLhttp2(c *check.C) { }) defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--debug", "--accesslog") + cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug", "--accesslog") defer display(c) err := cmd.Start() @@ -535,7 +535,7 @@ func (s *WebsocketSuite) TestHeaderAreForwared(c *check.C) { }) defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--debug") + cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug") defer display(c) err := cmd.Start() diff --git a/ip/checker.go b/ip/checker.go index 3cc3dccf0..ffe86c01e 100644 --- a/ip/checker.go +++ b/ip/checker.go @@ -60,7 +60,7 @@ func (ip *Checker) IsAuthorized(addr string) error { // Contains checks if provided address is in the trusted IPs func (ip *Checker) Contains(addr string) (bool, error) { - if len(addr) <= 0 { + if len(addr) == 0 { return false, errors.New("empty IP address") } diff --git a/middlewares/requestdecorator/hostresolver.go b/middlewares/requestdecorator/hostresolver.go index 551643deb..d728c8493 100644 --- a/middlewares/requestdecorator/hostresolver.go +++ b/middlewares/requestdecorator/hostresolver.go @@ -94,7 +94,7 @@ func cnameResolve(ctx context.Context, host string, resolvPath string) (*cnameRe result = append(result, tempRecord) } - if len(result) <= 0 { + if len(result) == 0 { return nil, nil } diff --git a/old/configuration/configuration.go b/old/configuration/configuration.go index 35a7597bd..b6f231d5b 100644 --- a/old/configuration/configuration.go +++ b/old/configuration/configuration.go @@ -29,9 +29,9 @@ import ( "github.com/containous/traefik/old/provider/rancher" "github.com/containous/traefik/old/provider/rest" "github.com/containous/traefik/old/provider/zk" + "github.com/containous/traefik/old/tls" "github.com/containous/traefik/old/types" acmeprovider "github.com/containous/traefik/provider/acme" - "github.com/containous/traefik/tls" newtypes "github.com/containous/traefik/types" "github.com/pkg/errors" lego "github.com/xenolf/lego/acme" diff --git a/old/configuration/configuration_test.go b/old/configuration/configuration_test.go deleted file mode 100644 index e3e421106..000000000 --- a/old/configuration/configuration_test.go +++ /dev/null @@ -1,224 +0,0 @@ -package configuration - -import ( - "testing" - - "github.com/containous/traefik/acme" - "github.com/containous/traefik/old/middlewares/tracing" - "github.com/containous/traefik/old/middlewares/tracing/jaeger" - "github.com/containous/traefik/old/middlewares/tracing/zipkin" - "github.com/containous/traefik/old/provider" - acmeprovider "github.com/containous/traefik/old/provider/acme" - "github.com/containous/traefik/old/provider/file" - "github.com/stretchr/testify/assert" -) - -const defaultConfigFile = "traefik.toml" - -func TestSetEffectiveConfigurationFileProviderFilename(t *testing.T) { - testCases := []struct { - desc string - fileProvider *file.Provider - wantFileProviderFilename string - wantFileProviderTraefikFile string - }{ - { - desc: "no filename for file provider given", - fileProvider: &file.Provider{}, - wantFileProviderFilename: "", - wantFileProviderTraefikFile: defaultConfigFile, - }, - { - desc: "filename for file provider given", - fileProvider: &file.Provider{BaseProvider: provider.BaseProvider{Filename: "other.toml"}}, - wantFileProviderFilename: "other.toml", - wantFileProviderTraefikFile: defaultConfigFile, - }, - { - desc: "directory for file provider given", - fileProvider: &file.Provider{Directory: "/"}, - wantFileProviderFilename: "", - wantFileProviderTraefikFile: defaultConfigFile, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - gc := &GlobalConfiguration{ - File: test.fileProvider, - } - - gc.SetEffectiveConfiguration(defaultConfigFile) - - assert.Equal(t, test.wantFileProviderFilename, gc.File.Filename) - assert.Equal(t, test.wantFileProviderTraefikFile, gc.File.TraefikFile) - }) - } -} - -func TestSetEffectiveConfigurationTracing(t *testing.T) { - testCases := []struct { - desc string - tracing *tracing.Tracing - expected *tracing.Tracing - }{ - { - desc: "no tracing configuration", - tracing: &tracing.Tracing{}, - expected: &tracing.Tracing{}, - }, - { - desc: "tracing bad backend name", - tracing: &tracing.Tracing{ - Backend: "powpow", - }, - expected: &tracing.Tracing{ - Backend: "powpow", - }, - }, - { - desc: "tracing jaeger backend name", - tracing: &tracing.Tracing{ - Backend: "jaeger", - Zipkin: &zipkin.Config{ - HTTPEndpoint: "http://localhost:9411/api/v1/spans", - SameSpan: false, - ID128Bit: true, - Debug: false, - }, - }, - expected: &tracing.Tracing{ - Backend: "jaeger", - Jaeger: &jaeger.Config{ - SamplingServerURL: "http://localhost:5778/sampling", - SamplingType: "const", - SamplingParam: 1.0, - LocalAgentHostPort: "127.0.0.1:6831", - Propagation: "jaeger", - Gen128Bit: false, - }, - Zipkin: nil, - }, - }, - { - desc: "tracing zipkin backend name", - tracing: &tracing.Tracing{ - Backend: "zipkin", - Jaeger: &jaeger.Config{ - SamplingServerURL: "http://localhost:5778/sampling", - SamplingType: "const", - SamplingParam: 1.0, - LocalAgentHostPort: "127.0.0.1:6831", - }, - }, - expected: &tracing.Tracing{ - Backend: "zipkin", - Jaeger: nil, - Zipkin: &zipkin.Config{ - HTTPEndpoint: "http://localhost:9411/api/v1/spans", - SameSpan: false, - ID128Bit: true, - Debug: false, - SampleRate: 1.0, - }, - }, - }, - { - desc: "tracing zipkin backend name value override", - tracing: &tracing.Tracing{ - Backend: "zipkin", - Jaeger: &jaeger.Config{ - SamplingServerURL: "http://localhost:5778/sampling", - SamplingType: "const", - SamplingParam: 1.0, - LocalAgentHostPort: "127.0.0.1:6831", - }, - Zipkin: &zipkin.Config{ - HTTPEndpoint: "http://powpow:9411/api/v1/spans", - SameSpan: true, - ID128Bit: true, - Debug: true, - SampleRate: 0.02, - }, - }, - expected: &tracing.Tracing{ - Backend: "zipkin", - Jaeger: nil, - Zipkin: &zipkin.Config{ - HTTPEndpoint: "http://powpow:9411/api/v1/spans", - SameSpan: true, - ID128Bit: true, - Debug: true, - SampleRate: 0.02, - }, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - gc := &GlobalConfiguration{ - Tracing: test.tracing, - } - - gc.SetEffectiveConfiguration(defaultConfigFile) - - assert.Equal(t, test.expected, gc.Tracing) - }) - } -} - -func TestInitACMEProvider(t *testing.T) { - testCases := []struct { - desc string - acmeConfiguration *acme.ACME - expectedConfiguration *acmeprovider.Provider - noError bool - }{ - { - desc: "No ACME configuration", - acmeConfiguration: nil, - expectedConfiguration: nil, - noError: true, - }, - { - desc: "ACME configuration with storage", - acmeConfiguration: &acme.ACME{Storage: "foo/acme.json"}, - expectedConfiguration: &acmeprovider.Provider{Configuration: &acmeprovider.Configuration{Storage: "foo/acme.json"}}, - noError: true, - }, - { - desc: "ACME configuration with no storage", - acmeConfiguration: &acme.ACME{}, - expectedConfiguration: nil, - noError: false, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - gc := &GlobalConfiguration{ - ACME: test.acmeConfiguration, - } - - configuration, err := gc.InitACMEProvider() - - assert.True(t, (err == nil) == test.noError) - - if test.expectedConfiguration == nil { - assert.Nil(t, configuration) - } else { - assert.Equal(t, test.expectedConfiguration.Storage, configuration.Storage) - } - }) - } -} diff --git a/config/static/convert.go b/old/configuration/convert.go similarity index 66% rename from config/static/convert.go rename to old/configuration/convert.go index ac7336198..a32a8fdeb 100644 --- a/config/static/convert.go +++ b/old/configuration/convert.go @@ -1,33 +1,30 @@ -package static +package configuration import ( - oldapi "github.com/containous/traefik/old/api" - "github.com/containous/traefik/old/configuration" - oldtracing "github.com/containous/traefik/old/middlewares/tracing" - oldfile "github.com/containous/traefik/old/provider/file" - oldtypes "github.com/containous/traefik/old/types" + "github.com/containous/traefik/config/static" + "github.com/containous/traefik/old/api" + "github.com/containous/traefik/old/middlewares/tracing" + "github.com/containous/traefik/old/provider/file" + "github.com/containous/traefik/old/types" "github.com/containous/traefik/ping" "github.com/containous/traefik/provider" - "github.com/containous/traefik/provider/file" + file2 "github.com/containous/traefik/provider/file" "github.com/containous/traefik/tracing/datadog" "github.com/containous/traefik/tracing/jaeger" "github.com/containous/traefik/tracing/zipkin" - "github.com/containous/traefik/types" + types2 "github.com/containous/traefik/types" ) // ConvertStaticConf FIXME sugar // Deprecated -func ConvertStaticConf(globalConfiguration configuration.GlobalConfiguration) Configuration { - staticConfiguration := Configuration{} +func ConvertStaticConf(globalConfiguration GlobalConfiguration) static.Configuration { + staticConfiguration := static.Configuration{} - staticConfiguration.EntryPoints = &EntryPoints{ - EntryPointList: make(EntryPointList), - Defaults: globalConfiguration.DefaultEntryPoints, - } + staticConfiguration.EntryPoints = make(static.EntryPoints) if globalConfiguration.EntryPoints != nil { for name, ep := range globalConfiguration.EntryPoints { - staticConfiguration.EntryPoints.EntryPointList[name] = EntryPoint{ + staticConfiguration.EntryPoints[name] = &static.EntryPoint{ Address: ep.Address, } } @@ -41,8 +38,7 @@ func ConvertStaticConf(globalConfiguration configuration.GlobalConfiguration) Co } staticConfiguration.API = convertAPI(globalConfiguration.API) - staticConfiguration.Constraints = convertConstraints(globalConfiguration.Constraints) - staticConfiguration.File = convertFile(globalConfiguration.File) + staticConfiguration.Providers.File = convertFile(globalConfiguration.File) staticConfiguration.Metrics = ConvertMetrics(globalConfiguration.Metrics) staticConfiguration.AccessLog = ConvertAccessLog(globalConfiguration.AccessLog) staticConfiguration.Tracing = ConvertTracing(globalConfiguration.Tracing) @@ -53,35 +49,35 @@ func ConvertStaticConf(globalConfiguration configuration.GlobalConfiguration) Co // ConvertAccessLog FIXME sugar // Deprecated -func ConvertAccessLog(old *oldtypes.AccessLog) *types.AccessLog { +func ConvertAccessLog(old *types.AccessLog) *types2.AccessLog { if old == nil { return nil } - accessLog := &types.AccessLog{ + accessLog := &types2.AccessLog{ FilePath: old.FilePath, Format: old.Format, BufferingSize: old.BufferingSize, } if old.Filters != nil { - accessLog.Filters = &types.AccessLogFilters{ - StatusCodes: types.StatusCodes(old.Filters.StatusCodes), + accessLog.Filters = &types2.AccessLogFilters{ + StatusCodes: types2.StatusCodes(old.Filters.StatusCodes), RetryAttempts: old.Filters.RetryAttempts, MinDuration: old.Filters.MinDuration, } } if old.Fields != nil { - accessLog.Fields = &types.AccessLogFields{ + accessLog.Fields = &types2.AccessLogFields{ DefaultMode: old.Fields.DefaultMode, - Names: types.FieldNames(old.Fields.Names), + Names: types2.FieldNames(old.Fields.Names), } if old.Fields.Headers != nil { - accessLog.Fields.Headers = &types.FieldHeaders{ + accessLog.Fields.Headers = &types2.FieldHeaders{ DefaultMode: old.Fields.Headers.DefaultMode, - Names: types.FieldHeaderNames(old.Fields.Headers.Names), + Names: types2.FieldHeaderNames(old.Fields.Headers.Names), } } } @@ -91,35 +87,35 @@ func ConvertAccessLog(old *oldtypes.AccessLog) *types.AccessLog { // ConvertMetrics FIXME sugar // Deprecated -func ConvertMetrics(old *oldtypes.Metrics) *types.Metrics { +func ConvertMetrics(old *types.Metrics) *types2.Metrics { if old == nil { return nil } - metrics := &types.Metrics{} + metrics := &types2.Metrics{} if old.Prometheus != nil { - metrics.Prometheus = &types.Prometheus{ + metrics.Prometheus = &types2.Prometheus{ EntryPoint: old.Prometheus.EntryPoint, - Buckets: types.Buckets(old.Prometheus.Buckets), + Buckets: types2.Buckets(old.Prometheus.Buckets), } } if old.Datadog != nil { - metrics.Datadog = &types.Datadog{ + metrics.Datadog = &types2.Datadog{ Address: old.Datadog.Address, PushInterval: old.Datadog.PushInterval, } } if old.StatsD != nil { - metrics.StatsD = &types.Statsd{ + metrics.StatsD = &types2.Statsd{ Address: old.StatsD.Address, PushInterval: old.StatsD.PushInterval, } } if old.InfluxDB != nil { - metrics.InfluxDB = &types.InfluxDB{ + metrics.InfluxDB = &types2.InfluxDB{ Address: old.InfluxDB.Address, Protocol: old.InfluxDB.Protocol, PushInterval: old.InfluxDB.PushInterval, @@ -135,12 +131,12 @@ func ConvertMetrics(old *oldtypes.Metrics) *types.Metrics { // ConvertTracing FIXME sugar // Deprecated -func ConvertTracing(old *oldtracing.Tracing) *Tracing { +func ConvertTracing(old *tracing.Tracing) *static.Tracing { if old == nil { return nil } - tra := &Tracing{ + tra := &static.Tracing{ Backend: old.Backend, ServiceName: old.ServiceName, SpanNameLimit: old.SpanNameLimit, @@ -177,19 +173,19 @@ func ConvertTracing(old *oldtracing.Tracing) *Tracing { return tra } -func convertAPI(old *oldapi.Handler) *API { +func convertAPI(old *api.Handler) *static.API { if old == nil { return nil } - api := &API{ + api := &static.API{ EntryPoint: old.EntryPoint, Dashboard: old.Dashboard, DashboardAssets: old.DashboardAssets, } if old.Statistics != nil { - api.Statistics = &types.Statistics{ + api.Statistics = &types2.Statistics{ RecentErrors: old.Statistics.RecentErrors, } } @@ -197,10 +193,10 @@ func convertAPI(old *oldapi.Handler) *API { return api } -func convertConstraints(oldConstraints oldtypes.Constraints) types.Constraints { - constraints := types.Constraints{} +func convertConstraints(oldConstraints types.Constraints) types2.Constraints { + constraints := types2.Constraints{} for _, value := range oldConstraints { - constraint := &types.Constraint{ + constraint := &types2.Constraint{ Key: value.Key, MustMatch: value.MustMatch, Regex: value.Regex, @@ -211,12 +207,12 @@ func convertConstraints(oldConstraints oldtypes.Constraints) types.Constraints { return constraints } -func convertFile(old *oldfile.Provider) *file.Provider { +func convertFile(old *file.Provider) *file2.Provider { if old == nil { return nil } - f := &file.Provider{ + f := &file2.Provider{ BaseProvider: provider.BaseProvider{ Watch: old.Watch, Filename: old.Filename, @@ -233,12 +229,12 @@ func convertFile(old *oldfile.Provider) *file.Provider { // ConvertHostResolverConfig FIXME // Deprecated -func ConvertHostResolverConfig(oldconfig *configuration.HostResolverConfig) *HostResolverConfig { +func ConvertHostResolverConfig(oldconfig *HostResolverConfig) *static.HostResolverConfig { if oldconfig == nil { return nil } - return &HostResolverConfig{ + return &static.HostResolverConfig{ CnameFlattening: oldconfig.CnameFlattening, ResolvConfig: oldconfig.ResolvConfig, ResolvDepth: oldconfig.ResolvDepth, diff --git a/old/configuration/entrypoints.go b/old/configuration/entrypoints.go index 3770eae01..b5dcedbc1 100644 --- a/old/configuration/entrypoints.go +++ b/old/configuration/entrypoints.go @@ -6,8 +6,8 @@ import ( "strings" "github.com/containous/traefik/old/log" + "github.com/containous/traefik/old/tls" "github.com/containous/traefik/old/types" - "github.com/containous/traefik/tls" ) // EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...) diff --git a/old/configuration/router/internal_router_test.go b/old/configuration/router/internal_router_test.go deleted file mode 100644 index 42e92dd4c..000000000 --- a/old/configuration/router/internal_router_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package router - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/containous/mux" - "github.com/containous/traefik/acme" - "github.com/containous/traefik/old/api" - "github.com/containous/traefik/old/configuration" - "github.com/containous/traefik/old/log" - "github.com/containous/traefik/old/ping" - acmeprovider "github.com/containous/traefik/old/provider/acme" - "github.com/containous/traefik/old/types" - "github.com/containous/traefik/safe" - "github.com/stretchr/testify/assert" - "github.com/urfave/negroni" -) - -func TestNewInternalRouterAggregatorWithAuth(t *testing.T) { - currentConfiguration := &safe.Safe{} - currentConfiguration.Set(types.Configurations{}) - - globalConfiguration := configuration.GlobalConfiguration{ - API: &api.Handler{ - EntryPoint: "traefik", - CurrentConfigurations: currentConfiguration, - }, - Ping: &ping.Handler{ - EntryPoint: "traefik", - }, - ACME: &acme.ACME{ - HTTPChallenge: &acmeprovider.HTTPChallenge{ - EntryPoint: "traefik", - }, - }, - EntryPoints: configuration.EntryPoints{ - "traefik": &configuration.EntryPoint{ - Auth: &types.Auth{ - Basic: &types.Basic{ - Users: types.Users{"test:test"}, - }, - }, - }, - }, - } - - testCases := []struct { - desc string - testedURL string - expectedStatusCode int - }{ - { - desc: "Wrong url", - testedURL: "/wrong", - expectedStatusCode: 502, - }, - { - desc: "Ping without auth", - testedURL: "/ping", - expectedStatusCode: 200, - }, - { - desc: "acme without auth", - testedURL: "/.well-known/acme-challenge/token", - expectedStatusCode: 404, - }, - { - desc: "api with auth", - testedURL: "/api", - expectedStatusCode: 401, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - router := NewInternalRouterAggregator(globalConfiguration, "traefik") - - internalMuxRouter := mux.NewRouter() - router.AddRoutes(internalMuxRouter) - internalMuxRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusBadGateway) - }) - - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, test.testedURL, nil) - internalMuxRouter.ServeHTTP(recorder, request) - - assert.Equal(t, test.expectedStatusCode, recorder.Code) - }) - } -} - -type MockInternalRouterFunc func(systemRouter *mux.Router) - -func (m MockInternalRouterFunc) AddRoutes(systemRouter *mux.Router) { - m(systemRouter) -} - -func TestWithMiddleware(t *testing.T) { - router := WithMiddleware{ - router: MockInternalRouterFunc(func(systemRouter *mux.Router) { - systemRouter.Handle("/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - if _, err := w.Write([]byte("router")); err != nil { - log.Error(err) - } - })) - }), - routerMiddlewares: []negroni.Handler{ - negroni.HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - if _, err := rw.Write([]byte("before middleware1|")); err != nil { - log.Error(err) - } - - next.ServeHTTP(rw, r) - - if _, err := rw.Write([]byte("|after middleware1")); err != nil { - log.Error(err) - } - - }), - negroni.HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - if _, err := rw.Write([]byte("before middleware2|")); err != nil { - log.Error(err) - } - - next.ServeHTTP(rw, r) - - if _, err := rw.Write([]byte("|after middleware2")); err != nil { - log.Error(err) - } - }), - }, - } - - internalMuxRouter := mux.NewRouter() - router.AddRoutes(internalMuxRouter) - - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, "/test", nil) - internalMuxRouter.ServeHTTP(recorder, request) - - obtained := recorder.Body.String() - - assert.Equal(t, "before middleware1|before middleware2|router|after middleware2|after middleware1", obtained) -} diff --git a/old/log/logger_test.go b/old/log/logger_test.go deleted file mode 100644 index c91202167..000000000 --- a/old/log/logger_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package log - -import ( - "io/ioutil" - "os" - "strings" - "testing" - "time" -) - -func TestLogRotation(t *testing.T) { - tempDir, err := ioutil.TempDir("", "traefik_") - if err != nil { - t.Fatalf("Error setting up temporary directory: %s", err) - } - - fileName := tempDir + "traefik.log" - if err := OpenFile(fileName); err != nil { - t.Fatalf("Error opening temporary file %s: %s", fileName, err) - } - defer CloseFile() - - rotatedFileName := fileName + ".rotated" - - iterations := 20 - halfDone := make(chan bool) - writeDone := make(chan bool) - go func() { - for i := 0; i < iterations; i++ { - Println("Test log line") - if i == iterations/2 { - halfDone <- true - } - } - writeDone <- true - }() - - <-halfDone - err = os.Rename(fileName, rotatedFileName) - if err != nil { - t.Fatalf("Error renaming file: %s", err) - } - - err = RotateFile() - if err != nil { - t.Fatalf("Error rotating file: %s", err) - } - - select { - case <-writeDone: - gotLineCount := lineCount(t, fileName) + lineCount(t, rotatedFileName) - if iterations != gotLineCount { - t.Errorf("Wanted %d written log lines, got %d", iterations, gotLineCount) - } - case <-time.After(500 * time.Millisecond): - t.Fatalf("test timed out") - } - - close(halfDone) - close(writeDone) -} - -func lineCount(t *testing.T, fileName string) int { - t.Helper() - fileContents, err := ioutil.ReadFile(fileName) - if err != nil { - t.Fatalf("Error reading from file %s: %s", fileName, err) - } - - count := 0 - for _, line := range strings.Split(string(fileContents), "\n") { - if strings.TrimSpace(line) == "" { - continue - } - count++ - } - - return count -} diff --git a/old/middlewares/redirect/redirect_test.go b/old/middlewares/redirect/redirect_test.go deleted file mode 100644 index 522d76821..000000000 --- a/old/middlewares/redirect/redirect_test.go +++ /dev/null @@ -1,182 +0,0 @@ -package redirect - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/containous/traefik/old/configuration" - "github.com/containous/traefik/testhelpers" - "github.com/containous/traefik/tls" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewEntryPointHandler(t *testing.T) { - testCases := []struct { - desc string - entryPoint *configuration.EntryPoint - permanent bool - url string - expectedURL string - expectedStatus int - errorExpected bool - }{ - { - desc: "HTTP to HTTPS", - entryPoint: &configuration.EntryPoint{Address: ":443", TLS: &tls.TLS{}}, - url: "http://foo:80", - expectedURL: "https://foo:443", - expectedStatus: http.StatusFound, - }, - { - desc: "HTTPS to HTTP", - entryPoint: &configuration.EntryPoint{Address: ":80"}, - url: "https://foo:443", - expectedURL: "http://foo:80", - expectedStatus: http.StatusFound, - }, - { - desc: "HTTP to HTTP", - entryPoint: &configuration.EntryPoint{Address: ":88"}, - url: "http://foo:80", - expectedURL: "http://foo:88", - expectedStatus: http.StatusFound, - }, - { - desc: "HTTP to HTTPS permanent", - entryPoint: &configuration.EntryPoint{Address: ":443", TLS: &tls.TLS{}}, - permanent: true, - url: "http://foo:80", - expectedURL: "https://foo:443", - expectedStatus: http.StatusMovedPermanently, - }, - { - desc: "HTTPS to HTTP permanent", - entryPoint: &configuration.EntryPoint{Address: ":80"}, - permanent: true, - url: "https://foo:443", - expectedURL: "http://foo:80", - expectedStatus: http.StatusMovedPermanently, - }, - { - desc: "invalid address", - entryPoint: &configuration.EntryPoint{Address: ":foo", TLS: &tls.TLS{}}, - url: "http://foo:80", - errorExpected: true, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - handler, err := NewEntryPointHandler(test.entryPoint, test.permanent) - - if test.errorExpected { - require.Error(t, err) - } else { - require.NoError(t, err) - - recorder := httptest.NewRecorder() - r := testhelpers.MustNewRequest(http.MethodGet, test.url, nil) - handler.ServeHTTP(recorder, r, nil) - - location, err := recorder.Result().Location() - require.NoError(t, err) - - assert.Equal(t, test.expectedURL, location.String()) - assert.Equal(t, test.expectedStatus, recorder.Code) - } - }) - } -} - -func TestNewRegexHandler(t *testing.T) { - testCases := []struct { - desc string - regex string - replacement string - permanent bool - url string - expectedURL string - expectedStatus int - errorExpected bool - }{ - { - desc: "simple redirection", - regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`, - replacement: "https://${1}bar$2:443$4", - url: "http://foo.com:80", - expectedURL: "https://foobar.com:443", - expectedStatus: http.StatusFound, - }, - { - desc: "use request header", - regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`, - replacement: `https://${1}{{ .Request.Header.Get "X-Foo" }}$2:443$4`, - url: "http://foo.com:80", - expectedURL: "https://foobar.com:443", - expectedStatus: http.StatusFound, - }, - { - desc: "URL doesn't match regex", - regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`, - replacement: "https://${1}bar$2:443$4", - url: "http://bar.com:80", - expectedStatus: http.StatusOK, - }, - { - desc: "invalid rewritten URL", - regex: `^(.*)$`, - replacement: "http://192.168.0.%31/", - url: "http://foo.com:80", - expectedStatus: http.StatusBadGateway, - }, - { - desc: "invalid regex", - regex: `^(.*`, - replacement: "$1", - url: "http://foo.com:80", - errorExpected: true, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - handler, err := NewRegexHandler(test.regex, test.replacement, test.permanent) - - if test.errorExpected { - require.Nil(t, handler) - require.Error(t, err) - } else { - require.NotNil(t, handler) - require.NoError(t, err) - - recorder := httptest.NewRecorder() - r := testhelpers.MustNewRequest(http.MethodGet, test.url, nil) - r.Header.Set("X-Foo", "bar") - next := func(rw http.ResponseWriter, req *http.Request) {} - handler.ServeHTTP(recorder, r, next) - - if test.expectedStatus == http.StatusMovedPermanently || test.expectedStatus == http.StatusFound { - assert.Equal(t, test.expectedStatus, recorder.Code) - - location, err := recorder.Result().Location() - require.NoError(t, err) - - assert.Equal(t, test.expectedURL, location.String()) - } else { - assert.Equal(t, test.expectedStatus, recorder.Code) - - location, err := recorder.Result().Location() - require.Errorf(t, err, "Location %v", location) - } - } - }) - } -} diff --git a/old/provider/acme/provider_test.go b/old/provider/acme/provider_test.go deleted file mode 100644 index 591726b90..000000000 --- a/old/provider/acme/provider_test.go +++ /dev/null @@ -1,684 +0,0 @@ -package acme - -import ( - "crypto/tls" - "testing" - - "github.com/containous/traefik/old/types" - "github.com/containous/traefik/safe" - traefiktls "github.com/containous/traefik/tls" - "github.com/stretchr/testify/assert" - "github.com/xenolf/lego/acme" -) - -func TestGetUncheckedCertificates(t *testing.T) { - wildcardMap := make(map[string]*tls.Certificate) - wildcardMap["*.traefik.wtf"] = &tls.Certificate{} - - wildcardSafe := &safe.Safe{} - wildcardSafe.Set(wildcardMap) - - domainMap := make(map[string]*tls.Certificate) - domainMap["traefik.wtf"] = &tls.Certificate{} - - domainSafe := &safe.Safe{} - domainSafe.Set(domainMap) - - testCases := []struct { - desc string - dynamicCerts *safe.Safe - staticCerts *safe.Safe - resolvingDomains map[string]struct{} - acmeCertificates []*Certificate - domains []string - expectedDomains []string - }{ - { - desc: "wildcard to generate", - domains: []string{"*.traefik.wtf"}, - expectedDomains: []string{"*.traefik.wtf"}, - }, - { - desc: "wildcard already exists in dynamic certificates", - domains: []string{"*.traefik.wtf"}, - dynamicCerts: wildcardSafe, - expectedDomains: nil, - }, - { - desc: "wildcard already exists in static certificates", - domains: []string{"*.traefik.wtf"}, - staticCerts: wildcardSafe, - expectedDomains: nil, - }, - { - desc: "wildcard already exists in ACME certificates", - domains: []string{"*.traefik.wtf"}, - acmeCertificates: []*Certificate{ - { - Domain: types.Domain{Main: "*.traefik.wtf"}, - }, - }, - expectedDomains: nil, - }, - { - desc: "domain CN and SANs to generate", - domains: []string{"traefik.wtf", "foo.traefik.wtf"}, - expectedDomains: []string{"traefik.wtf", "foo.traefik.wtf"}, - }, - { - desc: "domain CN already exists in dynamic certificates and SANs to generate", - domains: []string{"traefik.wtf", "foo.traefik.wtf"}, - dynamicCerts: domainSafe, - expectedDomains: []string{"foo.traefik.wtf"}, - }, - { - desc: "domain CN already exists in static certificates and SANs to generate", - domains: []string{"traefik.wtf", "foo.traefik.wtf"}, - staticCerts: domainSafe, - expectedDomains: []string{"foo.traefik.wtf"}, - }, - { - desc: "domain CN already exists in ACME certificates and SANs to generate", - domains: []string{"traefik.wtf", "foo.traefik.wtf"}, - acmeCertificates: []*Certificate{ - { - Domain: types.Domain{Main: "traefik.wtf"}, - }, - }, - expectedDomains: []string{"foo.traefik.wtf"}, - }, - { - desc: "domain already exists in dynamic certificates", - domains: []string{"traefik.wtf"}, - dynamicCerts: domainSafe, - expectedDomains: nil, - }, - { - desc: "domain already exists in static certificates", - domains: []string{"traefik.wtf"}, - staticCerts: domainSafe, - expectedDomains: nil, - }, - { - desc: "domain already exists in ACME certificates", - domains: []string{"traefik.wtf"}, - acmeCertificates: []*Certificate{ - { - Domain: types.Domain{Main: "traefik.wtf"}, - }, - }, - expectedDomains: nil, - }, - { - desc: "domain matched by wildcard in dynamic certificates", - domains: []string{"who.traefik.wtf", "foo.traefik.wtf"}, - dynamicCerts: wildcardSafe, - expectedDomains: nil, - }, - { - desc: "domain matched by wildcard in static certificates", - domains: []string{"who.traefik.wtf", "foo.traefik.wtf"}, - staticCerts: wildcardSafe, - expectedDomains: nil, - }, - { - desc: "domain matched by wildcard in ACME certificates", - domains: []string{"who.traefik.wtf", "foo.traefik.wtf"}, - acmeCertificates: []*Certificate{ - { - Domain: types.Domain{Main: "*.traefik.wtf"}, - }, - }, - expectedDomains: nil, - }, - { - desc: "root domain with wildcard in ACME certificates", - domains: []string{"traefik.wtf", "foo.traefik.wtf"}, - acmeCertificates: []*Certificate{ - { - Domain: types.Domain{Main: "*.traefik.wtf"}, - }, - }, - expectedDomains: []string{"traefik.wtf"}, - }, - { - desc: "all domains already managed by ACME", - domains: []string{"traefik.wtf", "foo.traefik.wtf"}, - resolvingDomains: map[string]struct{}{ - "traefik.wtf": {}, - "foo.traefik.wtf": {}, - }, - expectedDomains: []string{}, - }, - { - desc: "one domain already managed by ACME", - domains: []string{"traefik.wtf", "foo.traefik.wtf"}, - resolvingDomains: map[string]struct{}{ - "traefik.wtf": {}, - }, - expectedDomains: []string{"foo.traefik.wtf"}, - }, - { - desc: "wildcard domain already managed by ACME checks the domains", - domains: []string{"bar.traefik.wtf", "foo.traefik.wtf"}, - resolvingDomains: map[string]struct{}{ - "*.traefik.wtf": {}, - }, - expectedDomains: []string{}, - }, - { - desc: "wildcard domain already managed by ACME checks domains and another domain checks one other domain, one domain still unchecked", - domains: []string{"traefik.wtf", "bar.traefik.wtf", "foo.traefik.wtf", "acme.wtf"}, - resolvingDomains: map[string]struct{}{ - "*.traefik.wtf": {}, - "traefik.wtf": {}, - }, - expectedDomains: []string{"acme.wtf"}, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - if test.resolvingDomains == nil { - test.resolvingDomains = make(map[string]struct{}) - } - - acmeProvider := Provider{ - certificateStore: &traefiktls.CertificateStore{ - DynamicCerts: test.dynamicCerts, - StaticCerts: test.staticCerts, - }, - certificates: test.acmeCertificates, - resolvingDomains: test.resolvingDomains, - } - - domains := acmeProvider.getUncheckedDomains(test.domains, false) - assert.Equal(t, len(test.expectedDomains), len(domains), "Unexpected domains.") - }) - } -} - -func TestGetValidDomain(t *testing.T) { - testCases := []struct { - desc string - domains types.Domain - wildcardAllowed bool - dnsChallenge *DNSChallenge - expectedErr string - expectedDomains []string - }{ - { - desc: "valid wildcard", - domains: types.Domain{Main: "*.traefik.wtf"}, - dnsChallenge: &DNSChallenge{}, - wildcardAllowed: true, - expectedErr: "", - expectedDomains: []string{"*.traefik.wtf"}, - }, - { - desc: "no wildcard", - domains: types.Domain{Main: "traefik.wtf", SANs: []string{"foo.traefik.wtf"}}, - dnsChallenge: &DNSChallenge{}, - expectedErr: "", - wildcardAllowed: true, - expectedDomains: []string{"traefik.wtf", "foo.traefik.wtf"}, - }, - { - desc: "unauthorized wildcard", - domains: types.Domain{Main: "*.traefik.wtf"}, - dnsChallenge: &DNSChallenge{}, - wildcardAllowed: false, - expectedErr: "unable to generate a wildcard certificate in ACME provider for domain \"*.traefik.wtf\" from a 'Host' rule", - expectedDomains: nil, - }, - { - desc: "no domain", - domains: types.Domain{}, - dnsChallenge: nil, - wildcardAllowed: true, - expectedErr: "unable to generate a certificate in ACME provider when no domain is given", - expectedDomains: nil, - }, - { - desc: "no DNSChallenge", - domains: types.Domain{Main: "*.traefik.wtf", SANs: []string{"foo.traefik.wtf"}}, - dnsChallenge: nil, - wildcardAllowed: true, - expectedErr: "unable to generate a wildcard certificate in ACME provider for domain \"*.traefik.wtf,foo.traefik.wtf\" : ACME needs a DNSChallenge", - expectedDomains: nil, - }, - { - desc: "unauthorized wildcard with SAN", - domains: types.Domain{Main: "*.*.traefik.wtf", SANs: []string{"foo.traefik.wtf"}}, - dnsChallenge: &DNSChallenge{}, - wildcardAllowed: true, - expectedErr: "unable to generate a wildcard certificate in ACME provider for domain \"*.*.traefik.wtf,foo.traefik.wtf\" : ACME does not allow '*.*' wildcard domain", - expectedDomains: nil, - }, - { - desc: "wildcard and SANs", - domains: types.Domain{Main: "*.traefik.wtf", SANs: []string{"traefik.wtf"}}, - dnsChallenge: &DNSChallenge{}, - wildcardAllowed: true, - expectedErr: "", - expectedDomains: []string{"*.traefik.wtf", "traefik.wtf"}, - }, - { - desc: "unexpected SANs", - domains: types.Domain{Main: "*.traefik.wtf", SANs: []string{"*.acme.wtf"}}, - dnsChallenge: &DNSChallenge{}, - wildcardAllowed: true, - expectedErr: "unable to generate a certificate in ACME provider for domains \"*.traefik.wtf,*.acme.wtf\": SAN \"*.acme.wtf\" can not be a wildcard domain", - expectedDomains: nil, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - acmeProvider := Provider{Configuration: &Configuration{DNSChallenge: test.dnsChallenge}} - - domains, err := acmeProvider.getValidDomains(test.domains, test.wildcardAllowed) - - if len(test.expectedErr) > 0 { - assert.EqualError(t, err, test.expectedErr, "Unexpected error.") - } else { - assert.Equal(t, len(test.expectedDomains), len(domains), "Unexpected domains.") - } - }) - } -} - -func TestDeleteUnnecessaryDomains(t *testing.T) { - testCases := []struct { - desc string - domains []types.Domain - expectedDomains []types.Domain - }{ - { - desc: "no domain to delete", - domains: []types.Domain{ - { - Main: "acme.wtf", - SANs: []string{"traefik.acme.wtf", "foo.bar"}, - }, - { - Main: "*.foo.acme.wtf", - }, - { - Main: "acme02.wtf", - SANs: []string{"traefik.acme02.wtf", "bar.foo"}, - }, - }, - expectedDomains: []types.Domain{ - { - Main: "acme.wtf", - SANs: []string{"traefik.acme.wtf", "foo.bar"}, - }, - { - Main: "*.foo.acme.wtf", - SANs: []string{}, - }, - { - Main: "acme02.wtf", - SANs: []string{"traefik.acme02.wtf", "bar.foo"}, - }, - }, - }, - { - desc: "wildcard and root domain", - domains: []types.Domain{ - { - Main: "acme.wtf", - }, - { - Main: "*.acme.wtf", - SANs: []string{"acme.wtf"}, - }, - }, - expectedDomains: []types.Domain{ - { - Main: "acme.wtf", - SANs: []string{}, - }, - { - Main: "*.acme.wtf", - SANs: []string{}, - }, - }, - }, - { - desc: "2 equals domains", - domains: []types.Domain{ - { - Main: "acme.wtf", - SANs: []string{"traefik.acme.wtf", "foo.bar"}, - }, - { - Main: "acme.wtf", - SANs: []string{"traefik.acme.wtf", "foo.bar"}, - }, - }, - expectedDomains: []types.Domain{ - { - Main: "acme.wtf", - SANs: []string{"traefik.acme.wtf", "foo.bar"}, - }, - }, - }, - { - desc: "2 domains with same values", - domains: []types.Domain{ - { - Main: "acme.wtf", - SANs: []string{"traefik.acme.wtf"}, - }, - { - Main: "acme.wtf", - SANs: []string{"traefik.acme.wtf", "foo.bar"}, - }, - }, - expectedDomains: []types.Domain{ - { - Main: "acme.wtf", - SANs: []string{"traefik.acme.wtf"}, - }, - { - Main: "foo.bar", - SANs: []string{}, - }, - }, - }, - { - desc: "domain totally checked by wildcard", - domains: []types.Domain{ - { - Main: "who.acme.wtf", - SANs: []string{"traefik.acme.wtf", "bar.acme.wtf"}, - }, - { - Main: "*.acme.wtf", - }, - }, - expectedDomains: []types.Domain{ - { - Main: "*.acme.wtf", - SANs: []string{}, - }, - }, - }, - { - desc: "duplicated wildcard", - domains: []types.Domain{ - { - Main: "*.acme.wtf", - SANs: []string{"acme.wtf"}, - }, - { - Main: "*.acme.wtf", - }, - }, - expectedDomains: []types.Domain{ - { - Main: "*.acme.wtf", - SANs: []string{"acme.wtf"}, - }, - }, - }, - { - desc: "domain partially checked by wildcard", - domains: []types.Domain{ - { - Main: "traefik.acme.wtf", - SANs: []string{"acme.wtf", "foo.bar"}, - }, - { - Main: "*.acme.wtf", - }, - { - Main: "who.acme.wtf", - SANs: []string{"traefik.acme.wtf", "bar.acme.wtf"}, - }, - }, - expectedDomains: []types.Domain{ - { - Main: "acme.wtf", - SANs: []string{"foo.bar"}, - }, - { - Main: "*.acme.wtf", - SANs: []string{}, - }, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - acmeProvider := Provider{Configuration: &Configuration{Domains: test.domains}} - - acmeProvider.deleteUnnecessaryDomains() - assert.Equal(t, test.expectedDomains, acmeProvider.Domains, "unexpected domain") - }) - } -} - -func TestIsAccountMatchingCaServer(t *testing.T) { - testCases := []struct { - desc string - accountURI string - serverURI string - expected bool - }{ - { - desc: "acme staging with matching account", - accountURI: "https://acme-staging-v02.api.letsencrypt.org/acme/acct/1234567", - serverURI: "https://acme-staging-v02.api.letsencrypt.org/acme/directory", - expected: true, - }, - { - desc: "acme production with matching account", - accountURI: "https://acme-v02.api.letsencrypt.org/acme/acct/1234567", - serverURI: "https://acme-v02.api.letsencrypt.org/acme/directory", - expected: true, - }, - { - desc: "http only acme with matching account", - accountURI: "http://acme.api.letsencrypt.org/acme/acct/1234567", - serverURI: "http://acme.api.letsencrypt.org/acme/directory", - expected: true, - }, - { - desc: "different subdomains for account and server", - accountURI: "https://test1.example.org/acme/acct/1234567", - serverURI: "https://test2.example.org/acme/directory", - expected: false, - }, - { - desc: "different domains for account and server", - accountURI: "https://test.example1.org/acme/acct/1234567", - serverURI: "https://test.example2.org/acme/directory", - expected: false, - }, - { - desc: "different tld for account and server", - accountURI: "https://test.example.com/acme/acct/1234567", - serverURI: "https://test.example.org/acme/directory", - expected: false, - }, - { - desc: "malformed account url", - accountURI: "//|\\/test.example.com/acme/acct/1234567", - serverURI: "https://test.example.com/acme/directory", - expected: false, - }, - { - desc: "malformed server url", - accountURI: "https://test.example.com/acme/acct/1234567", - serverURI: "//|\\/test.example.com/acme/directory", - expected: false, - }, - { - desc: "malformed server and account url", - accountURI: "//|\\/test.example.com/acme/acct/1234567", - serverURI: "//|\\/test.example.com/acme/directory", - expected: false, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - result := isAccountMatchingCaServer(test.accountURI, test.serverURI) - - assert.Equal(t, test.expected, result) - }) - } -} - -func TestUseBackOffToObtainCertificate(t *testing.T) { - testCases := []struct { - desc string - domains []string - dnsChallenge *DNSChallenge - expectedResponse bool - }{ - { - desc: "only one single domain", - domains: []string{"acme.wtf"}, - dnsChallenge: &DNSChallenge{}, - expectedResponse: false, - }, - { - desc: "only one wildcard domain", - domains: []string{"*.acme.wtf"}, - dnsChallenge: &DNSChallenge{}, - expectedResponse: false, - }, - { - desc: "wildcard domain with no root domain", - domains: []string{"*.acme.wtf", "foo.acme.wtf", "bar.acme.wtf", "foo.bar"}, - dnsChallenge: &DNSChallenge{}, - expectedResponse: false, - }, - { - desc: "wildcard and root domain", - domains: []string{"*.acme.wtf", "foo.acme.wtf", "bar.acme.wtf", "acme.wtf"}, - dnsChallenge: &DNSChallenge{}, - expectedResponse: true, - }, - { - desc: "wildcard and root domain but no DNS challenge", - domains: []string{"*.acme.wtf", "acme.wtf"}, - dnsChallenge: nil, - expectedResponse: false, - }, - { - desc: "two wildcard domains (must never happen)", - domains: []string{"*.acme.wtf", "*.bar.foo"}, - dnsChallenge: nil, - expectedResponse: false, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - acmeProvider := Provider{Configuration: &Configuration{DNSChallenge: test.dnsChallenge}} - - actualResponse := acmeProvider.useCertificateWithRetry(test.domains) - assert.Equal(t, test.expectedResponse, actualResponse, "unexpected response to use backOff") - }) - } -} - -func TestInitAccount(t *testing.T) { - testCases := []struct { - desc string - account *Account - email string - keyType string - expectedAccount *Account - }{ - { - desc: "Existing account with all information", - account: &Account{ - Email: "foo@foo.net", - KeyType: acme.EC256, - }, - expectedAccount: &Account{ - Email: "foo@foo.net", - KeyType: acme.EC256, - }, - }, - { - desc: "Account nil", - email: "foo@foo.net", - keyType: "EC256", - expectedAccount: &Account{ - Email: "foo@foo.net", - KeyType: acme.EC256, - }, - }, - { - desc: "Existing account with no email", - account: &Account{ - KeyType: acme.RSA4096, - }, - email: "foo@foo.net", - keyType: "EC256", - expectedAccount: &Account{ - Email: "foo@foo.net", - KeyType: acme.EC256, - }, - }, - { - desc: "Existing account with no key type", - account: &Account{ - Email: "foo@foo.net", - }, - email: "bar@foo.net", - keyType: "EC256", - expectedAccount: &Account{ - Email: "foo@foo.net", - KeyType: acme.EC256, - }, - }, - { - desc: "Existing account and provider with no key type", - account: &Account{ - Email: "foo@foo.net", - }, - email: "bar@foo.net", - expectedAccount: &Account{ - Email: "foo@foo.net", - KeyType: acme.RSA4096, - }, - }, - } - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - acmeProvider := Provider{account: test.account, Configuration: &Configuration{Email: test.email, KeyType: test.keyType}} - - actualAccount, err := acmeProvider.initAccount() - assert.Nil(t, err, "Init account in error") - assert.Equal(t, test.expectedAccount.Email, actualAccount.Email, "unexpected email account") - assert.Equal(t, test.expectedAccount.KeyType, actualAccount.KeyType, "unexpected keyType account") - }) - } -} diff --git a/old/tls/certificate.go b/old/tls/certificate.go new file mode 100644 index 000000000..47b2a10eb --- /dev/null +++ b/old/tls/certificate.go @@ -0,0 +1,244 @@ +package tls + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "os" + "sort" + "strings" + + "github.com/containous/traefik/log" + "github.com/containous/traefik/tls/generate" +) + +var ( + // MinVersion Map of allowed TLS minimum versions + MinVersion = map[string]uint16{ + `VersionTLS10`: tls.VersionTLS10, + `VersionTLS11`: tls.VersionTLS11, + `VersionTLS12`: tls.VersionTLS12, + } + + // CipherSuites Map of TLS CipherSuites from crypto/tls + // Available CipherSuites defined at https://golang.org/pkg/crypto/tls/#pkg-constants + CipherSuites = map[string]uint16{ + `TLS_RSA_WITH_RC4_128_SHA`: tls.TLS_RSA_WITH_RC4_128_SHA, + `TLS_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + `TLS_RSA_WITH_AES_128_CBC_SHA`: tls.TLS_RSA_WITH_AES_128_CBC_SHA, + `TLS_RSA_WITH_AES_256_CBC_SHA`: tls.TLS_RSA_WITH_AES_256_CBC_SHA, + `TLS_RSA_WITH_AES_128_CBC_SHA256`: tls.TLS_RSA_WITH_AES_128_CBC_SHA256, + `TLS_RSA_WITH_AES_128_GCM_SHA256`: tls.TLS_RSA_WITH_AES_128_GCM_SHA256, + `TLS_RSA_WITH_AES_256_GCM_SHA384`: tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + `TLS_ECDHE_ECDSA_WITH_RC4_128_SHA`: tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + `TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + `TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA`: tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + `TLS_ECDHE_RSA_WITH_RC4_128_SHA`: tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, + `TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + `TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + `TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256`: tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + `TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + `TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + `TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305`: tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + `TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305`: tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + } +) + +// Certificate holds a SSL cert/key pair +// Certs and Key could be either a file path, or the file content itself +type Certificate struct { + CertFile FileOrContent + KeyFile FileOrContent +} + +// Certificates defines traefik certificates type +// Certs and Keys could be either a file path, or the file content itself +type Certificates []Certificate + +// FileOrContent hold a file path or content +type FileOrContent string + +func (f FileOrContent) String() string { + return string(f) +} + +// IsPath returns true if the FileOrContent is a file path, otherwise returns false +func (f FileOrContent) IsPath() bool { + _, err := os.Stat(f.String()) + return err == nil +} + +func (f FileOrContent) Read() ([]byte, error) { + var content []byte + if _, err := os.Stat(f.String()); err == nil { + content, err = ioutil.ReadFile(f.String()) + if err != nil { + return nil, err + } + } else { + content = []byte(f) + } + return content, nil +} + +// CreateTLSConfig creates a TLS config from Certificate structures +func (c *Certificates) CreateTLSConfig(entryPointName string) (*tls.Config, error) { + config := &tls.Config{} + domainsCertificates := make(map[string]map[string]*tls.Certificate) + + if c.isEmpty() { + config.Certificates = []tls.Certificate{} + + cert, err := generate.DefaultCertificate() + if err != nil { + return nil, err + } + + config.Certificates = append(config.Certificates, *cert) + } else { + for _, certificate := range *c { + err := certificate.AppendCertificates(domainsCertificates, entryPointName) + if err != nil { + log.Errorf("Unable to add a certificate to the entryPoint %q : %v", entryPointName, err) + continue + } + + for _, certDom := range domainsCertificates { + for _, cert := range certDom { + config.Certificates = append(config.Certificates, *cert) + } + } + } + } + return config, nil +} + +// isEmpty checks if the certificates list is empty +func (c *Certificates) isEmpty() bool { + if len(*c) == 0 { + return true + } + var key int + for _, cert := range *c { + if len(cert.CertFile.String()) != 0 && len(cert.KeyFile.String()) != 0 { + break + } + key++ + } + return key == len(*c) +} + +// AppendCertificates appends a Certificate to a certificates map sorted by entrypoints +func (c *Certificate) AppendCertificates(certs map[string]map[string]*tls.Certificate, ep string) error { + + certContent, err := c.CertFile.Read() + if err != nil { + return fmt.Errorf("unable to read CertFile : %v", err) + } + + keyContent, err := c.KeyFile.Read() + if err != nil { + return fmt.Errorf("unable to read KeyFile : %v", err) + } + tlsCert, err := tls.X509KeyPair(certContent, keyContent) + if err != nil { + return fmt.Errorf("unable to generate TLS certificate : %v", err) + } + + parsedCert, _ := x509.ParseCertificate(tlsCert.Certificate[0]) + + var SANs []string + if parsedCert.Subject.CommonName != "" { + SANs = append(SANs, parsedCert.Subject.CommonName) + } + if parsedCert.DNSNames != nil { + sort.Strings(parsedCert.DNSNames) + for _, dnsName := range parsedCert.DNSNames { + if dnsName != parsedCert.Subject.CommonName { + SANs = append(SANs, dnsName) + } + } + + } + if parsedCert.IPAddresses != nil { + for _, ip := range parsedCert.IPAddresses { + if ip.String() != parsedCert.Subject.CommonName { + SANs = append(SANs, ip.String()) + } + } + + } + certKey := strings.Join(SANs, ",") + + certExists := false + if certs[ep] == nil { + certs[ep] = make(map[string]*tls.Certificate) + } else { + for domains := range certs[ep] { + if domains == certKey { + certExists = true + break + } + } + } + if certExists { + log.Warnf("Into EntryPoint %s, try to add certificate for domains which already have this certificate (%s). The new certificate will not be append to the EntryPoint.", ep, certKey) + } else { + log.Debugf("Add certificate for domains %s", certKey) + certs[ep][certKey] = &tlsCert + } + + return err +} + +func (c *Certificate) getTruncatedCertificateName() string { + certName := c.CertFile.String() + + // Truncate certificate information only if it's a well formed certificate content with more than 50 characters + if !c.CertFile.IsPath() && strings.HasPrefix(certName, certificateHeader) && len(certName) > len(certificateHeader)+50 { + certName = strings.TrimPrefix(c.CertFile.String(), certificateHeader)[:50] + } + + return certName +} + +// String is the method to format the flag's value, part of the flag.Value interface. +// The String method's output will be used in diagnostics. +func (c *Certificates) String() string { + if len(*c) == 0 { + return "" + } + var result []string + for _, certificate := range *c { + result = append(result, certificate.CertFile.String()+","+certificate.KeyFile.String()) + } + return strings.Join(result, ";") +} + +// Set is the method to set the flag value, part of the flag.Value interface. +// Set's argument is a string to be parsed to set the flag. +// It's a comma-separated list, so we split it. +func (c *Certificates) Set(value string) error { + certificates := strings.Split(value, ";") + for _, certificate := range certificates { + files := strings.Split(certificate, ",") + if len(files) != 2 { + return fmt.Errorf("bad certificates format: %s", value) + } + *c = append(*c, Certificate{ + CertFile: FileOrContent(files[0]), + KeyFile: FileOrContent(files[1]), + }) + } + return nil +} + +// Type is type of the struct +func (c *Certificates) Type() string { + return "certificates" +} diff --git a/old/tls/certificate_store.go b/old/tls/certificate_store.go new file mode 100644 index 000000000..6ddb9407c --- /dev/null +++ b/old/tls/certificate_store.go @@ -0,0 +1,137 @@ +package tls + +import ( + "crypto/tls" + "net" + "sort" + "strings" + "time" + + "github.com/containous/traefik/log" + "github.com/containous/traefik/safe" + "github.com/patrickmn/go-cache" +) + +// CertificateStore store for dynamic and static certificates +type CertificateStore struct { + DynamicCerts *safe.Safe + StaticCerts *safe.Safe + DefaultCertificate *tls.Certificate + CertCache *cache.Cache + SniStrict bool +} + +// NewCertificateStore create a store for dynamic and static certificates +func NewCertificateStore() *CertificateStore { + return &CertificateStore{ + StaticCerts: &safe.Safe{}, + DynamicCerts: &safe.Safe{}, + CertCache: cache.New(1*time.Hour, 10*time.Minute), + } +} + +// GetAllDomains return a slice with all the certificate domain +func (c CertificateStore) GetAllDomains() []string { + var allCerts []string + + // Get static certificates + if c.StaticCerts != nil && c.StaticCerts.Get() != nil { + for domains := range c.StaticCerts.Get().(map[string]*tls.Certificate) { + allCerts = append(allCerts, domains) + } + } + + // 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) + } + } + return allCerts +} + +// GetBestCertificate returns the best match certificate, and caches the response +func (c CertificateStore) GetBestCertificate(clientHello *tls.ClientHelloInfo) *tls.Certificate { + domainToCheck := strings.ToLower(strings.TrimSpace(clientHello.ServerName)) + if len(domainToCheck) == 0 { + // If no ServerName is provided, Check for local IP address matches + host, _, err := net.SplitHostPort(clientHello.Conn.LocalAddr().String()) + if err != nil { + log.Debugf("Could not split host/port: %v", err) + } + domainToCheck = strings.TrimSpace(host) + } + + if cert, ok := c.CertCache.Get(domainToCheck); ok { + return cert.(*tls.Certificate) + } + + matchedCerts := map[string]*tls.Certificate{} + if c.DynamicCerts != nil && c.DynamicCerts.Get() != nil { + for domains, cert := range c.DynamicCerts.Get().(map[string]*tls.Certificate) { + for _, certDomain := range strings.Split(domains, ",") { + if MatchDomain(domainToCheck, certDomain) { + matchedCerts[certDomain] = cert + } + } + } + } + + if c.StaticCerts != nil && c.StaticCerts.Get() != nil { + for domains, cert := range c.StaticCerts.Get().(map[string]*tls.Certificate) { + for _, certDomain := range strings.Split(domains, ",") { + if MatchDomain(domainToCheck, certDomain) { + matchedCerts[certDomain] = cert + } + } + } + } + + if len(matchedCerts) > 0 { + // sort map by keys + keys := make([]string, 0, len(matchedCerts)) + for k := range matchedCerts { + keys = append(keys, k) + } + sort.Strings(keys) + + // cache best match + c.CertCache.SetDefault(domainToCheck, matchedCerts[keys[len(keys)-1]]) + return matchedCerts[keys[len(keys)-1]] + } + + return nil +} + +// ContainsCertificates checks if there are any certs in the store +func (c CertificateStore) ContainsCertificates() bool { + return c.StaticCerts.Get() != nil || c.DynamicCerts.Get() != nil +} + +// ResetCache clears the cache in the store +func (c CertificateStore) ResetCache() { + if c.CertCache != nil { + c.CertCache.Flush() + } +} + +// MatchDomain return true if a domain match the cert domain +func MatchDomain(domain string, certDomain string) bool { + if domain == certDomain { + return true + } + + for len(certDomain) > 0 && certDomain[len(certDomain)-1] == '.' { + certDomain = certDomain[:len(certDomain)-1] + } + + labels := strings.Split(domain, ".") + for i := range labels { + labels[i] = "*" + candidate := strings.Join(labels, ".") + if certDomain == candidate { + return true + } + } + return false +} diff --git a/old/tls/generate/generate.go b/old/tls/generate/generate.go new file mode 100644 index 000000000..91d73a731 --- /dev/null +++ b/old/tls/generate/generate.go @@ -0,0 +1,94 @@ +package generate + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/hex" + "encoding/pem" + "fmt" + "math/big" + "time" +) + +// DefaultDomain Traefik domain for the default certificate +const DefaultDomain = "TRAEFIK DEFAULT CERT" + +// DefaultCertificate generates random TLS certificates +func DefaultCertificate() (*tls.Certificate, error) { + randomBytes := make([]byte, 100) + _, err := rand.Read(randomBytes) + if err != nil { + return nil, err + } + zBytes := sha256.Sum256(randomBytes) + z := hex.EncodeToString(zBytes[:sha256.Size]) + domain := fmt.Sprintf("%s.%s.traefik.default", z[:32], z[32:]) + + certPEM, keyPEM, err := KeyPair(domain, time.Time{}) + if err != nil { + return nil, err + } + + certificate, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + return nil, err + } + + return &certificate, nil +} + +// KeyPair generates cert and key files +func KeyPair(domain string, expiration time.Time) ([]byte, []byte, error) { + rsaPrivKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, err + } + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaPrivKey)}) + + certPEM, err := PemCert(rsaPrivKey, domain, expiration) + if err != nil { + return nil, nil, err + } + return certPEM, keyPEM, nil +} + +// PemCert generates PEM cert file +func PemCert(privKey *rsa.PrivateKey, domain string, expiration time.Time) ([]byte, error) { + derBytes, err := derCert(privKey, expiration, domain) + if err != nil { + return nil, err + } + + return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil +} + +func derCert(privKey *rsa.PrivateKey, expiration time.Time, domain string) ([]byte, error) { + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, err + } + + if expiration.IsZero() { + expiration = time.Now().Add(365 * (24 * time.Hour)) + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: DefaultDomain, + }, + NotBefore: time.Now(), + NotAfter: expiration, + + KeyUsage: x509.KeyUsageKeyEncipherment, + BasicConstraintsValid: true, + DNSNames: []string{domain}, + } + + return x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey) +} diff --git a/old/tls/tls.go b/old/tls/tls.go new file mode 100644 index 000000000..4a72fc9fc --- /dev/null +++ b/old/tls/tls.go @@ -0,0 +1,101 @@ +package tls + +import ( + "crypto/tls" + "fmt" + "strings" + + "github.com/containous/traefik/log" + "github.com/sirupsen/logrus" +) + +const ( + certificateHeader = "-----BEGIN CERTIFICATE-----\n" +) + +// ClientCA defines traefik CA files for a entryPoint +// and it indicates if they are mandatory or have just to be analyzed if provided +type ClientCA struct { + Files FilesOrContents + Optional bool +} + +// TLS configures TLS for an entry point +type TLS struct { + MinVersion string `export:"true"` + CipherSuites []string + Certificates Certificates + ClientCA ClientCA + DefaultCertificate *Certificate + SniStrict bool `export:"true"` +} + +// FilesOrContents hold the CA we want to have in root +type FilesOrContents []FileOrContent + +// Configuration allows mapping a TLS certificate to a list of entrypoints +type Configuration struct { + EntryPoints []string + Certificate *Certificate +} + +// String is the method to format the flag's value, part of the flag.Value interface. +// The String method's output will be used in diagnostics. +func (r *FilesOrContents) String() string { + sliceOfString := make([]string, len([]FileOrContent(*r))) + for key, value := range *r { + sliceOfString[key] = value.String() + } + return strings.Join(sliceOfString, ",") +} + +// Set is the method to set the flag value, part of the flag.Value interface. +// Set's argument is a string to be parsed to set the flag. +// It's a comma-separated list, so we split it. +func (r *FilesOrContents) Set(value string) error { + filesOrContents := strings.Split(value, ",") + if len(filesOrContents) == 0 { + return fmt.Errorf("bad FilesOrContents format: %s", value) + } + for _, fileOrContent := range filesOrContents { + *r = append(*r, FileOrContent(fileOrContent)) + } + return nil +} + +// Get return the FilesOrContents list +func (r *FilesOrContents) Get() interface{} { + return *r +} + +// SetValue sets the FilesOrContents with val +func (r *FilesOrContents) SetValue(val interface{}) { + *r = val.(FilesOrContents) +} + +// Type is type of the struct +func (r *FilesOrContents) Type() string { + return "filesorcontents" +} + +// SortTLSPerEntryPoints converts TLS configuration sorted by Certificates into TLS configuration sorted by EntryPoints +func SortTLSPerEntryPoints(configurations []*Configuration, epConfiguration map[string]map[string]*tls.Certificate, defaultEntryPoints []string) { + if epConfiguration == nil { + epConfiguration = make(map[string]map[string]*tls.Certificate) + } + for _, conf := range configurations { + if conf.EntryPoints == nil || len(conf.EntryPoints) == 0 { + if log.GetLevel() >= logrus.DebugLevel { + log.Debugf("No entryPoint is defined to add the certificate %s, it will be added to the default entryPoints: %s", + conf.Certificate.getTruncatedCertificateName(), + strings.Join(defaultEntryPoints, ", ")) + } + conf.EntryPoints = append(conf.EntryPoints, defaultEntryPoints...) + } + for _, ep := range conf.EntryPoints { + if err := conf.Certificate.AppendCertificates(epConfiguration, ep); err != nil { + log.Errorf("Unable to append certificate %s to entrypoint %s: %v", conf.Certificate.getTruncatedCertificateName(), ep, err) + } + } + } +} diff --git a/provider/acme/provider.go b/provider/acme/provider.go index 8f39938e3..0d673cd9c 100644 --- a/provider/acme/provider.go +++ b/provider/acme/provider.go @@ -124,7 +124,7 @@ func (p *Provider) ListenRequest(domain string) (*tls.Certificate, error) { } // Init for compatibility reason the BaseProvider implements an empty Init -func (p *Provider) Init(_ types.Constraints) error { +func (p *Provider) Init() error { ctx := log.With(context.Background(), log.Str(log.ProviderName, "acme")) logger := log.FromContext(ctx) diff --git a/provider/acme/provider_test.go b/provider/acme/provider_test.go index f77e90f58..f792cfb48 100644 --- a/provider/acme/provider_test.go +++ b/provider/acme/provider_test.go @@ -25,10 +25,10 @@ func TestGetUncheckedCertificates(t *testing.T) { domainSafe := &safe.Safe{} domainSafe.Set(domainMap) + // FIXME Add a test for DefaultCertificate testCases := []struct { desc string dynamicCerts *safe.Safe - staticCerts *safe.Safe resolvingDomains map[string]struct{} acmeCertificates []*Certificate domains []string @@ -45,12 +45,6 @@ func TestGetUncheckedCertificates(t *testing.T) { dynamicCerts: wildcardSafe, expectedDomains: nil, }, - { - desc: "wildcard already exists in static certificates", - domains: []string{"*.traefik.wtf"}, - staticCerts: wildcardSafe, - expectedDomains: nil, - }, { desc: "wildcard already exists in ACME certificates", domains: []string{"*.traefik.wtf"}, @@ -72,12 +66,6 @@ func TestGetUncheckedCertificates(t *testing.T) { dynamicCerts: domainSafe, expectedDomains: []string{"foo.traefik.wtf"}, }, - { - desc: "domain CN already exists in static certificates and SANs to generate", - domains: []string{"traefik.wtf", "foo.traefik.wtf"}, - staticCerts: domainSafe, - expectedDomains: []string{"foo.traefik.wtf"}, - }, { desc: "domain CN already exists in ACME certificates and SANs to generate", domains: []string{"traefik.wtf", "foo.traefik.wtf"}, @@ -94,12 +82,6 @@ func TestGetUncheckedCertificates(t *testing.T) { dynamicCerts: domainSafe, expectedDomains: nil, }, - { - desc: "domain already exists in static certificates", - domains: []string{"traefik.wtf"}, - staticCerts: domainSafe, - expectedDomains: nil, - }, { desc: "domain already exists in ACME certificates", domains: []string{"traefik.wtf"}, @@ -116,12 +98,6 @@ func TestGetUncheckedCertificates(t *testing.T) { dynamicCerts: wildcardSafe, expectedDomains: nil, }, - { - desc: "domain matched by wildcard in static certificates", - domains: []string{"who.traefik.wtf", "foo.traefik.wtf"}, - staticCerts: wildcardSafe, - expectedDomains: nil, - }, { desc: "domain matched by wildcard in ACME certificates", domains: []string{"who.traefik.wtf", "foo.traefik.wtf"}, @@ -190,7 +166,6 @@ func TestGetUncheckedCertificates(t *testing.T) { acmeProvider := Provider{ certificateStore: &traefiktls.CertificateStore{ DynamicCerts: test.dynamicCerts, - StaticCerts: test.staticCerts, }, certificates: test.acmeCertificates, resolvingDomains: test.resolvingDomains, diff --git a/provider/aggregator/aggregator.go b/provider/aggregator/aggregator.go index f03400a2d..fa0933e0a 100644 --- a/provider/aggregator/aggregator.go +++ b/provider/aggregator/aggregator.go @@ -8,20 +8,16 @@ import ( "github.com/containous/traefik/log" "github.com/containous/traefik/provider" "github.com/containous/traefik/safe" - "github.com/containous/traefik/types" ) // ProviderAggregator aggregates providers. type ProviderAggregator struct { - providers []provider.Provider - constraints types.Constraints + providers []provider.Provider } // NewProviderAggregator returns an aggregate of all the providers configured in the static configuration. -func NewProviderAggregator(conf static.Configuration) ProviderAggregator { - p := ProviderAggregator{ - constraints: conf.Constraints, - } +func NewProviderAggregator(conf static.Providers) ProviderAggregator { + p := ProviderAggregator{} if conf.File != nil { p.quietAddProvider(conf.File) @@ -39,7 +35,7 @@ func (p *ProviderAggregator) quietAddProvider(provider provider.Provider) { // AddProvider adds a provider in the providers map. func (p *ProviderAggregator) AddProvider(provider provider.Provider) error { - err := provider.Init(p.constraints) + err := provider.Init() if err != nil { return err } @@ -48,7 +44,7 @@ func (p *ProviderAggregator) AddProvider(provider provider.Provider) error { } // Init the provider -func (p ProviderAggregator) Init(_ types.Constraints) error { +func (p ProviderAggregator) Init() error { return nil } diff --git a/provider/base_provider.go b/provider/base_provider.go index e3580ebbb..30359ecaf 100644 --- a/provider/base_provider.go +++ b/provider/base_provider.go @@ -25,8 +25,7 @@ type BaseProvider struct { } // Init for compatibility reason the BaseProvider implements an empty Init. -func (p *BaseProvider) Init(constraints types.Constraints) error { - p.Constraints = append(p.Constraints, constraints...) +func (p *BaseProvider) Init() error { return nil } diff --git a/provider/file/file.go b/provider/file/file.go index ef38f70c0..1fccce96e 100644 --- a/provider/file/file.go +++ b/provider/file/file.go @@ -15,7 +15,6 @@ import ( "github.com/containous/traefik/provider" "github.com/containous/traefik/safe" "github.com/containous/traefik/tls" - "github.com/containous/traefik/types" "github.com/pkg/errors" "gopkg.in/fsnotify.v1" ) @@ -32,8 +31,8 @@ type Provider struct { } // Init the provider -func (p *Provider) Init(constraints types.Constraints) error { - return p.BaseProvider.Init(constraints) +func (p *Provider) Init() error { + return p.BaseProvider.Init() } // Provide allows the file provider to provide configurations to traefik diff --git a/provider/provider.go b/provider/provider.go index 1708e1a48..038f0843c 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -3,7 +3,6 @@ package provider import ( "github.com/containous/traefik/config" "github.com/containous/traefik/safe" - "github.com/containous/traefik/types" ) // Provider defines methods of a provider. @@ -11,5 +10,5 @@ type Provider interface { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. Provide(configurationChan chan<- config.Message, pool *safe.Pool) error - Init(constraints types.Constraints) error + Init() error } diff --git a/server/roundtripper.go b/server/roundtripper.go index 195d8d713..30f0b973a 100644 --- a/server/roundtripper.go +++ b/server/roundtripper.go @@ -7,9 +7,11 @@ import ( "net/http" "time" + "github.com/containous/traefik/config/static" "github.com/containous/traefik/log" "github.com/containous/traefik/old/configuration" traefiktls "github.com/containous/traefik/tls" + "github.com/pkg/errors" "golang.org/x/net/http2" ) @@ -22,26 +24,30 @@ func (t *h2cTransportWrapper) RoundTrip(req *http.Request) (*http.Response, erro return t.Transport.RoundTrip(req) } -// createHTTPTransport creates an http.Transport configured with the GlobalConfiguration settings. +// createHTTPTransport creates an http.Transport 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 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 createHTTPTransport(globalConfiguration configuration.GlobalConfiguration) (*http.Transport, error) { +func createHTTPTransport(transportConfiguration *static.ServersTransport) (*http.Transport, error) { + if transportConfiguration == nil { + return nil, errors.New("no transport configuration given") + } + dialer := &net.Dialer{ Timeout: configuration.DefaultDialTimeout, KeepAlive: 30 * time.Second, DualStack: true, } - if globalConfiguration.ForwardingTimeouts != nil { - dialer.Timeout = time.Duration(globalConfiguration.ForwardingTimeouts.DialTimeout) + if transportConfiguration.ForwardingTimeouts != nil { + dialer.Timeout = time.Duration(transportConfiguration.ForwardingTimeouts.DialTimeout) } transport := &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: dialer.DialContext, - MaxIdleConnsPerHost: globalConfiguration.MaxIdleConnsPerHost, + MaxIdleConnsPerHost: transportConfiguration.MaxIdleConnsPerHost, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, @@ -56,17 +62,17 @@ func createHTTPTransport(globalConfiguration configuration.GlobalConfiguration) }, }) - if globalConfiguration.ForwardingTimeouts != nil { - transport.ResponseHeaderTimeout = time.Duration(globalConfiguration.ForwardingTimeouts.ResponseHeaderTimeout) + if transportConfiguration.ForwardingTimeouts != nil { + transport.ResponseHeaderTimeout = time.Duration(transportConfiguration.ForwardingTimeouts.ResponseHeaderTimeout) } - if globalConfiguration.InsecureSkipVerify { + if transportConfiguration.InsecureSkipVerify { transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} } - if len(globalConfiguration.RootCAs) > 0 { + if len(transportConfiguration.RootCAs) > 0 { transport.TLSClientConfig = &tls.Config{ - RootCAs: createRootCACertPool(globalConfiguration.RootCAs), + RootCAs: createRootCACertPool(transportConfiguration.RootCAs), } } diff --git a/server/router/route_appender_aggregator.go b/server/router/route_appender_aggregator.go index 3eebf4064..d9600be22 100644 --- a/server/router/route_appender_aggregator.go +++ b/server/router/route_appender_aggregator.go @@ -38,6 +38,7 @@ func NewRouteAppenderAggregator(ctx context.Context, chainBuilder chainBuilder, Statistics: conf.API.Statistics, DashboardAssets: conf.API.DashboardAssets, CurrentConfigurations: currentConfiguration, + Debug: conf.Global.Debug, }, routerMiddlewares: chain, }) diff --git a/server/router/route_appender_aggregator_test.go b/server/router/route_appender_aggregator_test.go index f35a131d2..65ea00f38 100644 --- a/server/router/route_appender_aggregator_test.go +++ b/server/router/route_appender_aggregator_test.go @@ -39,6 +39,7 @@ func TestNewRouteAppenderAggregator(t *testing.T) { { desc: "API with auth, ping without auth", staticConf: static.Configuration{ + Global: &static.Global{}, API: &static.API{ EntryPoint: "traefik", Middlewares: []string{"dumb"}, @@ -46,10 +47,8 @@ func TestNewRouteAppenderAggregator(t *testing.T) { Ping: &ping.Handler{ EntryPoint: "traefik", }, - EntryPoints: &static.EntryPoints{ - EntryPointList: map[string]static.EntryPoint{ - "traefik": {}, - }, + EntryPoints: static.EntryPoints{ + "traefik": {}, }, }, middles: map[string]alice.Constructor{ @@ -69,13 +68,12 @@ func TestNewRouteAppenderAggregator(t *testing.T) { { desc: "Wrong entrypoint name", staticConf: static.Configuration{ + Global: &static.Global{}, API: &static.API{ EntryPoint: "no", }, - EntryPoints: &static.EntryPoints{ - EntryPointList: map[string]static.EntryPoint{ - "traefik": {}, - }, + EntryPoints: static.EntryPoints{ + "traefik": {}, }, }, expected: map[string]int{ diff --git a/server/router/router.go b/server/router/router.go index 0c707efe9..2fd958d9f 100644 --- a/server/router/router.go +++ b/server/router/router.go @@ -44,8 +44,8 @@ type Manager struct { } // BuildHandlers Builds handler for all entry points -func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, defaultEntryPoints []string) map[string]http.Handler { - entryPointsRouters := m.filteredRouters(rootCtx, entryPoints, defaultEntryPoints) +func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) map[string]http.Handler { + entryPointsRouters := m.filteredRouters(rootCtx, entryPoints) entryPointHandlers := make(map[string]http.Handler) for entryPointName, routers := range entryPointsRouters { @@ -73,13 +73,13 @@ func contains(entryPoints []string, entryPointName string) bool { return false } -func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string, defaultEntryPoints []string) map[string]map[string]*config.Router { +func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string) map[string]map[string]*config.Router { entryPointsRouters := make(map[string]map[string]*config.Router) for rtName, rt := range m.configs { eps := rt.EntryPoints if len(eps) == 0 { - eps = defaultEntryPoints + eps = entryPoints } for _, entryPointName := range eps { if !contains(entryPoints, entryPointName) { diff --git a/server/router/router_test.go b/server/router/router_test.go index 71d6f8a14..b3fba1505 100644 --- a/server/router/router_test.go +++ b/server/router/router_test.go @@ -27,13 +27,12 @@ func TestRouterManager_Get(t *testing.T) { } testCases := []struct { - desc string - routersConfig map[string]*config.Router - serviceConfig map[string]*config.Service - middlewaresConfig map[string]*config.Middleware - entryPoints []string - defaultEntryPoints []string - expected ExpectedResult + desc string + routersConfig map[string]*config.Router + serviceConfig map[string]*config.Service + middlewaresConfig map[string]*config.Middleware + entryPoints []string + expected ExpectedResult }{ { desc: "no middleware", @@ -81,9 +80,8 @@ func TestRouterManager_Get(t *testing.T) { }, }, }, - entryPoints: []string{"web"}, - defaultEntryPoints: []string{"web"}, - expected: ExpectedResult{StatusCode: http.StatusOK}, + entryPoints: []string{"web"}, + expected: ExpectedResult{StatusCode: http.StatusOK}, }, { desc: "no middleware, no matching", @@ -209,7 +207,7 @@ func TestRouterManager_Get(t *testing.T) { routerManager := NewManager(test.routersConfig, serviceManager, middlewaresBuilder, responseModifierFactory) - handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, test.defaultEntryPoints) + handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints) w := httptest.NewRecorder() req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) @@ -309,7 +307,7 @@ func TestAccessLog(t *testing.T) { routerManager := NewManager(test.routersConfig, serviceManager, middlewaresBuilder, responseModifierFactory) - handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, test.defaultEntryPoints) + handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints) w := httptest.NewRecorder() req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) diff --git a/server/server.go b/server/server.go index ac11e4e16..0387de569 100644 --- a/server/server.go +++ b/server/server.go @@ -2,116 +2,49 @@ package server import ( "context" - "crypto/tls" - "crypto/x509" "encoding/json" - "fmt" - stdlog "log" - "net" "net/http" "os" "os/signal" "sync" "time" - "github.com/armon/go-proxyproto" "github.com/containous/traefik/cluster" "github.com/containous/traefik/config" "github.com/containous/traefik/config/static" - "github.com/containous/traefik/h2c" - "github.com/containous/traefik/ip" "github.com/containous/traefik/log" "github.com/containous/traefik/metrics" - "github.com/containous/traefik/middlewares" "github.com/containous/traefik/middlewares/accesslog" "github.com/containous/traefik/middlewares/requestdecorator" - "github.com/containous/traefik/old/configuration" "github.com/containous/traefik/provider" "github.com/containous/traefik/safe" "github.com/containous/traefik/server/middleware" - traefiktls "github.com/containous/traefik/tls" "github.com/containous/traefik/tracing" "github.com/containous/traefik/tracing/datadog" "github.com/containous/traefik/tracing/jaeger" "github.com/containous/traefik/tracing/zipkin" "github.com/containous/traefik/types" - "github.com/sirupsen/logrus" - "github.com/xenolf/lego/acme" ) -func newHijackConnectionTracker() *hijackConnectionTracker { - return &hijackConnectionTracker{ - conns: make(map[net.Conn]struct{}), - } -} - -type hijackConnectionTracker struct { - conns map[net.Conn]struct{} - lock sync.RWMutex -} - -// AddHijackedConnection add a connection in the tracked connections list -func (h *hijackConnectionTracker) AddHijackedConnection(conn net.Conn) { - h.lock.Lock() - defer h.lock.Unlock() - h.conns[conn] = struct{}{} -} - -// RemoveHijackedConnection remove a connection from the tracked connections list -func (h *hijackConnectionTracker) RemoveHijackedConnection(conn net.Conn) { - h.lock.Lock() - defer h.lock.Unlock() - delete(h.conns, conn) -} - -// Shutdown wait for the connection closing -func (h *hijackConnectionTracker) Shutdown(ctx context.Context) error { - ticker := time.NewTicker(500 * time.Millisecond) - defer ticker.Stop() - for { - h.lock.RLock() - if len(h.conns) == 0 { - return nil - } - h.lock.RUnlock() - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - } - } -} - -// Close close all the connections in the tracked connections list -func (h *hijackConnectionTracker) Close() { - for conn := range h.conns { - if err := conn.Close(); err != nil { - log.WithoutContext().Errorf("Error while closing Hijacked connection: %v", err) - } - delete(h.conns, conn) - } -} - // Server is the reverse-proxy/load-balancer engine type Server struct { - serverEntryPoints serverEntryPoints + entryPoints EntryPoints configurationChan chan config.Message configurationValidatedChan chan config.Message signals chan os.Signal stopChan chan bool currentConfigurations safe.Safe providerConfigUpdateMap map[string]chan config.Message - globalConfiguration configuration.GlobalConfiguration accessLoggerMiddleware *accesslog.Handler tracer *tracing.Tracing routinesPool *safe.Pool - leadership *cluster.Leadership + leadership *cluster.Leadership //FIXME Cluster defaultRoundTripper http.RoundTripper metricsRegistry metrics.Registry provider provider.Provider configurationListeners []func(config.Configuration) - entryPoints map[string]EntryPoint requestDecorator *requestdecorator.RequestDecorator + providersThrottleDuration time.Duration } // RouteAppenderFactory the route appender factory interface @@ -119,86 +52,6 @@ type RouteAppenderFactory interface { NewAppender(ctx context.Context, middlewaresBuilder *middleware.Builder, currentConfigurations *safe.Safe) types.RouteAppender } -// EntryPoint entryPoint information (configuration + internalRouter) -type EntryPoint struct { - RouteAppenderFactory RouteAppenderFactory - Configuration *configuration.EntryPoint - OnDemandListener func(string) (*tls.Certificate, error) - TLSALPNGetter func(string) (*tls.Certificate, error) - CertificateStore *traefiktls.CertificateStore -} - -type serverEntryPoints map[string]*serverEntryPoint - -type serverEntryPoint struct { - httpServer *h2c.Server - listener net.Listener - httpRouter *middlewares.HandlerSwitcher - certs *traefiktls.CertificateStore - onDemandListener func(string) (*tls.Certificate, error) - tlsALPNGetter func(string) (*tls.Certificate, error) - hijackConnectionTracker *hijackConnectionTracker -} - -func (s serverEntryPoint) Shutdown(ctx context.Context) { - var wg sync.WaitGroup - if s.httpServer != nil { - wg.Add(1) - go func() { - defer wg.Done() - if err := s.httpServer.Shutdown(ctx); err != nil { - if ctx.Err() == context.DeadlineExceeded { - logger := log.FromContext(ctx) - logger.Debugf("Wait server shutdown is over due to: %s", err) - err = s.httpServer.Close() - if err != nil { - logger.Error(err) - } - } - } - }() - } - - if s.hijackConnectionTracker != nil { - wg.Add(1) - go func() { - defer wg.Done() - if err := s.hijackConnectionTracker.Shutdown(ctx); err != nil { - if ctx.Err() == context.DeadlineExceeded { - logger := log.FromContext(ctx) - logger.Debugf("Wait hijack connection is over due to: %s", err) - s.hijackConnectionTracker.Close() - } - } - }() - } - - wg.Wait() -} - -// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted -// connections. -type tcpKeepAliveListener struct { - *net.TCPListener -} - -func (ln tcpKeepAliveListener) Accept() (net.Conn, error) { - tc, err := ln.AcceptTCP() - if err != nil { - return nil, err - } - - if err = tc.SetKeepAlive(true); err != nil { - return nil, err - } - - if err = tc.SetKeepAlivePeriod(3 * time.Minute); err != nil { - return nil, err - } - - return tc, nil -} - func setupTracing(conf *static.Tracing) tracing.TrackingBackend { switch conf.Backend { case jaeger.Name: @@ -214,13 +67,11 @@ func setupTracing(conf *static.Tracing) tracing.TrackingBackend { } // NewServer returns an initialized Server. -func NewServer(globalConfiguration configuration.GlobalConfiguration, provider provider.Provider, entrypoints map[string]EntryPoint) *Server { +func NewServer(staticConfiguration static.Configuration, provider provider.Provider, entryPoints EntryPoints) *Server { server := &Server{} - server.entryPoints = entrypoints server.provider = provider - server.globalConfiguration = globalConfiguration - server.serverEntryPoints = make(map[string]*serverEntryPoint) + server.entryPoints = entryPoints server.configurationChan = make(chan config.Message, 100) server.configurationValidatedChan = make(chan config.Message, 100) server.signals = make(chan os.Signal, 1) @@ -230,41 +81,36 @@ func NewServer(globalConfiguration configuration.GlobalConfiguration, provider p server.currentConfigurations.Set(currentConfigurations) server.providerConfigUpdateMap = make(map[string]chan config.Message) - transport, err := createHTTPTransport(globalConfiguration) + if staticConfiguration.Providers != nil { + server.providersThrottleDuration = time.Duration(staticConfiguration.Providers.ProvidersThrottleDuration) + } + + transport, err := createHTTPTransport(staticConfiguration.ServersTransport) if err != nil { - log.WithoutContext().Error(err) + log.WithoutContext().Errorf("Could not configure HTTP Transport, fallbacking on default transport: %v", err) server.defaultRoundTripper = http.DefaultTransport } else { server.defaultRoundTripper = transport } - if server.globalConfiguration.API != nil { - server.globalConfiguration.API.CurrentConfigurations = &server.currentConfigurations - } - server.routinesPool = safe.NewPool(context.Background()) - if globalConfiguration.Tracing != nil { - trackingBackend := setupTracing(static.ConvertTracing(globalConfiguration.Tracing)) + if staticConfiguration.Tracing != nil { + trackingBackend := setupTracing(staticConfiguration.Tracing) var err error - server.tracer, err = tracing.NewTracing(globalConfiguration.Tracing.ServiceName, globalConfiguration.Tracing.SpanNameLimit, trackingBackend) + server.tracer, err = tracing.NewTracing(staticConfiguration.Tracing.ServiceName, staticConfiguration.Tracing.SpanNameLimit, trackingBackend) if err != nil { log.WithoutContext().Warnf("Unable to create tracer: %v", err) } } - server.requestDecorator = requestdecorator.New(static.ConvertHostResolverConfig(globalConfiguration.HostResolver)) + server.requestDecorator = requestdecorator.New(staticConfiguration.HostResolver) - server.metricsRegistry = registerMetricClients(static.ConvertMetrics(globalConfiguration.Metrics)) + server.metricsRegistry = registerMetricClients(staticConfiguration.Metrics) - if globalConfiguration.Cluster != nil { - // leadership creation if cluster mode - server.leadership = cluster.NewLeadership(server.routinesPool.Ctx(), globalConfiguration.Cluster) - } - - if globalConfiguration.AccessLog != nil { + if staticConfiguration.AccessLog != nil { var err error - server.accessLoggerMiddleware, err = accesslog.NewHandler(static.ConvertAccessLog(globalConfiguration.AccessLog)) + server.accessLoggerMiddleware, err = accesslog.NewHandler(staticConfiguration.AccessLog) if err != nil { log.WithoutContext().Warnf("Unable to create access logger : %v", err) } @@ -272,8 +118,17 @@ func NewServer(globalConfiguration configuration.GlobalConfiguration, provider p return server } -// Start starts the server. -func (s *Server) Start() { +// Start starts the server and Stop/Close it when context is Done +func (s *Server) Start(ctx context.Context) { + go func() { + defer s.Close() + <-ctx.Done() + logger := log.FromContext(ctx) + logger.Info("I have to go...") + logger.Info("Stopping server gracefully") + s.Stop() + }() + s.startHTTPServers() s.startLeadership() s.routinesPool.Go(func(stop chan bool) { @@ -288,26 +143,6 @@ func (s *Server) Start() { }) } -// StartWithContext starts the server and Stop/Close it when context is Done -func (s *Server) StartWithContext(ctx context.Context) { - go func() { - defer s.Close() - <-ctx.Done() - logger := log.FromContext(ctx) - logger.Info("I have to go...") - - reqAcceptGraceTimeOut := time.Duration(s.globalConfiguration.LifeCycle.RequestAcceptGraceTimeout) - if reqAcceptGraceTimeOut > 0 { - logger.Infof("Waiting %s for incoming requests to cease", reqAcceptGraceTimeOut) - time.Sleep(reqAcceptGraceTimeOut) - } - - logger.Info("Stopping server gracefully") - s.Stop() - }() - s.Start() -} - // Wait blocks until server is shutted down. func (s *Server) Wait() { <-s.stopChan @@ -318,21 +153,16 @@ func (s *Server) Stop() { defer log.WithoutContext().Info("Server stopped") var wg sync.WaitGroup - for sepn, sep := range s.serverEntryPoints { + for epn, ep := range s.entryPoints { wg.Add(1) - go func(serverEntryPointName string, serverEntryPoint *serverEntryPoint) { + go func(entryPointName string, entryPoint *EntryPoint) { + ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName)) defer wg.Done() - logger := log.WithoutContext().WithField(log.EntryPointName, serverEntryPointName) - graceTimeOut := time.Duration(s.globalConfiguration.LifeCycle.GraceTimeOut) - ctx, cancel := context.WithTimeout(context.Background(), graceTimeOut) - logger.Debugf("Waiting %s seconds before killing connections on entrypoint %s...", graceTimeOut, serverEntryPointName) + entryPoint.Shutdown(ctx) - serverEntryPoint.Shutdown(ctx) - cancel() - - logger.Debugf("Entry point %s closed", serverEntryPointName) - }(sepn, sep) + log.FromContext(ctx).Debugf("Entry point %s closed", entryPointName) + }(epn, ep) } wg.Wait() s.stopChan <- true @@ -385,12 +215,15 @@ func (s *Server) stopLeadership() { } func (s *Server) startHTTPServers() { - s.serverEntryPoints = s.buildServerEntryPoints() + // Use an empty configuration in order to initialize the default handlers with internal routes + handlers := s.applyConfiguration(context.Background(), config.Configuration{}) + for entryPointName, handler := range handlers { + s.entryPoints[entryPointName].httpRouter.UpdateHandler(handler) + } - for newServerEntryPointName, newServerEntryPoint := range s.serverEntryPoints { - ctx := log.With(context.Background(), log.Str(log.EntryPointName, newServerEntryPointName)) - serverEntryPoint := s.setupServerEntryPoint(ctx, newServerEntryPointName, newServerEntryPoint) - go s.startServer(ctx, serverEntryPoint) + for entryPointName, entryPoint := range s.entryPoints { + ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName)) + go entryPoint.Start(ctx) } } @@ -416,39 +249,6 @@ func (s *Server) AddListener(listener func(config.Configuration)) { s.configurationListeners = append(s.configurationListeners, listener) } -// getCertificate allows to customize tlsConfig.GetCertificate behavior to get the certificates inserted dynamically -func (s *serverEntryPoint) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { - domainToCheck := types.CanonicalDomain(clientHello.ServerName) - - if s.tlsALPNGetter != nil { - cert, err := s.tlsALPNGetter(domainToCheck) - if err != nil { - return nil, err - } - - if cert != nil { - return cert, nil - } - } - - bestCertificate := s.certs.GetBestCertificate(clientHello) - if bestCertificate != nil { - return bestCertificate, nil - } - - if s.onDemandListener != nil && len(domainToCheck) > 0 { - // Only check for an onDemandCert if there is a domain name - return s.onDemandListener(domainToCheck) - } - - if s.certs.SniStrict { - return nil, fmt.Errorf("strict SNI enabled - No certificate found for domain: %q, closing connection", domainToCheck) - } - - log.WithoutContext().Debugf("Serving default certificate for request: %q", domainToCheck) - return s.certs.DefaultCertificate, nil -} - func (s *Server) startProvider() { jsonConf, err := json.Marshal(s.provider) if err != nil { @@ -466,229 +266,6 @@ func (s *Server) startProvider() { }) } -// creates a TLS config that allows terminating HTTPS for multiple domains using SNI -func (s *Server) createTLSConfig(entryPointName string, tlsOption *traefiktls.TLS, router *middlewares.HandlerSwitcher) (*tls.Config, error) { - if tlsOption == nil { - return nil, nil - } - - conf, err := tlsOption.Certificates.CreateTLSConfig(entryPointName) - if err != nil { - return nil, err - } - - s.serverEntryPoints[entryPointName].certs.DynamicCerts.Set(make(map[string]*tls.Certificate)) - - // ensure http2 enabled - conf.NextProtos = []string{"h2", "http/1.1", acme.ACMETLS1Protocol} - - if len(tlsOption.ClientCA.Files) > 0 { - pool := x509.NewCertPool() - for _, caFile := range tlsOption.ClientCA.Files { - data, err := caFile.Read() - if err != nil { - return nil, err - } - ok := pool.AppendCertsFromPEM(data) - if !ok { - return nil, fmt.Errorf("invalid certificate(s) in %s", caFile) - } - } - conf.ClientCAs = pool - if tlsOption.ClientCA.Optional { - conf.ClientAuth = tls.VerifyClientCertIfGiven - } else { - conf.ClientAuth = tls.RequireAndVerifyClientCert - } - } - - // FIXME onDemand - if s.globalConfiguration.ACME != nil { - // if entryPointName == s.globalConfiguration.ACME.EntryPoint { - // checkOnDemandDomain := func(domain string) bool { - // routeMatch := &mux.RouteMatch{} - // match := router.GetHandler().Match(&http.Request{URL: &url.URL{}, Host: domain}, routeMatch) - // if match && routeMatch.Route != nil { - // return true - // } - // return false - // } - // - // err := s.globalConfiguration.ACME.CreateClusterConfig(s.leadership, config, s.serverEntryPoints[entryPointName].certs.DynamicCerts, checkOnDemandDomain) - // if err != nil { - // return nil, err - // } - // } - } else { - conf.GetCertificate = s.serverEntryPoints[entryPointName].getCertificate - } - - if len(conf.Certificates) != 0 { - certMap := s.buildNameOrIPToCertificate(conf.Certificates) - - if s.entryPoints[entryPointName].CertificateStore != nil { - s.entryPoints[entryPointName].CertificateStore.StaticCerts.Set(certMap) - } - } - - // Remove certs from the TLS config object - conf.Certificates = []tls.Certificate{} - - // Set the minimum TLS version if set in the config TOML - if minConst, exists := traefiktls.MinVersion[tlsOption.MinVersion]; exists { - conf.PreferServerCipherSuites = true - conf.MinVersion = minConst - } - - // Set the list of CipherSuites if set in the config TOML - if tlsOption.CipherSuites != nil { - // if our list of CipherSuites is defined in the entryPoint config, we can re-initialize the suites list as empty - conf.CipherSuites = make([]uint16, 0) - for _, cipher := range tlsOption.CipherSuites { - if cipherConst, exists := traefiktls.CipherSuites[cipher]; exists { - conf.CipherSuites = append(conf.CipherSuites, cipherConst) - } else { - // CipherSuite listed in the toml does not exist in our listed - return nil, fmt.Errorf("invalid CipherSuite: %s", cipher) - } - } - } - - return conf, nil -} - -func (s *Server) startServer(ctx context.Context, serverEntryPoint *serverEntryPoint) { - logger := log.FromContext(ctx) - logger.Infof("Starting server on %s", serverEntryPoint.httpServer.Addr) - - var err error - if serverEntryPoint.httpServer.TLSConfig != nil { - err = serverEntryPoint.httpServer.ServeTLS(serverEntryPoint.listener, "", "") - } else { - err = serverEntryPoint.httpServer.Serve(serverEntryPoint.listener) - } - - if err != http.ErrServerClosed { - logger.Error("Cannot create server: %v", err) - } -} - -func (s *Server) setupServerEntryPoint(ctx context.Context, newServerEntryPointName string, newServerEntryPoint *serverEntryPoint) *serverEntryPoint { - newSrv, listener, err := s.prepareServer(ctx, newServerEntryPointName, s.entryPoints[newServerEntryPointName].Configuration, newServerEntryPoint.httpRouter) - if err != nil { - log.FromContext(ctx).Fatalf("Error preparing server: %v", err) - } - - serverEntryPoint := s.serverEntryPoints[newServerEntryPointName] - serverEntryPoint.httpServer = newSrv - serverEntryPoint.listener = listener - - serverEntryPoint.hijackConnectionTracker = newHijackConnectionTracker() - serverEntryPoint.httpServer.ConnState = func(conn net.Conn, state http.ConnState) { - switch state { - case http.StateHijacked: - serverEntryPoint.hijackConnectionTracker.AddHijackedConnection(conn) - case http.StateClosed: - serverEntryPoint.hijackConnectionTracker.RemoveHijackedConnection(conn) - } - } - - return serverEntryPoint -} - -func (s *Server) prepareServer(ctx context.Context, entryPointName string, entryPoint *configuration.EntryPoint, router *middlewares.HandlerSwitcher) (*h2c.Server, net.Listener, error) { - logger := log.FromContext(ctx) - - readTimeout, writeTimeout, idleTimeout := buildServerTimeouts(s.globalConfiguration) - logger. - WithField("readTimeout", readTimeout). - WithField("writeTimeout", writeTimeout). - WithField("idleTimeout", idleTimeout). - Infof("Preparing server %+v", entryPoint) - - tlsConfig, err := s.createTLSConfig(entryPointName, entryPoint.TLS, router) - if err != nil { - return nil, nil, fmt.Errorf("error creating TLS config: %v", err) - } - - listener, err := net.Listen("tcp", entryPoint.Address) - if err != nil { - return nil, nil, fmt.Errorf("error opening listener: %v", err) - } - - listener = tcpKeepAliveListener{listener.(*net.TCPListener)} - - if entryPoint.ProxyProtocol != nil { - listener, err = buildProxyProtocolListener(ctx, entryPoint, listener) - if err != nil { - return nil, nil, fmt.Errorf("error creating proxy protocol listener: %v", err) - } - } - - httpServerLogger := stdlog.New(logger.WriterLevel(logrus.DebugLevel), "", 0) - - return &h2c.Server{ - Server: &http.Server{ - Addr: entryPoint.Address, - Handler: router, - TLSConfig: tlsConfig, - ReadTimeout: readTimeout, - WriteTimeout: writeTimeout, - IdleTimeout: idleTimeout, - ErrorLog: httpServerLogger, - }, - }, - listener, - nil -} - -func buildProxyProtocolListener(ctx context.Context, entryPoint *configuration.EntryPoint, listener net.Listener) (net.Listener, error) { - var sourceCheck func(addr net.Addr) (bool, error) - if entryPoint.ProxyProtocol.Insecure { - sourceCheck = func(_ net.Addr) (bool, error) { - return true, nil - } - } else { - checker, err := ip.NewChecker(entryPoint.ProxyProtocol.TrustedIPs) - if err != nil { - return nil, err - } - - sourceCheck = func(addr net.Addr) (bool, error) { - ipAddr, ok := addr.(*net.TCPAddr) - if !ok { - return false, fmt.Errorf("type error %v", addr) - } - - return checker.ContainsIP(ipAddr.IP), nil - } - } - - log.FromContext(ctx).Infof("Enabling ProxyProtocol for trusted IPs %v", entryPoint.ProxyProtocol.TrustedIPs) - - return &proxyproto.Listener{ - Listener: listener, - SourceCheck: sourceCheck, - }, nil -} - -func buildServerTimeouts(globalConfig configuration.GlobalConfiguration) (readTimeout, writeTimeout, idleTimeout time.Duration) { - readTimeout = time.Duration(0) - writeTimeout = time.Duration(0) - if globalConfig.RespondingTimeouts != nil { - readTimeout = time.Duration(globalConfig.RespondingTimeouts.ReadTimeout) - writeTimeout = time.Duration(globalConfig.RespondingTimeouts.WriteTimeout) - } - - if globalConfig.RespondingTimeouts != nil { - idleTimeout = time.Duration(globalConfig.RespondingTimeouts.IdleTimeout) - } else { - idleTimeout = configuration.DefaultIdleTimeout - } - - return readTimeout, writeTimeout, idleTimeout -} - func registerMetricClients(metricsConfig *types.Metrics) metrics.Registry { if metricsConfig == nil { return metrics.NewVoidRegistry() @@ -734,24 +311,3 @@ func stopMetricsClients() { metrics.StopStatsd() metrics.StopInfluxDB() } - -func (s *Server) buildNameOrIPToCertificate(certs []tls.Certificate) map[string]*tls.Certificate { - certMap := make(map[string]*tls.Certificate) - for i := range certs { - cert := &certs[i] - x509Cert, err := x509.ParseCertificate(cert.Certificate[0]) - if err != nil { - continue - } - if len(x509Cert.Subject.CommonName) > 0 { - certMap[x509Cert.Subject.CommonName] = cert - } - for _, san := range x509Cert.DNSNames { - certMap[san] = cert - } - for _, ipSan := range x509Cert.IPAddresses { - certMap[ipSan.String()] = cert - } - } - return certMap -} diff --git a/server/server_configuration.go b/server/server_configuration.go index 1ded2da1a..d1492bba8 100644 --- a/server/server_configuration.go +++ b/server/server_configuration.go @@ -12,19 +12,15 @@ import ( "github.com/containous/alice" "github.com/containous/mux" "github.com/containous/traefik/config" - "github.com/containous/traefik/config/static" "github.com/containous/traefik/log" - "github.com/containous/traefik/middlewares" "github.com/containous/traefik/middlewares/accesslog" "github.com/containous/traefik/middlewares/requestdecorator" "github.com/containous/traefik/middlewares/tracing" - "github.com/containous/traefik/old/configuration" "github.com/containous/traefik/responsemodifiers" "github.com/containous/traefik/server/middleware" "github.com/containous/traefik/server/router" "github.com/containous/traefik/server/service" traefiktls "github.com/containous/traefik/tls" - "github.com/containous/traefik/tls/generate" "github.com/eapache/channels" "github.com/sirupsen/logrus" ) @@ -44,25 +40,25 @@ func (s *Server) loadConfiguration(configMsg config.Message) { s.metricsRegistry.ConfigReloadsCounter().Add(1) - handlers, certificates := s.loadConfig(newConfigurations, s.globalConfiguration) + handlers, certificates := s.loadConfig(newConfigurations) s.metricsRegistry.LastConfigReloadSuccessGauge().Set(float64(time.Now().Unix())) for entryPointName, handler := range handlers { - s.serverEntryPoints[entryPointName].httpRouter.UpdateHandler(handler) + s.entryPoints[entryPointName].httpRouter.UpdateHandler(handler) } - for entryPointName, serverEntryPoint := range s.serverEntryPoints { + for entryPointName, entryPoint := range s.entryPoints { eLogger := logger.WithField(log.EntryPointName, entryPointName) - if s.entryPoints[entryPointName].Configuration.TLS == nil { + if entryPoint.Certs == nil { if len(certificates[entryPointName]) > 0 { eLogger.Debugf("Cannot configure certificates for the non-TLS %s entryPoint.", entryPointName) } } else { - serverEntryPoint.certs.DynamicCerts.Set(certificates[entryPointName]) - serverEntryPoint.certs.ResetCache() + entryPoint.Certs.DynamicCerts.Set(certificates[entryPointName]) + entryPoint.Certs.ResetCache() } - eLogger.Infof("Server configuration reloaded on %s", s.serverEntryPoints[entryPointName].httpServer.Addr) + eLogger.Infof("Server configuration reloaded on %s", s.entryPoints[entryPointName].httpServer.Addr) } s.currentConfigurations.Set(newConfigurations) @@ -76,7 +72,7 @@ func (s *Server) loadConfiguration(configMsg config.Message) { // loadConfig returns a new gorilla.mux Route from the specified global configuration and the dynamic // provider configurations. -func (s *Server) loadConfig(configurations config.Configurations, globalConfiguration configuration.GlobalConfiguration) (map[string]http.Handler, map[string]map[string]*tls.Certificate) { +func (s *Server) loadConfig(configurations config.Configurations) (map[string]http.Handler, map[string]map[string]*tls.Certificate) { ctx := context.TODO() @@ -106,14 +102,12 @@ func (s *Server) loadConfig(configurations config.Configurations, globalConfigur // Get new certificates list sorted per entry points // Update certificates - entryPointsCertificates := s.loadHTTPSConfiguration(configurations, globalConfiguration.DefaultEntryPoints) + entryPointsCertificates := s.loadHTTPSConfiguration(configurations) return handlers, entryPointsCertificates } func (s *Server) applyConfiguration(ctx context.Context, configuration config.Configuration) map[string]http.Handler { - staticConfiguration := static.ConvertStaticConf(s.globalConfiguration) - var entryPoints []string for entryPointName := range s.entryPoints { entryPoints = append(entryPoints, entryPointName) @@ -125,7 +119,7 @@ func (s *Server) applyConfiguration(ctx context.Context, configuration config.Co routerManager := router.NewManager(configuration.Routers, serviceManager, middlewaresBuilder, responseModifierFactory) - handlers := routerManager.BuildHandlers(ctx, entryPoints, staticConfiguration.EntryPoints.Defaults) + handlers := routerManager.BuildHandlers(ctx, entryPoints) routerHandlers := make(map[string]http.Handler) @@ -145,7 +139,7 @@ func (s *Server) applyConfiguration(ctx context.Context, configuration config.Co if h, ok := handlers[entryPointName]; ok { internalMuxRouter.NotFoundHandler = h } else { - internalMuxRouter.NotFoundHandler = s.buildDefaultHTTPRouter() + internalMuxRouter.NotFoundHandler = buildDefaultHTTPRouter() } routerHandlers[entryPointName] = internalMuxRouter @@ -174,7 +168,6 @@ func (s *Server) applyConfiguration(ctx context.Context, configuration config.Co } func (s *Server) preLoadConfiguration(configMsg config.Message) { - providersThrottleDuration := time.Duration(s.globalConfiguration.ProvidersThrottleDuration) s.defaultConfigurationValues(configMsg.Configuration) currentConfigurations := s.currentConfigurations.Get().(config.Configurations) @@ -199,7 +192,7 @@ func (s *Server) preLoadConfiguration(configMsg config.Message) { providerConfigUpdateCh = make(chan config.Message) s.providerConfigUpdateMap[configMsg.ProviderName] = providerConfigUpdateCh s.routinesPool.Go(func(stop chan bool) { - s.throttleProviderConfigReload(providersThrottleDuration, s.configurationValidatedChan, providerConfigUpdateCh, stop) + s.throttleProviderConfigReload(s.providersThrottleDuration, s.configurationValidatedChan, providerConfigUpdateCh, stop) }) } @@ -263,12 +256,12 @@ func (s *Server) postLoadConfiguration() { // metrics.OnConfigurationUpdate(activeConfig) // } - if s.globalConfiguration.ACME == nil || s.leadership == nil || !s.leadership.IsLeader() { - return - } - // FIXME acme - // if s.globalConfiguration.ACME.OnHostRule { + // if s.staticConfiguration.ACME == nil || s.leadership == nil || !s.leadership.IsLeader() { + // return + // } + // + // if s.staticConfiguration.ACME.OnHostRule { // currentConfigurations := s.currentConfigurations.Get().(config.Configurations) // for _, config := range currentConfigurations { // for _, frontend := range config.Frontends { @@ -277,7 +270,7 @@ func (s *Server) postLoadConfiguration() { // // and is configured with ACME // acmeEnabled := false // for _, entryPoint := range frontend.EntryPoints { - // if s.globalConfiguration.ACME.EntryPoint == entryPoint && s.entryPoints[entryPoint].Configuration.TLS != nil { + // if s.staticConfiguration.ACME.EntryPoint == entryPoint && s.entryPoints[entryPoint].Configuration.TLS != nil { // acmeEnabled = true // break // } @@ -292,7 +285,7 @@ func (s *Server) postLoadConfiguration() { // } else if len(domains) == 0 { // log.Debugf("No domain parsed in rule %q", route.Rule) // } else { - // s.globalConfiguration.ACME.LoadCertificateForDomains(domains) + // s.staticConfiguration.ACME.LoadCertificateForDomains(domains) // } // } // } @@ -302,69 +295,23 @@ func (s *Server) postLoadConfiguration() { } // loadHTTPSConfiguration add/delete HTTPS certificate managed dynamically -func (s *Server) loadHTTPSConfiguration(configurations config.Configurations, defaultEntryPoints configuration.DefaultEntryPoints) map[string]map[string]*tls.Certificate { +func (s *Server) loadHTTPSConfiguration(configurations config.Configurations) map[string]map[string]*tls.Certificate { + var entryPoints []string + for entryPointName := range s.entryPoints { + entryPoints = append(entryPoints, entryPointName) + } + newEPCertificates := make(map[string]map[string]*tls.Certificate) // Get all certificates for _, config := range configurations { if config.TLS != nil && len(config.TLS) > 0 { - traefiktls.SortTLSPerEntryPoints(config.TLS, newEPCertificates, defaultEntryPoints) + traefiktls.SortTLSPerEntryPoints(config.TLS, newEPCertificates, entryPoints) } } return newEPCertificates } -func (s *Server) buildServerEntryPoints() map[string]*serverEntryPoint { - serverEntryPoints := make(map[string]*serverEntryPoint) - - ctx := context.Background() - - handlers := s.applyConfiguration(ctx, config.Configuration{}) - - for entryPointName, entryPoint := range s.entryPoints { - serverEntryPoints[entryPointName] = &serverEntryPoint{ - httpRouter: middlewares.NewHandlerSwitcher(handlers[entryPointName]), - onDemandListener: entryPoint.OnDemandListener, - tlsALPNGetter: entryPoint.TLSALPNGetter, - } - - if entryPoint.CertificateStore != nil { - serverEntryPoints[entryPointName].certs = entryPoint.CertificateStore - } else { - serverEntryPoints[entryPointName].certs = traefiktls.NewCertificateStore() - } - - if entryPoint.Configuration.TLS != nil { - logger := log.FromContext(ctx).WithField(log.EntryPointName, entryPointName) - - serverEntryPoints[entryPointName].certs.SniStrict = entryPoint.Configuration.TLS.SniStrict - - if entryPoint.Configuration.TLS.DefaultCertificate != nil { - cert, err := buildDefaultCertificate(entryPoint.Configuration.TLS.DefaultCertificate) - if err != nil { - logger.Error(err) - continue - } - serverEntryPoints[entryPointName].certs.DefaultCertificate = cert - } else { - cert, err := generate.DefaultCertificate() - if err != nil { - logger.Error(err) - continue - } - serverEntryPoints[entryPointName].certs.DefaultCertificate = cert - } - if len(entryPoint.Configuration.TLS.Certificates) > 0 { - config, _ := entryPoint.Configuration.TLS.Certificates.CreateTLSConfig(entryPointName) - certMap := s.buildNameOrIPToCertificate(config.Certificates) - serverEntryPoints[entryPointName].certs.StaticCerts.Set(certMap) - - } - } - } - return serverEntryPoints -} - -func (s *Server) buildDefaultHTTPRouter() *mux.Router { +func buildDefaultHTTPRouter() *mux.Router { rt := mux.NewRouter() rt.NotFoundHandler = http.HandlerFunc(http.NotFound) rt.SkipClean(true) diff --git a/server/server_configuration_test.go b/server/server_configuration_test.go index e44c3b337..03bea85ff 100644 --- a/server/server_configuration_test.go +++ b/server/server_configuration_test.go @@ -7,7 +7,7 @@ import ( "time" "github.com/containous/traefik/config" - "github.com/containous/traefik/old/configuration" + "github.com/containous/traefik/config/static" th "github.com/containous/traefik/testhelpers" "github.com/containous/traefik/tls" "github.com/stretchr/testify/assert" @@ -51,14 +51,8 @@ f9Oeos0UUothgiDktdQHxdNEwLjQf7lJJBzV+5OtwswCWA== -----END RSA PRIVATE KEY-----`) ) -func TestServerLoadCertificateWithDefaultEntryPoint(t *testing.T) { - globalConfig := configuration.GlobalConfiguration{ - DefaultEntryPoints: []string{"http", "https"}, - } - entryPoints := map[string]EntryPoint{ - "https": {Configuration: &configuration.EntryPoint{TLS: &tls.TLS{}}}, - "http": {Configuration: &configuration.EntryPoint{}}, - } +func TestServerLoadCertificateWithTLSEntryPoints(t *testing.T) { + staticConfig := static.Configuration{} dynamicConfigs := config.Configurations{ "config": &config.Configuration{ @@ -73,9 +67,16 @@ func TestServerLoadCertificateWithDefaultEntryPoint(t *testing.T) { }, } - srv := NewServer(globalConfig, nil, entryPoints) - _, mapsCerts := srv.loadConfig(dynamicConfigs, globalConfig) - if len(mapsCerts["https"]) == 0 { + srv := NewServer(staticConfig, nil, EntryPoints{ + "https": &EntryPoint{ + Certs: tls.NewCertificateStore(), + }, + "https2": &EntryPoint{ + Certs: tls.NewCertificateStore(), + }, + }) + _, mapsCerts := srv.loadConfig(dynamicConfigs) + if len(mapsCerts["https"]) == 0 || len(mapsCerts["https2"]) == 0 { t.Fatal("got error: https entryPoint must have TLS certificates.") } } @@ -86,15 +87,11 @@ func TestReuseService(t *testing.T) { })) defer testServer.Close() - entryPoints := map[string]EntryPoint{ - "http": {Configuration: &configuration.EntryPoint{ - ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}, - }}, + entryPoints := EntryPoints{ + "http": &EntryPoint{}, } - globalConfig := configuration.GlobalConfiguration{ - DefaultEntryPoints: []string{"http"}, - } + staticConfig := static.Configuration{} dynamicConfigs := config.Configurations{ "config": th.BuildConfiguration( @@ -118,14 +115,14 @@ func TestReuseService(t *testing.T) { ), } - srv := NewServer(globalConfig, nil, entryPoints) + srv := NewServer(staticConfig, nil, entryPoints) - serverEntryPoints, _ := srv.loadConfig(dynamicConfigs, globalConfig) + entrypointsHandlers, _ := srv.loadConfig(dynamicConfigs) // Test that the /ok path returns a status 200. responseRecorderOk := &httptest.ResponseRecorder{} requestOk := httptest.NewRequest(http.MethodGet, testServer.URL+"/ok", nil) - serverEntryPoints["http"].ServeHTTP(responseRecorderOk, requestOk) + entrypointsHandlers["http"].ServeHTTP(responseRecorderOk, requestOk) assert.Equal(t, http.StatusOK, responseRecorderOk.Result().StatusCode, "status code") @@ -133,7 +130,7 @@ func TestReuseService(t *testing.T) { // the basic authentication defined on the frontend. responseRecorderUnauthorized := &httptest.ResponseRecorder{} requestUnauthorized := httptest.NewRequest(http.MethodGet, testServer.URL+"/unauthorized", nil) - serverEntryPoints["http"].ServeHTTP(responseRecorderUnauthorized, requestUnauthorized) + entrypointsHandlers["http"].ServeHTTP(responseRecorderUnauthorized, requestUnauthorized) assert.Equal(t, http.StatusUnauthorized, responseRecorderUnauthorized.Result().StatusCode, "status code") } @@ -147,8 +144,8 @@ func TestThrottleProviderConfigReload(t *testing.T) { stop <- true }() - globalConfig := configuration.GlobalConfiguration{} - server := NewServer(globalConfig, nil, nil) + staticConfiguration := static.Configuration{} + server := NewServer(staticConfiguration, nil, nil) go server.throttleProviderConfigReload(throttleDuration, publishConfig, providerConfig, stop) diff --git a/server/server_entrypoint.go b/server/server_entrypoint.go new file mode 100644 index 000000000..849b58129 --- /dev/null +++ b/server/server_entrypoint.go @@ -0,0 +1,426 @@ +package server + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + stdlog "log" + "net" + "net/http" + "sync" + "time" + + "github.com/armon/go-proxyproto" + "github.com/containous/traefik/config/static" + "github.com/containous/traefik/h2c" + "github.com/containous/traefik/ip" + "github.com/containous/traefik/log" + "github.com/containous/traefik/middlewares" + "github.com/containous/traefik/old/configuration" + traefiktls "github.com/containous/traefik/tls" + "github.com/containous/traefik/tls/generate" + "github.com/containous/traefik/types" + "github.com/sirupsen/logrus" + "github.com/xenolf/lego/acme" +) + +// EntryPoints map of EntryPoint +type EntryPoints map[string]*EntryPoint + +// NewEntryPoint creates a new EntryPoint +func NewEntryPoint(ctx context.Context, configuration *static.EntryPoint) (*EntryPoint, error) { + logger := log.FromContext(ctx) + var err error + + router := middlewares.NewHandlerSwitcher(buildDefaultHTTPRouter()) + tracker := newHijackConnectionTracker() + + listener, err := buildListener(ctx, configuration) + if err != nil { + logger.Fatalf("Error preparing server: %v", err) + } + + var tlsConfig *tls.Config + var certificateStore *traefiktls.CertificateStore + if configuration.TLS != nil { + certificateStore, err = buildCertificateStore(*configuration.TLS) + if err != nil { + return nil, fmt.Errorf("error creating certificate store: %v", err) + } + + tlsConfig, err = buildTLSConfig(*configuration.TLS) + if err != nil { + return nil, fmt.Errorf("error creating TLS config: %v", err) + } + } + + entryPoint := &EntryPoint{ + httpRouter: router, + transportConfiguration: configuration.Transport, + hijackConnectionTracker: tracker, + listener: listener, + httpServer: buildServer(ctx, configuration, tlsConfig, router, tracker), + Certs: certificateStore, + } + + if tlsConfig != nil { + tlsConfig.GetCertificate = entryPoint.getCertificate + } + + return entryPoint, nil +} + +// EntryPoint holds everything about the entry point (httpServer, listener etc...) +type EntryPoint struct { + RouteAppenderFactory RouteAppenderFactory + httpServer *h2c.Server + listener net.Listener + httpRouter *middlewares.HandlerSwitcher + Certs *traefiktls.CertificateStore + OnDemandListener func(string) (*tls.Certificate, error) + TLSALPNGetter func(string) (*tls.Certificate, error) + hijackConnectionTracker *hijackConnectionTracker + transportConfiguration *static.EntryPointsTransport +} + +// Start starts listening for traffic +func (s *EntryPoint) Start(ctx context.Context) { + logger := log.FromContext(ctx) + logger.Infof("Starting server on %s", s.httpServer.Addr) + + var err error + if s.httpServer.TLSConfig != nil { + err = s.httpServer.ServeTLS(s.listener, "", "") + } else { + err = s.httpServer.Serve(s.listener) + } + + if err != http.ErrServerClosed { + logger.Error("Cannot start server: %v", err) + } +} + +// Shutdown handles the entrypoint shutdown process +func (s EntryPoint) Shutdown(ctx context.Context) { + logger := log.FromContext(ctx) + + reqAcceptGraceTimeOut := time.Duration(s.transportConfiguration.LifeCycle.RequestAcceptGraceTimeout) + if reqAcceptGraceTimeOut > 0 { + logger.Infof("Waiting %s for incoming requests to cease", reqAcceptGraceTimeOut) + time.Sleep(reqAcceptGraceTimeOut) + } + + graceTimeOut := time.Duration(s.transportConfiguration.LifeCycle.GraceTimeOut) + ctx, cancel := context.WithTimeout(ctx, graceTimeOut) + logger.Debugf("Waiting %s seconds before killing connections.", graceTimeOut) + + var wg sync.WaitGroup + if s.httpServer != nil { + wg.Add(1) + go func() { + defer wg.Done() + if err := s.httpServer.Shutdown(ctx); err != nil { + if ctx.Err() == context.DeadlineExceeded { + logger.Debugf("Wait server shutdown is overdue to: %s", err) + err = s.httpServer.Close() + if err != nil { + logger.Error(err) + } + } + } + }() + } + + if s.hijackConnectionTracker != nil { + wg.Add(1) + go func() { + defer wg.Done() + if err := s.hijackConnectionTracker.Shutdown(ctx); err != nil { + if ctx.Err() == context.DeadlineExceeded { + logger.Debugf("Wait hijack connection is overdue to: %s", err) + s.hijackConnectionTracker.Close() + } + } + }() + } + + wg.Wait() + cancel() +} + +// getCertificate allows to customize tlsConfig.GetCertificate behavior to get the certificates inserted dynamically +func (s *EntryPoint) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { + domainToCheck := types.CanonicalDomain(clientHello.ServerName) + + if s.TLSALPNGetter != nil { + cert, err := s.TLSALPNGetter(domainToCheck) + if err != nil { + return nil, err + } + + if cert != nil { + return cert, nil + } + } + + bestCertificate := s.Certs.GetBestCertificate(clientHello) + if bestCertificate != nil { + return bestCertificate, nil + } + + if s.OnDemandListener != nil && len(domainToCheck) > 0 { + // Only check for an onDemandCert if there is a domain name + return s.OnDemandListener(domainToCheck) + } + + if s.Certs.SniStrict { + return nil, fmt.Errorf("strict SNI enabled - No certificate found for domain: %q, closing connection", domainToCheck) + } + + log.WithoutContext().Debugf("Serving default certificate for request: %q", domainToCheck) + return s.Certs.DefaultCertificate, nil +} + +func newHijackConnectionTracker() *hijackConnectionTracker { + return &hijackConnectionTracker{ + conns: make(map[net.Conn]struct{}), + } +} + +type hijackConnectionTracker struct { + conns map[net.Conn]struct{} + lock sync.RWMutex +} + +// AddHijackedConnection add a connection in the tracked connections list +func (h *hijackConnectionTracker) AddHijackedConnection(conn net.Conn) { + h.lock.Lock() + defer h.lock.Unlock() + h.conns[conn] = struct{}{} +} + +// RemoveHijackedConnection remove a connection from the tracked connections list +func (h *hijackConnectionTracker) RemoveHijackedConnection(conn net.Conn) { + h.lock.Lock() + defer h.lock.Unlock() + delete(h.conns, conn) +} + +// Shutdown wait for the connection closing +func (h *hijackConnectionTracker) Shutdown(ctx context.Context) error { + ticker := time.NewTicker(500 * time.Millisecond) + defer ticker.Stop() + for { + h.lock.RLock() + if len(h.conns) == 0 { + return nil + } + h.lock.RUnlock() + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + } + } +} + +// Close close all the connections in the tracked connections list +func (h *hijackConnectionTracker) Close() { + for conn := range h.conns { + if err := conn.Close(); err != nil { + log.WithoutContext().Errorf("Error while closing Hijacked connection: %v", err) + } + delete(h.conns, conn) + } +} + +// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted +// connections. +type tcpKeepAliveListener struct { + *net.TCPListener +} + +func (ln tcpKeepAliveListener) Accept() (net.Conn, error) { + tc, err := ln.AcceptTCP() + if err != nil { + return nil, err + } + + if err = tc.SetKeepAlive(true); err != nil { + return nil, err + } + + if err = tc.SetKeepAlivePeriod(3 * time.Minute); err != nil { + return nil, err + } + + return tc, nil +} + +func buildProxyProtocolListener(ctx context.Context, entryPoint *static.EntryPoint, listener net.Listener) (net.Listener, error) { + var sourceCheck func(addr net.Addr) (bool, error) + if entryPoint.ProxyProtocol.Insecure { + sourceCheck = func(_ net.Addr) (bool, error) { + return true, nil + } + } else { + checker, err := ip.NewChecker(entryPoint.ProxyProtocol.TrustedIPs) + if err != nil { + return nil, err + } + + sourceCheck = func(addr net.Addr) (bool, error) { + ipAddr, ok := addr.(*net.TCPAddr) + if !ok { + return false, fmt.Errorf("type error %v", addr) + } + + return checker.ContainsIP(ipAddr.IP), nil + } + } + + log.FromContext(ctx).Infof("Enabling ProxyProtocol for trusted IPs %v", entryPoint.ProxyProtocol.TrustedIPs) + + return &proxyproto.Listener{ + Listener: listener, + SourceCheck: sourceCheck, + }, nil +} + +func buildServerTimeouts(entryPointsTransport static.EntryPointsTransport) (readTimeout, writeTimeout, idleTimeout time.Duration) { + readTimeout = time.Duration(0) + writeTimeout = time.Duration(0) + if entryPointsTransport.RespondingTimeouts != nil { + readTimeout = time.Duration(entryPointsTransport.RespondingTimeouts.ReadTimeout) + writeTimeout = time.Duration(entryPointsTransport.RespondingTimeouts.WriteTimeout) + } + + if entryPointsTransport.RespondingTimeouts != nil { + idleTimeout = time.Duration(entryPointsTransport.RespondingTimeouts.IdleTimeout) + } else { + idleTimeout = configuration.DefaultIdleTimeout + } + + return readTimeout, writeTimeout, idleTimeout +} + +func buildListener(ctx context.Context, entryPoint *static.EntryPoint) (net.Listener, error) { + listener, err := net.Listen("tcp", entryPoint.Address) + if err != nil { + return nil, fmt.Errorf("error opening listener: %v", err) + } + + listener = tcpKeepAliveListener{listener.(*net.TCPListener)} + + if entryPoint.ProxyProtocol != nil { + listener, err = buildProxyProtocolListener(ctx, entryPoint, listener) + if err != nil { + return nil, fmt.Errorf("error creating proxy protocol listener: %v", err) + } + } + return listener, nil +} + +func buildCertificateStore(tlsOption traefiktls.TLS) (*traefiktls.CertificateStore, error) { + certificateStore := traefiktls.NewCertificateStore() + certificateStore.DynamicCerts.Set(make(map[string]*tls.Certificate)) + + certificateStore.SniStrict = tlsOption.SniStrict + + if tlsOption.DefaultCertificate != nil { + cert, err := buildDefaultCertificate(tlsOption.DefaultCertificate) + if err != nil { + return nil, err + } + certificateStore.DefaultCertificate = cert + } else { + cert, err := generate.DefaultCertificate() + if err != nil { + return nil, err + } + certificateStore.DefaultCertificate = cert + } + return certificateStore, nil +} + +func buildServer(ctx context.Context, configuration *static.EntryPoint, tlsConfig *tls.Config, router http.Handler, tracker *hijackConnectionTracker) *h2c.Server { + logger := log.FromContext(ctx) + + readTimeout, writeTimeout, idleTimeout := buildServerTimeouts(*configuration.Transport) + logger. + WithField("readTimeout", readTimeout). + WithField("writeTimeout", writeTimeout). + WithField("idleTimeout", idleTimeout). + Infof("Preparing server") + + return &h2c.Server{ + Server: &http.Server{ + Addr: configuration.Address, + Handler: router, + TLSConfig: tlsConfig, + ReadTimeout: readTimeout, + WriteTimeout: writeTimeout, + IdleTimeout: idleTimeout, + ErrorLog: stdlog.New(logger.WriterLevel(logrus.DebugLevel), "", 0), + ConnState: func(conn net.Conn, state http.ConnState) { + switch state { + case http.StateHijacked: + tracker.AddHijackedConnection(conn) + case http.StateClosed: + tracker.RemoveHijackedConnection(conn) + } + }, + }, + } +} + +// creates a TLS config that allows terminating HTTPS for multiple domains using SNI +func buildTLSConfig(tlsOption traefiktls.TLS) (*tls.Config, error) { + conf := &tls.Config{} + + // ensure http2 enabled + conf.NextProtos = []string{"h2", "http/1.1", acme.ACMETLS1Protocol} + + if len(tlsOption.ClientCA.Files) > 0 { + pool := x509.NewCertPool() + for _, caFile := range tlsOption.ClientCA.Files { + data, err := caFile.Read() + if err != nil { + return nil, err + } + ok := pool.AppendCertsFromPEM(data) + if !ok { + return nil, fmt.Errorf("invalid certificate(s) in %s", caFile) + } + } + conf.ClientCAs = pool + if tlsOption.ClientCA.Optional { + conf.ClientAuth = tls.VerifyClientCertIfGiven + } else { + conf.ClientAuth = tls.RequireAndVerifyClientCert + } + } + + // Set the minimum TLS version if set in the config TOML + if minConst, exists := traefiktls.MinVersion[tlsOption.MinVersion]; exists { + conf.PreferServerCipherSuites = true + conf.MinVersion = minConst + } + + // Set the list of CipherSuites if set in the config TOML + if tlsOption.CipherSuites != nil { + // if our list of CipherSuites is defined in the entryPoint config, we can re-initialize the suites list as empty + conf.CipherSuites = make([]uint16, 0) + for _, cipher := range tlsOption.CipherSuites { + if cipherConst, exists := traefiktls.CipherSuites[cipher]; exists { + conf.CipherSuites = append(conf.CipherSuites, cipherConst) + } else { + // CipherSuite listed in the toml does not exist in our listed + return nil, fmt.Errorf("invalid CipherSuite: %s", cipher) + } + } + } + + return conf, nil +} diff --git a/server/server_test.go b/server/server_test.go index 811181833..fc8fedcf5 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -1,76 +1,18 @@ package server import ( - "context" "net/http" "net/http/httptest" "testing" "time" "github.com/containous/flaeg/parse" - "github.com/containous/mux" "github.com/containous/traefik/config" - "github.com/containous/traefik/middlewares" - "github.com/containous/traefik/old/configuration" + "github.com/containous/traefik/config/static" th "github.com/containous/traefik/testhelpers" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) -func TestPrepareServerTimeouts(t *testing.T) { - testCases := []struct { - desc string - globalConfig configuration.GlobalConfiguration - expectedIdleTimeout time.Duration - expectedReadTimeout time.Duration - expectedWriteTimeout time.Duration - }{ - { - desc: "full configuration", - globalConfig: configuration.GlobalConfiguration{ - RespondingTimeouts: &configuration.RespondingTimeouts{ - IdleTimeout: parse.Duration(10 * time.Second), - ReadTimeout: parse.Duration(12 * time.Second), - WriteTimeout: parse.Duration(14 * time.Second), - }, - }, - expectedIdleTimeout: 10 * time.Second, - expectedReadTimeout: 12 * time.Second, - expectedWriteTimeout: 14 * time.Second, - }, - { - desc: "using defaults", - globalConfig: configuration.GlobalConfiguration{}, - expectedIdleTimeout: 180 * time.Second, - expectedReadTimeout: 0 * time.Second, - expectedWriteTimeout: 0 * time.Second, - }, - } - - for _, test := range testCases { - test := test - - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - entryPointName := "http" - entryPoint := &configuration.EntryPoint{ - Address: "localhost:0", - ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}, - } - router := middlewares.NewHandlerSwitcher(mux.NewRouter()) - - srv := NewServer(test.globalConfig, nil, nil) - httpServer, _, err := srv.prepareServer(context.Background(), entryPointName, entryPoint, router) - require.NoError(t, err, "Unexpected error when preparing srv") - - assert.Equal(t, test.expectedIdleTimeout, httpServer.IdleTimeout, "IdleTimeout") - assert.Equal(t, test.expectedReadTimeout, httpServer.ReadTimeout, "ReadTimeout") - assert.Equal(t, test.expectedWriteTimeout, httpServer.WriteTimeout, "WriteTimeout") - }) - } -} - func TestListenProvidersSkipsEmptyConfigs(t *testing.T) { server, stop, invokeStopChan := setupListenProvider(10 * time.Millisecond) defer invokeStopChan() @@ -186,14 +128,13 @@ func setupListenProvider(throttleDuration time.Duration) (server *Server, stop c stop <- true } - globalConfig := configuration.GlobalConfiguration{ - EntryPoints: configuration.EntryPoints{ - "http": &configuration.EntryPoint{}, + staticConfiguration := static.Configuration{ + Providers: &static.Providers{ + ProvidersThrottleDuration: parse.Duration(throttleDuration), }, - ProvidersThrottleDuration: parse.Duration(throttleDuration), } - server = NewServer(globalConfig, nil, nil) + server = NewServer(staticConfiguration, nil, nil) go server.listenProviders(stop) return server, stop, invokeStopChan @@ -309,14 +250,14 @@ func TestServerResponseEmptyBackend(t *testing.T) { })) defer testServer.Close() - globalConfig := configuration.GlobalConfiguration{} - entryPointsConfig := map[string]EntryPoint{ - "http": {Configuration: &configuration.EntryPoint{ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}}}, + globalConfig := static.Configuration{} + entryPointsConfig := EntryPoints{ + "http": &EntryPoint{}, } dynamicConfigs := config.Configurations{"config": test.config(testServer.URL)} srv := NewServer(globalConfig, nil, entryPointsConfig) - entryPoints, _ := srv.loadConfig(dynamicConfigs, globalConfig) + entryPoints, _ := srv.loadConfig(dynamicConfigs) responseRecorder := &httptest.ResponseRecorder{} request := httptest.NewRequest(http.MethodGet, testServer.URL+requestPath, nil) diff --git a/tls/certificate_store.go b/tls/certificate_store.go index 6ddb9407c..997ca9107 100644 --- a/tls/certificate_store.go +++ b/tls/certificate_store.go @@ -2,6 +2,7 @@ package tls import ( "crypto/tls" + "crypto/x509" "net" "sort" "strings" @@ -15,7 +16,6 @@ import ( // CertificateStore store for dynamic and static certificates type CertificateStore struct { DynamicCerts *safe.Safe - StaticCerts *safe.Safe DefaultCertificate *tls.Certificate CertCache *cache.Cache SniStrict bool @@ -24,23 +24,42 @@ type CertificateStore struct { // NewCertificateStore create a store for dynamic and static certificates func NewCertificateStore() *CertificateStore { return &CertificateStore{ - StaticCerts: &safe.Safe{}, DynamicCerts: &safe.Safe{}, CertCache: cache.New(1*time.Hour, 10*time.Minute), } } -// GetAllDomains return a slice with all the certificate domain -func (c CertificateStore) GetAllDomains() []string { +func (c CertificateStore) getDefaultCertificateDomains() []string { var allCerts []string - // Get static certificates - if c.StaticCerts != nil && c.StaticCerts.Get() != nil { - for domains := range c.StaticCerts.Get().(map[string]*tls.Certificate) { - allCerts = append(allCerts, domains) - } + if c.DefaultCertificate == nil { + return allCerts } + x509Cert, err := x509.ParseCertificate(c.DefaultCertificate.Certificate[0]) + if err != nil { + log.WithoutContext().Errorf("Could not parse default certicate: %v", err) + return allCerts + } + + if len(x509Cert.Subject.CommonName) > 0 { + allCerts = append(allCerts, x509Cert.Subject.CommonName) + } + + allCerts = append(allCerts, x509Cert.DNSNames...) + + for _, ipSan := range x509Cert.IPAddresses { + allCerts = append(allCerts, ipSan.String()) + } + + return allCerts +} + +// GetAllDomains return a slice with all the certificate domain +func (c CertificateStore) GetAllDomains() []string { + + allCerts := c.getDefaultCertificateDomains() + // Get dynamic certificates if c.DynamicCerts != nil && c.DynamicCerts.Get() != nil { for domains := range c.DynamicCerts.Get().(map[string]*tls.Certificate) { @@ -77,16 +96,6 @@ func (c CertificateStore) GetBestCertificate(clientHello *tls.ClientHelloInfo) * } } - if c.StaticCerts != nil && c.StaticCerts.Get() != nil { - for domains, cert := range c.StaticCerts.Get().(map[string]*tls.Certificate) { - for _, certDomain := range strings.Split(domains, ",") { - if MatchDomain(domainToCheck, certDomain) { - matchedCerts[certDomain] = cert - } - } - } - } - if len(matchedCerts) > 0 { // sort map by keys keys := make([]string, 0, len(matchedCerts)) @@ -103,11 +112,6 @@ func (c CertificateStore) GetBestCertificate(clientHello *tls.ClientHelloInfo) * return nil } -// ContainsCertificates checks if there are any certs in the store -func (c CertificateStore) ContainsCertificates() bool { - return c.StaticCerts.Get() != nil || c.DynamicCerts.Get() != nil -} - // ResetCache clears the cache in the store func (c CertificateStore) ResetCache() { if c.CertCache != nil { diff --git a/tls/certificate_store_test.go b/tls/certificate_store_test.go index 34a13ec08..31e1f9f02 100644 --- a/tls/certificate_store_test.go +++ b/tls/certificate_store_test.go @@ -14,91 +14,45 @@ import ( ) func TestGetBestCertificate(t *testing.T) { + // FIXME Add tests for defaultCert testCases := []struct { desc string domainToCheck string - staticCert string dynamicCert string expectedCert string }{ { desc: "Empty Store, returns no certs", domainToCheck: "snitest.com", - staticCert: "", dynamicCert: "", expectedCert: "", }, { - desc: "Empty static cert store", + desc: "Best Match with no corresponding", domainToCheck: "snitest.com", - staticCert: "", - dynamicCert: "snitest.com", - expectedCert: "snitest.com", - }, - { - desc: "Empty dynamic cert store", - domainToCheck: "snitest.com", - staticCert: "snitest.com", - dynamicCert: "", - expectedCert: "snitest.com", + dynamicCert: "snitest.org", + expectedCert: "", }, { desc: "Best Match", domainToCheck: "snitest.com", - staticCert: "snitest.com", - dynamicCert: "snitest.org", + dynamicCert: "snitest.com", expectedCert: "snitest.com", }, { - desc: "Best Match with wildcard dynamic and exact static", + desc: "Best Match with dynamic wildcard", domainToCheck: "www.snitest.com", - staticCert: "www.snitest.com", - dynamicCert: "*.snitest.com", - expectedCert: "www.snitest.com", - }, - { - desc: "Best Match with wildcard static and exact dynamic", - domainToCheck: "www.snitest.com", - staticCert: "*.snitest.com", - dynamicCert: "www.snitest.com", - expectedCert: "www.snitest.com", - }, - { - desc: "Best Match with static wildcard only", - domainToCheck: "www.snitest.com", - staticCert: "*.snitest.com", - dynamicCert: "", - expectedCert: "*.snitest.com", - }, - { - desc: "Best Match with dynamic wildcard only", - domainToCheck: "www.snitest.com", - staticCert: "", dynamicCert: "*.snitest.com", expectedCert: "*.snitest.com", }, - { - desc: "Best Match with two wildcard certs", - domainToCheck: "foo.www.snitest.com", - staticCert: "*.www.snitest.com", - dynamicCert: "*.snitest.com", - expectedCert: "*.www.snitest.com", - }, } for _, test := range testCases { test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() - staticMap := map[string]*tls.Certificate{} dynamicMap := map[string]*tls.Certificate{} - if test.staticCert != "" { - cert, err := loadTestCert(test.staticCert) - require.NoError(t, err) - staticMap[test.staticCert] = cert - } - if test.dynamicCert != "" { cert, err := loadTestCert(test.dynamicCert) require.NoError(t, err) @@ -107,7 +61,6 @@ func TestGetBestCertificate(t *testing.T) { store := &CertificateStore{ DynamicCerts: safe.New(dynamicMap), - StaticCerts: safe.New(staticMap), CertCache: cache.New(1*time.Hour, 10*time.Minute), } diff --git a/tls/tls.go b/tls/tls.go index 4a72fc9fc..05761c2fe 100644 --- a/tls/tls.go +++ b/tls/tls.go @@ -24,7 +24,6 @@ type ClientCA struct { type TLS struct { MinVersion string `export:"true"` CipherSuites []string - Certificates Certificates ClientCA ClientCA DefaultCertificate *Certificate SniStrict bool `export:"true"` diff --git a/tracing/zipkin/zipkin.go b/tracing/zipkin/zipkin.go index 7b0f8a717..133ca5749 100644 --- a/tracing/zipkin/zipkin.go +++ b/tracing/zipkin/zipkin.go @@ -2,6 +2,7 @@ package zipkin import ( "io" + "time" "github.com/containous/traefik/log" "github.com/opentracing/opentracing-go" @@ -13,10 +14,11 @@ const Name = "zipkin" // Config provides configuration settings for a zipkin tracer. type Config struct { - HTTPEndpoint string `description:"HTTP Endpoint to report traces to." export:"false"` - SameSpan bool `description:"Use ZipKin SameSpan RPC style traces." export:"true"` - ID128Bit bool `description:"Use ZipKin 128 bit root span IDs." export:"true"` - Debug bool `description:"Enable Zipkin debug." export:"true"` + HTTPEndpoint string `description:"HTTP Endpoint to report traces to." export:"false"` + SameSpan bool `description:"Use Zipkin SameSpan RPC style traces." export:"true"` + ID128Bit bool `description:"Use Zipkin 128 bit root span IDs." export:"true"` + Debug bool `description:"Enable Zipkin debug." export:"true"` + SampleRate float64 `description:"The rate between 0.0 and 1.0 of requests to trace." export:"true"` } // Setup sets up the tracer @@ -33,6 +35,7 @@ func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error zipkin.ClientServerSameSpan(c.SameSpan), zipkin.TraceID128Bit(c.ID128Bit), zipkin.DebugMode(c.Debug), + zipkin.WithSampler(zipkin.NewBoundarySampler(c.SampleRate, time.Now().Unix())), ) if err != nil { return nil, nil, err