diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0146fa0e7..fa15f85bc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,7 +64,7 @@ Once your environment is set up and the Træfik repository cloned you can build cd ~/go/src/github.com/containous/traefik # Get go-bindata. Please note, the ellipses are required -go get github.com/jteeuwen/go-bindata/... +go get github.com/containous/go-bindata/... # Start build diff --git a/Gopkg.lock b/Gopkg.lock index a2e52093d..64fcdb25f 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1492,6 +1492,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "daede4415dbd8614087d38fc4d8cba45d679bbb7185bfca805091ef7b295341f" + inputs-digest = "f87576548de74d86b0e93a28551b97317addba5731345338272fdbb8a22ad77f" solver-name = "gps-cdcl" solver-version = 1 diff --git a/acme/acme.go b/acme/acme.go index 81ec7a91e..86bce6a37 100644 --- a/acme/acme.go +++ b/acme/acme.go @@ -295,6 +295,7 @@ func (a *ACME) leadershipListener(elected bool) error { // CreateLocalConfig creates a tls.config using local ACME configuration func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, certs *safe.Safe, checkOnDemandDomain func(domain string) bool) error { + defer a.runJobs() err := a.init() if err != nil { return err @@ -333,7 +334,9 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, certs *safe.Safe, checkO a.client, err = a.buildACMEClient(account) if err != nil { - return err + log.Errorf(`Failed to build ACME client: %s +Let's Encrypt functionality will be limited until traefik is restarted.`, err) + return nil } if needRegister { @@ -374,7 +377,6 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, certs *safe.Safe, checkO a.retrieveCertificates() a.renewCertificates() - a.runJobs() ticker := time.NewTicker(24 * time.Hour) safe.Go(func() { diff --git a/build.Dockerfile b/build.Dockerfile index 6d2e4e81e..006de77c1 100644 --- a/build.Dockerfile +++ b/build.Dockerfile @@ -4,7 +4,7 @@ RUN apk --update upgrade \ && apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar \ && rm -rf /var/cache/apk/* -RUN go get github.com/jteeuwen/go-bindata/... \ +RUN go get github.com/containous/go-bindata/... \ && go get github.com/golang/lint/golint \ && go get github.com/kisielk/errcheck \ && go get github.com/client9/misspell/cmd/misspell diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index a067cc810..7d08b4e1d 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -27,6 +27,7 @@ import ( "github.com/containous/traefik/types" "github.com/containous/traefik/version" "github.com/coreos/go-systemd/daemon" + "github.com/ogier/pflag" "github.com/sirupsen/logrus" ) @@ -75,6 +76,9 @@ Complete documentation is available at https://traefik.io`, } if _, err := f.Parse(usedCmd); err != nil { + if err == pflag.ErrHelp { + os.Exit(0) + } fmtlog.Printf("Error parsing command: %s\n", err) os.Exit(-1) } diff --git a/docs/basics.md b/docs/basics.md index fcfd36006..0e20bb66e 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -236,7 +236,7 @@ The following rules are both `Matchers` and `Modifiers`, so the `Matcher` portio By default, routes will be sorted (in descending order) using rules length (to avoid path overlap): `PathPrefix:/12345` will be matched before `PathPrefix:/1234` that will be matched before `PathPrefix:/1`. -You can customize priority by frontend: +You can customize priority by frontend. The priority value is added to the rule length during sorting: ```toml [frontends] @@ -254,7 +254,7 @@ You can customize priority by frontend: rule = "PathPrefix:/toto" ``` -Here, `frontend1` will be matched before `frontend2` (`10 > 5`). +Here, `frontend1` will be matched before `frontend2` (`(3 + 10 == 13) > (4 + 5 == 9)`). #### Custom headers diff --git a/docs/configuration/acme.md b/docs/configuration/acme.md index ffad21f29..09b4962ed 100644 --- a/docs/configuration/acme.md +++ b/docs/configuration/acme.md @@ -144,6 +144,18 @@ entryPoint = "https" If `HTTP-01` challenge is used, `acme.httpChallenge.entryPoint` has to be defined and reachable by Let's Encrypt through the port 80. These are Let's Encrypt limitations as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72). +### Let's Encrypt downtime + +Let's Encrypt functionality will be limited until Træfik is restarted. + +If Let's Encrypt is not reachable, these certificates will be used : + - ACME certificates already generated before downtime + - Expired ACME certificates + - Provided certificates + +!!! note + Default Træfik certificate will be used instead of ACME certificates for new (sub)domains (which need Let's Encrypt challenge). + ### `storage` ```toml diff --git a/docs/configuration/entrypoints.md b/docs/configuration/entrypoints.md index dc51f9616..1faa05beb 100644 --- a/docs/configuration/entrypoints.md +++ b/docs/configuration/entrypoints.md @@ -2,6 +2,8 @@ ## Reference +### TOML + ```toml [entryPoints] [entryPoints.http] @@ -64,6 +66,37 @@ # ... ``` +### CLI + +For more information about the CLI, see the documentation about [Traefik command](/basics/#traefik). + +```shell +--entryPoints='Name:http Address::80' +--entryPoints='Name:https Address::443 TLS' +``` + +!!! note + Whitespace is used as option separator and `,` is used as value separator for the list. + The names of the options are case-insensitive. + +All available options: + +```ini +Name:foo +Address::80 +TLS:goo,gii +TLS +CA:car +CA.Optional:true +Redirect.EntryPoint:https +Redirect.Regex:http://localhost/(.*) +Redirect.Replacement:http://mydomain/$1 +Compress:true +WhiteListSourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 +ProxyProtocol.TrustedIPs:192.168.0.1 +ProxyProtocol.Insecure:tue +ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 +``` ## Basic @@ -118,7 +151,11 @@ To redirect an entrypoint rewriting the URL. ``` !!! note - Please note that `regex` and `replacement` do not have to be set in the `redirect` structure if an entrypoint is defined for the redirection (they will not be used in this case). + Please note that `regex` and `replacement` do not have to be set in the `redirect` structure if an `entrypoint` is defined for the redirection (they will not be used in this case). + +Care should be taken when defining replacement expand variables: `$1x` is equivalent to `${1x}`, not `${1}x` (see [Regexp.Expand](https://golang.org/pkg/regexp/#Regexp.Expand)), so use `${1}` syntax. + +Regular expressions and replacements can be tested using online tools such as [Go Playground](https://play.golang.org/p/mWU9p-wk2ru) or the [Regex101](https://regex101.com/r/58sIgx/2). ## TLS diff --git a/docs/user-guide/cluster-docker-consul.md b/docs/user-guide/cluster-docker-consul.md index 48c5de9e1..4fb9027f1 100644 --- a/docs/user-guide/cluster-docker-consul.md +++ b/docs/user-guide/cluster-docker-consul.md @@ -35,14 +35,14 @@ TL;DR: ```shell $ traefik \ - --entrypoints=Name:http Address::80 Redirect.EntryPoint:https \ - --entrypoints=Name:https Address::443 TLS \ + --entrypoints='Name:http Address::80 Redirect.EntryPoint:https' \ + --entrypoints='Name:https Address::443 TLS' \ --defaultentrypoints=http,https ``` To listen to different ports, we need to create an entry point for each. -The CLI syntax is `--entrypoints=Name:a_name Address:an_ip_or_empty:a_port options`. +The CLI syntax is `--entrypoints='Name:a_name Address:an_ip_or_empty:a_port options'`. If you want to redirect traffic from one entry point to another, it's the option `Redirect.EntryPoint:entrypoint_name`. By default, we don't want to configure all our services to listen on http and https, we add a default entry point configuration: `--defaultentrypoints=http,https`. @@ -94,8 +94,8 @@ services: image: traefik:1.5 command: - "--api" - - "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https" - - "--entrypoints=Name:https Address::443 TLS" + - "--entrypoints='Name:http Address::80 Redirect.EntryPoint:https'" + - "--entrypoints='Name:https Address::443 TLS'" - "--defaultentrypoints=http,https" - "--acme" - "--acme.storage=/etc/traefik/acme/acme.json" @@ -204,8 +204,8 @@ services: command: - "storeconfig" - "--api" - - "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https" - - "--entrypoints=Name:https Address::443 TLS" + - "--entrypoints='Name:http Address::80 Redirect.EntryPoint:https'" + - "--entrypoints='Name:https Address::443 TLS'" - "--defaultentrypoints=http,https" - "--acme" - "--acme.storage=traefik/acme/account" diff --git a/docs/user-guide/docker-and-lets-encrypt.md b/docs/user-guide/docker-and-lets-encrypt.md index cca881979..68c11950d 100644 --- a/docs/user-guide/docker-and-lets-encrypt.md +++ b/docs/user-guide/docker-and-lets-encrypt.md @@ -110,7 +110,7 @@ entryPoint = "http" This is the minimum configuration required to do the following: -- Log `ERROR`-level messages (or more severe) to the console, but silence `DEBUG`-level messagse +- Log `ERROR`-level messages (or more severe) to the console, but silence `DEBUG`-level messages - Check for new versions of Træfik periodically - Create two entry points, namely an `HTTP` endpoint on port `80`, and an `HTTPS` endpoint on port `443` where all incoming traffic on port `80` will immediately get redirected to `HTTPS`. - Enable the Docker configuration backend and listen for container events on the Docker unix socket we've mounted earlier. However, **new containers will not be exposed by Træfik by default, we'll get into this in a bit!** diff --git a/docs/user-guide/kv-config.md b/docs/user-guide/kv-config.md index db2c551b0..4713bd0f0 100644 --- a/docs/user-guide/kv-config.md +++ b/docs/user-guide/kv-config.md @@ -1,6 +1,6 @@ # Key-value store configuration -Both [static global configuration](/user-guide/kv-config/#static-configuration-in-key-value-store) and [dynamic](/user-guide/kv-config/#dynamic-configuration-in-key-value-store) configuration can be sorted in a Key-value store. +Both [static global configuration](/user-guide/kv-config/#static-configuration-in-key-value-store) and [dynamic](/user-guide/kv-config/#dynamic-configuration-in-key-value-store) configuration can be stored in a Key-value store. This section explains how to launch Træfik using a configuration loaded from a Key-value store. diff --git a/integration/acme_test.go b/integration/acme_test.go index a5474c8a0..59fac40a1 100644 --- a/integration/acme_test.go +++ b/integration/acme_test.go @@ -142,6 +142,19 @@ func (s *AcmeSuite) TestOnHostRuleRetrieveAcmeCertificateWithDynamicWildcard(c * s.retrieveAcmeCertificate(c, testCase) } +// Test Let's encrypt down +func (s *AcmeSuite) TestNoValidLetsEncryptServer(c *check.C) { + cmd, display := s.traefikCmd(withConfigFile("fixtures/acme/wrong_acme.toml")) + defer display(c) + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + // Expected traefik works + err = try.GetRequest("http://127.0.0.1:8080/api/providers", 10*time.Second, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) +} + // Doing an HTTPS request and test the response certificate func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase AcmeTestCase) { file := s.adaptFile(c, testCase.traefikConfFilePath, struct { diff --git a/integration/fixtures/acme/wrong_acme.toml b/integration/fixtures/acme/wrong_acme.toml new file mode 100644 index 000000000..3f8f65051 --- /dev/null +++ b/integration/fixtures/acme/wrong_acme.toml @@ -0,0 +1,34 @@ +logLevel = "DEBUG" + +defaultEntryPoints = ["http", "https"] + +[api] + +[entryPoints] + [entryPoints.http] + address = ":8081" + [entryPoints.https] + address = ":5001" + [entryPoints.https.tls] + + +[acme] +email = "test@traefik.io" +storage = "/dev/null" +entryPoint = "https" +OnHostRule = true +caServer = "http://wrongurl:4000/directory" + +[file] + +[backends] + [backends.backend] + [backends.backend.servers.server1] + url = "http://127.0.0.1:9010" + + +[frontends] + [frontends.frontend] + backend = "backend" + [frontends.frontend.routes.test] + rule = "Host:traefik.acme.wtf" diff --git a/provider/docker/docker.go b/provider/docker/docker.go index 028e3f076..cc8954264 100644 --- a/provider/docker/docker.go +++ b/provider/docker/docker.go @@ -352,20 +352,18 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD for _, service := range serviceList { dData := parseService(service, networkMap) - if len(dData.NetworkSettings.Networks) > 0 { - useSwarmLB := isBackendLBSwarm(dData) - if useSwarmLB { + if isBackendLBSwarm(dData) { + if len(dData.NetworkSettings.Networks) > 0 { dockerDataList = append(dockerDataList, dData) + } + } else { + isGlobalSvc := service.Spec.Mode.Global != nil + dockerDataListTasks, err = listTasks(ctx, dockerClient, service.ID, dData, networkMap, isGlobalSvc) + if err != nil { + log.Warn(err) } else { - isGlobalSvc := service.Spec.Mode.Global != nil - - dockerDataListTasks, err = listTasks(ctx, dockerClient, service.ID, dData, networkMap, isGlobalSvc) - if err != nil { - log.Warn(err) - } else { - dockerDataList = append(dockerDataList, dockerDataListTasks...) - } + dockerDataList = append(dockerDataList, dockerDataListTasks...) } } } @@ -381,10 +379,11 @@ func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes } if service.Spec.EndpointSpec != nil { - switch service.Spec.EndpointSpec.Mode { - case swarmtypes.ResolutionModeDNSRR: - log.Warnf("Ignored endpoint-mode not supported, service name: %s", service.Spec.Annotations.Name) - case swarmtypes.ResolutionModeVIP: + if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeDNSRR { + if isBackendLBSwarm(dData) { + log.Warnf("Ignored %s endpoint-mode not supported, service name: %s. Fallback to Træfik load balancing", swarmtypes.ResolutionModeDNSRR, service.Spec.Annotations.Name) + } + } else if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeVIP { dData.NetworkSettings.Networks = make(map[string]*networkData) for _, virtualIP := range service.Endpoint.VirtualIPs { networkService := networkMap[virtualIP.NetworkID] diff --git a/provider/docker/swarm_test.go b/provider/docker/swarm_test.go index 851d4376a..242788133 100644 --- a/provider/docker/swarm_test.go +++ b/provider/docker/swarm_test.go @@ -80,6 +80,7 @@ type fakeServicesClient struct { dockerVersion string networks []dockertypes.NetworkResource services []swarm.Service + tasks []swarm.Task err error } @@ -95,10 +96,15 @@ func (c *fakeServicesClient) NetworkList(ctx context.Context, options dockertype return c.networks, c.err } +func (c *fakeServicesClient) TaskList(ctx context.Context, options dockertypes.TaskListOptions) ([]swarm.Task, error) { + return c.tasks, c.err +} + func TestListServices(t *testing.T) { testCases := []struct { desc string services []swarm.Service + tasks []swarm.Task dockerVersion string networks []dockertypes.NetworkResource expectedServices []string @@ -120,7 +126,8 @@ func TestListServices(t *testing.T) { swarmService( serviceName("service2"), serviceLabels(map[string]string{ - labelDockerNetwork: "barnet", + labelDockerNetwork: "barnet", + labelBackendLoadBalancerSwarm: "true", }), withEndpointSpec(modeDNSSR)), }, @@ -145,7 +152,8 @@ func TestListServices(t *testing.T) { swarmService( serviceName("service2"), serviceLabels(map[string]string{ - labelDockerNetwork: "barnet", + labelDockerNetwork: "barnet", + labelBackendLoadBalancerSwarm: "true", }), withEndpointSpec(modeDNSSR)), }, @@ -174,13 +182,65 @@ func TestListServices(t *testing.T) { "service1", }, }, + { + desc: "Should return service1 and service2", + services: []swarm.Service{ + swarmService( + serviceName("service1"), + serviceLabels(map[string]string{ + labelDockerNetwork: "barnet", + }), + withEndpointSpec(modeVIP), + withEndpoint( + virtualIP("yk6l57rfwizjzxxzftn4amaot", "10.11.12.13/24"), + virtualIP("2", "10.11.12.99/24"), + )), + swarmService( + serviceName("service2"), + serviceLabels(map[string]string{ + labelDockerNetwork: "barnet", + }), + withEndpointSpec(modeDNSSR)), + }, + tasks: []swarm.Task{ + swarmTask("id1", taskStatus(taskState(swarm.TaskStateRunning))), + swarmTask("id2", taskStatus(taskState(swarm.TaskStateRunning))), + }, + dockerVersion: "1.30", + networks: []dockertypes.NetworkResource{ + { + Name: "network_name", + ID: "yk6l57rfwizjzxxzftn4amaot", + Created: time.Now(), + Scope: "swarm", + Driver: "overlay", + EnableIPv6: false, + Internal: true, + Ingress: false, + ConfigOnly: false, + Options: map[string]string{ + "com.docker.network.driver.overlay.vxlanid_list": "4098", + "com.docker.network.enable_ipv6": "false", + }, + Labels: map[string]string{ + "com.docker.stack.namespace": "test", + }, + }, + }, + expectedServices: []string{ + "service1.0", + "service1.0", + "service2.0", + "service2.0", + }, + }, } for caseID, test := range testCases { test := test t.Run(strconv.Itoa(caseID), func(t *testing.T) { t.Parallel() - dockerClient := &fakeServicesClient{services: test.services, dockerVersion: test.dockerVersion, networks: test.networks} + dockerClient := &fakeServicesClient{services: test.services, tasks: test.tasks, dockerVersion: test.dockerVersion, networks: test.networks} serviceDockerData, err := listServices(context.Background(), dockerClient) assert.NoError(t, err)