1
0
mirror of https://github.com/containous/traefik.git synced 2025-09-08 13:44:22 +03:00

Compare commits

...

231 Commits

Author SHA1 Message Date
Emile Vauge
408ef0f5b7 Merge pull request #705 from containous/prepare-release-v1.1.0-rc1
Prepare release v1.1.0 rc1
2016-09-30 15:57:09 +02:00
Emile Vauge
b9f76394aa Update Mesos documentation 2016-09-30 15:37:52 +02:00
Emile Vauge
a96f483d56 Fix regression in acme.json secure mode 2016-09-30 15:06:12 +02:00
Emile Vauge
84cb9f15a4 Prepare release v1.1.0-rc1 2016-09-30 15:04:57 +02:00
Emile Vauge
d4da14cf18 Merge pull request #625 from containous/add-ha-acme-support
HA acme support
2016-09-30 13:34:59 +02:00
Emile Vauge
4ad4b8e0b8 Add ACME account to storeconfig command
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-30 12:29:39 +02:00
Emile Vauge
bb29d9c8ca Add documentation
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-30 12:29:39 +02:00
Emile Vauge
e72e65858f Challenge certs PEM encoding
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-30 11:26:25 +02:00
Emile Vauge
a42845502e Add ACME store
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-30 11:26:17 +02:00
Emile Vauge
bea5ad3f13 Add leadership election
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-30 11:26:05 +02:00
Emile Vauge
5a0440d6f8 Add KV datastore
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-30 11:26:05 +02:00
Emile Vauge
38b62d4ae3 Merge pull request #701 from containous/carry-pr-446
Carry PR 446 - Add sticky session support (round two!)
2016-09-30 11:25:26 +02:00
Emile Vauge
462d8b3e74 Fix Docker & KV templates for sticky
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-30 10:57:55 +02:00
Emile Vauge
291c3b6dbc Fix sticky sessions Docker/Marathon labels 2016-09-30 10:56:47 +02:00
Owen Marshall
df225d9170 Change cookie name to match documentation 2016-09-30 10:56:36 +02:00
Owen Marshall
592e981bd2 glide up 2016-09-30 10:56:36 +02:00
Owen Marshall
3d7c44735a Reorder sticky session definitions 2016-09-30 10:56:17 +02:00
Owen Marshall
81fddb4ccf missed a cherry pick, good start :=/ 2016-09-30 10:56:17 +02:00
Owen Marshall
c9d4c5ae3e Fix Docker integration problems 2016-09-30 10:56:17 +02:00
Owen Marshall
be5b1fd92b docs, sticky session for Docker 2016-09-30 10:56:17 +02:00
Owen Marshall
d78c419627 Add sticky support to DRR lb 2016-09-30 10:56:17 +02:00
Owen Marshall
dc52abf4ce Add sticky session support to Traefik.
This change adds sticky session support, by using the new
oxy/rr/StickySession feature.

To use it, set traefik.backend.sticky to true.

This is currently only implemented in the wrr load balancer, and against
the Marathon backend, but lifting it should be very doable.

In the wrr load balancer, a cookie called _TRAEFIK_SERVERNAME will be
set with the backend to use.  If the cookie is altered to an invalid
backend server, or the server is removed from the load balancer, the
next server will be used instead.

Otherwise, the cookie will be checked in Oxy's rr on access and if valid
the connection will be wired through to it.
2016-09-30 10:56:17 +02:00
Vincent Demeester
a13549cc28 Merge pull request #702 from tuier/http_compression
Add HTTP compression
2016-09-29 18:25:49 +02:00
tuier
baf4c474e3 Documentation 2016-09-28 23:05:01 +01:00
tuier
a58750992d lint 2016-09-28 23:04:18 +01:00
tuier
17546c3a08 Add HTTP compression 2016-09-28 23:04:18 +01:00
Emile Vauge
067f13b61c Merge pull request #690 from dtomcej/disable-tls10
Selectable TLS Versions
2016-09-28 23:52:34 +02:00
Daniel Tomcej
e249983c77 add TLS Min Version
silly copy paste
Add cipher selection

add TLS Min Version

silly copy paste

silly copy paste

add TLS Min Version

silly copy paste

fix formatting

Add cipher selection

linted

arg

update cipher map
2016-09-27 14:29:36 -06:00
Emile Vauge
454b191370 Merge pull request #687 from containous/some-fixes
Some fixes
2016-09-27 22:21:59 +02:00
Emile Vauge
a882a9d79f Fix constraints and add doc 2016-09-27 21:45:29 +02:00
Emile Vauge
89fc835bb2 Add Katacoda in docs 2016-09-27 21:45:29 +02:00
Emile Vauge
364958cbaf Move jobs backoff back into traefik
https://github.com/cenk/backoff/pull/27#issuecomment-245842725
2016-09-27 21:45:29 +02:00
Emile Vauge
1b6af2045e Clean web authentication 2016-09-27 21:45:29 +02:00
Emile Vauge
be09ff8e43 Merge pull request #602 from diegofernandes/master
#504 Initial support for Docker 1.12 Swarm Mode
2016-09-27 21:45:07 +02:00
Diego Osse Fernandes
99c8bffcbf Initial support for Docker 1.12 Swarm Mode 2016-09-27 14:21:38 -03:00
Emile Vauge
03d16d12d5 Merge pull request #697 from tuier/remove_unused_endpoint_marathon
Remove unused endpoint when using constraints with Marathon provider
2016-09-27 15:18:48 +02:00
tuier
1624c51cb5 remove unused endpoint when using constraints in marathon 2016-09-23 22:05:11 +01:00
Vincent Demeester
83aabefcc5 Merge pull request #696 from solidnerd/patch-1
Replace imagelayers.io with microbadger
2016-09-23 14:01:48 +02:00
Niclas Mietz
dfece708e1 Replace imagelayers.io with microbadger
This replace the badge of imagelayers.io with a badge microbadger.com because imagelayers.io doesn't work anymore through the registry v2 specification and docker hub supports only the v2 spec.
2016-09-23 13:03:44 +02:00
Emile Vauge
5d0f82ffbd Merge pull request #686 from tuier/marathon_constraints
feat(constraints): Supports constraints for Marathon provider
2016-09-23 09:58:17 +02:00
tuier
361dc94002 fmt 2016-09-21 17:27:48 +02:00
tuier
cc0fdf15ef test for task and application constraint 2016-09-21 17:27:47 +02:00
tuier
928675a847 feat(constraints): Support constraint for Marathon provider 2016-09-21 17:27:47 +02:00
Emile Vauge
12c1131b0c Merge pull request #689 from containous/carry-pr-439
Carry pr 439
2016-09-21 15:51:43 +02:00
Emile Vauge
bb1dde0469 Fix kv
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-21 14:33:45 +02:00
Samuel BERTHE
ced69b8397 refacto(constraint-kv): Move constraint checking out of template 2016-09-21 14:33:45 +02:00
Samuel BERTHE
013808956c feat(constraints): Supports kv stores backends 2016-09-21 14:33:45 +02:00
Emile Vauge
009057cb87 Merge pull request #688 from ydubreuil/fix-clean-url
Disable gorilla/mux URL cleaning to prevent sending redirect
2016-09-20 21:14:39 +02:00
Yoann Dubreuil
82cb21fca3 Disable gorilla/mux URL cleaning to prevent sending redirect
This fixes #167 and #651. By default, gorilla/mux cleans URL paths
such that adjacent slashes are collapsed into one single slash. This
behavior is pretty common in application stacks and it is fine, but
for Traefik, this can lead to incorrect URL paths forwarded to backend
servers.

See https://github.com/gorilla/mux/issues/94 for background.
2016-09-20 20:31:11 +02:00
Vincent Demeester
7e8937a332 Merge pull request #685 from dtomcej/update-docs
Update docs to improve contribution setup
2016-09-20 18:04:56 +02:00
dtomcej
e5dcfa0a2e Update docs for current install
silly spelling mistake

Document accepted values for logLevel.

Add possibility to use BindPort IPAddress 👼

Signed-off-by: Vincent Demeester <vincent@sbr.pm>

Update marathon

Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-20 09:13:52 -06:00
Emile Vauge
f4520a011a Merge pull request #648 from containous/update-marathon
Update marathon
2016-09-20 16:57:44 +02:00
Emile Vauge
98dd6ca460 Update marathon
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-20 16:01:45 +02:00
Emile Vauge
c3d9312240 Merge pull request #657 from vdemeester/use-port-ip-address
Add possibility to use BindPort IPAddress 👼
2016-09-20 15:20:48 +02:00
Vincent Demeester
5ea761e19f Add possibility to use BindPort IPAddress 👼
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-09-20 14:52:35 +02:00
Vincent Demeester
46a7860427 Merge pull request #676 from InQuicker/docs-log-levels
Document accepted values for logLevel.
2016-09-20 09:45:20 +02:00
Jimmy Cuadra
af9b63eaed Document accepted values for logLevel. 2016-09-19 16:07:53 -07:00
Emile Vauge
9a26e0db16 Merge pull request #610 from containous/merge-v1.0.2-master
Merge v1.0.2 master
2016-09-19 20:29:37 +02:00
Emile Vauge
efe6989fd3 Merge acme from v1.0.2
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-19 19:58:34 +02:00
Emile Vauge
aa1c9b80e3 Prepare release v1.0.2
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-19 16:49:21 +02:00
Emile Vauge
6981df3b9a Fix ACME TOS
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-19 16:49:21 +02:00
Emile Vauge
0d1ed625a8 Bump oxy version, fix streaming
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-19 16:49:21 +02:00
Emile Vauge
710fc56c6a Merge pull request #677 from SantoDE/master
Add basic auth support for web backend
2016-09-19 16:48:05 +02:00
Manuel Laufenberg
d5a15d6756 add basic auth support
implemented requested changes

fix docs

remove struct tag
2016-09-19 09:40:43 +02:00
Emile Vauge
b376da1829 Merge pull request #645 from jangie/add-backend-features-to-consul-catalog
enable consul catalog to use maxconn
2016-09-15 18:46:30 +02:00
Bruce Lee
f7f17f0057 enable consul catalog to use maxconn 2016-09-15 12:11:37 -04:00
Emile Vauge
d06b9c2992 Merge pull request #616 from jangie/master
Add ability for marathon provider to set maxconn values, loadbalancer algorithm, and circuit breaker expression
2016-09-15 17:19:12 +02:00
Bruce Lee
99ca5d0a03 Add ability for marathon provider to set maxconn values
Initial implementation: Force both to be present to trigger behavior.

add ability to see rendered template in debug

add support for loadbalancer and circuit breaker specification

add documentation for new configuration
2016-09-14 16:52:02 -04:00
Vincent Demeester
4783c7f70a Merge pull request #646 from jangie/add-backend-features-to-docker
Add backend features to docker
2016-09-14 22:48:59 +02:00
Bruce Lee
d89bdfbd27 Add backend features to docker 2016-09-14 14:51:31 -04:00
Diogo Gomes
1e324ad3bc If Marathon doesn't have healthcheck, assume it's ok (#665)
Healthcheck are not mandatory, so if a result is not present, assume it
is ok to continue. Fixes the case when a new leader is elected and
don't have any healthcheck result's, returning 404 to all requests.
https://github.com/containous/traefik/issues/653
2016-09-14 17:44:37 +02:00
Emile Vauge
52737e91e5 Merge pull request #660 from JayH5/acme-renew-30-days
ACME: renew certificates 30 days before expiry
2016-09-14 08:01:07 +02:00
Jamie Hewland
1872e2b63d ACME: renew certificates 30 days before expiry, like the official certbot client 2016-09-13 16:15:58 +02:00
Martijn Heemels
3c5605b793 Update broken link in sample config file. (#658) 2016-09-13 15:17:34 +02:00
Vincent Demeester
9a2b7cf5be Merge pull request #668 from yvespp/master
Kubernetes provider: traefik.frontend.rule.type logging
2016-09-13 10:56:20 +02:00
Yves Peter
1a20e9f9b4 Kubernetes provider: don't log a warning if traefik.frontend.rule.type is empty, log namespace and ingress if invalide. 2016-09-12 21:06:21 +02:00
Vincent Demeester
14d79e4eef Merge pull request #641 from errm/code-of-conduct
Adopt the Code Of Coduct from http://contributor-covenant.org
2016-08-30 22:26:29 +02:00
Ed Robinson
71f48d2aef Adopt the Code Of Coduct from http://contributor-covenant.org 2016-08-26 10:26:41 +01:00
Emile Vauge
312adca226 Merge pull request #630 from jangie/add-ping-handler
add PING handler to dashboard API
2016-08-25 23:10:26 +02:00
Bruce Lee
d35c6e77d7 add PING handler to dashboard API 2016-08-24 21:37:12 -04:00
Emile Vauge
1de21c86ae Merge pull request #639 from discordianfish/fish/fix-acme-perm
Use secure mode 600 instead of 644 for acme.json
2016-08-24 20:06:33 +02:00
Johannes 'fish' Ziemke
c709a592eb Use secure mode 600 instead of 644 for acme.json 2016-08-22 13:33:49 +02:00
Vincent Demeester
a54c544eb4 Merge pull request #637 from djalal/patch-1
docker clarification, fix dead urls, misc typos
2016-08-22 13:06:35 +02:00
djalal
7d936ec6aa docker clarification, fix dead urls, misc typos 2016-08-22 11:03:34 +02:00
Emile Vauge
f63ec1332f Merge pull request #628 from containous/mirgate-to-JobBackOff
Migrate to JobBackOff
2016-08-19 22:24:39 +02:00
Emile Vauge
d340ccd601 Migrate to JobBackOff 2016-08-19 14:24:09 +02:00
Vincent Demeester
95e8f0a31e Merge pull request #627 from containous/add-long-job-exponential-backoff
Add long job exponential backoff
2016-08-19 11:40:16 +02:00
Emile Vauge
97ddfcb17a Use long job RetryBackoff in providers 2016-08-19 11:09:54 +02:00
Emile Vauge
7bb5f9a1e4 Add long job RetryBackoff 2016-08-19 11:09:34 +02:00
Emile Vauge
11297b38c5 Remove misused Fatal* 2016-08-19 10:36:54 +02:00
Emile Vauge
fc19ab2868 Merge pull request #601 from containous/add-host-cert-acme-generation
Add Host cert ACME generation
2016-08-18 15:01:41 +02:00
Emile Vauge
5e01c0a7db Add Host cert ACME generation
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-08-18 14:20:55 +02:00
Vincent Demeester
f1c3d820f7 Merge pull request #617 from jangie/fix-nil-client-tls
fix for nil clientTLS causing issue
2016-08-17 09:46:17 +02:00
Bruce Lee
0757a75732 fix for nil clientTLS causing issue 2016-08-16 17:50:22 -04:00
Emile Vauge
f0ea45a0f8 Merge pull request #613 from keis/installable-systemd-unit
Make systemd unit installable
2016-08-16 15:11:59 +02:00
David Keijser
45f2335a60 Make systemd unit installable
Having a install section makes it possible to enable/disable traefik
using the standard systemd commands

`systemctl enable traefik`
`systemctl disable traefk`
2016-08-16 14:50:20 +02:00
Vincent Demeester
d629939cf3 Merge pull request #620 from containous/bump-go-1.7
Bump go v1.7
2016-08-16 12:58:11 +02:00
Emile Vauge
404f76dcb9 Bump go v1.7
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-08-16 11:11:47 +02:00
Vincent Demeester
498ce6b00c Merge pull request #573 from errm/fix-k8s-watch
Fix k8s watch
2016-08-16 09:32:50 +02:00
Ed Robinson
e3a8fd116d Don't filter the endpoint and service watches
We added the ability to filter the ingresses used by traefik based
on a label selector, but we shouldn't need to have matching
labels on every other resource, Ingress allready has a way
to explicty choose which pods end up in the load ballancer
(by refering to the membership of a particular service)
2016-08-16 06:50:50 +01:00
Ed Robinson
d33e09bcf3 Ignore .DS_Store (Mac junk) 2016-08-16 06:50:50 +01:00
Vincent Demeester
fb3bad3887 Merge pull request #619 from jangie/consistent-duration-logging
Make duration logging consistent
2016-08-15 18:30:25 +02:00
Bruce Lee
3a736ad4a8 Make duration logging consistent
GH #559 points out that the logging of the elapsed time is inconsistent
depending on the scale of the measured time; this is due to Duration’s
String handling.

With this PR, I propose that traefik logs millis, and not fractions of
millis.
2016-08-15 08:43:20 -04:00
Vincent Demeester
c1b0c41769 Merge pull request #609 from cocap10/update-staert-toml-default-pointer-behaviour
update staert and flaeg
2016-08-15 13:19:24 +02:00
Martin
c03274703e update staert and flaeg 2016-08-11 14:34:33 +02:00
Christian Winther
4cd08e88f6 Fix Consul prefix (#589)
Seemed to have been the cause of #587
2016-08-08 11:53:00 +02:00
Vincent Demeester
e2c4872030 Merge pull request #596 from errm/k8s-server-naming
Name servers after thier pods
2016-08-07 09:21:44 +02:00
Ed Robinson
d4f190e995 Name servers after thier pods
The TargetRef contains information from the object referenced
by the pod, unless the service has been set up with bare
endpoints - i.e. not pointing at pods this information
will be present.

It just makes the information that we show in the web-ui
a little more constent with that shown in kubectl
and the kuberntes dashboard.
2016-08-06 20:40:40 +01:00
Vincent Demeester
039107e837 Merge pull request #598 from keiths-osc/version_fix
Fixed binary script so traefik version command doesn't just print default values
2016-08-05 16:05:25 +02:00
Keith Shook
ef6c211275 Fixed binary script so traefik version command doesn't just print default values 2016-08-05 09:19:45 -04:00
Vincent Demeester
1f3accc0d7 Merge pull request #580 from cocap10/beautify-help
beautify help
2016-08-05 15:13:28 +02:00
Martin
2815f80063 beautify help
and fixes
2016-08-05 14:26:36 +02:00
Emile Vauge
fa645abee3 Add requirements.txt for netlify (#567)
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-08-05 12:32:49 +02:00
Micaël Mbagira
a86649def3 Upgrade directives name since we use angular-ui-bootstrap (#578) 2016-08-05 11:49:34 +02:00
Vincent Demeester
1fc4c56bc4 Merge pull request #588 from philk/kubernetes-priorities
Prioritize kubernetes routes by path length
2016-08-05 11:08:07 +02:00
Phil Kates
79dd72f53d Prioritize kubernetes routes by path length
The Kubernetes provider wasn't setting priorities which was causing
shorter paths to get chosen before longer ones. This now matches the
[documentation](https://github.com/containous/traefik/blob/master/docs/basics.md#priorities)
2016-08-03 11:37:22 -07:00
Andreas Jägle
ffa060ce56 Fix basic docs for configuration of multiple rules (#576) 2016-08-03 18:50:13 +02:00
Emile Vauge
5ce9719951 Merge pull request #557 from stuart-c/insecure_skip_verify
Add global InsecureSkipVerify option to disable certificate checking
2016-08-01 15:42:33 +02:00
Stuart Clark
914aa7d372 Add some documentation 2016-07-31 17:08:33 +01:00
Stuart Clark
4a88cbde3a Adjust formatting 2016-07-31 16:59:48 +01:00
Stuart Clark
4882519c0f Add global InsecureSkipVerify option to disable certificate checking
Signed-off-by: Stuart Clark <stuart.clark@Jahingo.com>
2016-07-31 16:59:48 +01:00
Vincent Demeester
7abe68fac1 Merge pull request #565 from containous/merge-v1.0.1-master
Merge v1.0.1 master
2016-07-28 21:00:13 +02:00
Emile Vauge
e62cca1e7c Merge v1.0.1
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-28 20:15:09 +02:00
Emile Vauge
a016741918 Merge pull request #547 from containous/add-basic-authentication
Add basic/digest auth
2016-07-28 19:01:46 +02:00
Emile Vauge
2f95810fa3 Add experimental Docker push
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-28 18:32:26 +02:00
Emile Vauge
16e2c3b1e0 Add basic/digest auth doc
Signed-off-by: Emile Vauge <emile@vauge.com>

Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-28 18:32:21 +02:00
Emile Vauge
bc8a92caa9 Add basic/digest auth tests
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-28 18:19:47 +02:00
Emile Vauge
3a5b67a3e1 Add basic/digest auth
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-28 18:19:47 +02:00
Emile Vauge
2a596b8162 Merge pull request #558 from micaelmbagira/webui-webpack-fountainjs
Move webui to FountainJS with Webpack
2016-07-27 22:29:17 +02:00
Micaël
e059239bc3 Move webui to FountainJS with Webpack 2016-07-27 16:16:02 +02:00
Vincent Demeester
986ad9fc57 Merge pull request #551 from cocap10/upload-config-kv
Add command storeconfig
2016-07-22 16:52:00 +02:00
Martin
1bb3d9be73 add command storeconfig
add test integration command storeconfig

update doc
2016-07-22 15:20:47 +02:00
Emile Vauge
ae31f19ef6 Merge pull request #552 from vdemeester/update-libkermit
Upgrade libkermit and dependencies
2016-07-21 19:13:27 +02:00
Vincent Demeester
c170ddc7ae Upgrade libkermit and dependencies
And use ContainerAPIClient where needed 👼

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-07-21 17:43:05 +02:00
Emile Vauge
58b6d92ce2 Merge pull request #553 from vdemeester/move-version-in-package
Move version.go in its own package…
2016-07-21 17:35:52 +02:00
Vincent Demeester
87a4d73556 Move version.go in its own package…
… making it possible to use in other packages ; and thus in the
User-Agent header for the docker client.

Also removing the dockerverion hack as it's not required anymore.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-07-21 16:33:49 +02:00
Emile Vauge
4c54a003fa Merge pull request #546 from vdemeester/bump-webui-node
Bump node to 6 for webui
2016-07-21 10:46:59 +02:00
Vincent Demeester
a5f3eabf8b Bump node to version 6 for webui
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-07-21 10:21:04 +02:00
Emile Vauge
3bf6c59d23 Merge pull request #545 from vdemeester/bump-go-1.6.3
Bump golang to 1.6.3
2016-07-21 10:20:33 +02:00
Vincent Demeester
ef83dea95c Bump golang to 1.6.3
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-07-21 09:50:17 +02:00
Vincent Demeester
686c23d25b Merge pull request #481 from cocap10/global-config-kv
Use KvStores as global config sources
2016-07-21 09:49:58 +02:00
Martin
b153e90ec5 add createStore() funcs and skip consul TLS test 2016-07-21 09:22:35 +02:00
Martin
38cc36980f update doc 2016-07-21 09:22:35 +02:00
Martin
b83fb525a8 Add TLS support for etcd and consul 2016-07-21 09:22:35 +02:00
Martin
e26e0955b3 add struct ClientTLS : supports either a paths to a file or directly the certificate 2016-07-21 09:22:35 +02:00
Martin
7ada80b619 Certificate can contain path or file contents
Signed-off-by: Martin <martin.piegay@zenika.com>
2016-07-21 09:22:35 +02:00
Martin
056e0fe2d9 Use KvStores as global config source 2016-07-21 09:22:35 +02:00
Vincent Demeester
9be0c67d5c Merge pull request #461 from andersbetner/client_tls_auth
Add endpoint option to authenticate by client tls cert.
2016-07-21 09:06:34 +02:00
Anders Betnér
664bc9cae0 Added tests for client certificate authentication 2016-07-20 14:41:38 +02:00
Anders Betnér
959c7dc783 Support for client certificate authentication 2016-07-20 14:41:38 +02:00
Emile Vauge
8e333d0a03 Merge pull request #353 from saagie/feature/mesos
add mesos provider inspired by mesos-dns & marathon provider
2016-07-20 12:49:09 +02:00
julien
5afcf17706 add mesos provider inspired by mesos-dns & marathon provider 2016-07-20 11:56:14 +02:00
Emile Vauge
a8d05294bc Merge pull request #542 from containous/prepare-release-1.0.1
Prepare release v1.0.1
2016-07-19 16:54:16 +02:00
Emile Vauge
1b25e492c7 Prepare release v1.0.1
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-19 16:05:03 +02:00
Vincent Demeester
61b22316d6 Merge pull request #538 from jimt/docs
Fix typos
2016-07-19 15:34:08 +02:00
Emile Vauge
be8ebdba46 Merge pull request #527 from containous/add-sse-support
Add sse support
2016-07-19 11:51:21 +02:00
Emile Vauge
2d759df47a Fix basics doc
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-19 10:29:48 +02:00
Emile Vauge
d1b5cf99d0 Add GraceTimeOut support
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-18 18:01:59 +02:00
Emile Vauge
516608d883 Bump containous/oxy version with SSE support
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-18 17:59:50 +02:00
Emile Vauge
bf95e6def9 Add deploy-pr on non master branch
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-18 17:59:50 +02:00
Emile Vauge
3c5cb31775 default values logs from WARN to DEBUG
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-18 17:59:50 +02:00
Jim Tittsler
d2f51fccb9 Fix typos 2016-07-18 14:55:45 +09:00
Emile Vauge
c13db04f6d Merge pull request #519 from errm/k8s-docs
Kubernetes user-guide
2016-07-17 15:46:45 +02:00
Ed Robinson
d3aa056151 Kubernetes user-guide 2016-07-15 09:49:08 +01:00
Ed Robinson
1c60f0b53b Fix the name of Traefik in the documentation 2016-07-15 09:49:08 +01:00
Emile Vauge
ca2b85f453 Merge pull request #514 from vdemeester/carry-pr-358
Carry #358 : Option to disable expose of all docker containers
2016-07-14 11:57:31 +02:00
Vincent Demeester
b80479f9ef Option to disable expose of all docker containers
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-07-14 11:32:15 +02:00
Vincent Demeester
0a9070c394 Merge pull request #531 from Jsewill/patch-2
Update server.go
2016-07-14 11:28:29 +02:00
Jsewill
bd29bac716 Update server.go
Fixed a bug that caused a panic when sending multiple signals: signals are no longer sent on the server.signals channel after it has closed.
2016-07-14 03:22:18 -05:00
Emile Vauge
d42a22f446 Merge pull request #512 from containous/fix-acme-ondemand
Fix acme checkOnDemandDomain
2016-07-14 10:13:24 +02:00
Emile Vauge
d1112a0feb Merge pull request #510 from vdemeester/remove-frontend-value-docker
Remove traefik.frontend.value support in docker…
2016-07-13 21:53:38 +02:00
Vincent Demeester
a73baded88 Remove traefik.frontend.value support in docker…
… provider 👼. Was deprecated in 1.0, removing it for 1.1.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-07-13 19:28:08 +02:00
Emile Vauge
24d3a698a0 Fix acme checkOnDemandDomain
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-13 19:25:47 +02:00
Emile Vauge
1eeba34806 Merge pull request #511 from errm/etcd-endpoint-default
Fix default etcd port
2016-07-13 19:23:58 +02:00
Emile Vauge
94fa95d747 Merge pull request #516 from pnegahdar/selector
Implement Kubernetes Selectors, minor kube endpoint fix
2016-07-13 19:22:40 +02:00
Emile Vauge
c98a561722 Fix GraceTimeOut flag description
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-13 17:49:00 +02:00
Parham Negahdar
9f6484a328 Fixes #363: Allow for kubernetes label selectors 2016-07-12 17:50:01 -04:00
Parham Negahdar
40c0ed092e Fixes #501: prioritize kube.endpoint passed via cli 2016-07-11 15:39:20 -04:00
Ed Robinson
c719aa3db8 Fix default etcd port
Fixes #508
2016-07-11 13:14:43 +01:00
Emile Vauge
8f8f72fa76 Merge pull request #496 from containous/prepare-release-1.0.0
Prepare release 1.0.0
2016-07-06 01:09:33 +02:00
Emile Vauge
4ae6d42871 Add Changelog
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 21:17:30 +02:00
Emile Vauge
64243382cf Add codename
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 21:03:37 +02:00
Emile Vauge
c7acb2d2c4 Update doc on combining multiple rules and priorities
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 21:03:06 +02:00
Emile Vauge
20795cf884 Add Russell-IO and errm in maintainers
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 21:02:14 +02:00
Vincent Demeester
6b9f64a273 Merge pull request #495 from containous/fix-windows-build
Fix windows builds
2016-07-05 16:40:07 +02:00
Emile Vauge
9e270c951a Fix windows builds
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 16:02:36 +02:00
Vincent Demeester
20308dc804 Merge pull request #494 from containous/fix-docker-network-host
Fix host Docker network
2016-07-05 14:41:48 +02:00
Emile Vauge
b1ecb1f61f Use of container.HostConfig.NetworkMode to detect host mode
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 14:11:33 +02:00
Emile Vauge
6fd8979754 Remove deprecated traefik.domain label
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 11:29:13 +02:00
Emile Vauge
050416224d Fix host Docker network
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 11:29:13 +02:00
Vincent Demeester
6e5a221180 Merge pull request #493 from containous/fix-empty-tls-flag
Fix empty tls flag
2016-07-05 11:27:53 +02:00
Emile Vauge
a1ab252303 Fix empty tls flag
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 10:56:48 +02:00
Emile Vauge
3c89fd51ee Merge pull request #491 from containous/fix-default-weight-in-loadConfig
Fix default weight in server.LoadConfig
2016-07-05 10:56:26 +02:00
Emile Vauge
018b8a6315 Fix default weight in server.LoadConfig
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 10:26:28 +02:00
Emile Vauge
ecaa146d5b Merge pull request #492 from containous/fix-webui-proxy
Fix webui proxying
2016-07-05 10:17:49 +02:00
Emile Vauge
f50a4d8c2a Fix webui proxying
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 09:34:17 +02:00
Emile Vauge
68b0e44fbd Merge pull request #490 from containous/fix-retry-headers
Fix retry headers, simplify ResponseRecorder
2016-07-05 09:18:36 +02:00
Emile Vauge
ac9946c697 Fix retry headers, simplify ResponseRecorder
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-04 19:32:19 +02:00
Vincent Demeester
a0a8bc24e8 Merge pull request #479 from containous/disable-constraints-temporarily
Disable constraints in doc until 1.1
2016-06-23 17:46:05 +02:00
Emile Vauge
06ab802bc6 Disable constraints in doc until 1.1
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-23 17:25:12 +02:00
Vincent Demeester
04ec757083 Merge pull request #477 from containous/fix-spamming-events
Fix spamming events in listenProviders
2016-06-23 17:09:59 +02:00
Emile Vauge
15e04bb55d Fix consul catalog issue with dot in serviceName
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-23 16:33:54 +02:00
Emile Vauge
e4ed7fd8f7 Fix bad circuit breaker expression
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-23 16:33:54 +02:00
Emile Vauge
fd5352b0c6 Fix empty rule
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-23 16:33:54 +02:00
Emile Vauge
606e667b88 Fix spamming events in listenProviders
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-23 16:33:54 +02:00
Vincent Demeester
2a209c23c4 Merge pull request #478 from keis/consul-stable-ordering-of-nodes
Sort nodes before creating consul catalog config
2016-06-23 16:33:22 +02:00
David Keijser
70305266dc Sort nodes before creating consul catalog config
The watch of consul can return for various reasons and not of all of
them require a reload of the config. The order of nodes provided by
consul is not stable so to ensure a identical config is generated for an
identical server set the nodes needs to be sorted before creating the
config.
2016-06-23 13:08:12 +02:00
Vincent Demeester
8e561d9f95 Merge pull request #441 from NicolasGeraud/patch-1
mount acme folder instead of file
2016-06-23 10:00:09 +02:00
Nicolas Géraud
f446cac43c mount acme folder instead of file
I you use traefik in Docker and use Let's Encrypt, you have to mount the folder containing the acme.json file instead of the file itself.
2016-06-23 00:02:01 +02:00
Vincent Demeester
7e1ceb9a3e Merge pull request #476 from containous/fix-empty-response
Fix empty responses
2016-06-22 16:39:40 +02:00
Emile Vauge
1b5e35461d Bump oxy to b57d6706e9ff606343c596940b60df7f90012d29
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-22 15:33:39 +02:00
Vincent Demeester
df75700015 Merge pull request #468 from containous/fix-marathon-tls-auth
Fix marathon TLS/basic auth
2016-06-22 14:23:16 +02:00
Emile Vauge
b586df6689 Fix marathon tests
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-22 13:56:28 +02:00
Emile Vauge
4ca2ff0495 Bump go-marathon a558128c87724cd7430060ef5aedf39f83937f55, add DCOS support
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-20 17:11:32 +02:00
Emile Vauge
93494c7e35 Fix errors load config (#470)
* Trim spaces in rules

Signed-off-by: Emile Vauge <emile@vauge.com>

* dont break the whole config while only one part is wrong

Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-20 15:19:52 +02:00
Emile Vauge
11874bc4ae Fix acme renew, add test (#472)
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-20 13:55:50 +02:00
Kevin Busse
dcf98d13c8 Fix typo in error message. (#471) 2016-06-20 12:15:31 +02:00
Vincent Demeester
2a735e815a Merge pull request #469 from kumy/patch-1
Typo: Replace French words by English ones
2016-06-18 16:24:54 +02:00
kumy
52de16b4c9 Merge branch 'master' into patch-1 2016-06-18 16:06:49 +02:00
Vincent Demeester
7133a28fdb Merge pull request #460 from containous/fix-websocket-hijack
Fix websocket connection Hijack
2016-06-18 15:50:30 +02:00
kumy
ade2ff97e0 Typo: Replace French words by English ones 2016-06-18 15:43:35 +02:00
Emile Vauge
450d86be7d Fix websocket connection Hijack
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-18 13:37:21 +02:00
Vincent Demeester
c9caf612eb Merge pull request #464 from containous/fix-memory-leak
Fix memory leak in listenProviders
2016-06-17 00:52:21 +02:00
Emile Vauge
56ef678c09 Fix memory leak in listenProviders
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-16 22:49:57 +02:00
Emile Vauge
29e647763a Merge pull request #438 from samber/TRAEFIK-311--support-docker-backend
feat(constraints): Supports constraints for docker backend
2016-06-13 00:29:28 +02:00
Samuel BERTHE
357150bcab fix(constainrs,docker): Syntax 2016-06-11 19:06:39 +02:00
Samuel BERTHE
f7224ff403 feat(constraints): Supports constraints for docker backend 2016-06-11 19:06:39 +02:00
Vincent Demeester
01ffad2e6e Merge pull request #450 from containous/fix-default-kv-configuration
Fix default KV configuration
2016-06-11 15:03:57 +02:00
Emile Vauge
223e8cafac Fix default KV configuration
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-09 23:44:49 +02:00
Emile Vauge
d1ffbd8a03 Merge pull request #443 from vdemeester/442-and-share-context
Fix panic if listContainers fails…
2016-06-09 09:37:48 +02:00
Vincent Demeester
f286cb9a34 Fix panic if listContainers fails…
… and also share context accross API call, as this is how it's meant to
be used (and it allows to skip some calls if `cancel` is called).

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-06-08 19:39:38 +02:00
190 changed files with 11414 additions and 2621 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
glide.lock binary

View File

@@ -2,16 +2,9 @@
### Building
You need either [Docker](https://github.com/docker/docker) and `make`, or `go` and `glide` in order to build traefik.
You need either [Docker](https://github.com/docker/docker) and `make` (Method 1), or `go` and `glide` (Method 2) in order to build traefik.
#### Setting up your `go` environment
- You need `go` v1.5
- You need to set `export GO15VENDOREXPERIMENT=1` environment variable
- You need `go-bindata` to be able to use `go generate` command (needed to build) : `go get github.com/jteeuwen/go-bindata/...`.
- If you clone Træfɪk into something like `~/go/src/github.com/traefik`, your `GOPATH` variable will have to be set to `~/go`: export `GOPATH=~/go`.
#### Using `Docker` and `Makefile`
#### Method 1: Using `Docker` and `Makefile`
You need to run the `binary` target. This will create binaries for Linux platform in the `dist` folder.
@@ -33,32 +26,51 @@ $ ls dist/
traefik*
```
#### Using `glide`
#### Method 2: Using `go` and `glide`
###### Setting up your `go` environment
- You need `go` v1.5+ (1.7 is acceptable)
- You need to set `$ export GO15VENDOREXPERIMENT=1` environment variable if you are using go v1.5 (it is already enabled in 1.6+)
- It is recommended you clone Træfɪk into a directory like `~/go/src/github.com/containous/traefik` (This is the official golang workspace hierarchy, and will allow dependencies to resolve properly)
- This will allow your `GOPATH` and `PATH` variable to be set to `~/go` via:
```
$ export GOPATH=~/go
$ export PATH=$PATH:$GOPATH/bin
```
This can be verified via `$ go env`
- You will want to add those 2 export lines to your `.bashrc` or `.bash_profile`
- You need `go-bindata` to be able to use `go generate` command (needed to build) : `$ go get github.com/jteeuwen/go-bindata/...` (Please note, the ellipses are required)
###### Setting up your `glide` environment
- Glide can be installed either via homebrew: `$ brew install glide` or via the official glide script: `$ curl https://glide.sh/get | sh`
The idea behind `glide` is the following :
- when checkout(ing) a project, **run `glide install`** to install
(`go get …`) the dependencies in the `GOPATH`.
- when checkout(ing) a project, run `$ glide install` from the cloned directory to install
(`go get …`) the dependencies in your `GOPATH`.
- if you need another dependency, import and use it in
the source, and **run `glide get github.com/Masterminds/cookoo`** to save it in
the source, and run `$ glide get github.com/Masterminds/cookoo` to save it in
`vendor` and add it to your `glide.yaml`.
```bash
$ glide install
# generate
# generate (Only required to integrate other components such as web dashboard)
$ go generate
# Simple go build
# Standard go build
$ go build
# Using gox to build multiple platform
$ gox "linux darwin" "386 amd64 arm" \
-output="dist/traefik_{{.OS}}-{{.Arch}}"
# run other commands like tests
$ go test ./...
ok _/home/vincent/src/github/vdemeester/traefik 0.004s
```
### Tests
##### Method 1: `Docker` and `make`
You can run unit tests using the `test-unit` target and the
integration test using the `test-integration` target.
@@ -77,7 +89,7 @@ ok github.com/containous/traefik 0.005s coverage: 4.1% of statements
Test success
```
For development purpose, you can specifiy which tests to run by using:
For development purposes, you can specify which tests to run by using:
```
# Run every tests in the MyTest suite
TESTFLAGS="-check.f MyTestSuite" make test-integration
@@ -94,6 +106,13 @@ TESTFLAGS="-check.f MyTestSuite.*Test" make test-integration
More: https://labix.org/gocheck
##### Method 2: `go` and `glide`
- Tests can be run from the cloned directory, by `$ go test ./...` which should return `ok` similar to:
```
ok _/home/vincent/src/github/vdemeester/traefik 0.004s
```
### Documentation
The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/)
@@ -113,13 +132,13 @@ Then install mkdocs with pip
$ pip install mkdocs
```
To test documentaion localy run `mkdocs serve` in the root directory, this should start a server localy to preview your changes.
To test documentation locally run `mkdocs serve` in the root directory, this should start a server locally to preview your changes.
```
$ mkdocs serve
INFO - Building documentation...
WARNING - Config value: 'theme'. Warning: The theme 'united' will be removed in an upcoming MkDocs release. See http://www.mkdocs.org/about/release-notes/ for more details
INFO - Cleaning site directory
INFO - Building documentation...
WARNING - Config value: 'theme'. Warning: The theme 'united' will be removed in an upcoming MkDocs release. See http://www.mkdocs.org/about/release-notes/ for more details
INFO - Cleaning site directory
[I 160505 22:31:24 server:281] Serving on http://127.0.0.1:8000
[I 160505 22:31:24 handlers:59] Start watching changes
[I 160505 22:31:24 handlers:61] Start detecting changes

2
.gitignore vendored
View File

@@ -2,7 +2,6 @@
gen.go
.idea
.intellij
log
*.iml
traefik
traefik.toml
@@ -13,3 +12,4 @@ static/
site/
*.log
*.exe
.DS_Store

View File

@@ -4,6 +4,7 @@ env:
- secure: btt4r13t09gQlHb6gYrvGC2yGCMMHfnp1Mz1RQedc4Mpf/FfT8aE6xmK2a2i9CCvskjrP0t/BFaS4yxIURjnFRn+ugQIEa0pLspB9UJArW/vgOSpIWM9/OQ/fg8z5XuMxN6Md4DL1/iLypMNSageA1x0TRdt89+D1N1dALpg5XRCXLFbC84TLi0gjlFuib9ibPKzEhLT+anCRJ6iZMzeupDSoaCVbAtJMoDvXw4+4AcRZ1+k4MybBLyCib5boaEOt4pTT88mz4Kk0YaMwPVJyg9Qv36VqyUcPS09Yd95LuyVQ4+tZt8Y1ccbIzULsK+sLM3hLCzxlmlpN3dQBlZJiiRtQde0mgGAKyC0P0A1XjuDTywcsa5edB+fTk1Dsewz9xZ9V0NmMz8t+UNZnaSsAPga9i86jULbXUUwMVSzVRc+Xgx02liB/8qI1xYC9FM6ilStt7rn7mF0k3KbiWhcptgeXjO6Lah9FjEKd5w4MXsdUSTi/86rQaLo+kj+XdaTrXCTulKHyRyQEUj+8V1w0oVz7pcGjePHd7y5oU9ByifVQy6sytuFBfRZvugM5bKHo+i0pcWvixrZS42DrzwxZJsspANOvqSe5ifVbvOkfUppQdCBIwptxV5N1b49XPKU3W/w34QJ8xGmKp3TFA7WwVCztriFHjPgiRpB3EG99Bg=
- REPO: $TRAVIS_REPO_SLUG
- VERSION: $TRAVIS_TAG
- CODENAME: camembert
matrix:
- DOCKER_VERSION=1.9.1
- DOCKER_VERSION=1.10.1

279
CHANGELOG.md Normal file
View File

@@ -0,0 +1,279 @@
# Change Log
## [v1.1.0-rc1](https://github.com/containous/traefik/tree/v1.1.0-rc1) (2016-09-30)
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0...v1.1.0-rc1)
**Implemented enhancements:**
- Feature Request: SSL Cipher Selection [\#535](https://github.com/containous/traefik/issues/535)
- Error with -consulcatalog and missing load balance method on 1.0.0 [\#524](https://github.com/containous/traefik/issues/524)
- Running Traefik with Docker 1.12 Swarm Mode [\#504](https://github.com/containous/traefik/issues/504)
- Kubernetes provider: should allow the master url to be override [\#501](https://github.com/containous/traefik/issues/501)
- \[FRONTEND\]\[LE\] Pre-generate SSL certificates for "Host:" rules [\#483](https://github.com/containous/traefik/issues/483)
- Frontend Rule evolution [\#437](https://github.com/containous/traefik/issues/437)
- Add a Changelog [\#388](https://github.com/containous/traefik/issues/388)
- Add label matching for kubernetes ingests [\#363](https://github.com/containous/traefik/issues/363)
- Acme in HA Traefik Scenario [\#348](https://github.com/containous/traefik/issues/348)
- HTTP Basic Auth support [\#77](https://github.com/containous/traefik/issues/77)
- Session affinity / stickiness / persistence [\#5](https://github.com/containous/traefik/issues/5)
- Kubernetes provider: traefik.frontend.rule.type logging [\#668](https://github.com/containous/traefik/pull/668) ([yvespp](https://github.com/yvespp))
**Fixed bugs:**
- traefik hangs - stops handling requests [\#662](https://github.com/containous/traefik/issues/662)
- Add long jobs in exponential backoff providers [\#626](https://github.com/containous/traefik/issues/626)
- Tip of tree crashes on invalid pointer on Marathon provider [\#624](https://github.com/containous/traefik/issues/624)
- ACME: revoke certificate on agreement update [\#579](https://github.com/containous/traefik/issues/579)
- WebUI: Providers tabs disappeared [\#577](https://github.com/containous/traefik/issues/577)
- traefik version command contains incorrect information when building from master branch [\#569](https://github.com/containous/traefik/issues/569)
- Flag --etcd.endpoint default [\#508](https://github.com/containous/traefik/issues/508)
- Conditional ACME on demand generation [\#505](https://github.com/containous/traefik/issues/505)
- Important delay with streams \(Mozilla EventSource\) [\#503](https://github.com/containous/traefik/issues/503)
- Traefik crashing [\#458](https://github.com/containous/traefik/issues/458)
- traefik.toml constraints error: `Expected map but found 'string'.` [\#451](https://github.com/containous/traefik/issues/451)
- Multiple path separators in the url path causing redirect [\#167](https://github.com/containous/traefik/issues/167)
**Closed issues:**
- Global InsecureSkipVerify does not work [\#700](https://github.com/containous/traefik/issues/700)
- \[documentation/feature\] Consul/etcd support atomic multiple key changes now [\#698](https://github.com/containous/traefik/issues/698)
- How to configure which network to use when starting traefik binary? [\#694](https://github.com/containous/traefik/issues/694)
- How to get multiple host headers working for docker labels? [\#692](https://github.com/containous/traefik/issues/692)
- Requests with URL-encoded characters are not forwarded correctly [\#684](https://github.com/containous/traefik/issues/684)
- Issue with global InsecureSkipVerify = true and self signed certificates [\#667](https://github.com/containous/traefik/issues/667)
- Docker exposedbydefault = false didn't work [\#663](https://github.com/containous/traefik/issues/663)
- \[ACME\] Auto SAN Detection [\#655](https://github.com/containous/traefik/issues/655)
- Fronting a domain with DNS A-record round-robin & ACME [\#654](https://github.com/containous/traefik/issues/654)
- Overriding toml configuration with environment variables [\#650](https://github.com/containous/traefik/issues/650)
- marathon provider exposedByDefault = false [\#647](https://github.com/containous/traefik/issues/647)
- Add status URL for service up checks [\#642](https://github.com/containous/traefik/issues/642)
- acme's storage file, containing private key, is word readable [\#638](https://github.com/containous/traefik/issues/638)
- wildcard domain with exclusions [\#633](https://github.com/containous/traefik/issues/633)
- Enable evenly distribution among backend [\#631](https://github.com/containous/traefik/issues/631)
- Traefik sporadically failing when proxying requests [\#615](https://github.com/containous/traefik/issues/615)
- TCP Proxy [\#608](https://github.com/containous/traefik/issues/608)
- How to use in Windows? [\#605](https://github.com/containous/traefik/issues/605)
- `ClientCAFiles` ignored [\#604](https://github.com/containous/traefik/issues/604)
- Let`s Encrypt enable in etcd [\#600](https://github.com/containous/traefik/issues/600)
- Support HTTP Basic Auth [\#599](https://github.com/containous/traefik/issues/599)
- Consul KV seem broken [\#587](https://github.com/containous/traefik/issues/587)
- HTTPS entryPoint not working [\#574](https://github.com/containous/traefik/issues/574)
- Traefik stuck when used as frontend for a streaming API [\#560](https://github.com/containous/traefik/issues/560)
- Exclude some frontends in consul catalog [\#555](https://github.com/containous/traefik/issues/555)
- Can I use Traefik without a domain name? [\#539](https://github.com/containous/traefik/issues/539)
- Priortities in 1.0.0 not behaving [\#506](https://github.com/containous/traefik/issues/506)
- Route by path [\#500](https://github.com/containous/traefik/issues/500)
- Container IP Lost [\#375](https://github.com/containous/traefik/issues/375)
**Merged pull requests:**
- Add HTTP compression [\#702](https://github.com/containous/traefik/pull/702) ([tuier](https://github.com/tuier))
- Carry PR 446 - Add sticky session support \(round two!\) [\#701](https://github.com/containous/traefik/pull/701) ([emilevauge](https://github.com/emilevauge))
- Remove unused endpoint when using constraints with Marathon provider [\#697](https://github.com/containous/traefik/pull/697) ([tuier](https://github.com/tuier))
- Replace imagelayers.io with microbadger [\#696](https://github.com/containous/traefik/pull/696) ([solidnerd](https://github.com/solidnerd))
- Selectable TLS Versions [\#690](https://github.com/containous/traefik/pull/690) ([dtomcej](https://github.com/dtomcej))
- Carry pr 439 [\#689](https://github.com/containous/traefik/pull/689) ([emilevauge](https://github.com/emilevauge))
- Disable gorilla/mux URL cleaning to prevent sending redirect [\#688](https://github.com/containous/traefik/pull/688) ([ydubreuil](https://github.com/ydubreuil))
- Some fixes [\#687](https://github.com/containous/traefik/pull/687) ([emilevauge](https://github.com/emilevauge))
- feat\(constraints\): Supports constraints for Marathon provider [\#686](https://github.com/containous/traefik/pull/686) ([tuier](https://github.com/tuier))
- Update docs to improve contribution setup [\#685](https://github.com/containous/traefik/pull/685) ([dtomcej](https://github.com/dtomcej))
- Add basic auth support for web backend [\#677](https://github.com/containous/traefik/pull/677) ([SantoDE](https://github.com/SantoDE))
- Document accepted values for logLevel. [\#676](https://github.com/containous/traefik/pull/676) ([jimmycuadra](https://github.com/jimmycuadra))
- If Marathon doesn't have healthcheck, assume it's ok [\#665](https://github.com/containous/traefik/pull/665) ([gomes](https://github.com/gomes))
- ACME: renew certificates 30 days before expiry [\#660](https://github.com/containous/traefik/pull/660) ([JayH5](https://github.com/JayH5))
- Update broken link and add a comment to sample config file [\#658](https://github.com/containous/traefik/pull/658) ([Yggdrasil](https://github.com/Yggdrasil))
- Add possibility to use BindPort IPAddress 👼 [\#657](https://github.com/containous/traefik/pull/657) ([vdemeester](https://github.com/vdemeester))
- Update marathon [\#648](https://github.com/containous/traefik/pull/648) ([emilevauge](https://github.com/emilevauge))
- Add backend features to docker [\#646](https://github.com/containous/traefik/pull/646) ([jangie](https://github.com/jangie))
- enable consul catalog to use maxconn [\#645](https://github.com/containous/traefik/pull/645) ([jangie](https://github.com/jangie))
- Adopt the Code Of Coduct from http://contributor-covenant.org [\#641](https://github.com/containous/traefik/pull/641) ([errm](https://github.com/errm))
- Use secure mode 600 instead of 644 for acme.json [\#639](https://github.com/containous/traefik/pull/639) ([discordianfish](https://github.com/discordianfish))
- docker clarification, fix dead urls, misc typos [\#637](https://github.com/containous/traefik/pull/637) ([djalal](https://github.com/djalal))
- add PING handler to dashboard API [\#630](https://github.com/containous/traefik/pull/630) ([jangie](https://github.com/jangie))
- Migrate to JobBackOff [\#628](https://github.com/containous/traefik/pull/628) ([emilevauge](https://github.com/emilevauge))
- Add long job exponential backoff [\#627](https://github.com/containous/traefik/pull/627) ([emilevauge](https://github.com/emilevauge))
- HA acme support [\#625](https://github.com/containous/traefik/pull/625) ([emilevauge](https://github.com/emilevauge))
- Bump go v1.7 [\#620](https://github.com/containous/traefik/pull/620) ([emilevauge](https://github.com/emilevauge))
- Make duration logging consistent [\#619](https://github.com/containous/traefik/pull/619) ([jangie](https://github.com/jangie))
- fix for nil clientTLS causing issue [\#617](https://github.com/containous/traefik/pull/617) ([jangie](https://github.com/jangie))
- Add ability for marathon provider to set maxconn values, loadbalancer algorithm, and circuit breaker expression [\#616](https://github.com/containous/traefik/pull/616) ([jangie](https://github.com/jangie))
- Make systemd unit installable [\#613](https://github.com/containous/traefik/pull/613) ([keis](https://github.com/keis))
- Merge v1.0.2 master [\#610](https://github.com/containous/traefik/pull/610) ([emilevauge](https://github.com/emilevauge))
- update staert and flaeg [\#609](https://github.com/containous/traefik/pull/609) ([cocap10](https://github.com/cocap10))
- \#504 Initial support for Docker 1.12 Swarm Mode [\#602](https://github.com/containous/traefik/pull/602) ([diegofernandes](https://github.com/diegofernandes))
- Add Host cert ACME generation [\#601](https://github.com/containous/traefik/pull/601) ([emilevauge](https://github.com/emilevauge))
- Fixed binary script so traefik version command doesn't just print default values [\#598](https://github.com/containous/traefik/pull/598) ([keiths-osc](https://github.com/keiths-osc))
- Name servers after thier pods [\#596](https://github.com/containous/traefik/pull/596) ([errm](https://github.com/errm))
- Fix Consul prefix [\#589](https://github.com/containous/traefik/pull/589) ([jippi](https://github.com/jippi))
- Prioritize kubernetes routes by path length [\#588](https://github.com/containous/traefik/pull/588) ([philk](https://github.com/philk))
- beautify help [\#580](https://github.com/containous/traefik/pull/580) ([cocap10](https://github.com/cocap10))
- Upgrade directives name since we use angular-ui-bootstrap [\#578](https://github.com/containous/traefik/pull/578) ([micaelmbagira](https://github.com/micaelmbagira))
- Fix basic docs for configuration of multiple rules [\#576](https://github.com/containous/traefik/pull/576) ([ajaegle](https://github.com/ajaegle))
- Fix k8s watch [\#573](https://github.com/containous/traefik/pull/573) ([errm](https://github.com/errm))
- Add requirements.txt for netlify [\#567](https://github.com/containous/traefik/pull/567) ([emilevauge](https://github.com/emilevauge))
- Merge v1.0.1 master [\#565](https://github.com/containous/traefik/pull/565) ([emilevauge](https://github.com/emilevauge))
- Move webui to FountainJS with Webpack [\#558](https://github.com/containous/traefik/pull/558) ([micaelmbagira](https://github.com/micaelmbagira))
- Add global InsecureSkipVerify option to disable certificate checking [\#557](https://github.com/containous/traefik/pull/557) ([stuart-c](https://github.com/stuart-c))
- Move version.go in its own package… [\#553](https://github.com/containous/traefik/pull/553) ([vdemeester](https://github.com/vdemeester))
- Upgrade libkermit and dependencies [\#552](https://github.com/containous/traefik/pull/552) ([vdemeester](https://github.com/vdemeester))
- Add command storeconfig [\#551](https://github.com/containous/traefik/pull/551) ([cocap10](https://github.com/cocap10))
- Add basic/digest auth [\#547](https://github.com/containous/traefik/pull/547) ([emilevauge](https://github.com/emilevauge))
- Bump node to 6 for webui [\#546](https://github.com/containous/traefik/pull/546) ([vdemeester](https://github.com/vdemeester))
- Bump golang to 1.6.3 [\#545](https://github.com/containous/traefik/pull/545) ([vdemeester](https://github.com/vdemeester))
- Fix typos [\#538](https://github.com/containous/traefik/pull/538) ([jimt](https://github.com/jimt))
- Kubernetes user-guide [\#519](https://github.com/containous/traefik/pull/519) ([errm](https://github.com/errm))
- Implement Kubernetes Selectors, minor kube endpoint fix [\#516](https://github.com/containous/traefik/pull/516) ([pnegahdar](https://github.com/pnegahdar))
- Carry \#358 : Option to disable expose of all docker containers [\#514](https://github.com/containous/traefik/pull/514) ([vdemeester](https://github.com/vdemeester))
- Remove traefik.frontend.value support in docker… [\#510](https://github.com/containous/traefik/pull/510) ([vdemeester](https://github.com/vdemeester))
- Use KvStores as global config sources [\#481](https://github.com/containous/traefik/pull/481) ([cocap10](https://github.com/cocap10))
- Add endpoint option to authenticate by client tls cert. [\#461](https://github.com/containous/traefik/pull/461) ([andersbetner](https://github.com/andersbetner))
- add mesos provider inspired by mesos-dns & marathon provider [\#353](https://github.com/containous/traefik/pull/353) ([skydjol](https://github.com/skydjol))
## [v1.0.2](https://github.com/containous/traefik/tree/v1.0.2) (2016-08-02)
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.1...v1.0.2)
**Fixed bugs:**
- ACME: revoke certificate on agreement update [\#579](https://github.com/containous/traefik/issues/579)
**Closed issues:**
- Exclude some frontends in consul catalog [\#555](https://github.com/containous/traefik/issues/555)
**Merged pull requests:**
- Bump oxy version, fix streaming [\#584](https://github.com/containous/traefik/pull/584) ([emilevauge](https://github.com/emilevauge))
- Fix ACME TOS [\#582](https://github.com/containous/traefik/pull/582) ([emilevauge](https://github.com/emilevauge))
## [v1.0.1](https://github.com/containous/traefik/tree/v1.0.1) (2016-07-19)
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0...v1.0.1)
**Implemented enhancements:**
- Error with -consulcatalog and missing load balance method on 1.0.0 [\#524](https://github.com/containous/traefik/issues/524)
- Kubernetes provider: should allow the master url to be override [\#501](https://github.com/containous/traefik/issues/501)
**Fixed bugs:**
- Flag --etcd.endpoint default [\#508](https://github.com/containous/traefik/issues/508)
- Conditional ACME on demand generation [\#505](https://github.com/containous/traefik/issues/505)
- Important delay with streams \(Mozilla EventSource\) [\#503](https://github.com/containous/traefik/issues/503)
**Closed issues:**
- Can I use Traefik without a domain name? [\#539](https://github.com/containous/traefik/issues/539)
- Priortities in 1.0.0 not behaving [\#506](https://github.com/containous/traefik/issues/506)
- Route by path [\#500](https://github.com/containous/traefik/issues/500)
**Merged pull requests:**
- Update server.go [\#531](https://github.com/containous/traefik/pull/531) ([Jsewill](https://github.com/Jsewill))
- Add sse support [\#527](https://github.com/containous/traefik/pull/527) ([emilevauge](https://github.com/emilevauge))
- Fix acme checkOnDemandDomain [\#512](https://github.com/containous/traefik/pull/512) ([emilevauge](https://github.com/emilevauge))
- Fix default etcd port [\#511](https://github.com/containous/traefik/pull/511) ([errm](https://github.com/errm))
## [v1.0.0](https://github.com/containous/traefik/tree/v1.0.0) (2016-07-05)
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0-rc3...v1.0.0)
**Fixed bugs:**
- Enable to define empty TLS option by flag for Let's Encrypt [\#488](https://github.com/containous/traefik/issues/488)
- \[Docker\] No IP in backend in host networking mode [\#487](https://github.com/containous/traefik/issues/487)
- Response is compressed when not requested [\#485](https://github.com/containous/traefik/issues/485)
- loadConfig modifies configuration causing same config check to fail [\#480](https://github.com/containous/traefik/issues/480)
**Closed issues:**
- svg logo [\#482](https://github.com/containous/traefik/issues/482)
- etcd tries to connect with TLS even with --etcd.tls=false [\#456](https://github.com/containous/traefik/issues/456)
- Zookeeper - KV connection error: Failed to test KV store connection [\#455](https://github.com/containous/traefik/issues/455)
- "Not Found" api response needed instead of 404 [\#454](https://github.com/containous/traefik/issues/454)
- domain label doesn't work on docker [\#447](https://github.com/containous/traefik/issues/447)
- Any chance of a windows release? [\#425](https://github.com/containous/traefik/issues/425)
**Merged pull requests:**
- Fix windows builds [\#495](https://github.com/containous/traefik/pull/495) ([emilevauge](https://github.com/emilevauge))
- Fix host Docker network [\#494](https://github.com/containous/traefik/pull/494) ([emilevauge](https://github.com/emilevauge))
- Fix empty tls flag [\#493](https://github.com/containous/traefik/pull/493) ([emilevauge](https://github.com/emilevauge))
- Fix webui proxying [\#492](https://github.com/containous/traefik/pull/492) ([emilevauge](https://github.com/emilevauge))
- Fix default weight in server.LoadConfig [\#491](https://github.com/containous/traefik/pull/491) ([emilevauge](https://github.com/emilevauge))
- Fix retry headers, simplify ResponseRecorder [\#490](https://github.com/containous/traefik/pull/490) ([emilevauge](https://github.com/emilevauge))
## [v1.0.0-rc3](https://github.com/containous/traefik/tree/v1.0.0-rc3) (2016-06-23)
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0-rc2...v1.0.0-rc3)
**Implemented enhancements:**
- support more than one rule to Docker backend [\#419](https://github.com/containous/traefik/issues/419)
**Fixed bugs:**
- consulCatalog issue when serviceName contains a dot [\#475](https://github.com/containous/traefik/issues/475)
- Issue with empty responses [\#463](https://github.com/containous/traefik/issues/463)
- Severe memory leak in beta.470 and beyond crashes Traefik server [\#462](https://github.com/containous/traefik/issues/462)
- Marathon that starts with a space causes parsing errors. [\#459](https://github.com/containous/traefik/issues/459)
- A frontend route without a rule \(or empty rule\) causes a crash when traefik starts [\#453](https://github.com/containous/traefik/issues/453)
- container dropped out when connecting to Docker Swarm [\#442](https://github.com/containous/traefik/issues/442)
- Traefik setting Accept-Encoding: gzip on requests \(Traefik may also be broken with chunked responses\) [\#421](https://github.com/containous/traefik/issues/421)
**Closed issues:**
- HTTP headers case gets modified [\#466](https://github.com/containous/traefik/issues/466)
- File frontend \> Marathon Backend [\#465](https://github.com/containous/traefik/issues/465)
- Websocket: Unable to hijack the connection [\#452](https://github.com/containous/traefik/issues/452)
- kubernetes: Received event spamming? [\#449](https://github.com/containous/traefik/issues/449)
- kubernetes: backends not updated when i scale replication controller? [\#448](https://github.com/containous/traefik/issues/448)
- Add href link on frontend [\#436](https://github.com/containous/traefik/issues/436)
- Multiple Domains Rule [\#430](https://github.com/containous/traefik/issues/430)
**Merged pull requests:**
- Disable constraints in doc until 1.1 [\#479](https://github.com/containous/traefik/pull/479) ([emilevauge](https://github.com/emilevauge))
- Sort nodes before creating consul catalog config [\#478](https://github.com/containous/traefik/pull/478) ([keis](https://github.com/keis))
- Fix spamming events in listenProviders [\#477](https://github.com/containous/traefik/pull/477) ([emilevauge](https://github.com/emilevauge))
- Fix empty responses [\#476](https://github.com/containous/traefik/pull/476) ([emilevauge](https://github.com/emilevauge))
- Fix acme renew [\#472](https://github.com/containous/traefik/pull/472) ([emilevauge](https://github.com/emilevauge))
- Fix typo in error message. [\#471](https://github.com/containous/traefik/pull/471) ([KevinBusse](https://github.com/KevinBusse))
- Fix errors load config [\#470](https://github.com/containous/traefik/pull/470) ([emilevauge](https://github.com/emilevauge))
- Typo: Replace French words by English ones [\#469](https://github.com/containous/traefik/pull/469) ([kumy](https://github.com/kumy))
- Fix marathon TLS/basic auth [\#468](https://github.com/containous/traefik/pull/468) ([emilevauge](https://github.com/emilevauge))
- Fix memory leak in listenProviders [\#464](https://github.com/containous/traefik/pull/464) ([emilevauge](https://github.com/emilevauge))
- Fix websocket connection Hijack [\#460](https://github.com/containous/traefik/pull/460) ([emilevauge](https://github.com/emilevauge))
- Fix default KV configuration [\#450](https://github.com/containous/traefik/pull/450) ([emilevauge](https://github.com/emilevauge))
- Fix panic if listContainers fails… [\#443](https://github.com/containous/traefik/pull/443) ([vdemeester](https://github.com/vdemeester))
- mount acme folder instead of file [\#441](https://github.com/containous/traefik/pull/441) ([NicolasGeraud](https://github.com/NicolasGeraud))
- feat\(constraints\): Supports constraints for docker backend [\#438](https://github.com/containous/traefik/pull/438) ([samber](https://github.com/samber))
## [v1.0.0-rc2](https://github.com/containous/traefik/tree/v1.0.0-rc2) (2016-06-07)
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0-rc1...v1.0.0-rc2)
**Implemented enhancements:**
- Add @samber to maintainers [\#440](https://github.com/containous/traefik/pull/440) ([emilevauge](https://github.com/emilevauge))
**Fixed bugs:**
- Panic on help [\#429](https://github.com/containous/traefik/issues/429)
- Bad default values in configuration [\#427](https://github.com/containous/traefik/issues/427)
**Closed issues:**
- Traefik doesn't listen on IPv4 ports [\#434](https://github.com/containous/traefik/issues/434)
- Not listening on port 80 [\#432](https://github.com/containous/traefik/issues/432)
- docs need updating for new frontend rules format [\#423](https://github.com/containous/traefik/issues/423)
- Does traefik supports for Mac? \(For devlelopment\) [\#417](https://github.com/containous/traefik/issues/417)
**Merged pull requests:**
- Allow multiple rules [\#435](https://github.com/containous/traefik/pull/435) ([fclaeys](https://github.com/fclaeys))
- Add routes priorities [\#433](https://github.com/containous/traefik/pull/433) ([emilevauge](https://github.com/emilevauge))
- Fix default configuration [\#428](https://github.com/containous/traefik/pull/428) ([emilevauge](https://github.com/emilevauge))
- Fix marathon groups subdomain [\#426](https://github.com/containous/traefik/pull/426) ([emilevauge](https://github.com/emilevauge))
- Fix travis tag check [\#422](https://github.com/containous/traefik/pull/422) ([emilevauge](https://github.com/emilevauge))
- log info about TOML configuration file using [\#420](https://github.com/containous/traefik/pull/420) ([cocap10](https://github.com/cocap10))
- Doc about skipping some integration tests with '-check.f ConsulCatalogSuite' [\#418](https://github.com/containous/traefik/pull/418) ([samber](https://github.com/samber))
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*

74
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,74 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at contact@containo.us
All complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@@ -5,7 +5,8 @@ TRAEFIK_ENVS := \
-e OS_PLATFORM_ARG \
-e TESTFLAGS \
-e VERBOSE \
-e VERSION
-e VERSION \
-e CODENAME
SRCS = $(shell git ls-files '*.go' | grep -v '^external/')
@@ -72,7 +73,7 @@ run-dev:
generate-webui: build-webui
if [ ! -d "static" ]; then \
mkdir -p static; \
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui gulp; \
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui npm run build; \
echo 'For more informations show `webui/readme.md`' > $$PWD/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md; \
fi

View File

@@ -6,14 +6,14 @@
[![Build Status](https://travis-ci.org/containous/traefik.svg?branch=master)](https://travis-ci.org/containous/traefik)
[![Docs](https://img.shields.io/badge/docs-current-brightgreen.svg)](https://docs.traefik.io)
[![Go Report Card](https://goreportcard.com/badge/kubernetes/helm)](http://goreportcard.com/report/containous/traefik)
[![Image Layer](https://badge.imagelayers.io/traefik:latest.svg)](https://imagelayers.io/?images=traefik)
[![](https://images.microbadger.com/badges/image/traefik.svg)](https://microbadger.com/images/traefik)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/containous/traefik/blob/master/LICENSE.md)
[![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com)
[![Twitter](https://img.shields.io/twitter/follow/traefikproxy.svg?style=social)](https://twitter.com/intent/follow?screen_name=traefikproxy)
Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Kubernetes](http://kubernetes.io/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Kubernetes](http://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Mesos](https://github.com/apache/mesos), [Kubernetes](http://kubernetes.io/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
## Overview
@@ -45,8 +45,8 @@ Run it and forget it!
- [It's fast](http://docs.traefik.io/benchmarks)
- No dependency hell, single binary made with go
- Rest API
- Multiple backends supported: Docker, Mesos/Marathon, Consul, Etcd, and more to come
- Watchers for backends, can listen change in backends to apply a new configuration automatically
- Multiple backends supported: Docker, Swarm, Kubernetes, Marathon, Mesos, Consul, Etcd, and more to come
- Watchers for backends, can listen for changes in backends to apply a new configuration automatically
- Hot-reloading of configuration. No need to restart the process
- Graceful shutdown http connections
- Circuit breakers on backends
@@ -59,13 +59,15 @@ Run it and forget it!
- Websocket support
- HTTP/2 support
- Retry request if network error
- [Let's Encrypt](https://letsencrypt.org) support (Automatic HTTPS)
- [Let's Encrypt](https://letsencrypt.org) support (Automatic HTTPS with renewal)
- High Availability with cluster mode
## Demo
## Quickstart
You can have a quick look at Træfɪk in this [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers.
Here is a talk (in french) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference.
You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Lets'Encrypt.
Here is a talk (in French) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference.
You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Let's Encrypt.
[![Traefik Devoxx France](http://img.youtube.com/vi/QvAz9mVx5TI/0.jpg)](http://www.youtube.com/watch?v=QvAz9mVx5TI)
@@ -112,10 +114,14 @@ You can find the complete documentation [here](https://docs.traefik.io).
Please refer to [this section](.github/CONTRIBUTING.md).
## Code Of Conduct
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
## Support
You can join [![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com) to get basic support.
If you prefer a commercial support, please contact [containo.us](https://containo.us) by mail: <mailto:support@containo.us>.
If you prefer commercial support, please contact [containo.us](https://containo.us) by mail: <mailto:support@containo.us>.
## Træfɪk here and there
@@ -145,9 +151,12 @@ software products.
Founded in 2014, Asteris creates next-generation infrastructure software for the modern datacenter. Asteris writes software that makes it easy for companies to implement continuous delivery and realtime data pipelines. We support the HashiCorp stack, along with Kubernetes, Apache Mesos, Spark and Kafka. We're core committers on mantl.io, consul-cli and mesos-consul.
## Maintainers
- Emile Vauge [@emilevauge](https://github.com/emilevauge)
- Vincent Demeester [@vdemeester](https://github.com/vdemeester)
- Samuel Berthe [@samber](https://github.com/samber)
- Russell Clare [@Russell-IO](https://github.com/Russell-IO)
- Ed Robinson [@errm](https://github.com/errm)
## Credits

202
acme/account.go Normal file
View File

@@ -0,0 +1,202 @@
package acme
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"errors"
"github.com/containous/traefik/log"
"github.com/xenolf/lego/acme"
"reflect"
"sync"
"time"
)
// Account is used to store lets encrypt registration info
type Account struct {
Email string
Registration *acme.RegistrationResource
PrivateKey []byte
DomainsCertificate DomainsCertificates
ChallengeCerts map[string]*ChallengeCert
}
// ChallengeCert stores a challenge certificate
type ChallengeCert struct {
Certificate []byte
PrivateKey []byte
certificate *tls.Certificate
}
// Init inits acccount struct
func (a *Account) Init() error {
err := a.DomainsCertificate.Init()
if err != nil {
return err
}
for _, cert := range a.ChallengeCerts {
if cert.certificate == nil {
certificate, err := tls.X509KeyPair(cert.Certificate, cert.PrivateKey)
if err != nil {
return err
}
cert.certificate = &certificate
}
if cert.certificate.Leaf == nil {
leaf, err := x509.ParseCertificate(cert.certificate.Certificate[0])
if err != nil {
return err
}
cert.certificate.Leaf = leaf
}
}
return nil
}
// NewAccount creates an account
func NewAccount(email string) (*Account, error) {
// Create a user. New accounts need an email and private key to start
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, err
}
domainsCerts := DomainsCertificates{Certs: []*DomainsCertificate{}}
domainsCerts.Init()
return &Account{
Email: email,
PrivateKey: x509.MarshalPKCS1PrivateKey(privateKey),
DomainsCertificate: DomainsCertificates{Certs: domainsCerts.Certs},
ChallengeCerts: map[string]*ChallengeCert{}}, nil
}
// GetEmail returns email
func (a *Account) GetEmail() string {
return a.Email
}
// GetRegistration returns lets encrypt registration resource
func (a *Account) GetRegistration() *acme.RegistrationResource {
return a.Registration
}
// GetPrivateKey returns private key
func (a *Account) GetPrivateKey() crypto.PrivateKey {
if privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey); err == nil {
return privateKey
}
log.Errorf("Cannot unmarshall private key %+v", a.PrivateKey)
return nil
}
// Certificate is used to store certificate info
type Certificate struct {
Domain string
CertURL string
CertStableURL string
PrivateKey []byte
Certificate []byte
}
// DomainsCertificates stores a certificate for multiple domains
type DomainsCertificates struct {
Certs []*DomainsCertificate
lock sync.RWMutex
}
// Init inits DomainsCertificates
func (dc *DomainsCertificates) Init() error {
dc.lock.Lock()
defer dc.lock.Unlock()
for _, domainsCertificate := range dc.Certs {
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
if err != nil {
return err
}
domainsCertificate.tlsCert = &tlsCert
}
return nil
}
func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain Domain) error {
dc.lock.Lock()
defer dc.lock.Unlock()
for _, domainsCertificate := range dc.Certs {
if reflect.DeepEqual(domain, domainsCertificate.Domains) {
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
if err != nil {
return err
}
domainsCertificate.Certificate = acmeCert
domainsCertificate.tlsCert = &tlsCert
return nil
}
}
return errors.New("Certificate to renew not found for domain " + domain.Main)
}
func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, domain Domain) (*DomainsCertificate, error) {
dc.lock.Lock()
defer dc.lock.Unlock()
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
if err != nil {
return nil, err
}
cert := DomainsCertificate{Domains: domain, Certificate: acmeCert, tlsCert: &tlsCert}
dc.Certs = append(dc.Certs, &cert)
return &cert, nil
}
func (dc *DomainsCertificates) getCertificateForDomain(domainToFind string) (*DomainsCertificate, bool) {
dc.lock.RLock()
defer dc.lock.RUnlock()
for _, domainsCertificate := range dc.Certs {
domains := []string{}
domains = append(domains, domainsCertificate.Domains.Main)
domains = append(domains, domainsCertificate.Domains.SANs...)
for _, domain := range domains {
if domain == domainToFind {
return domainsCertificate, true
}
}
}
return nil, false
}
func (dc *DomainsCertificates) exists(domainToFind Domain) (*DomainsCertificate, bool) {
dc.lock.RLock()
defer dc.lock.RUnlock()
for _, domainsCertificate := range dc.Certs {
if reflect.DeepEqual(domainToFind, domainsCertificate.Domains) {
return domainsCertificate, true
}
}
return nil, false
}
// DomainsCertificate contains a certificate for multiple domains
type DomainsCertificate struct {
Domains Domain
Certificate *Certificate
tlsCert *tls.Certificate
}
func (dc *DomainsCertificate) needRenew() bool {
for _, c := range dc.tlsCert.Certificate {
crt, err := x509.ParseCertificate(c)
if err != nil {
// If there's an error, we assume the cert is broken, and needs update
return true
}
// <= 7 days left, renew certificate
if crt.NotAfter.Before(time.Now().Add(time.Duration(24 * 30 * time.Hour))) {
return true
}
}
return false
}

View File

@@ -1,174 +1,38 @@
package acme
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/cenk/backoff"
"github.com/containous/staert"
"github.com/containous/traefik/cluster"
"github.com/containous/traefik/log"
"github.com/containous/traefik/safe"
"github.com/xenolf/lego/acme"
"golang.org/x/net/context"
"io/ioutil"
fmtlog "log"
"os"
"reflect"
"strings"
"sync"
"time"
)
// Account is used to store lets encrypt registration info
type Account struct {
Email string
Registration *acme.RegistrationResource
PrivateKey []byte
DomainsCertificate DomainsCertificates
}
// GetEmail returns email
func (a Account) GetEmail() string {
return a.Email
}
// GetRegistration returns lets encrypt registration resource
func (a Account) GetRegistration() *acme.RegistrationResource {
return a.Registration
}
// GetPrivateKey returns private key
func (a Account) GetPrivateKey() crypto.PrivateKey {
if privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey); err == nil {
return privateKey
}
log.Errorf("Cannot unmarshall private key %+v", a.PrivateKey)
return nil
}
// Certificate is used to store certificate info
type Certificate struct {
Domain string
CertURL string
CertStableURL string
PrivateKey []byte
Certificate []byte
}
// DomainsCertificates stores a certificate for multiple domains
type DomainsCertificates struct {
Certs []*DomainsCertificate
lock *sync.RWMutex
}
func (dc *DomainsCertificates) init() error {
if dc.lock == nil {
dc.lock = &sync.RWMutex{}
}
dc.lock.Lock()
defer dc.lock.Unlock()
for _, domainsCertificate := range dc.Certs {
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
if err != nil {
return err
}
domainsCertificate.tlsCert = &tlsCert
}
return nil
}
func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain Domain) error {
dc.lock.Lock()
defer dc.lock.Unlock()
for _, domainsCertificate := range dc.Certs {
if reflect.DeepEqual(domain, domainsCertificate.Domains) {
domainsCertificate.Certificate = acmeCert
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
if err != nil {
return err
}
domainsCertificate.tlsCert = &tlsCert
return nil
}
}
return errors.New("Certificate to renew not found for domain " + domain.Main)
}
func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, domain Domain) (*DomainsCertificate, error) {
dc.lock.Lock()
defer dc.lock.Unlock()
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
if err != nil {
return nil, err
}
cert := DomainsCertificate{Domains: domain, Certificate: acmeCert, tlsCert: &tlsCert}
dc.Certs = append(dc.Certs, &cert)
return &cert, nil
}
func (dc *DomainsCertificates) getCertificateForDomain(domainToFind string) (*DomainsCertificate, bool) {
dc.lock.RLock()
defer dc.lock.RUnlock()
for _, domainsCertificate := range dc.Certs {
domains := []string{}
domains = append(domains, domainsCertificate.Domains.Main)
domains = append(domains, domainsCertificate.Domains.SANs...)
for _, domain := range domains {
if domain == domainToFind {
return domainsCertificate, true
}
}
}
return nil, false
}
func (dc *DomainsCertificates) exists(domainToFind Domain) (*DomainsCertificate, bool) {
dc.lock.RLock()
defer dc.lock.RUnlock()
for _, domainsCertificate := range dc.Certs {
if reflect.DeepEqual(domainToFind, domainsCertificate.Domains) {
return domainsCertificate, true
}
}
return nil, false
}
// DomainsCertificate contains a certificate for multiple domains
type DomainsCertificate struct {
Domains Domain
Certificate *Certificate
tlsCert *tls.Certificate
}
func (dc *DomainsCertificate) needRenew() bool {
for _, c := range dc.tlsCert.Certificate {
crt, err := x509.ParseCertificate(c)
if err != nil {
// If there's an error, we assume the cert is broken, and needs update
return true
}
// <= 7 days left, renew certificate
if crt.NotAfter.Before(time.Now().Add(time.Duration(24 * 7 * time.Hour))) {
return true
}
}
return false
}
// ACME allows to connect to lets encrypt and retrieve certs
type ACME struct {
Email string `description:"Email address used for registration"`
Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
StorageFile string `description:"File used for certificates storage."`
OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
CAServer string `description:"CA server to use."`
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
storageLock sync.RWMutex
Email string `description:"Email address used for registration"`
Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
Storage string `description:"File or key used for certificates storage."`
StorageFile string // deprecated
OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
CAServer string `description:"CA server to use."`
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
client *acme.Client
defaultCertificate *tls.Certificate
store cluster.Store
challengeProvider *challengeProvider
checkOnDemandDomain func(domain string) bool
}
//Domains parse []Domain
@@ -212,60 +76,189 @@ type Domain struct {
SANs []string
}
// CreateConfig creates a tls.config from using ACME configuration
func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(domain string) bool) error {
func (a *ACME) init() error {
acme.Logger = fmtlog.New(ioutil.Discard, "", 0)
if len(a.StorageFile) == 0 {
return errors.New("Empty StorageFile, please provide a filename for certs storage")
}
log.Debugf("Generating default certificate...")
if len(tlsConfig.Certificates) == 0 {
// no certificates in TLS config, so we add a default one
cert, err := generateDefaultCertificate()
if err != nil {
return err
}
tlsConfig.Certificates = append(tlsConfig.Certificates, *cert)
}
var account *Account
var needRegister bool
// if certificates in storage, load them
if fileInfo, err := os.Stat(a.StorageFile); err == nil && fileInfo.Size() != 0 {
log.Infof("Loading ACME certificates...")
// load account
account, err = a.loadAccount(a)
if err != nil {
return err
}
} else {
log.Infof("Generating ACME Account...")
// Create a user. New accounts need an email and private key to start
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return err
}
account = &Account{
Email: a.Email,
PrivateKey: x509.MarshalPKCS1PrivateKey(privateKey),
}
account.DomainsCertificate = DomainsCertificates{Certs: []*DomainsCertificate{}, lock: &sync.RWMutex{}}
needRegister = true
}
client, err := a.buildACMEClient(account)
// no certificates in TLS config, so we add a default one
cert, err := generateDefaultCertificate()
if err != nil {
return err
}
a.defaultCertificate = cert
// TODO: to remove in the futurs
if len(a.StorageFile) > 0 && len(a.Storage) == 0 {
log.Warnf("ACME.StorageFile is deprecated, use ACME.Storage instead")
a.Storage = a.StorageFile
}
return nil
}
// CreateClusterConfig creates a tls.config using ACME configuration in cluster mode
func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tls.Config, checkOnDemandDomain func(domain string) bool) error {
err := a.init()
if err != nil {
return err
}
if len(a.Storage) == 0 {
return errors.New("Empty Store, please provide a key for certs storage")
}
a.checkOnDemandDomain = checkOnDemandDomain
tlsConfig.Certificates = append(tlsConfig.Certificates, *a.defaultCertificate)
tlsConfig.GetCertificate = a.getCertificate
listener := func(object cluster.Object) error {
account := object.(*Account)
account.Init()
if !leadership.IsLeader() {
a.client, err = a.buildACMEClient(account)
if err != nil {
log.Errorf("Error building ACME client %+v: %s", object, err.Error())
}
}
return nil
}
datastore, err := cluster.NewDataStore(
staert.KvSource{
Store: leadership.Store,
Prefix: a.Storage,
},
leadership.Pool.Ctx(), &Account{},
listener)
if err != nil {
return err
}
a.store = datastore
a.challengeProvider = &challengeProvider{store: a.store}
ticker := time.NewTicker(24 * time.Hour)
leadership.Pool.AddGoCtx(func(ctx context.Context) {
log.Infof("Starting ACME renew job...")
defer log.Infof("Stopped ACME renew job...")
select {
case <-ctx.Done():
return
case <-ticker.C:
if err := a.renewCertificates(); err != nil {
log.Errorf("Error renewing ACME certificate: %s", err.Error())
}
}
})
leadership.AddListener(func(elected bool) error {
if elected {
object, err := a.store.Load()
if err != nil {
return err
}
transaction, object, err := a.store.Begin()
if err != nil {
return err
}
account := object.(*Account)
account.Init()
var needRegister bool
if account == nil || len(account.Email) == 0 {
account, err = NewAccount(a.Email)
if err != nil {
return err
}
needRegister = true
}
if err != nil {
return err
}
a.client, err = a.buildACMEClient(account)
if err != nil {
return err
}
if needRegister {
// New users will need to register; be sure to save it
log.Debugf("Register...")
reg, err := a.client.Register()
if err != nil {
return err
}
account.Registration = reg
}
// The client has a URL to the current Let's Encrypt Subscriber
// Agreement. The user will need to agree to it.
log.Debugf("AgreeToTOS...")
err = a.client.AgreeToTOS()
if err != nil {
// Let's Encrypt Subscriber Agreement renew ?
reg, err := a.client.QueryRegistration()
if err != nil {
return err
}
account.Registration = reg
err = a.client.AgreeToTOS()
if err != nil {
log.Errorf("Error sending ACME agreement to TOS: %+v: %s", account, err.Error())
}
}
err = transaction.Commit(account)
if err != nil {
return err
}
safe.Go(func() {
a.retrieveCertificates()
if err := a.renewCertificates(); err != nil {
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
}
})
}
return nil
})
return nil
}
// CreateLocalConfig creates a tls.config using local ACME configuration
func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, checkOnDemandDomain func(domain string) bool) error {
err := a.init()
if err != nil {
return err
}
if len(a.Storage) == 0 {
return errors.New("Empty Store, please provide a filename for certs storage")
}
a.checkOnDemandDomain = checkOnDemandDomain
tlsConfig.Certificates = append(tlsConfig.Certificates, *a.defaultCertificate)
tlsConfig.GetCertificate = a.getCertificate
localStore := NewLocalStore(a.Storage)
a.store = localStore
a.challengeProvider = &challengeProvider{store: a.store}
var needRegister bool
var account *Account
if fileInfo, fileErr := os.Stat(a.Storage); fileErr == nil && fileInfo.Size() != 0 {
log.Infof("Loading ACME Account...")
// load account
object, err := localStore.Load()
if err != nil {
return err
}
account = object.(*Account)
} else {
log.Infof("Generating ACME Account...")
account, err = NewAccount(a.Email)
if err != nil {
return err
}
needRegister = true
}
log.Infof("buildACMEClient...")
a.client, err = a.buildACMEClient(account)
if err != nil {
return err
}
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
wrapperChallengeProvider := newWrapperChallengeProvider()
client.SetChallengeProvider(acme.TLSSNI01, wrapperChallengeProvider)
if needRegister {
// New users will need to register; be sure to save it
reg, err := client.Register()
log.Infof("Register...")
reg, err := a.client.Register()
if err != nil {
return err
}
@@ -274,40 +267,42 @@ func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(doma
// The client has a URL to the current Let's Encrypt Subscriber
// Agreement. The user will need to agree to it.
err = client.AgreeToTOS()
log.Infof("AgreeToTOS...")
err = a.client.AgreeToTOS()
if err != nil {
// Let's Encrypt Subscriber Agreement renew ?
reg, err := a.client.QueryRegistration()
if err != nil {
return err
}
account.Registration = reg
err = a.client.AgreeToTOS()
if err != nil {
log.Errorf("Error sending ACME agreement to TOS: %+v: %s", account, err.Error())
}
}
// save account
transaction, _, err := a.store.Begin()
if err != nil {
return err
}
err = transaction.Commit(account)
if err != nil {
return err
}
safe.Go(func() {
a.retrieveCertificates(client, account)
a.retrieveCertificates()
if err := a.renewCertificates(); err != nil {
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
}
})
tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
if challengeCert, ok := wrapperChallengeProvider.getCertificate(clientHello.ServerName); ok {
return challengeCert, nil
}
if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
return domainCert.tlsCert, nil
}
if a.OnDemand {
if CheckOnDemandDomain != nil && !CheckOnDemandDomain(clientHello.ServerName) {
return nil, nil
}
return a.loadCertificateOnDemand(client, account, clientHello)
}
return nil, nil
}
ticker := time.NewTicker(24 * time.Hour)
safe.Go(func() {
for {
select {
case <-ticker.C:
if err := a.renewCertificates(client, account); err != nil {
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
}
for range ticker.C {
if err := a.renewCertificates(); err != nil {
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
}
}
@@ -315,25 +310,53 @@ func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(doma
return nil
}
func (a *ACME) retrieveCertificates(client *acme.Client, account *Account) {
func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
account := a.store.Get().(*Account)
if challengeCert, ok := a.challengeProvider.getCertificate(clientHello.ServerName); ok {
log.Debugf("ACME got challenge %s", clientHello.ServerName)
return challengeCert, nil
}
if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
log.Debugf("ACME got domain cert %s", clientHello.ServerName)
return domainCert.tlsCert, nil
}
if a.OnDemand {
if a.checkOnDemandDomain != nil && !a.checkOnDemandDomain(clientHello.ServerName) {
return nil, nil
}
return a.loadCertificateOnDemand(clientHello)
}
log.Debugf("ACME got nothing %s", clientHello.ServerName)
return nil, nil
}
func (a *ACME) retrieveCertificates() {
log.Infof("Retrieving ACME certificates...")
for _, domain := range a.Domains {
// check if cert isn't already loaded
account := a.store.Get().(*Account)
if _, exists := account.DomainsCertificate.exists(domain); !exists {
domains := []string{}
domains = append(domains, domain.Main)
domains = append(domains, domain.SANs...)
certificateResource, err := a.getDomainsCertificates(client, domains)
certificateResource, err := a.getDomainsCertificates(domains)
if err != nil {
log.Errorf("Error getting ACME certificate for domain %s: %s", domains, err.Error())
continue
}
transaction, object, err := a.store.Begin()
if err != nil {
log.Errorf("Error creating ACME store transaction from domain %s: %s", domain, err.Error())
continue
}
account = object.(*Account)
_, err = account.DomainsCertificate.addCertificateForDomains(certificateResource, domain)
if err != nil {
log.Errorf("Error adding ACME certificate for domain %s: %s", domains, err.Error())
continue
}
if err = a.saveAccount(account); err != nil {
if err = transaction.Commit(account); err != nil {
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
continue
}
@@ -342,19 +365,27 @@ func (a *ACME) retrieveCertificates(client *acme.Client, account *Account) {
log.Infof("Retrieved ACME certificates")
}
func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
func (a *ACME) renewCertificates() error {
log.Debugf("Testing certificate renew...")
account := a.store.Get().(*Account)
for _, certificateResource := range account.DomainsCertificate.Certs {
if certificateResource.needRenew() {
transaction, object, err := a.store.Begin()
if err != nil {
return err
}
account = object.(*Account)
log.Debugf("Renewing certificate %+v", certificateResource.Domains)
renewedCert, err := client.RenewCertificate(acme.CertificateResource{
renewedCert, err := a.client.RenewCertificate(acme.CertificateResource{
Domain: certificateResource.Certificate.Domain,
CertURL: certificateResource.Certificate.CertURL,
CertStableURL: certificateResource.Certificate.CertStableURL,
PrivateKey: certificateResource.Certificate.PrivateKey,
Certificate: certificateResource.Certificate.Certificate,
}, false)
}, true)
if err != nil {
return err
log.Errorf("Error renewing certificate: %v", err)
continue
}
log.Debugf("Renewed certificate %+v", certificateResource.Domains)
renewedACMECert := &Certificate{
@@ -366,84 +397,126 @@ func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
}
err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
if err != nil {
return err
log.Errorf("Error renewing certificate: %v", err)
continue
}
if err = a.saveAccount(account); err != nil {
return err
if err = transaction.Commit(account); err != nil {
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
continue
}
}
}
return nil
}
func (a *ACME) buildACMEClient(Account *Account) (*acme.Client, error) {
func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
log.Debugf("Building ACME client...")
caServer := "https://acme-v01.api.letsencrypt.org/directory"
if len(a.CAServer) > 0 {
caServer = a.CAServer
}
client, err := acme.NewClient(caServer, Account, acme.RSA4096)
client, err := acme.NewClient(caServer, account, acme.RSA4096)
if err != nil {
return nil, err
}
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
err = client.SetChallengeProvider(acme.TLSSNI01, a.challengeProvider)
if err != nil {
return nil, err
}
return client, nil
}
func (a *ACME) loadCertificateOnDemand(client *acme.Client, Account *Account, clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
if certificateResource, ok := Account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
account := a.store.Get().(*Account)
if certificateResource, ok := account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
return certificateResource.tlsCert, nil
}
Certificate, err := a.getDomainsCertificates(client, []string{clientHello.ServerName})
certificate, err := a.getDomainsCertificates([]string{clientHello.ServerName})
if err != nil {
return nil, err
}
log.Debugf("Got certificate on demand for domain %s", clientHello.ServerName)
cert, err := Account.DomainsCertificate.addCertificateForDomains(Certificate, Domain{Main: clientHello.ServerName})
transaction, object, err := a.store.Begin()
if err != nil {
return nil, err
}
if err = a.saveAccount(Account); err != nil {
account = object.(*Account)
cert, err := account.DomainsCertificate.addCertificateForDomains(certificate, Domain{Main: clientHello.ServerName})
if err != nil {
return nil, err
}
if err = transaction.Commit(account); err != nil {
return nil, err
}
return cert.tlsCert, nil
}
func (a *ACME) loadAccount(acmeConfig *ACME) (*Account, error) {
a.storageLock.RLock()
defer a.storageLock.RUnlock()
Account := Account{
DomainsCertificate: DomainsCertificates{},
}
file, err := ioutil.ReadFile(acmeConfig.StorageFile)
if err != nil {
return nil, err
}
if err := json.Unmarshal(file, &Account); err != nil {
return nil, err
}
err = Account.DomainsCertificate.init()
if err != nil {
return nil, err
}
log.Infof("Loaded ACME config from storage %s", acmeConfig.StorageFile)
return &Account, nil
// LoadCertificateForDomains loads certificates from ACME for given domains
func (a *ACME) LoadCertificateForDomains(domains []string) {
safe.Go(func() {
operation := func() error {
if a.client == nil {
return fmt.Errorf("ACME client still not built")
}
return nil
}
notify := func(err error, time time.Duration) {
log.Errorf("Error getting ACME client: %v, retrying in %s", err, time)
}
ebo := backoff.NewExponentialBackOff()
ebo.MaxElapsedTime = 30 * time.Second
err := backoff.RetryNotify(operation, ebo, notify)
if err != nil {
log.Errorf("Error getting ACME client: %v", err)
return
}
account := a.store.Get().(*Account)
var domain Domain
if len(domains) == 0 {
// no domain
return
} else if len(domains) > 1 {
domain = Domain{Main: domains[0], SANs: domains[1:]}
} else {
domain = Domain{Main: domains[0]}
}
if _, exists := account.DomainsCertificate.exists(domain); exists {
// domain already exists
return
}
certificate, err := a.getDomainsCertificates(domains)
if err != nil {
log.Errorf("Error getting ACME certificates %+v : %v", domains, err)
return
}
log.Debugf("Got certificate for domains %+v", domains)
transaction, object, err := a.store.Begin()
if err != nil {
log.Errorf("Error creating transaction %+v : %v", domains, err)
return
}
account = object.(*Account)
_, err = account.DomainsCertificate.addCertificateForDomains(certificate, domain)
if err != nil {
log.Errorf("Error adding ACME certificates %+v : %v", domains, err)
return
}
if err = transaction.Commit(account); err != nil {
log.Errorf("Error Saving ACME account %+v: %v", account, err)
return
}
})
}
func (a *ACME) saveAccount(Account *Account) error {
a.storageLock.Lock()
defer a.storageLock.Unlock()
// write account to file
data, err := json.MarshalIndent(Account, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile(a.StorageFile, data, 0644)
}
func (a *ACME) getDomainsCertificates(client *acme.Client, domains []string) (*Certificate, error) {
func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
log.Debugf("Loading ACME certificates %s...", domains)
bundle := true
certificate, failures := client.ObtainCertificate(domains, bundle, nil)
certificate, failures := a.client.ObtainCertificate(domains, bundle, nil)
if len(failures) > 0 {
log.Error(failures)
return nil, fmt.Errorf("Cannot obtain certificates %s+v", failures)

View File

@@ -2,6 +2,7 @@ package acme
import (
"reflect"
"sync"
"testing"
)
@@ -16,7 +17,7 @@ func TestDomainsSet(t *testing.T) {
ds := Domains{}
ds.Set(in)
if !reflect.DeepEqual(check, ds) {
t.Errorf("Expected %+v\nGo %+v", check, ds)
t.Errorf("Expected %+v\nGot %+v", check, ds)
}
}
}
@@ -55,7 +56,203 @@ func TestDomainsSetAppend(t *testing.T) {
for i, in := range inSlice {
ds.Set(in)
if !reflect.DeepEqual(checkSlice[i], ds) {
t.Errorf("Expected %s %+v\nGo %+v", in, checkSlice[i], ds)
t.Errorf("Expected %s %+v\nGot %+v", in, checkSlice[i], ds)
}
}
}
func TestCertificatesRenew(t *testing.T) {
domainsCertificates := DomainsCertificates{
lock: sync.RWMutex{},
Certs: []*DomainsCertificate{
{
Domains: Domain{
Main: "foo1.com",
SANs: []string{}},
Certificate: &Certificate{
Domain: "foo1.com",
CertURL: "url",
CertStableURL: "url",
PrivateKey: []byte(`
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA6OqHGdwGy20+3Jcz9IgfN4IR322X2Hhwk6n8Hss/Ws7FeTZo
PvXW8uHeI1bmQJsy9C6xo3odzO64o7prgMZl5eDw5fk1mmUij3J3nM3gwtc/Cc+8
ADXGldauASdHBFTRvWQge0Pv/Q5U0fyL2VCHoR9mGv4CQ7nRNKPus0vYJMbXoTbO
8z4sIbNz3Ov9o/HGMRb8D0rNPTMdC62tHSbiO1UoxLXr9dcBOGt786AsiRTJ8bq9
GCVQgzd0Wftb8z6ddW2YuWrmExlkHdfC4oG0D5SU1QB4ldPyl7fhVWlfHwC1NX+c
RnDSEeYkAcdvvIekdM/yH+z62XhwToM0E9TCzwIDAQABAoIBACq3EC3S50AZeeTU
qgeXizoP1Z1HKQjfFa5PB1jSZ30M3LRdIQMi7NfASo/qmPGSROb5RUS42YxC34PP
ZXXJbNiaxzM13/m/wHXURVFxhF3XQc1X1p+nPRMvutulS2Xk9E4qdbaFgBbFsRKN
oUwqc6U97+jVWq72/gIManNhXnNn1n1SRLBEkn+WStMPn6ZvWRlpRMjhy0c1mpwg
u6em92HvMvfKPQ60naUhdKp+q0rsLp2YKWjiytos9ENSYI5gAGLIDhKeqiD8f92E
4FGPmNRipwxCE2SSvZFlM26tRloWVcBPktRN79hUejE8iopiqVS0+4h/phZ2wG0D
18cqVpECgYEA+qmagnhm0LLvwVkUN0B2nRARQEFinZDM4Hgiv823bQvc9I8dVTqJ
aIQm5y4Y5UA3xmyDsRoO7GUdd0oVeh9GwTONzMRCOny/mOuOC51wXPhKHhI0O22u
sfbOHszl+bxl6ZQMUJa2/I8YIWBLU5P+fTgrfNwBEgZ3YPwUV5tyHNcCgYEA7eAv
pjQkbJNRq/fv/67sojN7N9QoH84egN5cZFh5d8PJomnsvy5JDV4WaG1G6mJpqjdD
YRVdFw5oZ4L8yCVdCeK9op896Uy51jqvfSe3+uKmNqE0qDHgaLubQNI8yYc5sacW
fYJBmDR6rNIeE7Q2240w3CdKfREuXdDnhyTTEskCgYBFeAnFTP8Zqe2+hSSQJ4J4
BwLw7u4Yww+0yja/N5E1XItRD/TOMRnx6GYrvd/ScVjD2kEpLRKju2ZOMC8BmHdw
hgwvitjcAsTK6cWFPI3uhjVsXhkxuzUmR0Naz+iQrQEFmi1LjGmMV1AVt+1IbYSj
SZTr1sFJMJeXPmWY3hDjIwKBgQC4H9fCJoorIL0PB5NVreishHzT8fw84ibqSTPq
2DDtazcf6C3AresN1c4ydqN1uUdg4fXdp9OujRBzTwirQ4CIrmFrBye89g7CrBo6
Hgxivh06G/3OUw0JBG5f9lvnAiy+Pj9CVxi+36A1NU7ioZP0zY0MW71koW/qXlFY
YkCfQQKBgBqwND/c3mPg7iY4RMQ9XjrKfV9o6FMzA51lAinjujHlNgsBmqiR951P
NA3kWZQ73D3IxeLEMaGHpvS7andPN3Z2qPhe+FbJKcF6ZZNTrFQkh/Fpz3wmYPo1
GIL4+09kNgMRWapaROqI+/3+qJQ+GVJZIPfYC0poJOO6vYqifWe8
-----END RSA PRIVATE KEY-----
`),
Certificate: []byte(`
-----BEGIN CERTIFICATE-----
MIIC+TCCAeGgAwIBAgIJAK78ukR/Qu4rMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV
BAMMCGZvbzEuY29tMB4XDTE2MDYxOTIyMDMyM1oXDTI2MDYxNzIyMDMyM1owEzER
MA8GA1UEAwwIZm9vMS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDo6ocZ3AbLbT7clzP0iB83ghHfbZfYeHCTqfweyz9azsV5Nmg+9dby4d4jVuZA
mzL0LrGjeh3M7rijumuAxmXl4PDl+TWaZSKPcneczeDC1z8Jz7wANcaV1q4BJ0cE
VNG9ZCB7Q+/9DlTR/IvZUIehH2Ya/gJDudE0o+6zS9gkxtehNs7zPiwhs3Pc6/2j
8cYxFvwPSs09Mx0Lra0dJuI7VSjEtev11wE4a3vzoCyJFMnxur0YJVCDN3RZ+1vz
Pp11bZi5auYTGWQd18LigbQPlJTVAHiV0/KXt+FVaV8fALU1f5xGcNIR5iQBx2+8
h6R0z/If7PrZeHBOgzQT1MLPAgMBAAGjUDBOMB0GA1UdDgQWBBRFLH1wF6BT51uq
yWNqBnCrPFIglzAfBgNVHSMEGDAWgBRFLH1wF6BT51uqyWNqBnCrPFIglzAMBgNV
HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAr7aH3Db6TeAZkg4Zd7SoF2q11
erzv552PgQUyezMZcRBo2q1ekmUYyy2600CBiYg51G+8oUqjJKiKnBuaqbMX7pFa
FsL7uToZCGA57cBaVejeB+p24P5bxoJGKCMeZcEBe5N93Tqu5WBxNEX7lQUo6TSs
gSN2Olf3/grNKt5V4BduSIQZ+YHlPUWLTaz5B1MXKSUqjmabARP9lhjO14u9USvi
dMBDFskJySQ6SUfz3fyoXELoDOVbRZETuSodpw+aFCbEtbcQCLT3A0FG+BEPayZH
tt19zKUlr6e+YFpyjQPGZ7ZkY7iMgHEkhKrXx2DiZ1+cif3X1xfXWQr0S5+E
-----END CERTIFICATE-----
`),
},
},
{
Domains: Domain{
Main: "foo2.com",
SANs: []string{}},
Certificate: &Certificate{
Domain: "foo2.com",
CertURL: "url",
CertStableURL: "url",
PrivateKey: []byte(`
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA7rIVuSrZ3FfYXhR3qaWwfVcgiqKS//yXFzNqkJS6mz9nRCNT
lPawvrCFIRKdR7UO7xD7A5VTcbrGOAaTvrEaH7mB/4FGL+gN4AiTbVFpKXngAYEW
A3//zeBZ7XUSWaQ+CNC+l796JeoDvQD++KwCke4rVD1pGN1hpVEeGhwzyKOYPKLo
4+AGVe1LFWw4U/v8Iil1/gBBehZBILuhASpXy4W132LJPl76/EbGqh0nVz2UlFqU
HRxO+2U2ba4YIpI+0/VOQ9Cq/TzHSUdTTLfBHE/Qb+aDBfptMWTRvAngLqUglOcZ
Fi6SAljxEkJO6z6btmoVUWsoKBpbIHDC5++dZwIDAQABAoIBAAD8rYhRfAskNdnV
vdTuwXcTOCg6md8DHWDULpmgc9EWhwfKGZthFcQEGNjVKd9VCVXFvTP7lxe+TPmI
VW4Rb2k4LChxUWf7TqthfbKTBptMTLfU39Ft4xHn3pdTx5qlSjhhHJimCwxDFnbe
nS9MDsqpsHYtttSKfc/gMP6spS4sNPZ/r9zseT3eWkBEhn+FQABxJiuPcQ7q7S+Q
uOghmr7f3FeYvizQOhBtULsLrK/hsmQIIB4amS1QlpNWKbIoiUPNPjCA5PVQyAER
waYjuc7imBbeD98L/z8bRTlEskSKjtPSEXGVHa9OYdBU+02Ci6TjKztUp6Ho7JE9
tcHj+eECgYEA+9Ntv6RqIdpT/4/52JYiR+pOem3U8tweCOmUqm/p/AWyfAJTykqt
cJ8RcK1MfM+uoa5Sjm8hIcA2XPVEqH2J50PC4w04Q3xtfsz3xs7KJWXQCoha8D0D
ZIFNroEPnld0qOuJzpIIteXTrCLhSu17ZhN+Wk+5gJ7Ewu/QMM5OPjECgYEA8qbw
zfwSjE6jkrqO70jzqSxgi2yjo0vMqv+BNBuhxhDTBXnKQI1KsHoiS0FkSLSJ9+DS
CT3WEescD2Lumdm2s9HXvaMmnDSKBY58NqCGsNzZifSgmj1H/yS9FX8RXfSjXcxq
RDvTbD52/HeaCiOxHZx8JjmJEb+ZKJC4MDvjtxcCgYBM516GvgEjYXdxfliAiijh
6W4Z+Vyk5g/ODPc3rYG5U0wUjuljx7Z7xDghPusy2oGsIn5XvRxTIE35yXU0N1Jb
69eiWzEpeuA9bv7kGdal4RfNf6K15wwYL1y3w/YvFuorg/LLwNEkK5Ge6e//X9Ll
c2KM1fgCjXntRitAHGDMoQKBgDnkgodioLpA+N3FDN0iNqAiKlaZcOFA8G/LzfO0
tAAhe3dO+2YzT6KTQSNbUqXWDSTKytHRowVbZrJ1FCA4xVJZunNQPaH/Fv8EY7ZU
zk3cIzq61qZ2AHtrNIGwc2BLQb7bSm9FJsgojxLlJidNJLC/6Q7lo0JMyCnZfVhk
sYu5AoGAZt/MfyFTKm674UddSNgGEt86PyVYbLMnRoAXOaNB38AE12kaYHPil1tL
FnL8OQLpbX5Qo2JGgeZRlpMJ4Jxw2zzvUKr/n+6khaLxHmtX48hMu2QM7ZvnkZCs
Kkgz6v+Wcqm94ugtl3HSm+u9xZzVQxN6gu/jZQv3VpQiAZHjPYc=
-----END RSA PRIVATE KEY-----
`),
Certificate: []byte(`
-----BEGIN CERTIFICATE-----
MIIC+TCCAeGgAwIBAgIJAK25/Z9Jz6IBMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV
BAMMCGZvbzIuY29tMB4XDTE2MDYyMDA5MzUyNloXDTI2MDYxODA5MzUyNlowEzER
MA8GA1UEAwwIZm9vMi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDushW5KtncV9heFHeppbB9VyCKopL//JcXM2qQlLqbP2dEI1OU9rC+sIUhEp1H
tQ7vEPsDlVNxusY4BpO+sRofuYH/gUYv6A3gCJNtUWkpeeABgRYDf//N4FntdRJZ
pD4I0L6Xv3ol6gO9AP74rAKR7itUPWkY3WGlUR4aHDPIo5g8oujj4AZV7UsVbDhT
+/wiKXX+AEF6FkEgu6EBKlfLhbXfYsk+Xvr8RsaqHSdXPZSUWpQdHE77ZTZtrhgi
kj7T9U5D0Kr9PMdJR1NMt8EcT9Bv5oMF+m0xZNG8CeAupSCU5xkWLpICWPESQk7r
Ppu2ahVRaygoGlsgcMLn751nAgMBAAGjUDBOMB0GA1UdDgQWBBQ6FZWqB9qI4NN+
2jFY6xH8uoUTnTAfBgNVHSMEGDAWgBQ6FZWqB9qI4NN+2jFY6xH8uoUTnTAMBgNV
HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCRhuf2dQhIEOmSOGgtRELF2wB6
NWXt0lCty9x4u+zCvITXV8Z0C34VQGencO3H2bgyC3ZxNpPuwZfEc2Pxe8W6bDc/
OyLckk9WLo00Tnr2t7rDOeTjEGuhXFZkhIbJbKdAH8cEXrxKR8UXWtZgTv/b8Hv/
g6tbeH6TzBsdMoFtUCsyWxygYwnLU+quuYvE2s9FiCegf2mdYTCh/R5J5n/51gfB
uC+NakKMfaCvNg3mOAFSYC/0r0YcKM/5ldKGTKTCVJAMhnmBnyRc/70rKkVRFy2g
iIjUFs+9aAgfCiL0WlyyXYAtIev2gw4FHUVlcT/xKks+x8Kgj6e5LTIrRRwW
-----END CERTIFICATE-----
`),
},
},
},
}
newCertificate := &Certificate{
Domain: "foo1.com",
CertURL: "url",
CertStableURL: "url",
PrivateKey: []byte(`
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA1OdSuXK2zeSLf0UqgrI4pjkpaqhra++pnda4Li4jXo151svi
Sn7DSynJOoq1jbfRJAoyDhxsBC4S4RuD54U5elJ4wLPZXmHRsvb+NwiHs9VmDqwu
It21btuqeNMebkab5cnDnC6KKufMhXRcRAlluYXyCkQe/+N+LlUQd6Js34TixMpk
eQOX4/OVrokSyVRnIq4u+o0Ufe7z5+41WVH63tcy7Hwi7244aLUzZCs+QQa2Dw6f
qEwjbonr974fM68UxDjTZEQy9u24yDzajhDBp1OTAAklh7U+li3g9dSyNVBFXqEu
nW2fyBvLqeJOSTihqfcrACB/YYhYOX94vMXELQIDAQABAoIBAFYK3t3fxI1VTiMz
WsjTKh3TgC+AvVkz1ILbojfXoae22YS7hUrCDD82NgMYx+LsZPOBw1T8m5Lc4/hh
3F8W8nHDHtYSWUjRk6QWOgsXwXAmUEahw0uH+qlA0ZZfDC9ZDexCLHHURTat03Qj
4J4GhjwCLB2GBlk4IWisLCmNVR7HokrpfIw4oM1aB5E21Tl7zh/x7ikRijEkUsKw
7YhaMeLJqBnMnAdV63hhF7FaDRjl8P2s/3octz/6pqDIABrDrUW3KAkNYCZIWdhF
Kk0wRMbZ/WrYT9GIGoJe7coQC7ezTrlrEkAFEIPGHCLkgXB/0TyuSy0yY59e4zmi
VvHoWUECgYEA/rOL2KJ/p+TZW7+YbsUzs0+F+M+G6UCr0nWfYN9MKmNtmns3eLDG
+pIpBMc5mjqeJR/sCCdkD8OqHC202Y8e4sr0pKSBeBofh2BmXtpyu3QQ50Pa63RS
SK6mYUrFqPmFFDbNGpFI4sIeI+Vf6hm96FQPnyPtUTGqk39m0RbWM/UCgYEA1f04
Nf3wbqwqIHZjYpPmymfjleyMn3hGUjpi7pmI6inXGMk3nkeG1cbOhnfPxL5BWD12
3RqHI2B4Z4r0BMyjctDNb1TxhMIpm5+PKm5KeeKfoYA85IS0mEeq6VdMm3mL1x/O
3LYvcUvAEVf6pWX/+ZFLMudqhF3jbTrdNOC6ZFkCgYBKpEeJdyW+CD0CvEVpwPUD
yXxTjE3XMZKpHLtWYlop2fWW3iFFh1jouci3k8L3xdHuw0oioZibXhYOJ/7l+yFs
CVpknakrj0xKGiAmEBKriLojbClN80rh7fzoakc+29D6OY0mCgm4GndGwcO4EU8s
NOZXFupHbyy0CRQSloSzuQKBgQC1Z/MtIlefGuijmHlsakGuuR+gS2ZzEj1bHBAe
gZ4mFM46PuqdjblqpR0TtaI3AarXqVOI4SJLBU9NR+jR4MF3Zjeh9/q/NvKa8Usn
B1Svu0TkXphAiZenuKnVIqLY8tNvzZFKXlAd1b+/dDwR10SHR3rebnxINmfEg7Bf
UVvyEQKBgAEjI5O6LSkLNpbVn1l2IO8u8D2RkFqs/Sbx78uFta3f9Gddzb4wMnt3
jVzymghCLp4Qf1ump/zC5bcQ8L97qmnjJ+H8X9HwmkqetuI362JNnz+12YKVDIWi
wI7SJ8BwDqYMrLw6/nE+degn39KedGDH8gz5cZcdlKTZLjbuBOfU
-----END RSA PRIVATE KEY-----
`),
Certificate: []byte(`
-----BEGIN CERTIFICATE-----
MIIC+TCCAeGgAwIBAgIJAPQiOiQcwYaRMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV
BAMMCGZvbzEuY29tMB4XDTE2MDYxOTIyMTE1NFoXDTI2MDYxNzIyMTE1NFowEzER
MA8GA1UEAwwIZm9vMS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDU51K5crbN5It/RSqCsjimOSlqqGtr76md1rguLiNejXnWy+JKfsNLKck6irWN
t9EkCjIOHGwELhLhG4PnhTl6UnjAs9leYdGy9v43CIez1WYOrC4i3bVu26p40x5u
RpvlycOcLooq58yFdFxECWW5hfIKRB7/434uVRB3omzfhOLEymR5A5fj85WuiRLJ
VGciri76jRR97vPn7jVZUfre1zLsfCLvbjhotTNkKz5BBrYPDp+oTCNuiev3vh8z
rxTEONNkRDL27bjIPNqOEMGnU5MACSWHtT6WLeD11LI1UEVeoS6dbZ/IG8up4k5J
OKGp9ysAIH9hiFg5f3i8xcQtAgMBAAGjUDBOMB0GA1UdDgQWBBQPfkS5ehpstmSb
8CGJE7GxSCxl2DAfBgNVHSMEGDAWgBQPfkS5ehpstmSb8CGJE7GxSCxl2DAMBgNV
HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQA99A+itS9ImdGRGgHZ5fSusiEq
wkK5XxGyagL1S0f3VM8e78VabSvC0o/xdD7DHVg6Az8FWxkkksH6Yd7IKfZZUzvs
kXQhlOwWpxgmguSmAs4uZTymIoMFRVj3nG664BcXkKu4Yd9UXKNOWP59zgvrCJMM
oIsmYiq5u0MFpM31BwfmmW3erqIcfBI9OJrmr1XDzlykPZNWtUSSfVuNQ8d4bim9
XH8RfVLeFbqDydSTCHIFvYthH/ESbpRCiGJHoJ8QLfOkhD1k2fI0oJZn5RVtG2W8
bZME3gHPYCk1QFZUptriMCJ5fMjCgxeOTR+FAkstb/lTRuCc4UyILJguIMar
-----END CERTIFICATE-----
`),
}
err := domainsCertificates.renewCertificates(
newCertificate,
Domain{
Main: "foo1.com",
SANs: []string{}})
if err != nil {
t.Errorf("Error in renewCertificates :%v", err)
}
if len(domainsCertificates.Certs) != 2 {
t.Errorf("Expected domainsCertificates length %d %+v\nGot %+v", 2, domainsCertificates.Certs, len(domainsCertificates.Certs))
}
if !reflect.DeepEqual(domainsCertificates.Certs[0].Certificate, newCertificate) {
t.Errorf("Expected new certificate %+v \nGot %+v", newCertificate, domainsCertificates.Certs[0].Certificate)
}
}

View File

@@ -2,55 +2,95 @@ package acme
import (
"crypto/tls"
"strings"
"sync"
"crypto/x509"
"fmt"
"github.com/cenk/backoff"
"github.com/containous/traefik/cluster"
"github.com/containous/traefik/log"
"github.com/xenolf/lego/acme"
"time"
)
type wrapperChallengeProvider struct {
challengeCerts map[string]*tls.Certificate
lock sync.RWMutex
var _ acme.ChallengeProviderTimeout = (*challengeProvider)(nil)
type challengeProvider struct {
store cluster.Store
lock sync.RWMutex
}
func newWrapperChallengeProvider() *wrapperChallengeProvider {
return &wrapperChallengeProvider{
challengeCerts: map[string]*tls.Certificate{},
func (c *challengeProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
log.Debugf("Challenge GetCertificate %s", domain)
if !strings.HasSuffix(domain, ".acme.invalid") {
return nil, false
}
}
func (c *wrapperChallengeProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
c.lock.RLock()
defer c.lock.RUnlock()
if cert, ok := c.challengeCerts[domain]; ok {
return cert, true
account := c.store.Get().(*Account)
if account.ChallengeCerts == nil {
return nil, false
}
return nil, false
account.Init()
var result *tls.Certificate
operation := func() error {
for _, cert := range account.ChallengeCerts {
for _, dns := range cert.certificate.Leaf.DNSNames {
if domain == dns {
result = cert.certificate
return nil
}
}
}
return fmt.Errorf("Cannot find challenge cert for domain %s", domain)
}
notify := func(err error, time time.Duration) {
log.Errorf("Error getting cert: %v, retrying in %s", err, time)
}
ebo := backoff.NewExponentialBackOff()
ebo.MaxElapsedTime = 60 * time.Second
err := backoff.RetryNotify(operation, ebo, notify)
if err != nil {
log.Errorf("Error getting cert: %v", err)
return nil, false
}
return result, true
}
func (c *wrapperChallengeProvider) Present(domain, token, keyAuth string) error {
cert, err := acme.TLSSNI01ChallengeCert(keyAuth)
if err != nil {
return err
}
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
func (c *challengeProvider) Present(domain, token, keyAuth string) error {
log.Debugf("Challenge Present %s", domain)
cert, _, err := TLSSNI01ChallengeCert(keyAuth)
if err != nil {
return err
}
c.lock.Lock()
defer c.lock.Unlock()
for i := range cert.Leaf.DNSNames {
c.challengeCerts[cert.Leaf.DNSNames[i]] = &cert
transaction, object, err := c.store.Begin()
if err != nil {
return err
}
return nil
account := object.(*Account)
if account.ChallengeCerts == nil {
account.ChallengeCerts = map[string]*ChallengeCert{}
}
account.ChallengeCerts[domain] = &cert
return transaction.Commit(account)
}
func (c *wrapperChallengeProvider) CleanUp(domain, token, keyAuth string) error {
func (c *challengeProvider) CleanUp(domain, token, keyAuth string) error {
log.Debugf("Challenge CleanUp %s", domain)
c.lock.Lock()
defer c.lock.Unlock()
delete(c.challengeCerts, domain)
return nil
transaction, object, err := c.store.Begin()
if err != nil {
return err
}
account := object.(*Account)
delete(account.ChallengeCerts, domain)
return transaction.Commit(account)
}
func (c *challengeProvider) Timeout() (timeout, interval time.Duration) {
return 60 * time.Second, 5 * time.Second
}

View File

@@ -1,6 +1,8 @@
package acme
import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
@@ -76,3 +78,48 @@ func generateDerCert(privKey *rsa.PrivateKey, expiration time.Time, domain strin
return x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
}
// TLSSNI01ChallengeCert returns a certificate and target domain for the `tls-sni-01` challenge
func TLSSNI01ChallengeCert(keyAuth string) (ChallengeCert, string, error) {
// generate a new RSA key for the certificates
var tempPrivKey crypto.PrivateKey
tempPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return ChallengeCert{}, "", err
}
rsaPrivKey := tempPrivKey.(*rsa.PrivateKey)
rsaPrivPEM := pemEncode(rsaPrivKey)
zBytes := sha256.Sum256([]byte(keyAuth))
z := hex.EncodeToString(zBytes[:sha256.Size])
domain := fmt.Sprintf("%s.%s.acme.invalid", z[:32], z[32:])
tempCertPEM, err := generatePemCert(rsaPrivKey, domain)
if err != nil {
return ChallengeCert{}, "", err
}
certificate, err := tls.X509KeyPair(tempCertPEM, rsaPrivPEM)
if err != nil {
return ChallengeCert{}, "", err
}
return ChallengeCert{Certificate: tempCertPEM, PrivateKey: rsaPrivPEM, certificate: &certificate}, domain, nil
}
func pemEncode(data interface{}) []byte {
var pemBlock *pem.Block
switch key := data.(type) {
case *ecdsa.PrivateKey:
keyBytes, _ := x509.MarshalECPrivateKey(key)
pemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}
case *rsa.PrivateKey:
pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
break
case *x509.CertificateRequest:
pemBlock = &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: key.Raw}
break
case []byte:
pemBlock = &pem.Block{Type: "CERTIFICATE", Bytes: []byte(data.([]byte))}
}
return pem.EncodeToMemory(pemBlock)
}

85
acme/localStore.go Normal file
View File

@@ -0,0 +1,85 @@
package acme
import (
"encoding/json"
"fmt"
"github.com/containous/traefik/cluster"
"github.com/containous/traefik/log"
"io/ioutil"
"sync"
)
var _ cluster.Store = (*LocalStore)(nil)
// LocalStore is a store using a file as storage
type LocalStore struct {
file string
storageLock sync.RWMutex
account *Account
}
// NewLocalStore create a LocalStore
func NewLocalStore(file string) *LocalStore {
return &LocalStore{
file: file,
}
}
// Get atomically a struct from the file storage
func (s *LocalStore) Get() cluster.Object {
s.storageLock.RLock()
defer s.storageLock.RUnlock()
return s.account
}
// Load loads file into store
func (s *LocalStore) Load() (cluster.Object, error) {
s.storageLock.Lock()
defer s.storageLock.Unlock()
account := &Account{}
file, err := ioutil.ReadFile(s.file)
if err != nil {
return nil, err
}
if err := json.Unmarshal(file, &account); err != nil {
return nil, err
}
account.Init()
s.account = account
log.Infof("Loaded ACME config from store %s", s.file)
return account, nil
}
// Begin creates a transaction with the KV store.
func (s *LocalStore) Begin() (cluster.Transaction, cluster.Object, error) {
s.storageLock.Lock()
return &localTransaction{LocalStore: s}, s.account, nil
}
var _ cluster.Transaction = (*localTransaction)(nil)
type localTransaction struct {
*LocalStore
dirty bool
}
// Commit allows to set an object in the file storage
func (t *localTransaction) Commit(object cluster.Object) error {
t.LocalStore.account = object.(*Account)
defer t.storageLock.Unlock()
if t.dirty {
return fmt.Errorf("Transaction already used. Please begin a new one.")
}
// write account to file
data, err := json.MarshalIndent(object, "", " ")
if err != nil {
return err
}
err = ioutil.WriteFile(t.file, data, 0600)
if err != nil {
return err
}
t.dirty = true
return nil
}

View File

@@ -6,7 +6,7 @@ package main
import (
"net/http"
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/log"
)
// OxyLogger implements oxy Logger interface with logrus.

View File

@@ -1,4 +1,4 @@
FROM golang:1.6.2
FROM golang:1.7
RUN go get github.com/Masterminds/glide \
&& go get github.com/jteeuwen/go-bindata/... \

253
cluster/datastore.go Normal file
View File

@@ -0,0 +1,253 @@
package cluster
import (
"encoding/json"
"fmt"
"github.com/cenk/backoff"
"github.com/containous/staert"
"github.com/containous/traefik/job"
"github.com/containous/traefik/log"
"github.com/docker/libkv/store"
"github.com/satori/go.uuid"
"golang.org/x/net/context"
"sync"
"time"
)
// Metadata stores Object plus metadata
type Metadata struct {
object Object
Object []byte
Lock string
}
// NewMetadata returns new Metadata
func NewMetadata(object Object) *Metadata {
return &Metadata{object: object}
}
// Marshall marshalls object
func (m *Metadata) Marshall() error {
var err error
m.Object, err = json.Marshal(m.object)
return err
}
func (m *Metadata) unmarshall() error {
if len(m.Object) == 0 {
return nil
}
return json.Unmarshal(m.Object, m.object)
}
// Listener is called when Object has been changed in KV store
type Listener func(Object) error
var _ Store = (*Datastore)(nil)
// Datastore holds a struct synced in a KV store
type Datastore struct {
kv staert.KvSource
ctx context.Context
localLock *sync.RWMutex
meta *Metadata
lockKey string
listener Listener
}
// NewDataStore creates a Datastore
func NewDataStore(kvSource staert.KvSource, ctx context.Context, object Object, listener Listener) (*Datastore, error) {
datastore := Datastore{
kv: kvSource,
ctx: ctx,
meta: &Metadata{object: object},
lockKey: kvSource.Prefix + "/lock",
localLock: &sync.RWMutex{},
listener: listener,
}
err := datastore.watchChanges()
if err != nil {
return nil, err
}
return &datastore, nil
}
func (d *Datastore) watchChanges() error {
stopCh := make(chan struct{})
kvCh, err := d.kv.Watch(d.lockKey, stopCh)
if err != nil {
return err
}
go func() {
ctx, cancel := context.WithCancel(d.ctx)
operation := func() error {
for {
select {
case <-ctx.Done():
stopCh <- struct{}{}
return nil
case _, ok := <-kvCh:
if !ok {
cancel()
return err
}
err = d.reload()
if err != nil {
return err
}
// log.Debugf("Datastore object change received: %+v", d.meta)
if d.listener != nil {
err := d.listener(d.meta.object)
if err != nil {
log.Errorf("Error calling datastore listener: %s", err)
}
}
}
}
}
notify := func(err error, time time.Duration) {
log.Errorf("Error in watch datastore: %+v, retrying in %s", err, time)
}
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
if err != nil {
log.Errorf("Error in watch datastore: %v", err)
}
}()
return nil
}
func (d *Datastore) reload() error {
log.Debugf("Datastore reload")
d.localLock.Lock()
err := d.kv.LoadConfig(d.meta)
if err != nil {
d.localLock.Unlock()
return err
}
err = d.meta.unmarshall()
if err != nil {
d.localLock.Unlock()
return err
}
d.localLock.Unlock()
return nil
}
// Begin creates a transaction with the KV store.
func (d *Datastore) Begin() (Transaction, Object, error) {
id := uuid.NewV4().String()
log.Debugf("Transaction %s begins", id)
remoteLock, err := d.kv.NewLock(d.lockKey, &store.LockOptions{TTL: 20 * time.Second, Value: []byte(id)})
if err != nil {
return nil, nil, err
}
stopCh := make(chan struct{})
ctx, cancel := context.WithCancel(d.ctx)
var errLock error
go func() {
_, errLock = remoteLock.Lock(stopCh)
cancel()
}()
select {
case <-ctx.Done():
if errLock != nil {
return nil, nil, errLock
}
case <-d.ctx.Done():
stopCh <- struct{}{}
return nil, nil, d.ctx.Err()
}
// we got the lock! Now make sure we are synced with KV store
operation := func() error {
meta := d.get()
if meta.Lock != id {
return fmt.Errorf("Object lock value: expected %s, got %s", id, meta.Lock)
}
return nil
}
notify := func(err error, time time.Duration) {
log.Errorf("Datastore sync error: %v, retrying in %s", err, time)
err = d.reload()
if err != nil {
log.Errorf("Error reloading: %+v", err)
}
}
ebo := backoff.NewExponentialBackOff()
ebo.MaxElapsedTime = 60 * time.Second
err = backoff.RetryNotify(operation, ebo, notify)
if err != nil {
return nil, nil, fmt.Errorf("Datastore cannot sync: %v", err)
}
// we synced with KV store, we can now return Setter
return &datastoreTransaction{
Datastore: d,
remoteLock: remoteLock,
id: id,
}, d.meta.object, nil
}
func (d *Datastore) get() *Metadata {
d.localLock.RLock()
defer d.localLock.RUnlock()
return d.meta
}
// Load load atomically a struct from the KV store
func (d *Datastore) Load() (Object, error) {
d.localLock.Lock()
defer d.localLock.Unlock()
err := d.kv.LoadConfig(d.meta)
if err != nil {
return nil, err
}
err = d.meta.unmarshall()
if err != nil {
return nil, err
}
return d.meta.object, nil
}
// Get atomically a struct from the KV store
func (d *Datastore) Get() Object {
d.localLock.RLock()
defer d.localLock.RUnlock()
return d.meta.object
}
var _ Transaction = (*datastoreTransaction)(nil)
type datastoreTransaction struct {
*Datastore
remoteLock store.Locker
dirty bool
id string
}
// Commit allows to set an object in the KV store
func (s *datastoreTransaction) Commit(object Object) error {
s.localLock.Lock()
defer s.localLock.Unlock()
if s.dirty {
return fmt.Errorf("Transaction already used. Please begin a new one.")
}
s.Datastore.meta.object = object
err := s.Datastore.meta.Marshall()
if err != nil {
return err
}
err = s.kv.StoreConfig(s.Datastore.meta)
if err != nil {
return err
}
err = s.remoteLock.Unlock()
if err != nil {
return err
}
s.dirty = true
log.Debugf("Transaction commited %s", s.id)
return nil
}

102
cluster/leadership.go Normal file
View File

@@ -0,0 +1,102 @@
package cluster
import (
"github.com/cenk/backoff"
"github.com/containous/traefik/log"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/leadership"
"golang.org/x/net/context"
"time"
)
// Leadership allows leadership election using a KV store
type Leadership struct {
*safe.Pool
*types.Cluster
candidate *leadership.Candidate
leader safe.Safe
listeners []LeaderListener
}
// NewLeadership creates a leadership
func NewLeadership(ctx context.Context, cluster *types.Cluster) *Leadership {
return &Leadership{
Pool: safe.NewPool(ctx),
Cluster: cluster,
candidate: leadership.NewCandidate(cluster.Store, cluster.Store.Prefix+"/leader", cluster.Node, 20*time.Second),
listeners: []LeaderListener{},
}
}
// LeaderListener is called when leadership has changed
type LeaderListener func(elected bool) error
// Participate tries to be a leader
func (l *Leadership) Participate(pool *safe.Pool) {
pool.GoCtx(func(ctx context.Context) {
log.Debugf("Node %s running for election", l.Cluster.Node)
defer log.Debugf("Node %s no more running for election", l.Cluster.Node)
backOff := backoff.NewExponentialBackOff()
operation := func() error {
return l.run(l.candidate, ctx)
}
notify := func(err error, time time.Duration) {
log.Errorf("Leadership election error %+v, retrying in %s", err, time)
}
err := backoff.RetryNotify(operation, backOff, notify)
if err != nil {
log.Errorf("Cannot elect leadership %+v", err)
}
})
}
// AddListener adds a leadership listerner
func (l *Leadership) AddListener(listener LeaderListener) {
l.listeners = append(l.listeners, listener)
}
// Resign resigns from being a leader
func (l *Leadership) Resign() {
l.candidate.Resign()
log.Infof("Node %s resigned", l.Cluster.Node)
}
func (l *Leadership) run(candidate *leadership.Candidate, ctx context.Context) error {
electedCh, errCh := candidate.RunForElection()
for {
select {
case elected := <-electedCh:
l.onElection(elected)
case err := <-errCh:
return err
case <-ctx.Done():
l.candidate.Resign()
return nil
}
}
}
func (l *Leadership) onElection(elected bool) {
if elected {
log.Infof("Node %s elected leader ♚", l.Cluster.Node)
l.leader.Set(true)
l.Start()
} else {
log.Infof("Node %s elected slave ♝", l.Cluster.Node)
l.leader.Set(false)
l.Stop()
}
for _, listener := range l.listeners {
err := listener(elected)
if err != nil {
log.Errorf("Error calling Leadership listener: %s", err)
}
}
}
// IsLeader returns true if current node is leader
func (l *Leadership) IsLeader() bool {
return l.leader.Get().(bool)
}

16
cluster/store.go Normal file
View File

@@ -0,0 +1,16 @@
package cluster
// Object is the struct to store
type Object interface{}
// Store is a generic interface to represents a storage
type Store interface {
Load() (Object, error)
Get() Object
Begin() (Transaction, Object, error)
}
// Transaction allows to set a struct in the KV store
type Transaction interface {
Commit(object Object) error
}

View File

@@ -1,36 +1,41 @@
package main
import (
"crypto/tls"
"errors"
"fmt"
"github.com/containous/traefik/acme"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/types"
"os"
"regexp"
"strings"
"time"
"github.com/containous/traefik/acme"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/types"
)
// TraefikConfiguration holds GlobalConfiguration and other stuff
type TraefikConfiguration struct {
GlobalConfiguration
ConfigFile string `short:"c" description:"Configuration file to use (TOML)."`
GlobalConfiguration `mapstructure:",squash"`
ConfigFile string `short:"c" description:"Configuration file to use (TOML)."`
}
// GlobalConfiguration holds global configuration (with providers, etc.).
// It's populated from the traefik configuration file passed as an argument to the binary.
type GlobalConfiguration struct {
GraceTimeOut int64 `short:"g" description:"Configuration file to use (TOML)."`
GraceTimeOut int64 `short:"g" description:"Duration to give active requests a chance to finish during hot-reload"`
Debug bool `short:"d" description:"Enable debug mode"`
AccessLogsFile string `description:"Access logs file"`
TraefikLogsFile string `description:"Traefik logs file"`
LogLevel string `short:"l" description:"Log level"`
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'"`
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags."`
Cluster *types.Cluster `description:"Enable clustering"`
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags"`
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"`
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
ProvidersThrottleDuration time.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."`
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used"`
InsecureSkipVerify bool `description:"Disable SSL certificate verification"`
Retry *Retry `description:"Enable retry sending request if network error"`
Docker *provider.Docker `description:"Enable Docker backend"`
File *provider.File `description:"Enable File backend"`
@@ -42,6 +47,7 @@ type GlobalConfiguration struct {
Zookeeper *provider.Zookepper `description:"Enable Zookeeper backend"`
Boltdb *provider.BoltDb `description:"Enable Boltdb backend"`
Kubernetes *provider.Kubernetes `description:"Enable Kubernetes backend"`
Mesos *provider.Mesos `description:"Enable Mesos backend"`
}
// DefaultEntryPoints holds default entry points
@@ -68,7 +74,9 @@ func (dep *DefaultEntryPoints) Set(value string) error {
}
// Get return the EntryPoints map
func (dep *DefaultEntryPoints) Get() interface{} { return DefaultEntryPoints(*dep) }
func (dep *DefaultEntryPoints) Get() interface{} {
return DefaultEntryPoints(*dep)
}
// SetValue sets the EntryPoints map with val
func (dep *DefaultEntryPoints) SetValue(val interface{}) {
@@ -93,7 +101,7 @@ func (ep *EntryPoints) String() string {
// 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 {
regex := regexp.MustCompile("(?:Name:(?P<Name>\\S*))\\s*(?:Address:(?P<Address>\\S*))?\\s*(?:TLS:(?P<TLS>\\S*))?\\s*(?:Redirect.EntryPoint:(?P<RedirectEntryPoint>\\S*))?\\s*(?:Redirect.Regex:(?P<RedirectRegex>\\S*))?\\s*(?:Redirect.Replacement:(?P<RedirectReplacement>\\S*))?")
regex := regexp.MustCompile("(?:Name:(?P<Name>\\S*))\\s*(?:Address:(?P<Address>\\S*))?\\s*(?:TLS:(?P<TLS>\\S*))?\\s*((?P<TLSACME>TLS))?\\s*(?:CA:(?P<CA>\\S*))?\\s*(?:Redirect.EntryPoint:(?P<RedirectEntryPoint>\\S*))?\\s*(?:Redirect.Regex:(?P<RedirectRegex>\\S*))?\\s*(?:Redirect.Replacement:(?P<RedirectReplacement>\\S*))?\\s*(?:Compress:(?P<Compress>\\S*))?")
match := regex.FindAllStringSubmatch(value, -1)
if match == nil {
return errors.New("Bad EntryPoints format: " + value)
@@ -114,6 +122,14 @@ func (ep *EntryPoints) Set(value string) error {
tls = &TLS{
Certificates: certs,
}
} else if len(result["TLSACME"]) > 0 {
tls = &TLS{
Certificates: Certificates{},
}
}
if len(result["CA"]) > 0 {
files := strings.Split(result["CA"], ",")
tls.ClientCAFiles = files
}
var redirect *Redirect
if len(result["RedirectEntryPoint"]) > 0 || len(result["RedirectRegex"]) > 0 || len(result["RedirectReplacement"]) > 0 {
@@ -124,17 +140,25 @@ func (ep *EntryPoints) Set(value string) error {
}
}
compress := false
if len(result["Compress"]) > 0 {
compress = strings.EqualFold(result["Compress"], "enable") || strings.EqualFold(result["Compress"], "on")
}
(*ep)[result["Name"]] = &EntryPoint{
Address: result["Address"],
TLS: tls,
Redirect: redirect,
Compress: compress,
}
return nil
}
// Get return the EntryPoints map
func (ep *EntryPoints) Get() interface{} { return EntryPoints(*ep) }
func (ep *EntryPoints) Get() interface{} {
return EntryPoints(*ep)
}
// SetValue sets the EntryPoints map with val
func (ep *EntryPoints) SetValue(val interface{}) {
@@ -152,6 +176,8 @@ type EntryPoint struct {
Address string
TLS *TLS
Redirect *Redirect
Auth *types.Auth
Compress bool
}
// Redirect configures a redirection of an entry point to another, or to an URL
@@ -163,12 +189,74 @@ type Redirect struct {
// TLS configures TLS for an entry point
type TLS struct {
Certificates Certificates
MinVersion string
CipherSuites []string
Certificates Certificates
ClientCAFiles []string
}
// Map of allowed TLS minimum versions
var minVersion = map[string]uint16{
`VersionTLS10`: tls.VersionTLS10,
`VersionTLS11`: tls.VersionTLS11,
`VersionTLS12`: tls.VersionTLS12,
}
// Map of TLS CipherSuites from crypto/tls
var cipherSuites = map[string]uint16{
`TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
`TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
`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_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_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_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
`TLS_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
}
// Certificates defines traefik certificates type
// Certs and Keys could be either a file path, or the file content itself
type Certificates []Certificate
//CreateTLSConfig creates a TLS config from Certificate structures
func (certs *Certificates) CreateTLSConfig() (*tls.Config, error) {
config := &tls.Config{}
config.Certificates = []tls.Certificate{}
certsSlice := []Certificate(*certs)
for _, v := range certsSlice {
isAPath := false
_, errCert := os.Stat(v.CertFile)
_, errKey := os.Stat(v.KeyFile)
if errCert == nil {
if errKey == nil {
isAPath = true
} else {
return nil, fmt.Errorf("Bad TLS Certificate KeyFile format. Expected a path.")
}
} else if errKey == nil {
return nil, fmt.Errorf("Bad TLS Certificate KeyFile format. Expected a path.")
}
cert := tls.Certificate{}
var err error
if isAPath {
cert, err = tls.LoadX509KeyPair(v.CertFile, v.KeyFile)
if err != nil {
return nil, err
}
} else {
cert, err = tls.X509KeyPair([]byte(v.CertFile), []byte(v.KeyFile))
if err != nil {
return nil, err
}
}
config.Certificates = append(config.Certificates, cert)
}
return config, nil
}
// 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 (certs *Certificates) String() string {
@@ -199,6 +287,7 @@ func (certs *Certificates) Type() string {
}
// 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 string
KeyFile string
@@ -206,8 +295,7 @@ type Certificate struct {
// Retry contains request retry config
type Retry struct {
Attempts int `description:"Number of attempts"`
MaxMem int64 `description:"Maximum request body to be stored in memory in Mo"`
Attempts int `description:"Number of attempts"`
}
// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
@@ -215,8 +303,9 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
//default Docker
var defaultDocker provider.Docker
defaultDocker.Watch = true
defaultDocker.ExposedByDefault = true
defaultDocker.Endpoint = "unix:///var/run/docker.sock"
defaultDocker.TLS = &provider.DockerTLS{}
defaultDocker.SwarmMode = false
// default File
var defaultFile provider.File
@@ -238,8 +327,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
var defaultConsul provider.Consul
defaultConsul.Watch = true
defaultConsul.Endpoint = "127.0.0.1:8500"
defaultConsul.Prefix = "/traefik"
defaultConsul.TLS = &provider.KvTLS{}
defaultConsul.Prefix = "traefik"
defaultConsul.Constraints = []types.Constraint{}
// default ConsulCatalog
@@ -250,9 +338,8 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
// default Etcd
var defaultEtcd provider.Etcd
defaultEtcd.Watch = true
defaultEtcd.Endpoint = "127.0.0.1:400"
defaultEtcd.Endpoint = "127.0.0.1:2379"
defaultEtcd.Prefix = "/traefik"
defaultEtcd.TLS = &provider.KvTLS{}
defaultEtcd.Constraints = []types.Constraint{}
//default Zookeeper
@@ -272,9 +359,17 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
//default Kubernetes
var defaultKubernetes provider.Kubernetes
defaultKubernetes.Watch = true
defaultKubernetes.Endpoint = "127.0.0.1:8080"
defaultKubernetes.Endpoint = ""
defaultKubernetes.LabelSelector = ""
defaultKubernetes.Constraints = []types.Constraint{}
// default Mesos
var defaultMesos provider.Mesos
defaultMesos.Watch = true
defaultMesos.Endpoint = "http://127.0.0.1:5050"
defaultMesos.ExposedByDefault = true
defaultMesos.Constraints = []types.Constraint{}
defaultConfiguration := GlobalConfiguration{
Docker: &defaultDocker,
File: &defaultFile,
@@ -286,7 +381,8 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
Zookeeper: &defaultZookeeper,
Boltdb: &defaultBoltDb,
Kubernetes: &defaultKubernetes,
Retry: &Retry{MaxMem: 2},
Mesos: &defaultMesos,
Retry: &Retry{},
}
return &TraefikConfiguration{
GlobalConfiguration: defaultConfiguration,

View File

@@ -4,3 +4,6 @@ Description=Traefik
[Service]
ExecStart=/usr/bin/traefik --configFile=/etc/traefik.toml
Restart=on-failure
[Install]
WantedBy=multi-user.target

View File

@@ -30,7 +30,7 @@ Entrypoints are the network entry points into Træfɪk.
They can be defined using:
- a port (80, 443...)
- SSL (Certificates. Keys...)
- SSL (Certificates, Keys, authentication with a client certificate signed by a trusted CA...)
- redirection to another entrypoint (redirect `HTTP` to `HTTPS`)
Here is an example of entrypoints definition:
@@ -50,10 +50,27 @@ Here is an example of entrypoints definition:
```
- Two entrypoints are defined `http` and `https`.
- `http` listens on port `80` et `https` on port `443`.
- We enable SSL en `https` by giving a certificate and a key.
- `http` listens on port `80` and `https` on port `443`.
- We enable SSL on `https` by giving a certificate and a key.
- We also redirect all the traffic from entrypoint `http` to `https`.
And here is another example with client certificate authentication:
```toml
[entryPoints]
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
clientCAFiles = ["tests/clientca1.crt", "tests/clientca2.crt"]
[[entryPoints.https.tls.certificates]]
certFile = "tests/traefik.crt"
keyFile = "tests/traefik.key"
```
- We enable SSL on `https` by giving a certificate and a key.
- One or several files containing Certificate Authorities in PEM format are added.
- It is possible to have multiple CA:s in the same file or keep them in separate files.
## Frontends
A frontend is a set of rules that forwards the incoming traffic from an entrypoint to a backend.
@@ -80,25 +97,64 @@ Here is an example of frontends definition:
[frontends.frontend1]
backend = "backend2"
[frontends.frontend1.routes.test_1]
rule = "Host: test.localhost, test2.localhost"
rule = "Host:test.localhost,test2.localhost"
[frontends.frontend2]
backend = "backend1"
passHostHeader = true
priority = 10
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host: localhost, {subdomain:[a-z]+}.localhost"
rule = "Host:localhost,{subdomain:[a-z]+}.localhost"
[frontends.frontend3]
backend = "backend2"
rule = "Host: test3.localhost;Path:/test"
[frontends.frontend3.routes.test_1]
rule = "Host:test3.localhost;Path:/test"
```
- Three frontends are defined: `frontend1`, `frontend2` and `frontend3`
- `frontend1` will forward the traffic to the `backend2` if the rule `Host: test.localhost, test2.localhost` is matched
- `frontend2` will forward the traffic to the `backend1` if the rule `Host: localhost, {subdomain:[a-z]+}.localhost` is matched (forwarding client `Host` header to the backend)
- `frontend3` will forward the traffic to the `backend2` if the rules `Host: test3.localhost` and `Path:/test` are matched
- `frontend1` will forward the traffic to the `backend2` if the rule `Host:test.localhost,test2.localhost` is matched
- `frontend2` will forward the traffic to the `backend1` if the rule `Host:localhost,{subdomain:[a-z]+}.localhost` is matched (forwarding client `Host` header to the backend)
- `frontend3` will forward the traffic to the `backend2` if the rules `Host:test3.localhost` **AND** `Path:/test` are matched
By default, routes will be sorted using rules length (to avoid path overlap):
### Combining multiple rules
As seen in the previous example, you can combine multiple rules.
In TOML file, you can use multiple routes:
```toml
[frontends.frontend3]
backend = "backend2"
[frontends.frontend3.routes.test_1]
rule = "Host:test3.localhost"
[frontends.frontend3.routes.test_2]
rule = "Path:/test"
```
Here `frontend3` will forward the traffic to the `backend2` if the rules `Host:test3.localhost` **AND** `Path:/test` are matched.
You can also use the notation using a `;` separator, same result:
```toml
[frontends.frontend3]
backend = "backend2"
[frontends.frontend3.routes.test_1]
rule = "Host:test3.localhost;Path:/test"
```
Finally, you can create a rule to bind multiple domains or Path to a frontend, using the `,` separator:
```toml
[frontends.frontend2]
[frontends.frontend2.routes.test_1]
rule = "Host:test1.localhost,test2.localhost"
[frontends.frontend3]
backend = "backend2"
[frontends.frontend3.routes.test_1]
rule = "Path:/test1,/test2"
```
### Priorities
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:
@@ -119,6 +175,8 @@ You can customize priority by frontend:
rule = "PathPrefix:/toto"
```
Here, `frontend1` will be matched before `frontend2` (`10 > 5`).
## Backends
A backend is responsible to load-balance the traffic coming from one or more frontends to a set of http servers.
@@ -164,6 +222,17 @@ For example:
- Another possible value for `extractorfunc` is `client.ip` which will categorize requests based on client source ip.
- Lastly `extractorfunc` can take the value of `request.header.ANY_HEADER` which will categorize requests based on `ANY_HEADER` that you provide.
Sticky sessions are supported with both load balancers. When sticky sessions are enabled, a cookie called `_TRAEFIK_BACKEND` is set on the initial
request. On subsequent requests, the client will be directed to the backend stored in the cookie if it is still healthy. If not, a new backend
will be assigned.
For example:
```toml
[backends]
[backends.backend1]
[backends.backend1.loadbalancer]
sticky = true
```
## Servers
Servers are simply defined using a `URL`. You can also apply a custom `weight` to each server (this will be used by load-balancing).
@@ -197,9 +266,30 @@ Here is an example of backends and servers definition:
- `backend2` will forward the traffic to two servers: `http://172.17.0.4:80"` with weight `1` and `http://172.17.0.5:80` with weight `2` using `drr` load-balancing strategy.
- a circuit breaker is added on `backend1` using the expression `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window
# Launch
# Configuration
Træfɪk's configuration has two parts:
- The [static Træfɪk configuration](/basics#static-trfk-configuration) which is loaded only at the begining.
- The [dynamic Træfɪk configuration](/basics#dynamic-trfk-configuration) which can be hot-reloaded (no need to restart the process).
## Static Træfɪk configuration
The static configuration is the global configuration which setting up connections to configuration backends and entrypoints.
Træfɪk can be configured using many configuration sources with the following precedence order.
Each item takes precedence over the item below it:
- [Key-value Store](/basics/#key-value-stores)
- [Arguments](/basics/#arguments)
- [Configuration file](/basics/#configuration-file)
- Default
It means that arguments overrides configuration file, and Key-value Store overrides arguments.
### Configuration file
Træfɪk can be configured using a TOML file configuration, arguments, or both.
By default, Træfɪk will try to find a `traefik.toml` in the following places:
- `/etc/traefik/`
@@ -212,15 +302,63 @@ You can override this by setting a `configFile` argument:
$ traefik --configFile=foo/bar/myconfigfile.toml
```
Træfɪk uses the following precedence order. Each item takes precedence over the item below it:
Please refer to the [global configuration](/toml/#global-configuration) section to get documentation on it.
- arguments
- configuration file
- default
### Arguments
It means that arguments overrides configuration file.
Each argument is described in the help section:
Each argument (and command) is described in the help section:
```bash
$ traefik --help
```
Note that all default values will be displayed as well.
### Key-value stores
Træfɪk supports several Key-value stores:
- [Consul](https://consul.io)
- [etcd](https://coreos.com/etcd/)
- [ZooKeeper](https://zookeeper.apache.org/)
- [boltdb](https://github.com/boltdb/bolt)
Please refer to the [User Guide Key-value store configuration](/user-guide/kv-config/) section to get documentation on it.
## Dynamic Træfɪk configuration
The dynamic configuration concerns :
- [Frontends](/basics/#frontends)
- [Backends](/basics/#backends)
- [Servers](/basics/#servers)
Træfɪk can hot-reload those rules which could be provided by [multiple configuration backends](/toml/#configuration-backends).
We only need to enable `watch` option to make Træfɪk watch configuration backend changes and generate its configuration automatically.
Routes to services will be created and updated instantly at any changes.
Please refer to the [configuration backends](/toml/#configuration-backends) section to get documentation on it.
# Commands
Usage: `traefik [command] [--flag=flag_argument]`
List of Træfɪk available commands with description :                                                             
- `version` : Print version 
- `storeconfig` : Store the static traefik configuration into a Key-value stores. Please refer to the [Store Træfɪk configuration](/user-guide/kv-config/#store-trfk-configuration) section to get documentation on it.
Each command may have related flags.
All those related flags will be displayed with :
```bash
$ traefik [command] --help
```
Note that each command is described at the begining of the help section:
```bash
$ traefik --help
```

View File

@@ -36,7 +36,9 @@ Routes to your services will be created instantly.
Run it and forget it!
## Demo
## Quickstart
You can have a quick look at Træfɪk in this [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers.
Here is a talk (in french) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference.
You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Lets'Encrypt.

View File

@@ -26,6 +26,8 @@
#
# Optional
# Default: "ERROR"
# Accepted values, in order of severity: "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "PANIC"
# Messages at and above the selected level will be logged.
#
# logLevel = "ERROR"
@@ -46,6 +48,13 @@
#
# MaxIdleConnsPerHost = 200
# If set to true invalid SSL certificates are accepted for backends.
# Note: This disables detection of man-in-the-middle attacks so should only be used on secure backend networks.
# Optional
# Default: false
#
# InsecureSkipVerify = true
# Entrypoints to be used by frontends that do not specify any entrypoint.
# Each frontend can specify its own entrypoints.
#
@@ -55,6 +64,53 @@
# defaultEntryPoints = ["http", "https"]
```
### Constraints
In a micro-service architecture, with a central service discovery, setting constraints limits Træfɪk scope to a smaller number of routes.
Træfɪk filters services according to service attributes/tags set in your configuration backends.
Supported backends:
- Docker
- Consul K/V
- BoltDB
- Zookeeper
- Etcd
- Consul Catalog
Supported filters:
- ```tag```
```
# Constraints definition
#
# Optional
#
# Simple matching constraint
# constraints = ["tag==api"]
#
# Simple mismatching constraint
# constraints = ["tag!=api"]
#
# Globbing
# constraints = ["tag==us-*"]
#
# Backend-specific constraint
# [consulCatalog]
# endpoint = 127.0.0.1:8500
# constraints = ["tag==api"]
#
# Multiple constraints
# - "tag==" must match with at least one tag
# - "tag!=" must match with none of tags
# constraints = ["tag!=us-*", "tag!=asia-*"]
# [consulCatalog]
# endpoint = 127.0.0.1:8500
# constraints = ["tag==api", "tag!=v*-beta"]
```
## Entrypoints definition
```toml
@@ -89,6 +145,64 @@
# [entryPoints.http.redirect]
# regex = "^http://localhost/(.*)"
# replacement = "http://mydomain/$1"
#
# Only accept clients that present a certificate signed by a specified
# Certificate Authority (CA)
# ClientCAFiles can be configured with multiple CA:s in the same file or
# use multiple files containing one or several CA:s. The CA:s has to be in PEM format.
# All clients will be required to present a valid cert.
# The requirement will apply to all server certs in the entrypoint
# In the example below both snitest.com and snitest.org will require client certs
#
# [entryPoints]
# [entryPoints.https]
# address = ":443"
# [entryPoints.https.tls]
# ClientCAFiles = ["tests/clientca1.crt", "tests/clientca2.crt"]
# [[entryPoints.https.tls.certificates]]
# CertFile = "integration/fixtures/https/snitest.com.cert"
# KeyFile = "integration/fixtures/https/snitest.com.key"
# [[entryPoints.https.tls.certificates]]
# CertFile = "integration/fixtures/https/snitest.org.cert"
# KeyFile = "integration/fixtures/https/snitest.org.key"
#
# To enable basic auth on an entrypoint
# with 2 user/pass: test:test and test2:test2
# Passwords can be encoded in MD5, SHA1 and BCrypt: you can use htpasswd to generate those ones
# [entryPoints]
# [entryPoints.http]
# address = ":80"
# [entryPoints.http.auth.basic]
# users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
#
# To enable digest auth on an entrypoint
# with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
# You can use htdigest to generate those ones
# [entryPoints]
# [entryPoints.http]
# address = ":80"
# [entryPoints.http.auth.basic]
# users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
#
# To specify an https entrypoint with a minimum TLS version, and specifying an array of cipher suites (from crypto/tls):
# [entryPoints]
# [entryPoints.https]
# address = ":443"
# [entryPoints.https.tls]
# MinVersion = "VersionTLS12"
# CipherSuites = ["TLS_RSA_WITH_AES_256_GCM_SHA384"]
# [[entryPoints.https.tls.certificates]]
# CertFile = "integration/fixtures/https/snitest.com.cert"
# KeyFile = "integration/fixtures/https/snitest.com.key"
# [[entryPoints.https.tls.certificates]]
# CertFile = "integration/fixtures/https/snitest.org.cert"
# KeyFile = "integration/fixtures/https/snitest.org.key"
# To enable compression support using gzip format:
# [entryPoints]
# [entryPoints.http]
# address = ":80"
# compress = true
[entryPoints]
[entryPoints.http]
@@ -110,13 +224,6 @@
# Default: (number servers in backend) -1
#
# attempts = 3
# Sets the maximum request body to be stored in memory in Mo
#
# Optional
# Default: 2
#
# maxMem = 3
```
## ACME (Let's Encrypt) configuration
@@ -140,12 +247,18 @@
#
email = "test@traefik.io"
# File used for certificates storage.
# WARNING, if you use Traefik in Docker, don't forget to mount this file as a volume.
# File or key used for certificates storage.
# WARNING, if you use Traefik in Docker, you have 2 options:
# - create a file on your host and mount it as a volume
# storageFile = "acme.json"
# $ docker run -v "/my/host/acme.json:acme.json" traefik
# - mount the folder containing the file as a volume
# storageFile = "/etc/traefik/acme/acme.json"
# $ docker run -v "/my/host/acme:/etc/traefik/acme" traefik
#
# Required
#
storageFile = "acme.json"
storage = "acme.json" # or "traefik/acme/account" if using KV store
# Entrypoint to proxy acme challenge to.
# WARNING, must point to an entrypoint on port 443
@@ -156,12 +269,19 @@ entryPoint = "https"
# Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate.
# WARNING, TLS handshakes will be slow when requesting a hostname certificate for the first time, this can leads to DoS attacks.
# WARNING, Take note that Let's Encrypt have rate limiting: https://community.letsencrypt.org/t/quick-start-guide/1631
# WARNING, Take note that Let's Encrypt have rate limiting: https://letsencrypt.org/docs/rate-limits
#
# Optional
#
# onDemand = true
# Enable certificate generation on frontends Host rules. This will request a certificate from Let's Encrypt for each frontend with a Host rule.
# For example, a rule Host:test1.traefik.io,test2.traefik.io will request a certificate with main domain test1.traefik.io and SAN test2.traefik.io.
#
# Optional
#
# OnHostRule = true
# CA server to use
# Uncomment the line to run on the staging let's encrypt server
# Leave comment to go to prod
@@ -173,7 +293,7 @@ entryPoint = "https"
# Domains list
# You can provide SANs (alternative domains) to each main domain
# All domains must have A/AAAA records pointing to Traefik
# WARNING, Take note that Let's Encrypt have rate limiting: https://community.letsencrypt.org/t/quick-start-guide/1631
# WARNING, Take note that Let's Encrypt have rate limiting: https://letsencrypt.org/docs/rate-limits
# Each domain & SANs will lead to a certificate request.
#
# [[acme.domains]]
@@ -195,58 +315,13 @@ entryPoint = "https"
main = "local4.com"
```
## Constraints
In a micro-service architecture, with a central service discovery, setting constraints limits Træfɪk scope to a smaller number of routes.
Træfɪk filters services according to service attributes/tags set in your configuration backends.
Supported backends:
- Consul Catalog
Supported filters:
- ```tag```
```
# Constraints definition
#
# Optional
#
# Simple matching constraint
# constraints = ["tag==api"]
# Simple mismatching constraint
# constraints = ["tag!=api"]
# Globbing
# constraints = ["tag==us-*"]
# Backend-specific constraint
# [consulCatalog]
# endpoint = 127.0.0.1:8500
# constraints = ["tag==api"]
# Multiple constraints
# - "tag==" must match with at least one tag
# - "tag!=" must match with none of tags
# constraints = ["tag!=us-*", "tag!=asia-*"]
# [consulCatalog]
# endpoint = 127.0.0.1:8500
# constraints = ["tag==api", "tag!=v*-beta"]
```
# Configuration backends
## File backend
Like any other reverse proxy, Træfɪk can be configured with a file. You have two choices:
- simply add your configuration at the end of the global configuration file `traefik.toml` :
- simply add your configuration at the end of the global configuration file `traefik.toml`:
```toml
# traefik.toml
@@ -387,7 +462,7 @@ watch = true
## API backend
Træfik can be configured using a restful api.
Træfik can be configured using a RESTful api.
To enable it:
```toml
@@ -405,6 +480,18 @@ address = ":8080"
#
# Optional
# ReadOnly = false
#
# To enable basic auth on the webui
# with 2 user/pass: test:test and test2:test2
# Passwords can be encoded in MD5, SHA1 and BCrypt: you can use htpasswd to generate those ones
# [web.auth.basic]
# users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
# To enable digest auth on the webui
# with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
# You can use htdigest to generate those ones
# [web.auth.basic]
# users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
```
- `/`: provides a simple HTML frontend of Træfik
@@ -412,6 +499,26 @@ address = ":8080"
![Web UI Providers](img/web.frontend.png)
![Web UI Health](img/traefik-health.png)
- `/ping`: `GET` simple endpoint to check for Træfik process liveness.
```sh
$ curl -sv "http://localhost:8080/ping"
* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /ping HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Thu, 25 Aug 2016 01:35:36 GMT
< Content-Length: 2
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host localhost left intact
OK
```
- `/health`: `GET` json metrics
```sh
@@ -567,6 +674,28 @@ watch = true
#
# filename = "docker.tmpl"
# Expose containers by default in traefik
#
# Optional
# Default: true
#
exposedbydefault = true
# Use the IP address from the binded port instead of the inner network one. For specific use-case :)
#
# Optional
# Default: false
#
usebindportip = true
# Use Swarm Mode services as data provider
#
# Optional
# Default: false
#
swarmmode = false
# Enable docker TLS connection
#
# [docker.tls]
@@ -579,6 +708,11 @@ watch = true
Labels can be used on containers to override default behaviour:
- `traefik.backend=foo`: assign the container to `foo` backend
- `traefik.backend.maxconn.amount=10`: set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect.
- `traefik.backend.maxconn.extractorfunc=client.ip`: set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect.
- `traefik.backend.loadbalancer.method=drr`: override the default `wrr` load balancer algorithm
- `traefik.backend.loadbalancer.sticky=true`: enable backend sticky sessions
- `traefik.backend.circuitbreaker.expression=NetworkErrorRatio() > 0.5`: create a [circuit breaker](/basics/#backends) to be used against the backend
- `traefik.port=80`: register this port. Useful when the container exposes multiples ports.
- `traefik.protocol=https`: override the default `http` protocol
- `traefik.weight=10`: assign this weight to the container
@@ -587,9 +721,9 @@ Labels can be used on containers to override default behaviour:
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.priority=10`: override default frontend priority
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
- `traefik.domain=traefik.localhost`: override the default domain
- `traefik.docker.network`: Set the docker network to use for connections to this container
NB: when running inside a container, Træfɪk will need network access through `docker network connect <network> <traefik-container>`
## Marathon backend
@@ -622,7 +756,6 @@ endpoint = "http://127.0.0.1:8080"
watch = true
# Default domain used.
# Can be overridden by setting the "traefik.domain" label on an application.
#
# Required
#
@@ -664,11 +797,22 @@ domain = "marathon.localhost"
#
# [marathon.TLS]
# InsecureSkipVerify = true
# DCOSToken for DCOS environment, This will override the Authorization header
#
# Optional
#
# dcosToken = "xxxxxx"
```
Labels can be used on containers to override default behaviour:
- `traefik.backend=foo`: assign the application to `foo` backend
- `traefik.backend.maxconn.amount=10`: set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect.
- `traefik.backend.maxconn.extractorfunc=client.ip`: set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect.
- `traefik.backend.loadbalancer.method=drr`: override the default `wrr` load balancer algorithm
- `traefik.backend.loadbalancer.sticky=true`: enable backend sticky sessions
- `traefik.backend.circuitbreaker.expression=NetworkErrorRatio() > 0.5`: create a [circuit breaker](/basics/#backends) to be used against the backend
- `traefik.portIndex=1`: register port by index in the application's ports array. Useful when the application exposes multiple ports.
- `traefik.port=80`: register the explicit application port value. Cannot be used alongside `traefik.portIndex`.
- `traefik.protocol=https`: override the default `http` protocol
@@ -678,9 +822,91 @@ Labels can be used on containers to override default behaviour:
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.priority=10`: override default frontend priority
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
- `traefik.domain=traefik.localhost`: override the default domain
## Mesos generic backend
Træfɪk can be configured to use Mesos as a backend configuration:
```toml
################################################################
# Mesos configuration backend
################################################################
# Enable Mesos configuration backend
#
# Optional
#
[mesos]
# Mesos server endpoint.
# You can also specify multiple endpoint for Mesos:
# endpoint = "192.168.35.40:5050,192.168.35.41:5050,192.168.35.42:5050"
# endpoint = "zk://192.168.35.20:2181,192.168.35.21:2181,192.168.35.22:2181/mesos"
#
# Required
#
endpoint = "http://127.0.0.1:8080"
# Enable watch Mesos changes
#
# Optional
#
watch = true
# Default domain used.
# Can be overridden by setting the "traefik.domain" label on an application.
#
# Required
#
domain = "mesos.localhost"
# Override default configuration template. For advanced users :)
#
# Optional
#
# filename = "mesos.tmpl"
# Expose Mesos apps by default in traefik
#
# Optional
# Default: false
#
# ExposedByDefault = true
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config
#
# Optional
#
# [mesos.TLS]
# InsecureSkipVerify = true
# Zookeeper timeout (in seconds)
#
# Optional
#
# ZkDetectionTimeout = 30
# Polling interval (in seconds)
#
# Optional
#
# RefreshSeconds = 30
# IP sources (e.g. host, docker, mesos, rkt)
#
# Optional
#
# IPSources = "host"
# HTTP Timeout (in seconds)
#
# Optional
#
# StateTimeoutSecond = "host"
```
## Kubernetes Ingress backend
@@ -708,6 +934,10 @@ Træfɪk can be configured to use Kubernetes Ingress as a backend configuration:
#
# endpoint = "http://localhost:8080"
# namespaces = ["default","production"]
#
# See: http://kubernetes.io/docs/user-guide/labels/#list-and-watch-filtering
# labelselector = "A and not B"
#
```
Annotations can be used on containers to override default behaviour for the whole Ingress resource:
@@ -766,7 +996,7 @@ prefix = "traefik"
# insecureskipverify = true
```
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure.
## Consul catalog backend
@@ -800,13 +1030,6 @@ domain = "consul.localhost"
# Optional
#
prefix = "traefik"
# Constraint on Consul catalog tags
#
# Optional
#
constraints = ["tag==api", "tag==he*ld"]
# Matching with containers having this tag: "traefik.tags=api,helloworld"
```
This backend will create routes matching on hostname based on the service name
@@ -819,6 +1042,8 @@ Additional settings can be defined using Consul Catalog tags:
- `traefik.backend.weight=10`: assign this weight to the container
- `traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5`
- `traefik.backend.loadbalancer=drr`: override the default load balancing mode
- `traefik.backend.maxconn.amount=10`: set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect.
- `traefik.backend.maxconn.extractorfunc=client.ip`: set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect.
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.priority=10`: override default frontend priority
@@ -843,7 +1068,7 @@ Træfɪk can be configured to use Etcd as a backend configuration:
#
# Required
#
endpoint = "127.0.0.1:4001"
endpoint = "127.0.0.1:2379"
# Enable watch Etcd changes
#
@@ -874,7 +1099,7 @@ prefix = "/traefik"
# insecureskipverify = true
```
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure.
## Zookeeper backend
@@ -917,7 +1142,7 @@ prefix = "/traefik"
# filename = "zookeeper.tmpl"
```
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure.
## BoltDB backend
@@ -959,85 +1184,4 @@ prefix = "/traefik"
# filename = "boltdb.tmpl"
```
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
## Key-value storage structure
The Keys-Values structure should look (using `prefix = "/traefik"`):
- backend 1
| Key | Value |
|--------------------------------------------------------|-----------------------------|
| `/traefik/backends/backend1/circuitbreaker/expression` | `NetworkErrorRatio() > 0.5` |
| `/traefik/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik/backends/backend1/servers/server1/weight` | `10` |
| `/traefik/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
| `/traefik/backends/backend1/servers/server2/weight` | `1` |
- backend 2
| Key | Value |
|-----------------------------------------------------|------------------------|
| `/traefik/backends/backend2/maxconn/amount` | `10` |
| `/traefik/backends/backend2/maxconn/extractorfunc` | `request.host` |
| `/traefik/backends/backend2/loadbalancer/method` | `drr` |
| `/traefik/backends/backend2/servers/server1/url` | `http://172.17.0.4:80` |
| `/traefik/backends/backend2/servers/server1/weight` | `1` |
| `/traefik/backends/backend2/servers/server2/url` | `http://172.17.0.5:80` |
| `/traefik/backends/backend2/servers/server2/weight` | `2` |
- frontend 1
| Key | Value |
|---------------------------------------------------|-----------------------|
| `/traefik/frontends/frontend1/backend` | `backend2` |
| `/traefik/frontends/frontend1/routes/test_1/rule` | `Host:test.localhost` |
- frontend 2
| Key | Value |
|----------------------------------------------------|--------------------|
| `/traefik/frontends/frontend2/backend` | `backend1` |
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
| `/traefik/frontends/frontend2/priority` | `10` |
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` |
## Atomic configuration changes
The [Etcd](https://github.com/coreos/etcd/issues/860) and [Consul](https://github.com/hashicorp/consul/issues/886) backends do not support updating multiple keys atomically. As a result, it may be possible for Træfɪk to read an intermediate configuration state despite judicious use of the `--providersThrottleDuration` flag. To solve this problem, Træfɪk supports a special key called `/traefik/alias`. If set, Træfɪk use the value as an alternative key prefix.
Given the key structure below, Træfɪk will use the `http://172.17.0.2:80` as its only backend (frontend keys have been omitted for brevity).
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/1` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
When an atomic configuration change is required, you may write a new configuration at an alternative prefix. Here, although the `/traefik_configurations/2/...` keys have been set, the old configuration is still active because the `/traefik/alias` key still points to `/traefik_configurations/1`:
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/1` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` configuration becomes active atomically. Here, we have a 50% balance between the `http://172.17.0.3:80` and the `http://172.17.0.4:80` hosts while no traffic is sent to the `172.17.0.2:80` host:
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/2` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.3:80` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.4:80` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik` prefix. Further, if the `/traefik/alias` key is set, all other sibling keys with the `/traefik` prefix are ignored.
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure.

View File

@@ -0,0 +1,19 @@
# Clustering / High Availability
This guide explains how tu use Træfɪk in high availability mode.
In order to deploy and configure multiple Træfɪk instances, without copying the same configuration file on each instance, we will use a distributed Key-Value store.
## Prerequisites
You will need a working KV store cluster.
## File configuration to KV store migration
We created a special Træfɪk command to help configuring your Key Value store from a Træfɪk TOML configuration file.
Please refer to [this section](/user-guide/kv-config/#store-configuration-in-key-value-store) to get more details.
## Deploy a Træfɪk cluster
Once your Træfɪk configuration is uploaded on your KV store, you can start each Træfɪk instance.
A Træfɪk cluster is based on a master/slave model. When starting, Træfɪk will elect a master. If this instance fails, another master will be automatically elected.

View File

@@ -29,6 +29,7 @@ defaultEntryPoints = ["http", "https"]
CertFile = "integration/fixtures/https/snitest.org.cert"
KeyFile = "integration/fixtures/https/snitest.org.key"
```
Note that we can either give path to certificate file or directly the file content itself ([like in this TOML example](/user-guide/kv-config/#upload-the-configuration-in-the-key-value-store)).
## HTTP redirect on HTTPS
@@ -96,3 +97,21 @@ entryPoint = "https"
backend = "backend2"
rule = "Path:/test"
```
## Enable Basic authentication in an entrypoint
With two user/pass:
- `test`:`test`
- `test2`:`test2`
Passwords are encoded in MD5: you can use htpasswd to generate those ones.
```
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.auth.basic]
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
```

View File

@@ -0,0 +1,425 @@
# Kubernetes Ingress Controller
This guide explains how to use Træfɪk as an Ingress controller in a Kubernetes cluster.
If you are not familiar with Ingresses in Kubernetes you might want to read the [Kubernetes user guide](http://kubernetes.io/docs/user-guide/ingress/)
The config files used in this guide can be found in the [examples directory](https://github.com/containous/traefik/tree/master/examples/k8s)
## Prerequisites
1. A working Kubernetes cluster. If you want to follow along with this guide, you should setup [minikube](http://kubernetes.io/docs/getting-started-guides/minikube/)
on your machine, as it is the quickest way to get a local Kubernetes cluster setup for experimentation and development.
2. The `kubectl` binary should be [installed on your workstation](http://kubernetes.io/docs/getting-started-guides/minikube/#download-kubectl).
## Deploy Træfɪk
We are going to deploy Træfɪk with a
[Deployment](http://kubernetes.io/docs/user-guide/deployments/), as this will
allow you to easily roll out config changes or update the image.
```yaml
apiVersion: v1
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: traefik-ingress-controller
namespace: kube-system
labels:
k8s-app: traefik-ingress-lb
spec:
replicas: 1
selector:
matchLabels:
k8s-app: traefik-ingress-lb
template:
metadata:
labels:
k8s-app: traefik-ingress-lb
name: traefik-ingress-lb
version: v1.0.0
spec:
terminationGracePeriodSeconds: 60
containers:
- image: traefik:v1.0.0
name: traefik-ingress-lb
resources:
limits:
cpu: 200m
memory: 30Mi
requests:
cpu: 100m
memory: 20Mi
ports:
- containerPort: 80
hostPort: 80
- containerPort: 8080
args:
- --web
- --kubernetes
```
[examples/k8s/traefik.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/traefik.yaml)
> notice that we binding port 80 on the Træfɪk container to port 80 on the host.
> With a multi node cluster we might expose Træfɪk with a NodePort or LoadBalancer service
> and run more than 1 replica of Træfɪk for high availability.
To deploy Træfɪk to your cluster start by submitting the deployment to the cluster with `kubectl`:
```sh
kubectl apply -f examples/k8s/traefik.yaml
```
### Check the deployment
Now lets check if our deployment was successful.
Start by listing the pods in the `kube-system` namespace:
```sh
$kubectl --namespace=kube-system get pods
NAME READY STATUS RESTARTS AGE
kube-addon-manager-minikubevm 1/1 Running 0 4h
kubernetes-dashboard-s8krj 1/1 Running 0 4h
traefik-ingress-controller-678226159-eqseo 1/1 Running 0 7m
```
You should see that after submitting the Deployment to Kubernetes it has launched
a pod, and it is now running. _It might take a few moments for kubenetes to pull
the Træfɪk image and start the container._
> You could also check the deployment with the Kubernetes dashboard, run
> `minikube dashboard` to open it in your browser, then choose the `kube-system`
> namespace from the menu at the top right of the screen.
You should now be able to access Træfɪk on port 80 of your minikube instance.
```sh
curl $(minikube ip)
404 page not found
```
> We expect to see a 404 response here as we haven't yet given Træfɪk any configuration.
## Submitting An Ingress to the cluster.
Lets start by creating a Service and an Ingress that will expose the
[Træfɪk Web UI](https://github.com/containous/traefik#web-ui).
```yaml
apiVersion: v1
kind: Service
metadata:
name: traefik-web-ui
namespace: kube-system
spec:
selector:
k8s-app: traefik-ingress-lb
ports:
- port: 80
targetPort: 8080
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: traefik-web-ui
namespace: kube-system
spec:
rules:
- host: traefik-ui.local
http:
paths:
- backend:
serviceName: traefik-web-ui
servicePort: 80
```
[examples/k8s/ui.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/ui.yaml)
```sh
kubectl apply -f examples/k8s/ui.yaml
```
Now lets setup an entry in our /etc/hosts file to route `traefik-ui.local`
to our cluster.
> In production you would want to set up real dns entries.
> You can get the ip address of your minikube instance by running `minikube ip`
```
echo "$(minikube ip) traefik-ui.local" | sudo tee -a /etc/hosts
```
We should now be able to visit [traefik-ui.local](http://traefik-ui.local) in the browser and view the Træfɪk Web UI.
## Name based routing
In this example we are going to setup websites for 3 of the United Kingdoms
best loved cheeses, Cheddar, Stilton and Wensleydale.
First lets start by launching the 3 pods for the cheese websites.
```yaml
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: stilton
labels:
app: cheese
cheese: stilton
spec:
replicas: 2
selector:
matchLabels:
app: cheese
task: stilton
template:
metadata:
labels:
app: cheese
task: stilton
version: v0.0.1
spec:
containers:
- name: cheese
image: errm/cheese:stilton
resources:
requests:
cpu: 100m
memory: 50Mi
limits:
cpu: 100m
memory: 50Mi
ports:
- containerPort: 80
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: cheddar
labels:
app: cheese
cheese: cheddar
spec:
replicas: 2
selector:
matchLabels:
app: cheese
task: cheddar
template:
metadata:
labels:
app: cheese
task: cheddar
version: v0.0.1
spec:
containers:
- name: cheese
image: errm/cheese:cheddar
resources:
requests:
cpu: 100m
memory: 50Mi
limits:
cpu: 100m
memory: 50Mi
ports:
- containerPort: 80
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: wensleydale
labels:
app: cheese
cheese: wensleydale
spec:
replicas: 2
selector:
matchLabels:
app: cheese
task: wensleydale
template:
metadata:
labels:
app: cheese
task: wensleydale
version: v0.0.1
spec:
containers:
- name: cheese
image: errm/cheese:wensleydale
resources:
requests:
cpu: 100m
memory: 50Mi
limits:
cpu: 100m
memory: 50Mi
ports:
- containerPort: 80
```
[examples/k8s/cheese-deployments.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-deployments.yaml)
```sh
kubectl apply -f examples/k8s/cheese-deployments.yaml
```
Next we need to setup a service for each of the cheese pods.
```yaml
---
apiVersion: v1
kind: Service
metadata:
name: stilton
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: cheese
task: stilton
---
apiVersion: v1
kind: Service
metadata:
name: cheddar
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: cheese
task: cheddar
---
apiVersion: v1
kind: Service
metadata:
name: wensleydale
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: cheese
task: wensleydale
```
[examples/k8s/cheese-services.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-services.yaml)
```sh
kubectl apply -f examples/k8s/cheese-services.yaml
```
Now we can submit an ingress for the cheese websites.
```yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: cheese
spec:
rules:
- host: stilton.local
http:
paths:
- path: /
backend:
serviceName: stilton
servicePort: http
- host: cheddar.local
http:
paths:
- path: /
backend:
serviceName: cheddar
servicePort: http
- host: wensleydale.local
http:
paths:
- path: /
backend:
serviceName: wensleydale
servicePort: http
```
[examples/k8s/cheese-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-ingress.yaml)
> Notice that we list each hostname, and add a backend service.
```sh
kubectl apply -f examples/k8s/cheese-ingress.yaml
```
Now visit the [Træfɪk dashboard](http://traefik-ui.local/) and you should
see a frontend for each host. Along with a backend listing for each service
with a Server set up for each pod.
If you edit your `/etc/hosts` again you should be able to access the cheese
websites in your browser.
```sh
echo "$(minikube ip) stilton.local cheddar.local wensleydale.local" | sudo tee -a /etc/hosts
```
* [Stilton](http://stilton.local/)
* [Cheddar](http://cheddar.local/)
* [Wensleydale](http://wensleydale.local/)
## Path based routing
Now lets suppose that our fictional client has decided that while they are
super happy about our cheesy web design, when they asked for 3 websites
they had not really bargained on having to buy 3 domain names.
No problem, we say, why don't we reconfigure the sites to host all 3 under one domain.
```yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: cheeses
annotations:
traefik.frontend.rule.type: pathprefixstrip
spec:
rules:
- host: cheeses.local
http:
paths:
- path: /stilton
backend:
serviceName: stilton
servicePort: http
- path: /cheddar
backend:
serviceName: cheddar
servicePort: http
- path: /wensleydale
backend:
serviceName: wensleydale
servicePort: http
```
[examples/k8s/cheeses-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheeses-ingress.yaml)
> Notice that we are configuring Træfɪk to strip the prefix from the url path
> with the `traefik.frontend.rule.type` annotation so that we can use
> the containers from the previous example without modification.
```sh
kubectl apply -f examples/k8s/cheeses-ingress.yaml
```
```sh
echo "$(minikube ip) cheeses.local" | sudo tee -a /etc/hosts
```
You should now be able to visit the websites in your browser.
* [cheeses.local/stilton](http://cheeses.local/stilton/)
* [cheeses.local/cheddar](http://cheeses.local/cheddar/)
* [cheeses.local/wensleydale](http://cheeses.local/wensleydale/)

View File

@@ -0,0 +1,328 @@
# 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.
This section explains how to launch Træfɪk using a configuration loaded from a Key-value store.
Træfɪk supports several Key-value stores:
- [Consul](https://consul.io)
- [etcd](https://coreos.com/etcd/)
- [ZooKeeper](https://zookeeper.apache.org/)
- [boltdb](https://github.com/boltdb/bolt)
# Static configuration in Key-value store
We will see the steps to set it up with an easy example.
Note that we could do the same with any other Key-value Store.
## docker-compose file for Consul
The Træfɪk global configuration will be getted from a [Consul](https://consul.io) store.
First we have to launch Consul in a container.
The [docker-compose file](https://docs.docker.com/compose/compose-file/) allows us to launch Consul and four instances of the trivial app [emilevauge/whoamI](https://github.com/emilevauge/whoamI) :
```yml
consul:
image: progrium/consul
command: -server -bootstrap -log-level debug -ui-dir /ui
ports:
- "8400:8400"
- "8500:8500"
- "8600:53/udp"
expose:
- "8300"
- "8301"
- "8301/udp"
- "8302"
- "8302/udp"
whoami1:
image: emilevauge/whoami
whoami2:
image: emilevauge/whoami
whoami3:
image: emilevauge/whoami
whoami4:
image: emilevauge/whoami
```
## Upload the configuration in the Key-value store
We should now fill the store with the Træfɪk global configuration, as we do with a [TOML file configuration](/toml).
To do that, we can send the Key-value pairs via [curl commands](https://www.consul.io/intro/getting-started/kv.html) or via the [Web UI](https://www.consul.io/intro/getting-started/ui.html).
Hopefully, Træfɪk allows automation of this process using the `storeconfig` subcommand.
Please refer to the [store Træfɪk configuration](/user-guide/kv-config/#store-configuration-in-key-value-store) section to get documentation on it.
Here is the toml configuration we would like to store in the Key-value Store :
```toml
logLevel = "DEBUG"
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
CertFile = "integration/fixtures/https/snitest.com.cert"
KeyFile = "integration/fixtures/https/snitest.com.key"
[[entryPoints.https.tls.certificates]]
CertFile = """-----BEGIN CERTIFICATE-----
<cert file content>
-----END CERTIFICATE-----"""
KeyFile = """-----BEGIN CERTIFICATE-----
<key file content>
-----END CERTIFICATE-----"""
[consul]
endpoint = "127.0.0.1:8500"
watch = true
prefix = "traefik"
[web]
address = ":8081"
```
And there, the same global configuration in the Key-value Store (using `prefix = "traefik"`):
| Key | Value |
|-----------------------------------------------------------|---------------------------------------------------------------|
| `/traefik/loglevel` | `DEBUG` |
| `/traefik/defaultentrypoints/0` | `http` |
| `/traefik/defaultentrypoints/1` | `https` |
| `/traefik/entrypoints/http/address` | `:80` |
| `/traefik/entrypoints/https/address` | `:443` |
| `/traefik/entrypoints/https/tls/certificates/0/certfile` | `integration/fixtures/https/snitest.com.cert` |
| `/traefik/entrypoints/https/tls/certificates/0/keyfile` | `integration/fixtures/https/snitest.com.key` |
| `/traefik/entrypoints/https/tls/certificates/1/certfile` | `--BEGIN CERTIFICATE--<cert file content>--END CERTIFICATE--` |
| `/traefik/entrypoints/https/tls/certificates/1/keyfile` | `--BEGIN CERTIFICATE--<key file content>--END CERTIFICATE--` |
| `/traefik/consul/endpoint` | `127.0.0.1:8500` |
| `/traefik/consul/watch` | `true` |
| `/traefik/consul/prefix` | `traefik` |
| `/traefik/web/address` | `:8081` |
In case you are setting key values manually,:
- Remember to specify the indexes (`0`,`1`, `2`, ... ) under prefixes `/traefik/defaultentrypoints/` and `/traefik/entrypoints/https/tls/certificates/` in order to match the global configuration structure.
- Be careful to give the correct IP address and port on the key `/traefik/consul/endpoint`.
Note that we can either give path to certificate file or directly the file content itself.
## Launch Træfɪk
We will now launch Træfɪk in a container.
We use CLI flags to setup the connection between Træfɪk and Consul.
All the rest of the global configuration is stored in Consul.
Here is the [docker-compose file](https://docs.docker.com/compose/compose-file/) :
```yml
traefik:
image: traefik
command: --consul --consul.endpoint=127.0.0.1:8500
ports:
- "80:80"
- "8080:8080"
```
NB : Be careful to give the correct IP address and port in the flag `--consul.endpoint`.
## TLS support
So far, only [Consul](https://consul.io) and [etcd](https://coreos.com/etcd/) support TLS connections.
To set it up, we should enable [consul security](https://www.consul.io/docs/internals/security.html) (or [etcd security](https://coreos.com/etcd/docs/latest/security.html)).
Then, we have to provide CA, Cert and Key to Træfɪk using `consul` flags :
- `--consul.tls`
- `--consul.tls.ca=path/to/the/file`
- `--consul.tls.cert=path/to/the/file`
- `--consul.tls.key=path/to/the/file`
Or etcd flags :
- `--etcd.tls`
- `--etcd.tls.ca=path/to/the/file`
- `--etcd.tls.cert=path/to/the/file`
- `--etcd.tls.key=path/to/the/file`
Note that we can either give directly directly the file content itself (instead of the path to certificate) in a TOML file configuration.
Remember the command `traefik --help` to display the updated list of flags.
# Dynamic configuration in Key-value store
Following our example, we will provide backends/frontends rules to Træfɪk.
Note that this section is independent of the way Træfɪk got its static configuration.
It means that the static configuration can either come from the same Key-value store or from any other sources.
## Key-value storage structure
Here is the toml configuration we would like to store in the store :
```toml
[file]
# rules
[backends]
[backends.backend1]
[backends.backend1.circuitbreaker]
expression = "NetworkErrorRatio() > 0.5"
[backends.backend1.servers.server1]
url = "http://172.17.0.2:80"
weight = 10
[backends.backend1.servers.server2]
url = "http://172.17.0.3:80"
weight = 1
[backends.backend2]
[backends.backend1.maxconn]
amount = 10
extractorfunc = "request.host"
[backends.backend2.LoadBalancer]
method = "drr"
[backends.backend2.servers.server1]
url = "http://172.17.0.4:80"
weight = 1
[backends.backend2.servers.server2]
url = "http://172.17.0.5:80"
weight = 2
[frontends]
[frontends.frontend1]
backend = "backend2"
[frontends.frontend1.routes.test_1]
rule = "Host:test.localhost"
[frontends.frontend2]
backend = "backend1"
passHostHeader = true
priority = 10
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host:{subdomain:[a-z]+}.localhost"
[frontends.frontend3]
entrypoints = ["http", "https"] # overrides defaultEntryPoints
backend = "backend2"
rule = "Path:/test"
```
And there, the same dynamic configuration in a KV Store (using `prefix = "traefik"`):
- backend 1
| Key | Value |
|--------------------------------------------------------|-----------------------------|
| `/traefik/backends/backend1/circuitbreaker/expression` | `NetworkErrorRatio() > 0.5` |
| `/traefik/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik/backends/backend1/servers/server1/weight` | `10` |
| `/traefik/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
| `/traefik/backends/backend1/servers/server2/weight` | `1` |
| `/traefik/backends/backend1/servers/server2/tags` | `api,helloworld` |
- backend 2
| Key | Value |
|-----------------------------------------------------|------------------------|
| `/traefik/backends/backend2/maxconn/amount` | `10` |
| `/traefik/backends/backend2/maxconn/extractorfunc` | `request.host` |
| `/traefik/backends/backend2/loadbalancer/method` | `drr` |
| `/traefik/backends/backend2/servers/server1/url` | `http://172.17.0.4:80` |
| `/traefik/backends/backend2/servers/server1/weight` | `1` |
| `/traefik/backends/backend2/servers/server2/url` | `http://172.17.0.5:80` |
| `/traefik/backends/backend2/servers/server2/weight` | `2` |
| `/traefik/backends/backend2/servers/server2/tags` | `web` |
- frontend 1
| Key | Value |
|---------------------------------------------------|-----------------------|
| `/traefik/frontends/frontend1/backend` | `backend2` |
| `/traefik/frontends/frontend1/routes/test_1/rule` | `Host:test.localhost` |
- frontend 2
| Key | Value |
|----------------------------------------------------|--------------------|
| `/traefik/frontends/frontend2/backend` | `backend1` |
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
| `/traefik/frontends/frontend2/priority` | `10` |
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` |
## Atomic configuration changes
Træfɪk can watch the backends/frontends configuration changes and generate its configuration automatically.
Note that only backends/frontends rules are dynamic, the rest of the Træfɪk configuration stay static.
The [Etcd](https://github.com/coreos/etcd/issues/860) and [Consul](https://github.com/hashicorp/consul/issues/886) backends do not support updating multiple keys atomically. As a result, it may be possible for Træfɪk to read an intermediate configuration state despite judicious use of the `--providersThrottleDuration` flag. To solve this problem, Træfɪk supports a special key called `/traefik/alias`. If set, Træfɪk use the value as an alternative key prefix.
Given the key structure below, Træfɪk will use the `http://172.17.0.2:80` as its only backend (frontend keys have been omitted for brevity).
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/1` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
When an atomic configuration change is required, you may write a new configuration at an alternative prefix. Here, although the `/traefik_configurations/2/...` keys have been set, the old configuration is still active because the `/traefik/alias` key still points to `/traefik_configurations/1`:
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/1` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` configuration becomes active atomically. Here, we have a 50% balance between the `http://172.17.0.3:80` and the `http://172.17.0.4:80` hosts while no traffic is sent to the `172.17.0.2:80` host:
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/2` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.3:80` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.4:80` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik/alias`.
Further, if the `/traefik/alias` key is set, all other configuration with `/traefik/backends` or `/traefik/frontends` prefix are ignored.
# Store configuration in Key-value store
Don't forget to [setup the connection between Træfɪk and Key-value store](/user-guide/kv-config/#launch-trfk).
The static Træfɪk configuration in a key-value store can be automatically created and updated, using the [`storeconfig` subcommand](/basics/#commands).
```bash
$ traefik storeconfig [flags] ...
```
This command is here only to automate the [process which upload the configuration into the Key-value store](/user-guide/kv-config/#upload-the-configuration-in-the-key-value-store).
Træfɪk will not start but the [static configuration](/basics/#static-trfk-configuration) will be uploaded into the Key-value store.
If you configured ACME (Let's Encrypt), your registration account and your certificates will also be uploaded.
To upload your ACME certificates to the KV store, get your traefik TOML file and add the new `storage` option in the `acme` section:
```
[acme]
email = "test@traefik.io"
storage = "traefik/acme/account" # the key where to store your certificates in the KV store
storageFile = "acme.json" # your old certificates store
```
Call `traefik storeconfig` to upload your config in the KV store.
Then remove the line `storageFile = "acme.json"` from your TOML config file.
That's it!

View File

@@ -1,21 +1,25 @@
consul:
image: progrium/consul
command: -server -bootstrap -advertise 12.0.0.254 -log-level debug -ui-dir /ui
ports:
- "8400:8400"
- "8500:8500"
- "8600:53/udp"
expose:
- "8300"
- "8301"
- "8301/udp"
- "8302"
- "8302/udp"
version: '2'
services:
consul:
image: progrium/consul
command: -server -bootstrap -advertise 12.0.0.254 -log-level debug -ui-dir /ui
ports:
- "8400:8400"
- "8500:8500"
- "8600:53/udp"
expose:
- "8300"
- "8301"
- "8301/udp"
- "8302"
- "8302/udp"
registrator:
image: gliderlabs/registrator:master
command: -internal consulkv://consul:8500/traefik
volumes:
- /var/run/docker.sock:/tmp/docker.sock
links:
- consul
registrator:
depends_on:
- consul
image: gliderlabs/registrator:master
command: -internal consul://consul:8500
volumes:
- /var/run/docker.sock:/tmp/docker.sock
links:
- consul

View File

@@ -1,4 +1,4 @@
etcd:
image: gcr.io/google_containers/etcd:2.2.1
net: host
command: ['/usr/local/bin/etcd', '--addr=127.0.0.1:4001', '--bind-addr=0.0.0.0:4001', '--data-dir=/var/etcd/data']
command: ['/usr/local/bin/etcd', '--addr=127.0.0.1:2379', '--bind-addr=0.0.0.0:2379', '--data-dir=/var/etcd/data']

View File

@@ -1,43 +1,59 @@
zk:
image: bobrik/zookeeper
net: host
environment:
ZK_CONFIG: tickTime=2000,initLimit=10,syncLimit=5,maxClientCnxns=128,forceSync=no,clientPort=2181
ZK_ID: 1
master:
image: mesosphere/mesos-master:0.28.1-2.0.20.ubuntu1404
net: host
environment:
MESOS_ZK: zk://127.0.0.1:2181/mesos
MESOS_HOSTNAME: 127.0.0.1
MESOS_IP: 127.0.0.1
MESOS_QUORUM: 1
MESOS_CLUSTER: docker-compose
MESOS_WORK_DIR: /var/lib/mesos
slave:
image: mesosphere/mesos-slave:0.28.1-2.0.20.ubuntu1404
net: host
pid: host
privileged: true
environment:
MESOS_MASTER: zk://127.0.0.1:2181/mesos
MESOS_HOSTNAME: 127.0.0.1
MESOS_IP: 127.0.0.1
MESOS_CONTAINERIZERS: docker,mesos
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup
- /usr/bin/docker:/usr/bin/docker:ro
- /usr/lib/x86_64-linux-gnu/libapparmor.so.1:/usr/lib/x86_64-linux-gnu/libapparmor.so.1:ro
- /var/run/docker.sock:/var/run/docker.sock
- /lib/x86_64-linux-gnu/libsystemd-journal.so.0:/lib/x86_64-linux-gnu/libsystemd-journal.so.0
marathon:
image: mesosphere/marathon:v1.1.1
net: host
environment:
MARATHON_MASTER: zk://127.0.0.1:2181/mesos
MARATHON_ZK: zk://127.0.0.1:2181/marathon
MARATHON_HOSTNAME: 127.0.0.1
command: --event_subscriber http_callback
version: '2'
services:
zookeeper:
image: netflixoss/exhibitor:1.5.2
hostname: zookeeper
ports:
- "2181:2181"
mesos-master:
image: mesosphere/marathon:v1.2.0-RC6
hostname: mesos-master
entrypoint: [ "mesos-master" ]
ports:
- "5050:5050"
links:
- zookeeper
environment:
- MESOS_CLUSTER=local
- MESOS_HOSTNAME=mesos-master.docker
- MESOS_LOG_DIR=/var/log
- MESOS_WORK_DIR=/var/lib/mesos
- MESOS_QUORUM=1
- MESOS_ZK=zk://zookeeper:2181/mesos
mesos-slave:
image: mesosphere/mesos-slave-dind:0.2.4_mesos-0.27.2_docker-1.8.2_ubuntu-14.04.4
entrypoint:
- mesos-slave
privileged: true
hostname: mesos-slave
ports:
- "5051:5051"
links:
- zookeeper
- mesos-master
environment:
- MESOS_CONTAINERIZERS=docker,mesos
- MESOS_ISOLATOR=cgroups/cpu,cgroups/mem
- MESOS_LOG_DIR=/var/log
- MESOS_MASTER=zk://zookeeper:2181/mesos
- MESOS_PORT=5051
- MESOS_WORK_DIR=/var/lib/mesos
- MESOS_EXECUTOR_REGISTRATION_TIMEOUT=5mins
- MESOS_EXECUTOR_SHUTDOWN_GRACE_PERIOD=90secs
- MESOS_DOCKER_STOP_TIMEOUT=60secs
- MESOS_RESOURCES=cpus:2;mem:2048;disk:20480;ports(*):[12000-12999]
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
marathon:
image: mesosphere/marathon:v1.2.0-RC6
ports:
- "8080:8080"
links:
- zookeeper
- mesos-master
extra_hosts:
- "mesos-slave:172.17.0.1"
environment:
- MARATHON_ZK=zk://zookeeper:2181/marathon
- MARATHON_MASTER=zk://zookeeper:2181/mesos

View File

@@ -1,25 +1,25 @@
#!/bin/sh
# backend 1
curl -i -H "Accept: application/json" -X PUT -d value="NetworkErrorRatio() > 0.5" http://localhost:4001/v2/keys/traefik/backends/backend1/circuitbreaker/expression
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.2:80" http://localhost:4001/v2/keys/traefik/backends/backend1/servers/server1/url
curl -i -H "Accept: application/json" -X PUT -d value="10" http://localhost:4001/v2/keys/traefik/backends/backend1/servers/server1/weight
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.3:80" http://localhost:4001/v2/keys/traefik/backends/backend1/servers/server2/url
curl -i -H "Accept: application/json" -X PUT -d value="1" http://localhost:4001/v2/keys/traefik/backends/backend1/servers/server2/weight
curl -i -H "Accept: application/json" -X PUT -d value="NetworkErrorRatio() > 0.5" http://localhost:2379/v2/keys/traefik/backends/backend1/circuitbreaker/expression
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.2:80" http://localhost:2379/v2/keys/traefik/backends/backend1/servers/server1/url
curl -i -H "Accept: application/json" -X PUT -d value="10" http://localhost:2379/v2/keys/traefik/backends/backend1/servers/server1/weight
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.3:80" http://localhost:2379/v2/keys/traefik/backends/backend1/servers/server2/url
curl -i -H "Accept: application/json" -X PUT -d value="1" http://localhost:2379/v2/keys/traefik/backends/backend1/servers/server2/weight
# backend 2
curl -i -H "Accept: application/json" -X PUT -d value="drr" http://localhost:4001/v2/keys/traefik/backends/backend2/loadbalancer/method
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.4:80" http://localhost:4001/v2/keys/traefik/backends/backend2/servers/server1/url
curl -i -H "Accept: application/json" -X PUT -d value="1" http://localhost:4001/v2/keys/traefik/backends/backend2/servers/server1/weight
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.5:80" http://localhost:4001/v2/keys/traefik/backends/backend2/servers/server2/url
curl -i -H "Accept: application/json" -X PUT -d value="2" http://localhost:4001/v2/keys/traefik/backends/backend2/servers/server2/weight
curl -i -H "Accept: application/json" -X PUT -d value="drr" http://localhost:2379/v2/keys/traefik/backends/backend2/loadbalancer/method
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.4:80" http://localhost:2379/v2/keys/traefik/backends/backend2/servers/server1/url
curl -i -H "Accept: application/json" -X PUT -d value="1" http://localhost:2379/v2/keys/traefik/backends/backend2/servers/server1/weight
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.5:80" http://localhost:2379/v2/keys/traefik/backends/backend2/servers/server2/url
curl -i -H "Accept: application/json" -X PUT -d value="2" http://localhost:2379/v2/keys/traefik/backends/backend2/servers/server2/weight
# frontend 1
curl -i -H "Accept: application/json" -X PUT -d value="backend2" http://localhost:4001/v2/keys/traefik/frontends/frontend1/backend
curl -i -H "Accept: application/json" -X PUT -d value="http" http://localhost:4001/v2/keys/traefik/frontends/frontend1/entrypoints
curl -i -H "Accept: application/json" -X PUT -d value="Host:test.localhost" http://localhost:4001/v2/keys/traefik/frontends/frontend1/routes/test_1/rule
curl -i -H "Accept: application/json" -X PUT -d value="backend2" http://localhost:2379/v2/keys/traefik/frontends/frontend1/backend
curl -i -H "Accept: application/json" -X PUT -d value="http" http://localhost:2379/v2/keys/traefik/frontends/frontend1/entrypoints
curl -i -H "Accept: application/json" -X PUT -d value="Host:test.localhost" http://localhost:2379/v2/keys/traefik/frontends/frontend1/routes/test_1/rule
# frontend 2
curl -i -H "Accept: application/json" -X PUT -d value="backend1" http://localhost:4001/v2/keys/traefik/frontends/frontend2/backend
curl -i -H "Accept: application/json" -X PUT -d value="http" http://localhost:4001/v2/keys/traefik/frontends/frontend2/entrypoints
curl -i -H "Accept: application/json" -X PUT -d value="Path:/test" http://localhost:4001/v2/keys/traefik/frontends/frontend2/routes/test_2/rule
curl -i -H "Accept: application/json" -X PUT -d value="backend1" http://localhost:2379/v2/keys/traefik/frontends/frontend2/backend
curl -i -H "Accept: application/json" -X PUT -d value="http" http://localhost:2379/v2/keys/traefik/frontends/frontend2/entrypoints
curl -i -H "Accept: application/json" -X PUT -d value="Path:/test" http://localhost:2379/v2/keys/traefik/frontends/frontend2/routes/test_2/rule

View File

@@ -0,0 +1,99 @@
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: stilton
labels:
app: cheese
cheese: stilton
spec:
replicas: 2
selector:
matchLabels:
app: cheese
task: stilton
template:
metadata:
labels:
app: cheese
task: stilton
version: v0.0.1
spec:
containers:
- name: cheese
image: errm/cheese:stilton
resources:
requests:
cpu: 100m
memory: 50Mi
limits:
cpu: 100m
memory: 50Mi
ports:
- containerPort: 80
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: cheddar
labels:
app: cheese
cheese: cheddar
spec:
replicas: 2
selector:
matchLabels:
app: cheese
task: cheddar
template:
metadata:
labels:
app: cheese
task: cheddar
version: v0.0.1
spec:
containers:
- name: cheese
image: errm/cheese:cheddar
resources:
requests:
cpu: 100m
memory: 50Mi
limits:
cpu: 100m
memory: 50Mi
ports:
- containerPort: 80
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: wensleydale
labels:
app: cheese
cheese: wensleydale
spec:
replicas: 2
selector:
matchLabels:
app: cheese
task: wensleydale
template:
metadata:
labels:
app: cheese
task: wensleydale
version: v0.0.1
spec:
containers:
- name: cheese
image: errm/cheese:wensleydale
resources:
requests:
cpu: 100m
memory: 50Mi
limits:
cpu: 100m
memory: 50Mi
ports:
- containerPort: 80

View File

@@ -0,0 +1,27 @@
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: cheese
spec:
rules:
- host: stilton.local
http:
paths:
- path: /
backend:
serviceName: stilton
servicePort: http
- host: cheddar.local
http:
paths:
- path: /
backend:
serviceName: cheddar
servicePort: http
- host: wensleydale.local
http:
paths:
- path: /
backend:
serviceName: wensleydale
servicePort: http

View File

@@ -0,0 +1,39 @@
---
apiVersion: v1
kind: Service
metadata:
name: stilton
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: cheese
task: stilton
---
apiVersion: v1
kind: Service
metadata:
name: cheddar
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: cheese
task: cheddar
---
apiVersion: v1
kind: Service
metadata:
name: wensleydale
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: cheese
task: wensleydale

View File

@@ -0,0 +1,23 @@
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: cheeses
annotations:
traefik.frontend.rule.type: pathprefixstrip
spec:
rules:
- host: cheeses.local
http:
paths:
- path: /stilton
backend:
serviceName: stilton
servicePort: http
- path: /cheddar
backend:
serviceName: cheddar
servicePort: http
- path: /wensleydale
backend:
serviceName: wensleydale
servicePort: http

40
examples/k8s/traefik.yaml Normal file
View File

@@ -0,0 +1,40 @@
apiVersion: v1
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: traefik-ingress-controller
namespace: kube-system
labels:
k8s-app: traefik-ingress-lb
spec:
replicas: 1
selector:
matchLabels:
k8s-app: traefik-ingress-lb
template:
metadata:
labels:
k8s-app: traefik-ingress-lb
name: traefik-ingress-lb
version: v1.0.0
spec:
terminationGracePeriodSeconds: 60
containers:
- image: traefik:v1.0.0
name: traefik-ingress-lb
resources:
limits:
cpu: 200m
memory: 30Mi
requests:
cpu: 100m
memory: 20Mi
ports:
- name: http
containerPort: 80
hostPort: 80
- name: admin
containerPort: 8080
args:
- --web
- --kubernetes

28
examples/k8s/ui.yaml Normal file
View File

@@ -0,0 +1,28 @@
---
apiVersion: v1
kind: Service
metadata:
name: traefik-web-ui
namespace: kube-system
spec:
selector:
k8s-app: traefik-ingress-lb
ports:
- name: web
port: 80
targetPort: 8080
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: traefik-web-ui
namespace: kube-system
spec:
rules:
- host: traefik-ui.local
http:
paths:
- path: /
backend:
serviceName: traefik-web-ui
servicePort: web

View File

@@ -6,7 +6,4 @@ Copyright
//go:generate mkdir -p static
//go:generate go-bindata -pkg autogen -o autogen/gen.go ./static/... ./templates/...
//go:generate mkdir -p vendor/github.com/docker/docker/autogen/dockerversion
//go:generate cp script/dockerversion vendor/github.com/docker/docker/autogen/dockerversion/dockerversion.go
package main

276
glide.lock generated
View File

@@ -1,42 +1,33 @@
hash: 5a6dbc30a69abd002736bd5113e0f783c448faee20a0791c724ec2c3c1cfb8bb
updated: 2016-06-03T18:11:43.839017153+02:00
hash: 39ff28cc1d13d5915a870b14491ece1849c4eaf5a56cecd50a7676ecee6c6143
updated: 2016-09-30T11:27:29.529525636+02:00
imports:
- name: github.com/abbot/go-http-auth
version: cb4372376e1e00e9f6ab9ec142e029302c9e7140
- name: github.com/boltdb/bolt
version: dfb21201d9270c1082d5fb0f07f500311ff72f18
version: 5cc10bbbc5c141029940133bb33c9e969512a698
- name: github.com/BurntSushi/toml
version: f0aeabca5a127c4078abb8c8d64298b147264b55
version: 99064174e013895bbd9b025c31100bd1d9b590ca
- name: github.com/BurntSushi/ty
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
subpackages:
- fun
- name: github.com/cenkalti/backoff
version: a6030178a585d5972d4d33ce61f4a1fa40eaaed0
- name: github.com/cenk/backoff
version: 8edc80b07f38c27352fb186d971c628a6c32552b
- name: github.com/codahale/hdrhistogram
version: 9208b142303c12d8899bae836fd524ac9338b4fd
version: f8ad88b59a584afeee9d334eff879b104439117b
- name: github.com/codegangsta/cli
version: bf4a526f48af7badd25d2cb02d587e1b01be3b50
version: 1efa31f08b9333f1bd4882d61f9d668a70cd902e
- name: github.com/codegangsta/negroni
version: feacfc52d357c844f524c794947493483ed881b3
version: dc6b9d037e8dab60cbfc09c61d6932537829be8b
- name: github.com/containous/flaeg
version: b98687da5c323650f4513fda6b6203fcbdec9313
version: a731c034dda967333efce5f8d276aeff11f8ff87
- name: github.com/containous/mux
version: a819b77bba13f0c0cbe36e437bc2e948411b3996
- name: github.com/containous/oxy
version: 183212964e13e7b8afe01a08b193d04300554a68
subpackages:
- cbreaker
- connlimit
- forward
- roundrobin
- stream
- utils
- name: github.com/containous/staert
version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
version: 92329254783dc01174f03302d51d7cf2c9ff84cf
- name: github.com/coreos/etcd
version: c400d05d0aa73e21e431c16145e558d624098018
version: 1c9e0a0e33051fed6c05c141e6fcbfe5c7f2a899
subpackages:
- Godeps/_workspace/src/github.com/ugorji/go/codec
- Godeps/_workspace/src/golang.org/x/net/context
- client
- pkg/pathutil
- pkg/types
@@ -45,43 +36,114 @@ imports:
subpackages:
- spew
- name: github.com/docker/distribution
version: feddf6cd4e439577ab270d8e3ba63a5d7c5c0d55
version: 87917f30529e6a7fca8eaff2932424915fb11225
subpackages:
- reference
- context
- digest
- reference
- registry/api/errcode
- registry/api/v2
- registry/client
- registry/client/auth
- registry/client/transport
- registry/storage/cache
- registry/storage/cache/memory
- uuid
- name: github.com/docker/docker
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
version: 534753663161334baba06f13b8efa4cad22b5bc5
subpackages:
- api/types/backend
- builder
- builder/dockerignore
- cliconfig
- cliconfig/configfile
- daemon/graphdriver
- image
- image/v1
- layer
- namesgenerator
- opts
- pkg/archive
- pkg/chrootarchive
- pkg/fileutils
- pkg/gitutils
- pkg/homedir
- pkg/httputils
- pkg/idtools
- pkg/ioutils
- pkg/jsonlog
- pkg/jsonmessage
- pkg/longpath
- pkg/mflag
- pkg/mount
- pkg/namesgenerator
- pkg/plugins
- pkg/plugins/transport
- pkg/pools
- pkg/progress
- pkg/promise
- pkg/random
- pkg/reexec
- pkg/signal
- pkg/stdcopy
- pkg/streamformatter
- pkg/stringid
- pkg/symlink
- pkg/system
- pkg/tarsum
- pkg/term
- pkg/term/windows
- pkg/urlutil
- reference
- registry
- runconfig/opts
- name: github.com/docker/engine-api
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
version: 62043eb79d581a32ea849645277023c550732e52
subpackages:
- client
- types
- types/events
- types/filters
- client/transport
- client/transport/cancellable
- types
- types/blkiodev
- types/container
- types/events
- types/filters
- types/network
- types/reference
- types/registry
- types/strslice
- types/swarm
- types/time
- types/versions
- types/blkiodev
- types/strslice
- name: github.com/docker/go-connections
version: c7838b258fbfa3fe88eecfb2a0e08ea0dbd6a646
version: 990a1a1a70b0da4c4cb70e117971a4f0babfbf1a
subpackages:
- nat
- sockets
- tlsconfig
- nat
- name: github.com/docker/go-units
version: 09dda9d4b0d748c57c14048906d3d094a58ec0c9
version: f2d77a61e3c169b43402a0a1e84f06daf29b8190
- name: github.com/docker/leadership
version: bfc7753dd48af19513b29deec23c364bf0f274eb
- name: github.com/docker/libcompose
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
version: d1876c1d68527a49c0aac22a0b161acc7296b740
subpackages:
- config
- docker
- docker/builder
- docker/client
- docker/network
- labels
- logger
- lookup
- project
- project/events
- project/options
- utils
- version
- yaml
- name: github.com/docker/libkv
version: 7283ef27ed32fe267388510a91709b307bb9942c
version: 35d3e2084c650109e7bcc7282655b1bc8ba924ff
subpackages:
- store
- store/boltdb
@@ -93,9 +155,15 @@ imports:
- name: github.com/elazarl/go-bindata-assetfs
version: 57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2
- name: github.com/gambol99/go-marathon
version: ade11d1dc2884ee1f387078fc28509559b6235d1
version: a558128c87724cd7430060ef5aedf39f83937f55
- name: github.com/go-check/check
version: 4f90aeace3a26ad7021961c297b22c42160c7b25
- name: github.com/gogo/protobuf
version: e33835a643a970c11ac74f6333f5f6866387a101
subpackages:
- proto
- name: github.com/golang/glog
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
- name: github.com/google/go-querystring
version: 9235644dd9e52eeae6fa48efd539fdc351a0af53
subpackages:
@@ -103,44 +171,70 @@ imports:
- name: github.com/gorilla/context
version: aed02d124ae4a0e94fea4541c8effd05bf0c8296
- name: github.com/hashicorp/consul
version: 802b29ab948dedb7f7b1b903f535bdf250388c50
version: fce7d75609a04eeb9d4bf41c8dc592aac18fc97d
subpackages:
- api
- name: github.com/hashicorp/go-cleanhttp
version: 875fb671b3ddc66f8e2f0acc33829c8cb989a38d
- name: github.com/hashicorp/serf
version: e4ec8cc423bbe20d26584b96efbeb9102e16d05f
version: 6c4672d66fc6312ddde18399262943e21175d831
subpackages:
- coordinate
- serf
- name: github.com/jarcoal/httpmock
version: 145b10d659265440f062c31ea15326166bae56ee
- name: github.com/libkermit/compose
version: cadc5a3b83a15790174bd7fbc75ea2529785e772
subpackages:
- check
- name: github.com/libkermit/docker
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
- name: github.com/libkermit/docker-check
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
- name: github.com/mailgun/log
version: 2f35a4607f1abf71f97f77f99b0de8493ef6f4ef
version: 55e3595409924fcfbb850811e5a7cdbe8960a0b7
- name: github.com/mailgun/manners
version: fada45142db3f93097ca917da107aa3fad0ffcb5
- name: github.com/mailgun/multibuf
version: 565402cd71fbd9c12aa7e295324ea357e970a61e
- name: github.com/mailgun/timetools
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
- name: github.com/mattn/go-shellwords
version: 525bedee691b5a8df547cb5cf9f86b7fb1883e24
- name: github.com/mesos/mesos-go
version: 068d5470506e3780189fe607af40892814197c5e
subpackages:
- detector
- detector/zoo
- mesos
- mesosproto
- mesosutil
- upid
- name: github.com/mesosphere/mesos-dns
version: b47dc4c19f215e98da687b15b4c64e70f629bea5
repo: https://github.com/containous/mesos-dns.git
vcs: git
subpackages:
- detect
- errorutil
- logging
- models
- records
- records/labels
- records/state
- util
- name: github.com/Microsoft/go-winio
version: 4f1a71750d95a5a8a46c40a67ffbed8129c2f138
version: ce2922f643c8fd76b46cadc7f404a06282678b34
- name: github.com/miekg/dns
version: 48ab6605c66ac797e07f615101c3e9e10e932b66
version: 5d001d020961ae1c184f9f8152fdc73810481677
- name: github.com/mitchellh/mapstructure
version: d2dd0262208475919e1a362f675cfc0e7c10e905
- name: github.com/moul/http2curl
version: b1479103caacaa39319f75e7f57fc545287fca0d
- name: github.com/NYTimes/gziphandler
version: f6438dbf4a82c56684964b03956aa727b0d7816b
- name: github.com/ogier/pflag
version: 45c278ab3607870051a2ea9040bb85fcb8557481
- name: github.com/opencontainers/runc
version: 3211c9f721237f55a16da9c111e3d7e8777e53b5
version: 1a81e9ab1f138c091fe5c86d0883f87716088527
subpackages:
- libcontainer/user
- name: github.com/parnurzeal/gorequest
version: f17fef20c518e688f4edb3eb2af148462ecab3ef
version: 045012d33ef41ea146c1b675df9296d0dc1a212d
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
@@ -148,66 +242,84 @@ imports:
- name: github.com/ryanuber/go-glob
version: 572520ed46dbddaed19ea3d9541bdd0494163693
- name: github.com/samuel/go-zookeeper
version: 4b20de542e40ed2b89d65ae195fc20a330919b92
version: e64db453f3512cade908163702045e0f31137843
subpackages:
- zk
- name: github.com/satori/go.uuid
version: 879c5887cd475cd7864858769793b2ceb0d44feb
- name: github.com/Sirupsen/logrus
version: f3cfb454f4c209e6668c95216c4744b8fddb2356
version: a283a10442df8dc09befd873fab202bf8a253d6a
- name: github.com/streamrail/concurrent-map
version: 65a174a3a4188c0b7099acbc6cfa0c53628d3287
- name: github.com/stretchr/objx
version: cbeaeb16a013161a98496fad62933b1d21786672
- name: github.com/stretchr/testify
version: 8d64eb7173c7753d6419fd4a9caf057398611364
version: d77da356e56a7428ad25149ca77381849a6a5232
subpackages:
- mock
- assert
- mock
- name: github.com/thoas/stats
version: 69e3c072eec2df2df41afe6214f62eb940e4cd80
version: 79b768ff1780f4e5b0ed132e192bfeefe9f85a9c
- name: github.com/tv42/zbase32
version: 03389da7e0bf9844767f82690f4d68fc097a1306
- name: github.com/ugorji/go
version: b94837a2404ab90efe9289e77a70694c355739cb
subpackages:
- codec
- name: github.com/unrolled/render
version: 198ad4d8b8a4612176b804ca10555b222a086b40
- name: github.com/vdemeester/docker-events
version: 20e6d2db238723e68197a9e3c6c34c99a9893a9c
version: be74d4929ec1ad118df54349fda4b0cba60f849b
- name: github.com/vdemeester/shakers
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
- name: github.com/vulcand/oxy
version: 11677428db34c4a05354d66d028174d0e3c6e905
version: 4298f24d572dc554eb984f2ffdf6bdd54d4bd613
repo: https://github.com/containous/oxy.git
vcs: git
subpackages:
- cbreaker
- connlimit
- forward
- memmetrics
- roundrobin
- stream
- utils
- name: github.com/vulcand/predicate
version: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
version: 19b9dde14240d94c804ae5736ad0e1de10bf8fe6
- name: github.com/vulcand/route
version: cb89d787ddbb1c5849a7ac9f79004c1fd12a4a32
- name: github.com/vulcand/vulcand
version: 475540bb016702d5b7cc4674e37f48ee3e144a69
version: 28a4e5c0892167589737b95ceecbcef00295be50
subpackages:
- plugin/rewrite
- conntracker
- plugin
- plugin/rewrite
- router
- name: github.com/xenolf/lego
version: 30a7a8e8821de3532192d1240a45e53c6204f603
version: b2fad6198110326662e9e356a97199078a4a775c
subpackages:
- acme
- name: golang.org/x/crypto
version: 5bcd134fee4dd1475da17714aac19c0aa0142e2f
version: d81fdb778bf2c40a91b24519d60cdc5767318829
subpackages:
- bcrypt
- blowfish
- ocsp
- name: golang.org/x/net
version: 6460565bec1e8891e29ff478184c71b9e443ac36
version: b400c2eff1badec7022a8c8f5bea058b6315eed7
subpackages:
- context
- publicsuffix
- proxy
- publicsuffix
- name: golang.org/x/sys
version: eb2c74142fd19a79b3f237334c7384d5167b1b46
version: 62bee037599929a6e9146f29d10dd5208c43507d
subpackages:
- unix
- windows
- name: gopkg.in/fsnotify.v1
version: 30411dbcefb7a1da7e84f75530ad3abe4011b4f8
version: a8a77c9133d2d6fd8334f3260d06f60e8d80a5fb
- name: gopkg.in/mgo.v2
version: b6e2fa371e64216a45e61072a96d4e3859f169da
version: 29cc868a5ca65f401ff318143f9408d02f4799cc
subpackages:
- bson
- name: gopkg.in/square/go-jose.v1
@@ -215,4 +327,30 @@ imports:
subpackages:
- cipher
- json
devImports: []
testImports:
- name: github.com/Azure/go-ansiterm
version: fa152c58bc15761d0200cb75fe958b89a9d4888e
subpackages:
- winterm
- name: github.com/cloudfoundry-incubator/candiedyaml
version: 99c3df83b51532e3615f851d8c2dbb638f5313bf
- name: github.com/flynn/go-shlex
version: 3f9db97f856818214da2e1057f8ad84803971cff
- name: github.com/gorilla/mux
version: 9fa818a44c2bf1396a17f9d5a3c0f6dd39d2ff8e
- name: github.com/libkermit/docker-check
version: cbe0ef03b3d23070eac4d00ba8828f2cc7f7e5a3
- name: github.com/spf13/pflag
version: 5644820622454e71517561946e3d94b9f9db6842
- name: github.com/vbatts/tar-split
version: 6810cedb21b2c3d0b9bb8f9af12ff2dc7a2f14df
subpackages:
- archive/tar
- tar/asm
- tar/storage
- name: github.com/xeipuuv/gojsonpointer
version: e0fe6f68307607d540ed8eac07a342c33fa1b54a
- name: github.com/xeipuuv/gojsonreference
version: e02fc20de94c78484cd5ffb007f8af96be030a45
- name: github.com/xeipuuv/gojsonschema
version: 00f9fafb54d2244d291b86ab63d12c38bd5c3886

View File

@@ -5,11 +5,14 @@ import:
subpackages:
- fun
- package: github.com/Sirupsen/logrus
- package: github.com/cenkalti/backoff
- package: github.com/cenk/backoff
- package: github.com/codegangsta/negroni
- package: github.com/containous/flaeg
version: b98687da5c323650f4513fda6b6203fcbdec9313
- package: github.com/containous/oxy
version: a731c034dda967333efce5f8d276aeff11f8ff87
- package: github.com/vulcand/oxy
version: 4298f24d572dc554eb984f2ffdf6bdd54d4bd613
repo: https://github.com/containous/oxy.git
vcs: git
subpackages:
- cbreaker
- connlimit
@@ -18,9 +21,9 @@ import:
- stream
- utils
- package: github.com/containous/staert
version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
version: 92329254783dc01174f03302d51d7cf2c9ff84cf
- package: github.com/docker/engine-api
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
version: 62043eb79d581a32ea849645277023c550732e52
subpackages:
- client
- types
@@ -39,7 +42,7 @@ import:
- store/zookeeper
- package: github.com/elazarl/go-bindata-assetfs
- package: github.com/gambol99/go-marathon
version: ade11d1dc2884ee1f387078fc28509559b6235d1
version: a558128c87724cd7430060ef5aedf39f83937f55
- package: github.com/containous/mux
- package: github.com/hashicorp/consul
subpackages:
@@ -53,27 +56,49 @@ import:
- package: github.com/thoas/stats
- package: github.com/unrolled/render
- package: github.com/vdemeester/docker-events
version: be74d4929ec1ad118df54349fda4b0cba60f849b
- package: github.com/vulcand/vulcand
subpackages:
- plugin/rewrite
- package: github.com/xenolf/lego
version: b2fad6198110326662e9e356a97199078a4a775c
subpackages:
- acme
- package: golang.org/x/net
subpackages:
- context
- package: gopkg.in/fsnotify.v1
- package: github.com/libkermit/docker-check
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
- package: github.com/libkermit/compose
version: cadc5a3b83a15790174bd7fbc75ea2529785e772
- package: github.com/libkermit/docker
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
version: 55e3595409924fcfbb850811e5a7cdbe8960a0b7
- package: github.com/docker/docker
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
version: 534753663161334baba06f13b8efa4cad22b5bc5
subpackages:
- namesgenerator
- package: github.com/go-check/check
- package: github.com/docker/libcompose
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
version: d1876c1d68527a49c0aac22a0b161acc7296b740
- package: github.com/mattn/go-shellwords
- package: github.com/vdemeester/shakers
- package: github.com/ryanuber/go-glob
- package: github.com/mesos/mesos-go
subpackages:
- mesosproto
- mesos
- upid
- mesosutil
- detector
- package: github.com/jarcoal/httpmock
- package: github.com/mesosphere/mesos-dns
version: b47dc4c19f215e98da687b15b4c64e70f629bea5
repo: https://github.com/containous/mesos-dns.git
vcs: git
- package: github.com/tv42/zbase32
- package: github.com/abbot/go-http-auth
- package: github.com/miekg/dns
version: 5d001d020961ae1c184f9f8152fdc73810481677
- package: github.com/NYTimes/gziphandler
- package: github.com/docker/leadership
- package: github.com/satori/go.uuid
version: ^1.1.0

View File

@@ -75,7 +75,7 @@ func (s *AccessLogSuite) TestAccessLog(c *check.C) {
c.Assert(tokens[9], checker.Equals, fmt.Sprintf("%d", i+1))
c.Assert(strings.HasPrefix(tokens[10], "frontend"), checker.True)
c.Assert(strings.HasPrefix(tokens[11], "http://127.0.0.1:808"), checker.True)
c.Assert(regexp.MustCompile("^\\d+\\.\\d+.*s$").MatchString(tokens[12]), checker.True)
c.Assert(regexp.MustCompile("^\\d+ms$").MatchString(tokens[12]), checker.True)
}
}
c.Assert(count, checker.Equals, 3)

View File

@@ -5,17 +5,22 @@ import (
"os/exec"
"time"
"github.com/containous/staert"
"github.com/docker/libkv"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/consul"
"github.com/go-check/check"
"golang.org/x/net/context"
"errors"
"github.com/containous/traefik/cluster"
"github.com/containous/traefik/integration/utils"
"github.com/containous/traefik/provider"
checker "github.com/vdemeester/shakers"
"io/ioutil"
"os"
"strings"
"sync"
)
// Consul test suites (using libcompose)
@@ -24,7 +29,7 @@ type ConsulSuite struct {
kv store.Store
}
func (s *ConsulSuite) SetUpSuite(c *check.C) {
func (s *ConsulSuite) setupConsul(c *check.C) {
s.createComposeProject(c, "consul")
s.composeProject.Start(c)
@@ -52,7 +57,56 @@ func (s *ConsulSuite) SetUpSuite(c *check.C) {
c.Assert(err, checker.IsNil)
}
func (s *ConsulSuite) setupConsulTLS(c *check.C) {
s.createComposeProject(c, "consul_tls")
s.composeProject.Start(c)
consul.Register()
clientTLS := &provider.ClientTLS{
CA: "resources/tls/ca.cert",
Cert: "resources/tls/consul.cert",
Key: "resources/tls/consul.key",
InsecureSkipVerify: true,
}
TLSConfig, err := clientTLS.CreateTLSConfig()
c.Assert(err, checker.IsNil)
kv, err := libkv.NewStore(
store.CONSUL,
[]string{s.composeProject.Container(c, "consul").NetworkSettings.IPAddress + ":8585"},
&store.Config{
ConnectionTimeout: 10 * time.Second,
TLS: TLSConfig,
},
)
if err != nil {
c.Fatal("Cannot create store consul")
}
s.kv = kv
// wait for consul
err = utils.Try(60*time.Second, func() error {
_, err := kv.Exists("test")
if err != nil {
return err
}
return nil
})
c.Assert(err, checker.IsNil)
}
func (s *ConsulSuite) TearDownTest(c *check.C) {
// shutdown and delete compose project
if s.composeProject != nil {
s.composeProject.Stop(c)
}
}
func (s *ConsulSuite) TearDownSuite(c *check.C) {}
func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
s.setupConsul(c)
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost})
defer os.Remove(file)
@@ -70,6 +124,7 @@ func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
}
func (s *ConsulSuite) TestNominalConfiguration(c *check.C) {
s.setupConsul(c)
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost})
defer os.Remove(file)
@@ -190,3 +245,279 @@ func (s *ConsulSuite) TestNominalConfiguration(c *check.C) {
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 404)
}
func (s *ConsulSuite) TestGlobalConfiguration(c *check.C) {
s.setupConsul(c)
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
err := s.kv.Put("traefik/entrypoints/http/address", []byte(":8001"), nil)
c.Assert(err, checker.IsNil)
// wait for consul
err = utils.Try(60*time.Second, func() error {
_, err := s.kv.Exists("traefik/entrypoints/http/address")
if err != nil {
return err
}
return nil
})
c.Assert(err, checker.IsNil)
// start traefik
cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_web.toml", "--consul", "--consul.endpoint="+consulHost+":8500")
// cmd.Stdout = os.Stdout
// cmd.Stderr = os.Stderr
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
whoami1 := s.composeProject.Container(c, "whoami1")
whoami2 := s.composeProject.Container(c, "whoami2")
whoami3 := s.composeProject.Container(c, "whoami3")
whoami4 := s.composeProject.Container(c, "whoami4")
backend1 := map[string]string{
"traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5",
"traefik/backends/backend1/servers/server1/url": "http://" + whoami1.NetworkSettings.IPAddress + ":80",
"traefik/backends/backend1/servers/server1/weight": "10",
"traefik/backends/backend1/servers/server2/url": "http://" + whoami2.NetworkSettings.IPAddress + ":80",
"traefik/backends/backend1/servers/server2/weight": "1",
}
backend2 := map[string]string{
"traefik/backends/backend2/loadbalancer/method": "drr",
"traefik/backends/backend2/servers/server1/url": "http://" + whoami3.NetworkSettings.IPAddress + ":80",
"traefik/backends/backend2/servers/server1/weight": "1",
"traefik/backends/backend2/servers/server2/url": "http://" + whoami4.NetworkSettings.IPAddress + ":80",
"traefik/backends/backend2/servers/server2/weight": "2",
}
frontend1 := map[string]string{
"traefik/frontends/frontend1/backend": "backend2",
"traefik/frontends/frontend1/entrypoints": "http",
"traefik/frontends/frontend1/priority": "1",
"traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost",
}
frontend2 := map[string]string{
"traefik/frontends/frontend2/backend": "backend1",
"traefik/frontends/frontend2/entrypoints": "http",
"traefik/frontends/frontend2/priority": "10",
"traefik/frontends/frontend2/routes/test_2/rule": "Path:/test",
}
for key, value := range backend1 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range backend2 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range frontend1 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range frontend2 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
// wait for consul
err = utils.Try(60*time.Second, func() error {
_, err := s.kv.Exists("traefik/frontends/frontend2/routes/test_2/rule")
if err != nil {
return err
}
return nil
})
c.Assert(err, checker.IsNil)
// wait for traefik
err = utils.TryRequest("http://127.0.0.1:8080/api/providers", 60*time.Second, func(res *http.Response) error {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
if !strings.Contains(string(body), "Path:/test") {
return errors.New("Incorrect traefik config")
}
return nil
})
c.Assert(err, checker.IsNil)
//check
client := &http.Client{}
req, err := http.NewRequest("GET", "http://127.0.0.1:8001/", nil)
c.Assert(err, checker.IsNil)
req.Host = "test.localhost"
response, err := client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(response.StatusCode, checker.Equals, 200)
}
func (s *ConsulSuite) skipTestGlobalConfigurationWithClientTLS(c *check.C) {
c.Skip("wait for relative path issue in the composefile")
s.setupConsulTLS(c)
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
err := s.kv.Put("traefik/web/address", []byte(":8081"), nil)
c.Assert(err, checker.IsNil)
// wait for consul
err = utils.Try(60*time.Second, func() error {
_, err := s.kv.Exists("traefik/web/address")
if err != nil {
return err
}
return nil
})
c.Assert(err, checker.IsNil)
// start traefik
cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_web.toml",
"--consul", "--consul.endpoint="+consulHost+":8585",
"--consul.tls.ca=resources/tls/ca.cert",
"--consul.tls.cert=resources/tls/consul.cert",
"--consul.tls.key=resources/tls/consul.key",
"--consul.tls.insecureskipverify")
// cmd.Stdout = os.Stdout
// cmd.Stderr = os.Stderr
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// wait for traefik
err = utils.TryRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, func(res *http.Response) error {
_, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
return nil
})
c.Assert(err, checker.IsNil)
}
func (s *ConsulSuite) TestCommandStoreConfig(c *check.C) {
s.setupConsul(c)
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
cmd := exec.Command(traefikBinary, "storeconfig", "--configFile=fixtures/simple_web.toml", "--consul.endpoint="+consulHost+":8500")
err := cmd.Start()
c.Assert(err, checker.IsNil)
// wait for traefik finish without error
cmd.Wait()
//CHECK
checkmap := map[string]string{
"/traefik/loglevel": "DEBUG",
"/traefik/defaultentrypoints/0": "http",
"/traefik/entrypoints/http/address": ":8000",
"/traefik/web/address": ":8080",
"/traefik/consul/endpoint": (consulHost + ":8500"),
}
for key, value := range checkmap {
var p *store.KVPair
err = utils.Try(60*time.Second, func() error {
p, err = s.kv.Get(key)
if err != nil {
return err
}
return nil
})
c.Assert(err, checker.IsNil)
c.Assert(string(p.Value), checker.Equals, value)
}
}
type TestStruct struct {
String string
Int int
}
func (s *ConsulSuite) TestDatastore(c *check.C) {
s.setupConsul(c)
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
kvSource, err := staert.NewKvSource(store.CONSUL, []string{consulHost + ":8500"}, &store.Config{
ConnectionTimeout: 10 * time.Second,
}, "traefik")
c.Assert(err, checker.IsNil)
ctx := context.Background()
datastore1, err := cluster.NewDataStore(*kvSource, ctx, &TestStruct{}, nil)
c.Assert(err, checker.IsNil)
datastore2, err := cluster.NewDataStore(*kvSource, ctx, &TestStruct{}, nil)
c.Assert(err, checker.IsNil)
setter1, _, err := datastore1.Begin()
c.Assert(err, checker.IsNil)
err = setter1.Commit(&TestStruct{
String: "foo",
Int: 1,
})
c.Assert(err, checker.IsNil)
time.Sleep(2 * time.Second)
test1 := datastore1.Get().(*TestStruct)
c.Assert(test1.String, checker.Equals, "foo")
test2 := datastore2.Get().(*TestStruct)
c.Assert(test2.String, checker.Equals, "foo")
setter2, _, err := datastore2.Begin()
c.Assert(err, checker.IsNil)
err = setter2.Commit(&TestStruct{
String: "bar",
Int: 2,
})
c.Assert(err, checker.IsNil)
time.Sleep(2 * time.Second)
test1 = datastore1.Get().(*TestStruct)
c.Assert(test1.String, checker.Equals, "bar")
test2 = datastore2.Get().(*TestStruct)
c.Assert(test2.String, checker.Equals, "bar")
wg := &sync.WaitGroup{}
wg.Add(4)
go func() {
for i := 0; i < 100; i++ {
setter1, _, err := datastore1.Begin()
c.Assert(err, checker.IsNil)
err = setter1.Commit(&TestStruct{
String: "datastore1",
Int: i,
})
c.Assert(err, checker.IsNil)
}
wg.Done()
}()
go func() {
for i := 0; i < 100; i++ {
setter2, _, err := datastore2.Begin()
c.Assert(err, checker.IsNil)
err = setter2.Commit(&TestStruct{
String: "datastore2",
Int: i,
})
c.Assert(err, checker.IsNil)
}
wg.Done()
}()
go func() {
for i := 0; i < 100; i++ {
test1 := datastore1.Get().(*TestStruct)
c.Assert(test1, checker.NotNil)
}
wg.Done()
}()
go func() {
for i := 0; i < 100; i++ {
test2 := datastore2.Get().(*TestStruct)
c.Assert(test2, checker.NotNil)
}
wg.Done()
}()
wg.Wait()
}

View File

@@ -8,6 +8,7 @@ import (
checker "github.com/vdemeester/shakers"
"crypto/tls"
"errors"
"fmt"
"github.com/containous/traefik/integration/utils"
@@ -25,12 +26,12 @@ type EtcdSuite struct {
kv store.Store
}
func (s *EtcdSuite) SetUpSuite(c *check.C) {
func (s *EtcdSuite) SetUpTest(c *check.C) {
s.createComposeProject(c, "etcd")
s.composeProject.Start(c)
etcd.Register()
url := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress + ":4001"
url := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress + ":2379"
kv, err := libkv.NewStore(
store.ETCD,
[]string{url},
@@ -54,6 +55,15 @@ func (s *EtcdSuite) SetUpSuite(c *check.C) {
c.Assert(err, checker.IsNil)
}
func (s *EtcdSuite) TearDownTest(c *check.C) {
// shutdown and delete compose project
if s.composeProject != nil {
s.composeProject.Stop(c)
}
}
func (s *EtcdSuite) TearDownSuite(c *check.C) {}
func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
file := s.adaptFile(c, "fixtures/etcd/simple.toml", struct{ EtcdHost string }{etcdHost})
@@ -193,3 +203,266 @@ func (s *EtcdSuite) TestNominalConfiguration(c *check.C) {
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 404)
}
func (s *EtcdSuite) TestGlobalConfiguration(c *check.C) {
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
err := s.kv.Put("/traefik/entrypoints/http/address", []byte(":8001"), nil)
c.Assert(err, checker.IsNil)
// wait for etcd
err = utils.Try(60*time.Second, func() error {
_, err := s.kv.Exists("/traefik/entrypoints/http/address")
if err != nil {
return err
}
return nil
})
c.Assert(err, checker.IsNil)
// start traefik
cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_web.toml", "--etcd", "--etcd.endpoint="+etcdHost+":4001")
// cmd.Stdout = os.Stdout
// cmd.Stderr = os.Stderr
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
whoami1 := s.composeProject.Container(c, "whoami1")
whoami2 := s.composeProject.Container(c, "whoami2")
whoami3 := s.composeProject.Container(c, "whoami3")
whoami4 := s.composeProject.Container(c, "whoami4")
backend1 := map[string]string{
"/traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5",
"/traefik/backends/backend1/servers/server1/url": "http://" + whoami1.NetworkSettings.IPAddress + ":80",
"/traefik/backends/backend1/servers/server1/weight": "10",
"/traefik/backends/backend1/servers/server2/url": "http://" + whoami2.NetworkSettings.IPAddress + ":80",
"/traefik/backends/backend1/servers/server2/weight": "1",
}
backend2 := map[string]string{
"/traefik/backends/backend2/loadbalancer/method": "drr",
"/traefik/backends/backend2/servers/server1/url": "http://" + whoami3.NetworkSettings.IPAddress + ":80",
"/traefik/backends/backend2/servers/server1/weight": "1",
"/traefik/backends/backend2/servers/server2/url": "http://" + whoami4.NetworkSettings.IPAddress + ":80",
"/traefik/backends/backend2/servers/server2/weight": "2",
}
frontend1 := map[string]string{
"/traefik/frontends/frontend1/backend": "backend2",
"/traefik/frontends/frontend1/entrypoints": "http",
"/traefik/frontends/frontend1/priority": "1",
"/traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost",
}
frontend2 := map[string]string{
"/traefik/frontends/frontend2/backend": "backend1",
"/traefik/frontends/frontend2/entrypoints": "http",
"/traefik/frontends/frontend2/priority": "10",
"/traefik/frontends/frontend2/routes/test_2/rule": "Path:/test",
}
for key, value := range backend1 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range backend2 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range frontend1 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range frontend2 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
// wait for etcd
err = utils.Try(60*time.Second, func() error {
_, err := s.kv.Exists("/traefik/frontends/frontend2/routes/test_2/rule")
if err != nil {
return err
}
return nil
})
c.Assert(err, checker.IsNil)
// wait for traefik
err = utils.TryRequest("http://127.0.0.1:8080/api/providers", 60*time.Second, func(res *http.Response) error {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
if !strings.Contains(string(body), "Path:/test") {
return errors.New("Incorrect traefik config")
}
return nil
})
c.Assert(err, checker.IsNil)
//check
client := &http.Client{}
req, err := http.NewRequest("GET", "http://127.0.0.1:8001/", nil)
c.Assert(err, checker.IsNil)
req.Host = "test.localhost"
response, err := client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(response.StatusCode, checker.Equals, 200)
}
func (s *EtcdSuite) TestCertificatesContentstWithSNIConfigHandshake(c *check.C) {
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
// start traefik
cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_web.toml", "--etcd", "--etcd.endpoint="+etcdHost+":4001")
// cmd.Stdout = os.Stdout
// cmd.Stderr = os.Stderr
whoami1 := s.composeProject.Container(c, "whoami1")
whoami2 := s.composeProject.Container(c, "whoami2")
whoami3 := s.composeProject.Container(c, "whoami3")
whoami4 := s.composeProject.Container(c, "whoami4")
//Copy the contents of the certificate files into ETCD
snitestComCert, err := ioutil.ReadFile("fixtures/https/snitest.com.cert")
c.Assert(err, checker.IsNil)
snitestComKey, err := ioutil.ReadFile("fixtures/https/snitest.com.key")
c.Assert(err, checker.IsNil)
snitestOrgCert, err := ioutil.ReadFile("fixtures/https/snitest.org.cert")
c.Assert(err, checker.IsNil)
snitestOrgKey, err := ioutil.ReadFile("fixtures/https/snitest.org.key")
c.Assert(err, checker.IsNil)
globalConfig := map[string]string{
"/traefik/entrypoints/https/address": ":4443",
"/traefik/entrypoints/https/tls/certificates/0/certfile": string(snitestComCert),
"/traefik/entrypoints/https/tls/certificates/0/keyfile": string(snitestComKey),
"/traefik/entrypoints/https/tls/certificates/1/certfile": string(snitestOrgCert),
"/traefik/entrypoints/https/tls/certificates/1/keyfile": string(snitestOrgKey),
"/traefik/defaultentrypoints/0": "https",
}
backend1 := map[string]string{
"/traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5",
"/traefik/backends/backend1/servers/server1/url": "http://" + whoami1.NetworkSettings.IPAddress + ":80",
"/traefik/backends/backend1/servers/server1/weight": "10",
"/traefik/backends/backend1/servers/server2/url": "http://" + whoami2.NetworkSettings.IPAddress + ":80",
"/traefik/backends/backend1/servers/server2/weight": "1",
}
backend2 := map[string]string{
"/traefik/backends/backend2/loadbalancer/method": "drr",
"/traefik/backends/backend2/servers/server1/url": "http://" + whoami3.NetworkSettings.IPAddress + ":80",
"/traefik/backends/backend2/servers/server1/weight": "1",
"/traefik/backends/backend2/servers/server2/url": "http://" + whoami4.NetworkSettings.IPAddress + ":80",
"/traefik/backends/backend2/servers/server2/weight": "2",
}
frontend1 := map[string]string{
"/traefik/frontends/frontend1/backend": "backend2",
"/traefik/frontends/frontend1/entrypoints": "http",
"/traefik/frontends/frontend1/priority": "1",
"/traefik/frontends/frontend1/routes/test_1/rule": "Host:snitest.com",
}
frontend2 := map[string]string{
"/traefik/frontends/frontend2/backend": "backend1",
"/traefik/frontends/frontend2/entrypoints": "http",
"/traefik/frontends/frontend2/priority": "10",
"/traefik/frontends/frontend2/routes/test_2/rule": "Host:snitest.org",
}
for key, value := range globalConfig {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range backend1 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range backend2 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range frontend1 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range frontend2 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
// wait for etcd
err = utils.Try(60*time.Second, func() error {
_, err := s.kv.Exists("/traefik/frontends/frontend2/routes/test_2/rule")
if err != nil {
return err
}
return nil
})
c.Assert(err, checker.IsNil)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// wait for traefik
err = utils.TryRequest("http://127.0.0.1:8080/api/providers", 60*time.Second, func(res *http.Response) error {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
if !strings.Contains(string(body), "Host:snitest.org") {
return errors.New("Incorrect traefik config")
}
return nil
})
c.Assert(err, checker.IsNil)
//check
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
ServerName: "snitest.com",
}
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
defer conn.Close()
err = conn.Handshake()
c.Assert(err, checker.IsNil, check.Commentf("TLS handshake error"))
cs := conn.ConnectionState()
err = cs.PeerCertificates[0].VerifyHostname("snitest.com")
c.Assert(err, checker.IsNil, check.Commentf("certificate did not match SNI servername"))
}
func (s *EtcdSuite) TestCommandStoreConfig(c *check.C) {
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
cmd := exec.Command(traefikBinary, "storeconfig", "--configFile=fixtures/simple_web.toml", "--etcd.endpoint="+etcdHost+":4001")
err := cmd.Start()
c.Assert(err, checker.IsNil)
// wait for traefik finish without error
cmd.Wait()
//CHECK
checkmap := map[string]string{
"/traefik/loglevel": "DEBUG",
"/traefik/defaultentrypoints/0": "http",
"/traefik/entrypoints/http/address": ":8000",
"/traefik/web/address": ":8080",
"/traefik/etcd/endpoint": (etcdHost + ":4001"),
}
for key, value := range checkmap {
var p *store.KVPair
err = utils.Try(60*time.Second, func() error {
p, err = s.kv.Get(key)
if err != nil {
return err
}
return nil
})
c.Assert(err, checker.IsNil)
c.Assert(string(p.Value), checker.Equals, value)
}
}

View File

@@ -12,3 +12,4 @@ logLevel = "DEBUG"
endpoint = "{{.DockerHost}}"
domain = "docker.localhost"
exposedbydefault = true

View File

@@ -8,9 +8,9 @@ logLevel = "DEBUG"
[etcd]
endpoint = "{{.EtcdHost}}:4001"
endpoint = "{{.EtcdHost}}:2379"
prefix = "/traefik"
watch = true
[web]
address = ":8081"
address = ":8081"

View File

@@ -0,0 +1,25 @@
# This is how the certs were created
```bash
openssl req -new -newkey rsa:2048 -x509 -days 3650 -extensions v3_ca -keyout ca1.pem -out ca1.crt
openssl req -new -newkey rsa:2048 -x509 -days 3650 -extensions v3_ca -keyout ca2.pem -out ca2.crt
openssl req -new -newkey rsa:2048 -x509 -days 3650 -extensions v3_ca -keyout ca3.pem -out ca3.crt
openssl rsa -in ca1.pem -out ca1.key
openssl rsa -in ca2.pem -out ca2.key
openssl rsa -in ca3.pem -out ca3.key
cat ca1.crt ca2.crt > ca1and2.crt
rm ca1.pem ca2.pem ca3.pem
openssl genrsa -out client1.key 2048
openssl genrsa -out client2.key 2048
openssl genrsa -out client3.key 2048
openssl req -key client1.key -new -out client1.csr
openssl req -key client2.key -new -out client2.csr
openssl req -key client3.key -new -out client3.csr
openssl x509 -req -days 3650 -in client1.csr -CA ca1.crt -CAkey ca1.key -CAcreateserial -out client1.crt
openssl x509 -req -days 3650 -in client2.csr -CA ca2.crt -CAkey ca2.key -CAcreateserial -out client2.crt
openssl x509 -req -days 3650 -in client3.csr -CA ca3.crt -CAkey ca3.key -CAcreateserial -out client3.crt
```

View File

@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDMjCCAhqgAwIBAgIJAKXHiSnQw6LqMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV
BAMTD2NhMS5leGFtcGxlLmNvbTAeFw0xNjA2MTgxMzAyNDdaFw0yNjA2MTYxMzAy
NDdaMBoxGDAWBgNVBAMTD2NhMS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAL9ZNf1Pqu30i/DUyAAbEVFfCvGEmN9hfGAK44IrBqfC
1ziW2Lfg2AkswNIC/T6M+lcoN0ftPhJpnP2Cdz9U/gF9FMd/XAGY/SOiun7wC8so
qdab7CMDlHP1c/XiL7lGEdm9RfynLcJ5JJn2X7mXwEZTviFtiJVmaoAl3TVNy3MZ
ZyfjNac9sA5idpX66TpVO9tE1gu71nRkBvTEzO/IYv8rcWQmogvH7DN3UurP3RUK
weij01rekG3OOOXUlQgZO6mhuvrKes9Xoc901bmTkOgTq7wIFf2AZozU4wy6kZfM
0sdzmjMpuEr7oROepvtzFiVyNIEGDJ3QvEEY4QJaFvcCAwEAAaN7MHkwHQYDVR0O
BBYEFFyJ/cSOOvcsfu+WLZbi/u3t8W/uMEoGA1UdIwRDMEGAFFyJ/cSOOvcsfu+W
LZbi/u3t8W/uoR6kHDAaMRgwFgYDVQQDEw9jYTEuZXhhbXBsZS5jb22CCQClx4kp
0MOi6jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCOBLJJF0esBVLX
xmj0xa0TREXTxco40e/fmUU1cGYgl1UCCZI7MLDcl6k6Km9Sbp/LCpZx88mtLwGY
wUss2mQ058kqiUrpb/U8xEbglLrRtsp1y8z7lood/8ru39zj1/9X4MFyqNi6390I
zxZNf2QauUS1TMxgv6UhVE52JaAL+sn2hqA6IaSYeT9NFzFsulCr29mxlIC9SzUr
Mbqri9LKX5aciy78+hQBKdXoJ5raRwttBvULabOrLhZdyvvL6QfcdgRV+JOT7vKn
htQahWSKoqhdpM6Q2pXP42/MyuKXFB5Nk8fnFiIoXH0Bs9vlPLOvToM2jYJ+LlDd
85qbL4eP
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAv1k1/U+q7fSL8NTIABsRUV8K8YSY32F8YArjgisGp8LXOJbY
t+DYCSzA0gL9Poz6Vyg3R+0+Emmc/YJ3P1T+AX0Ux39cAZj9I6K6fvALyyip1pvs
IwOUc/Vz9eIvuUYR2b1F/KctwnkkmfZfuZfARlO+IW2IlWZqgCXdNU3LcxlnJ+M1
pz2wDmJ2lfrpOlU720TWC7vWdGQG9MTM78hi/ytxZCaiC8fsM3dS6s/dFQrB6KPT
Wt6Qbc445dSVCBk7qaG6+sp6z1ehz3TVuZOQ6BOrvAgV/YBmjNTjDLqRl8zSx3Oa
Mym4SvuhE56m+3MWJXI0gQYMndC8QRjhAloW9wIDAQABAoIBAGJ9g8mn6R5kImfK
zksno4lTt2lLS/im0AMLd8E3bkyJgIgTNOeopupKC9HNUhaRMAYOoC24kpudmv3t
2n1RvRB9FmX9SxlTavCdwQq3egqPGqRpS2lWXWI2dAKa8t+VjniZ8N00G9yeyFUr
OGhqEMDiN9oy6/uiZK0jUDIwocjS5FZMBh+epM7/CnKj3uvqarmFXKcJ4ni28ww4
RPrXDm+VvXa30/hK8q8Eo3C3u39TMvNEaRqMP/zqRY89fbpd1+Okno79dugFhz7D
r/Jae9z4ChFBXegDmA/OkWOdLY5LyvwvpJpONjD/5wImY1OAJlFTg7S+2FcSVvCF
diUJ7/ECgYEA9pHYlJsWAo/izRUVhKRtBAVVjnlidxExuvOGNXpyPjZd5ruXochu
J6tAKA0rSE4RsISFVCrkQmjDgjyKa2D+o/hsTTlW3yrD4TSLI8/MrDtfCw9XRqeE
KqfeqT79Hh0icnsUVYH4eoND9CKuJ/B9NcdyUqRPm7Pnrx07SnhGHd8CgYEAxqqy
MPIDO2dadRqUIhWwMPIBegkZC1eeuv4pNEyukZc4+pXRshKXhvhmvz5NgsaSsKxZ
O6FgqzgTceLEubVYF4hvy1TC+3Fc/PFvh4Fo3SKjtiJRJjRREDWBu6hl16Cw/83j
k6Im//8WD1ri9iFf8RjrBwYH1xHqGTkNEUHl+ekCgYEAzlIWD6uCDFzIJGGLIvXP
fvjTsadivE039r7Fw8QVCnfFtUetxyOHAUysH5d9a0BgTvtk8Zv+ao9tYXI1RUrh
aOV8AlaDmbQYOj8UWsAL/OalTgTlO+r6jhLwH2DkvqkUZQUWa8KY4DMszoGihysW
KsUcpYh2UMyGhqKINXVU/rMCgYEAqJxbG9trDtHLHjRuoPcTUJc01aQ/EzdMSpxH
0FF8n6he/Z6GGMJaxHyyh4GTO3jZKwU7vrZaWzb+mdvC53KXz3FGoKXRzqIKL8uh
wrn8jCJIG97ITMp+OmmPL/veY8HIN3NAwR4QR5jx2hpjIk51JSTm5FEj+k8EBmA7
TPhG/XECgYA9e9B0jgR2aFSAWzpGMZYPW+NdGQlySv94AJmfF8U5J7PmU2BojvVn
bhWNSQk2LI/mTjLgB+liYtLqFGkgIrJdbBOQ8hKSBPGQltSR0Dvf0ZK/0F1hqDTW
m3AUvPZthNMNJIYkTav5a246tyKkmg11nUQsgoqdxCrEiLyv48PFnw==
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1 @@
83E81F36599F4400

View File

@@ -0,0 +1,40 @@
-----BEGIN CERTIFICATE-----
MIIDMjCCAhqgAwIBAgIJAKXHiSnQw6LqMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV
BAMTD2NhMS5leGFtcGxlLmNvbTAeFw0xNjA2MTgxMzAyNDdaFw0yNjA2MTYxMzAy
NDdaMBoxGDAWBgNVBAMTD2NhMS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAL9ZNf1Pqu30i/DUyAAbEVFfCvGEmN9hfGAK44IrBqfC
1ziW2Lfg2AkswNIC/T6M+lcoN0ftPhJpnP2Cdz9U/gF9FMd/XAGY/SOiun7wC8so
qdab7CMDlHP1c/XiL7lGEdm9RfynLcJ5JJn2X7mXwEZTviFtiJVmaoAl3TVNy3MZ
ZyfjNac9sA5idpX66TpVO9tE1gu71nRkBvTEzO/IYv8rcWQmogvH7DN3UurP3RUK
weij01rekG3OOOXUlQgZO6mhuvrKes9Xoc901bmTkOgTq7wIFf2AZozU4wy6kZfM
0sdzmjMpuEr7oROepvtzFiVyNIEGDJ3QvEEY4QJaFvcCAwEAAaN7MHkwHQYDVR0O
BBYEFFyJ/cSOOvcsfu+WLZbi/u3t8W/uMEoGA1UdIwRDMEGAFFyJ/cSOOvcsfu+W
LZbi/u3t8W/uoR6kHDAaMRgwFgYDVQQDEw9jYTEuZXhhbXBsZS5jb22CCQClx4kp
0MOi6jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCOBLJJF0esBVLX
xmj0xa0TREXTxco40e/fmUU1cGYgl1UCCZI7MLDcl6k6Km9Sbp/LCpZx88mtLwGY
wUss2mQ058kqiUrpb/U8xEbglLrRtsp1y8z7lood/8ru39zj1/9X4MFyqNi6390I
zxZNf2QauUS1TMxgv6UhVE52JaAL+sn2hqA6IaSYeT9NFzFsulCr29mxlIC9SzUr
Mbqri9LKX5aciy78+hQBKdXoJ5raRwttBvULabOrLhZdyvvL6QfcdgRV+JOT7vKn
htQahWSKoqhdpM6Q2pXP42/MyuKXFB5Nk8fnFiIoXH0Bs9vlPLOvToM2jYJ+LlDd
85qbL4eP
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDMjCCAhqgAwIBAgIJAKjhXgiuPQexMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV
BAMTD2NhMi5leGFtcGxlLmNvbTAeFw0xNjA2MTgxMzAzMjJaFw0yNjA2MTYxMzAz
MjJaMBoxGDAWBgNVBAMTD2NhMi5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAMx8S4U3tdeMGn1NEUNWCmD7pIYUCUhtORrn2rqF5b2M
ZQJZXAIfWJ7KrGjn8W7KPx8/V2FREHF1Z6v1fpB2rfCIFo97HszhQEt6lduKup2j
09ItpFjec7RahwaMksYDwl4PaxgKe2OYdLFJ/QIv8+I01vWPXFmHgZkBHQWhR5nV
TvGM6MU834e+PXxCXfcaC8VYpbHYKYxHmM5Sxa5V9WlppBBshB0OL+KrCPXwPqHl
StZPkG2p2qJUjCZ38uDx605RYaORZ0eDhrKj4M3lJzOTTcC4I77BzTb74+GcRT+R
lJMrWrS22jNZONnawBdbTWIFM4PzaqVvE7qVwZK1M5UCAwEAAaN7MHkwHQYDVR0O
BBYEFPooSq3ZvoyIzRQ96/dwUC0LDBvRMEoGA1UdIwRDMEGAFPooSq3ZvoyIzRQ9
6/dwUC0LDBvRoR6kHDAaMRgwFgYDVQQDEw9jYTIuZXhhbXBsZS5jb22CCQCo4V4I
rj0HsTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCvRgu11LrF7G9X
yuvUwBZJ8FgjAMPwXQIAYg47tlvD9ZDiZgXVulWOm6aHpT520MjNO9f0oKpsrSsh
7bsO4GSkbTPgGekbw4P3JtXAvlBEB5uabpdmF37Pg9s7dU/MeXCElzWF+yLVAo7o
Hj1UlENxh08FzlErNw6Djy2FZAADeSZ3LmHUl+50rrp5/DxrEhkHFm8dTTjFVPnK
KrnYLM8R7+v2Ysk6hTy4kwyiTKVZurK7ELRvS0RxWhtbVCXJ2HS1lv/LgEH1hyIP
SwvyZ25JhcGrBAL/jpzTxdDEGsPfUSVfrUhrhDWxg0dzY+ptwdTWHqxyR2YKmOgU
dKYIz/nK
-----END CERTIFICATE-----

View File

@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDMjCCAhqgAwIBAgIJAKjhXgiuPQexMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV
BAMTD2NhMi5leGFtcGxlLmNvbTAeFw0xNjA2MTgxMzAzMjJaFw0yNjA2MTYxMzAz
MjJaMBoxGDAWBgNVBAMTD2NhMi5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAMx8S4U3tdeMGn1NEUNWCmD7pIYUCUhtORrn2rqF5b2M
ZQJZXAIfWJ7KrGjn8W7KPx8/V2FREHF1Z6v1fpB2rfCIFo97HszhQEt6lduKup2j
09ItpFjec7RahwaMksYDwl4PaxgKe2OYdLFJ/QIv8+I01vWPXFmHgZkBHQWhR5nV
TvGM6MU834e+PXxCXfcaC8VYpbHYKYxHmM5Sxa5V9WlppBBshB0OL+KrCPXwPqHl
StZPkG2p2qJUjCZ38uDx605RYaORZ0eDhrKj4M3lJzOTTcC4I77BzTb74+GcRT+R
lJMrWrS22jNZONnawBdbTWIFM4PzaqVvE7qVwZK1M5UCAwEAAaN7MHkwHQYDVR0O
BBYEFPooSq3ZvoyIzRQ96/dwUC0LDBvRMEoGA1UdIwRDMEGAFPooSq3ZvoyIzRQ9
6/dwUC0LDBvRoR6kHDAaMRgwFgYDVQQDEw9jYTIuZXhhbXBsZS5jb22CCQCo4V4I
rj0HsTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCvRgu11LrF7G9X
yuvUwBZJ8FgjAMPwXQIAYg47tlvD9ZDiZgXVulWOm6aHpT520MjNO9f0oKpsrSsh
7bsO4GSkbTPgGekbw4P3JtXAvlBEB5uabpdmF37Pg9s7dU/MeXCElzWF+yLVAo7o
Hj1UlENxh08FzlErNw6Djy2FZAADeSZ3LmHUl+50rrp5/DxrEhkHFm8dTTjFVPnK
KrnYLM8R7+v2Ysk6hTy4kwyiTKVZurK7ELRvS0RxWhtbVCXJ2HS1lv/LgEH1hyIP
SwvyZ25JhcGrBAL/jpzTxdDEGsPfUSVfrUhrhDWxg0dzY+ptwdTWHqxyR2YKmOgU
dKYIz/nK
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAzHxLhTe114wafU0RQ1YKYPukhhQJSG05GufauoXlvYxlAllc
Ah9YnsqsaOfxbso/Hz9XYVEQcXVnq/V+kHat8IgWj3sezOFAS3qV24q6naPT0i2k
WN5ztFqHBoySxgPCXg9rGAp7Y5h0sUn9Ai/z4jTW9Y9cWYeBmQEdBaFHmdVO8Yzo
xTzfh749fEJd9xoLxVilsdgpjEeYzlLFrlX1aWmkEGyEHQ4v4qsI9fA+oeVK1k+Q
banaolSMJnfy4PHrTlFho5FnR4OGsqPgzeUnM5NNwLgjvsHNNvvj4ZxFP5GUkyta
tLbaM1k42drAF1tNYgUzg/NqpW8TupXBkrUzlQIDAQABAoIBAGFMg2LQL2Zw8+nL
UfuIZUfgdViXEBO2ZQW4bQtzyu12cFm9y1n3MGPebEs+klL1STPFH/7eY8SY6MuZ
9K8oyXs6RgHfw7gZNk6z9bqROFrqKVBJB3qB3uxiZv1mxjASednn3D2EP1IUqPHz
EsCHsLRiECaoIHk5USFMtlKHe1pmmsvQrQX7EV9Qg0VSGvQlgxc/Pcg/WeB6uT6u
CS2serWpUE2dBUTJisnUuL7F5/3JbPEPbUG4eeTcO8IafvgdOgFEc5qUlYCFFai0
fvjSabXrJO9QE1Huw0gyC/5FHlVr5x4aJ8NzPKcMRYqn7jpdwA0eyLyBo/KtPIbJ
6s0PFAECgYEA98cKuyaBXpPyG7/Y0C89Mzlt5+Qr0fpPksH6GEelPJVdhrdXP32W
66ROgCVZpf2pQeCCHfXyWdZQwEdSf+8ee1DJMSNgIm4Usqp6yIDS0iZ7pPWz0KSI
un/dm3lRE7hFMIQfbNf3rA0WD8Ani3c76eZruwQ5DNdXNOM+z1DN38UCgYEA00V4
6UOCcA3romkXuIyeyh/tuJ6K1J3ApUxA+E42f4raSMSMgnlAwpL0Wmt11bBOmToi
UAtwFcTfJRJSOvfmM/nd66592FAV/D4xcDIiNGh4xNDi8LSKmSj0WRYPU3YjkdFN
SwI48LmQKMfj3P8fClazKsdcDccfO4pyhEK98ZECgYEAt8QZw1/1hw22/Lm2tgCz
JTCswNXLYjqBldjkAenxNROaf/WucdpVeoMr7YLGEIQnakJ2fn4QtmxrC5BaMaRJ
OTBbZ2RTQnXeR/yEf/x7X31HKrtIF7BP7/Ixi8PYTAXY2vjCzdkHScWS3S+opJlU
CE/rCpNBNLLpbMI1rVDCv/kCgYEAkP3/sg67yQ00prx7JBOVsl/hNK/R1YMCQC8p
838x1axEjGYfjDeM4zwZaKiRMPsTpgMIo2iGHtqCzh1Zw9B38znLPMD+6uJjhD5m
jXpKkS8VmvVEmi89Y0mBEFacZAoS9TLwWccHruWa8vHkBror4luIEJbLLUV3wNQO
LYjkdJECgYBcIjZ1iQiOmFL8lm/JlPOs2JcT33fjnubreHkiG42dZFN2S8D5MdU5
JBP6IVVllPmbptw9T4wcw+bjVa0LQtQMGZLMxdx5nJp5dmFE0Pj8MjLpLy641Vlx
5sv2O+eRpt4yCiuHcuvDrKPGTyM2YqF7ilQwSC5Cfki155InnU2QUg==
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1 @@
9EAC6D05226C2216

View File

@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDMjCCAhqgAwIBAgIJAK/JGxwwmv1jMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV
BAMTD2NhMy5leGFtcGxlLmNvbTAeFw0xNjA2MTgxMzAzNTNaFw0yNjA2MTYxMzAz
NTNaMBoxGDAWBgNVBAMTD2NhMy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAO0B9iUp5w0m1NWC9QYWhxSE/emmmKcx99DWnzKZoIbj
TSRQtyhx+9c2z1dYAFZQpdVRSKQFn1IO8s51wlIc01KLFflz4EvSfAKZiAnkOOez
wzVQ8JWgKfOJV/ZctFPo4xtdhQmO1+U+YgSfU0ASEhHvHbIPTUJNRTfkJsGygq4q
/p9uA1TsjM4bh6AkiD1OlGjp0lbkzn3LLYpXWvgGsuejsdVkJS5pn2NKjkqVhhEg
g7hKKqm8Nc3mb+vGhw/fNppN/xeOswpMPaW77LppyFoDd/OmqqWrbzn2Fqw1nELh
zfo7AkKPyRm8eU3wSTIdmaXx1R5qPjqEmYrrDZ2HXa8CAwEAAaN7MHkwHQYDVR0O
BBYEFMR6dBZAeGgkxwSC/62xGwLEdXCdMEoGA1UdIwRDMEGAFMR6dBZAeGgkxwSC
/62xGwLEdXCdoR6kHDAaMRgwFgYDVQQDEw9jYTMuZXhhbXBsZS5jb22CCQCvyRsc
MJr9YzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQB3VgvPnLEEfbj4
Z61q8oKneklZV+WpDyWSodI6M1l/0pXJCTDRROJ37KaQHLJRQo+rMJiYKvQkCU+y
9JhLdRdMEzy++9hIWiNbDiy3BNMUiQOS1234WVFBosQ6uXNhXbL/Anl4xgiFFRZG
FehjPo0XRvxmBHnrnE1Rce0EmU/1bwVglu8e7mG5bs0gQrXTRlTkxvucyi+B6npF
2vuzxj4q+KgeEYURxCt95JoULtMY2c0VifcdweYDO/2sYEhOVi1N+PhPvZxJD6vR
CxIuT6K3nRe58b1J/f7TH/dvURIb1mVG8+EDQVqa1bzH3JfytsIVG5VL1hppQlgZ
Y0G4haMn
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA7QH2JSnnDSbU1YL1BhaHFIT96aaYpzH30NafMpmghuNNJFC3
KHH71zbPV1gAVlCl1VFIpAWfUg7yznXCUhzTUosV+XPgS9J8ApmICeQ457PDNVDw
laAp84lX9ly0U+jjG12FCY7X5T5iBJ9TQBISEe8dsg9NQk1FN+QmwbKCrir+n24D
VOyMzhuHoCSIPU6UaOnSVuTOfcstilda+Aay56Ox1WQlLmmfY0qOSpWGESCDuEoq
qbw1zeZv68aHD982mk3/F46zCkw9pbvsumnIWgN386aqpatvOfYWrDWcQuHN+jsC
Qo/JGbx5TfBJMh2ZpfHVHmo+OoSZiusNnYddrwIDAQABAoIBAD87j71YkaFro8sX
NmIabo2l8cx9uyqYZUKdkDnCzRZP3Iv80PEEgClqISVvgB+HQsdH+XZxXZFaFaPJ
vT+FG0hhfUphhQ0VqipTZf0lm50N094MqzNwWOD12rcLAr2EW9s4Nz9WkflCjIop
K9/jMlkAj86q0HUJApen0kNJah4nLPnkqKC9BQipGe2goERHA5N8MS/k/ODJrOzI
qdD77wE5oov5sIePsGp3zCKNw89qoVTfkH8eYos6lPsAibYfgm5z7LwEtfe0ZizG
myQfAYZx3Orl2eNxAb0c1dw+hNYKfeNAwn6h4J8AKuBHawZMb2ztlTj0ZludrhQC
VuwAcrkCgYEA9sFsszjoSO8pXDnbaQ8UNGwy+C1t0fcZIOxIebKPcfipGio0R9vr
SXEEfRQb+YdIFkQpe4hwAHt1Q75zh8z+oOTq8EHprxAwI9bzgyaEIHtGibvs99XT
iWSPtL274CISiwSL8NzMl/orD6sDhmJqiXhwtf2SDubUJu3gz13CeRsCgYEA9eMM
CYiOc4wLxKqyCqe3R86vnBFVauxp9eq9XTLvD+XoGqOksXupP8rE0jx26ILmKiQZ
z99MGJoQicEpo+BW3L9wr6OJQZSrs+NqWCxlmFRJL+p3sw53B4zjgYaimNl5KH4G
8pn7XbyRXtqhSBQ2kuNrkVI4SNxdEi1K+PoZ6v0CgYEAkwVcRsy5WftloVW3rTkW
yMVO+R/YNyoLBtrBtAD4BugpmTVcQRR/dBqqmfvJTzuTb/Dc5oW8dg0ZKWvoWhmB
/Utn0A71tSDoDfKc1J+2ScQpmxclceUtTMdl+EK0Fi827S2gU7q7DDI6RfOW/hLV
d2MThNu4krhl32wMboFmxdECgYACwAhZbvKQ7kcPaw1Uuy18mx4xs6vt5zkELBz0
Fua/mcWvzpa/+W8aLI1pAI4f6Z7jZ8X2Ijw6pjZ7I/LwR0kRbP64qC6X0i7dczS0
ScLVIlQzOf8evJGuPvAoebYF2aDWSBqRyhEaqkpB8lYNdVRq7io81NuWTQipdGI7
SKjTjQKBgBNSbDUWS2CAc+fsM/fBvYHKgrigVcKyvWwvb5LRXpWgPQH4LbqhG4uA
g/mFTB5B1UBg9exN/dX6uegREdRA1/X+jRAzCqXYTFESo0/UrJhJQZ3waFKJ5PZK
WChrSl6Lg5IMF2jYP5W0HwzbPPgRGibyELYBS5gZdAZpHgZToXeT
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1 @@
813218563E2DA0DE

View File

@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICszCCAZsCCQCD6B82WZ9D/jANBgkqhkiG9w0BAQUFADAaMRgwFgYDVQQDEw9j
YTEuZXhhbXBsZS5jb20wHhcNMTYwNjE4MTMzODQ0WhcNMjYwNjE2MTMzODQ0WjAd
MRswGQYDVQQDExJjbGllbjEuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQDH75aclHoZkQfmeH3XpapxyF2/K73SpesY8Y8I3B33WnQc
vIy5y554pPJMtGH3ZwiN6ifo3TBEs/2WjSOWYwxfXh3utllYArApelSgUrI7SBkw
0MqVm9NG+X9cCTeWsCf+nldHOCnCARuyBEpLeRDPVlNmfgdNK2ar0KqqEPnN5UV+
k968nAuqSDtRL7Yl7R/uxEq4MglM/ocxOpGIrLTFh1eclPVaQ/dNsEJpkrnYQlFZ
aI1sWDzWoqtpAO15PgBBNnkW9EJGrF8dAds64U2jYBZLMKuHwvuERkEgOKEdUrB3
uu1dWJxS5BCumWM1C3xs6qsLeonWxZ5GXjjWObZNAgMBAAEwDQYJKoZIhvcNAQEF
BQADggEBAJKME0zm/0eokmXMCLJhKYgm8hDKOHKRFRZl7vwy9SC9cwhdlhcPEeeP
5M+dXQCtEQWgo7phoJX8nBipZ/Y0lsvDD/I3XucIkUlbOW4rk18L83nBIN4paKzW
I4CMJ6FQ72thP7L7wC/lzp3+qUCxmcpGjw9pkU3b1pQPkxBfOvfGtRFMG6E5+xj/
MtL3owJzpIH2f7vtmIszBPcgFWpvB0Sq0eJ+TwuC1huvcnmP+YZ7Iz0JhsSRw+pU
yiO9ByItBbGfK8x+DfUwCVsCL7vNscpjvTCgT3x2FNvS+XmiHZmZtpRGJPzvdI0m
Bd615VD5z+SoG/SiemqDGmt2Ank/zcI=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICYjCCAUoCAQAwHTEbMBkGA1UEAxMSY2xpZW4xLmV4YW1wbGUuY29tMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx++WnJR6GZEH5nh916Wqcchdvyu9
0qXrGPGPCNwd91p0HLyMucueeKTyTLRh92cIjeon6N0wRLP9lo0jlmMMX14d7rZZ
WAKwKXpUoFKyO0gZMNDKlZvTRvl/XAk3lrAn/p5XRzgpwgEbsgRKS3kQz1ZTZn4H
TStmq9CqqhD5zeVFfpPevJwLqkg7US+2Je0f7sRKuDIJTP6HMTqRiKy0xYdXnJT1
WkP3TbBCaZK52EJRWWiNbFg81qKraQDteT4AQTZ5FvRCRqxfHQHbOuFNo2AWSzCr
h8L7hEZBIDihHVKwd7rtXVicUuQQrpljNQt8bOqrC3qJ1sWeRl441jm2TQIDAQAB
oAAwDQYJKoZIhvcNAQEFBQADggEBAEZ67vahAVydtW6LTXFI0cVY88vqunCWpOzz
UgJAzUnWG84CGDiyezj/llv/Nq3YbEEpBuxp/prOEwrJXAi/+tjx7wCh2iLJDqo2
aNRUiAvR/XZgafxq4NUrAze70u7BWR3QX+XSaxmIEEX1z1KJDGTfY6tYpCZNlUr+
/Hl6MXwlpWX0WR26zIrjx5u0dEsY4pviN6NxTZRQJxbQO1H1wHr6poVngOhIdErp
h2ZcqvTcASTkIEdKR6R8E2iYklgxIHNLWKaHZ6aBqW7lW17WKNSiGPfPVAtFhUTk
tBmgdVreAwMj+AdaweBVt0uBqb/9UKhqNThEnh4kJn1I0pMJzP4=
-----END CERTIFICATE REQUEST-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAx++WnJR6GZEH5nh916Wqcchdvyu90qXrGPGPCNwd91p0HLyM
ucueeKTyTLRh92cIjeon6N0wRLP9lo0jlmMMX14d7rZZWAKwKXpUoFKyO0gZMNDK
lZvTRvl/XAk3lrAn/p5XRzgpwgEbsgRKS3kQz1ZTZn4HTStmq9CqqhD5zeVFfpPe
vJwLqkg7US+2Je0f7sRKuDIJTP6HMTqRiKy0xYdXnJT1WkP3TbBCaZK52EJRWWiN
bFg81qKraQDteT4AQTZ5FvRCRqxfHQHbOuFNo2AWSzCrh8L7hEZBIDihHVKwd7rt
XVicUuQQrpljNQt8bOqrC3qJ1sWeRl441jm2TQIDAQABAoIBAQCtD942uu7VooxM
GpATUfsvclhzWdF9vNC7TpyY9q+ZpFpNZYgKaw5JL73sV1dVZ4IoFT9mec+GKKag
4pqjWikjg7w1HPJJFEqYHKOUAwDz/3yOnKw+xBslnGF5sSDE9sYnx7eUljDPFVZ7
yOrmWW0Li5W1afG4ApFkt8KCYx9X8E0Mren2nfqoobM2l2LKFcF1Xs+M0iUAOoeN
ojS/NTvxjZibm92CMblp7x6e51y+oq3TJFoUwFSAj3U26jyubL5sYpJeAeTxyZg6
Y+UcEGmCpW5gsZSvRxvNxzCS4bCl9KOZXvyFtcVswHppfTynba6x8hDF7LkfJS7H
z/Ut+e+hAoGBAO2d2M2316eBpwP7x99DQSFpg7E/emdcfdRuHDEonBeJr2X2Fw0a
O/ZtUxcccovy4wrJcqiZsmjqetRUZ6ymsOaaASsPG21ChegFjm9D6+ZtejHpbuo3
8HQ//LW7hqoiRQh3ODihfYCTwwxIIuwAdUzoxpM9Yu57Zg7reYuNh48LAoGBANdn
c003ay1cq1fuuDJKENj10UZGotRdBxt+X1A7MhpMAqSaYJ+V2XOXpuMbIiX/qDfF
M4hcQhJygoCozNzsynztyIjpGtl57AG95igOi2Hah0OOMt/1Z9UwaukIaHHo8Tyk
sPZYoxBTstZcdsyHnGdU6n90SA9oYLBB89E3AocHAoGBAITR27M6FTCLl2jxn0qc
FFbx3OwB2JDYMXnBxr5vvbimfMWYpk/rnyLi/zQG8bxqmyCXdCDsML7WeqwfNgha
8L0lzotcGW+cZK9KE9D7/WvDPC+UFSyU8jJ45fBLjz2ghEf0JBf7pORvM/K0i9ix
dN/1qbH5+Ufm8Chc1Yb9KI37AoGAAbxDoYugwWzNtJenxD/0gsr4NKi9Bxj4xa/u
9KaFcNDL9KeJv79lURkXrxy42bWFlW1xTNfxcFSb2I2DmQQPXZJM202FedsRm7H7
+LalSNSJ4nFy13sSqxUIx3fZ35EQ4HwzMMjmB2ulNTTpgBxXlj2I5h35tqYQoVrm
q/jVfGECgYEAkU0L9bp1NPMzXgVJ2Os1VPSzoOywUQfx4NCJhTA1oZR+20JFsQBN
b6g0q6xean0xDuXjDRrjPET5V/GPOQ7stAPTLtqN42XPuRcFzSNj7Skh+ALTP5JS
bNZgBMwxQsbS89bUjRTDlRK/isuNIyUn0Zn7QlEsZEvJw0cNR3wPOc4=
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICtDCCAZwCCQCerG0FImwiFjANBgkqhkiG9w0BAQUFADAaMRgwFgYDVQQDEw9j
YTIuZXhhbXBsZS5jb20wHhcNMTYwNjE4MTgxNDUxWhcNMjYwNjE2MTgxNDUxWjAe
MRwwGgYDVQQDExNjbGllbnQyLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA9LaFxeiG1qpBTUIEl2vxvojOSWR1xkayaGtGsXBuqucf
wifulsVC7dw+t7lW42pWutIuR98iflAZ9+tFX7TsEITNIyV64ePn9TF935LW2DFm
AFkqYdZcTJ4qOkdsbbiHznlaIDPbMhIZFd9L8NEhrDTHuTtCav3g5B5V4okJfeNh
iSpKm3WLHP6lFwG1RISLhHTCeIMFxer49iHiQ+A33TV2l0bQGcv4e1+OoRsXGKGs
3oY6RJZ0GqzjeYqoybsLGBvZPPd2e8nH3RZac66XHMexsHHTV7L2tpWZm+JunMRg
hMXONc0b3V9mbUdrjHY/aGDPADEevZA4LLztGUc3VQIDAQABMA0GCSqGSIb3DQEB
BQUAA4IBAQCFo6IXUznH/iSuJtWrMtMkJTEg7o2qKRDgFApzw1J2URdvyYery15w
6FddKFvkYhNLFl9Nb3Z8HLxruZrrItqwjR2kIG9lW00uxnwIcgwTibmwDQL5nr7m
1cWzelhY/TVwBpLXRMg1YOXU8NRkT1VjkTUCpyIETI8b+wed67MkrofOadaY+FUL
gk1F3yDKz35UYIKnlxKwvrdySE8WFza0PmiXQDtTG1moTpe1BDEK1b60vhfudMBK
9vhE8kTooF01+su9gLUcrjVknI9H5PHtXID7FDiZ/disIAaWqSQLuvg/Kvb/cAFd
PwTKgnJQVcTKXkz2leJ6fsvzYwlANob1
-----END CERTIFICATE-----

View File

@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICYzCCAUsCAQAwHjEcMBoGA1UEAxMTY2xpZW50Mi5leGFtcGxlLmNvbTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPS2hcXohtaqQU1CBJdr8b6Izklk
dcZGsmhrRrFwbqrnH8In7pbFQu3cPre5VuNqVrrSLkffIn5QGffrRV+07BCEzSMl
euHj5/Uxfd+S1tgxZgBZKmHWXEyeKjpHbG24h855WiAz2zISGRXfS/DRIaw0x7k7
Qmr94OQeVeKJCX3jYYkqSpt1ixz+pRcBtUSEi4R0wniDBcXq+PYh4kPgN901dpdG
0BnL+HtfjqEbFxihrN6GOkSWdBqs43mKqMm7Cxgb2Tz3dnvJx90WWnOulxzHsbBx
01ey9raVmZvibpzEYITFzjXNG91fZm1Ha4x2P2hgzwAxHr2QOCy87RlHN1UCAwEA
AaAAMA0GCSqGSIb3DQEBBQUAA4IBAQDHvJVKkKIqCWrJ9sZWQEYBaki76woJMjFW
Ihyd12mzNfUW25hqfk7stablqu+CM/DhwOqLkxQleGAlp0BFo1wBOUDOgfrH5NVS
9lAl7L/roEyRGH6V5/Hsbwi8zDsGOzWCuZk/gNGIZpB1c3TRXBUHsdqpz9FReDZf
0HRD/7CH8hl96ZQTqhHE6+ysHzBB/4CuqbXVtTEhH52FdzCOpt5X0D6Pl/3lNlVd
gMHAssoEa5E00XtjeJdxXuIKYbGLgldj6v+hHFX7k9UNveAXgYBbGtUQ9gA+uEf/
qosVPEyvULj3aCJ8BSBulzPlhl9rNa/8Q1qUmzyCj28j3E4I22Oo
-----END CERTIFICATE REQUEST-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA9LaFxeiG1qpBTUIEl2vxvojOSWR1xkayaGtGsXBuqucfwifu
lsVC7dw+t7lW42pWutIuR98iflAZ9+tFX7TsEITNIyV64ePn9TF935LW2DFmAFkq
YdZcTJ4qOkdsbbiHznlaIDPbMhIZFd9L8NEhrDTHuTtCav3g5B5V4okJfeNhiSpK
m3WLHP6lFwG1RISLhHTCeIMFxer49iHiQ+A33TV2l0bQGcv4e1+OoRsXGKGs3oY6
RJZ0GqzjeYqoybsLGBvZPPd2e8nH3RZac66XHMexsHHTV7L2tpWZm+JunMRghMXO
Nc0b3V9mbUdrjHY/aGDPADEevZA4LLztGUc3VQIDAQABAoIBAQDRxjVetjoAgup/
w/wToeEVqEjN+WRMmAYQJQXwzaTQtFgxI/IPJQJ+zLKm5CZrxJichdhOnCUBisD4
GaLarElAz9baLiLsyWXqdoakxUePBKmf2s/OFugAdgVU+C0m0Wz5vmVX/ZwFjCYc
7dI3mc73xDcBvp7tAL1sT+Tn0PlmA3xURssiqC0J+4EtYzfHl1MvcQuU8JsVQjO3
GvGWMr9EBO3oPa6yx3oWD4dn7xHLcCkuSJ6arIvASEaTyPg0Iu7roPrC7AXA+oGq
+fbzJMqYZW6pMb8HZmxMt7X/srEq1kiyMYFy5fr+aun/vQ6596xjfFroEENJQY96
+jir0biBAoGBAPutA9/2yo/fchRWLgpsWZ1SLXRWewFYqxlA7DluYXvqciYCXuKe
S/+gLqHklHsc8YUwbEgW2oI9GPJ3iQps6XVNBaF9GvGjSrA+R6Ha3IT9ZUgvN4/d
WOYiNRw5+eZ5PfxTufNK6EwXNwKR/siGEnWJ8AH0oNibTVzJHvMYSP2ZAoGBAPjq
4a9MV6X7eShKHJtkqp33WWQWa3bidlmthhxjhPFlVnjJDj70oKGT5b/YcEFGBxPN
JvTFJplQe0kLaeC49fPaEefARJe+HuCfUc1C73/q2o0pzvWf6Ut+W8ZZLKSC7aHH
ZFAiZeMzzbCiqAbAAQFIgDp6m2U9mRYPTxKskoUdAoGBAMPEJzl1XMdhBfnvt0yA
T4ziOV0/T9sSP7UbHSTnSYj8KuKKAYjBnVgwH1Xq2dyR/QSfT/sbW8jnAZrJhJ3J
bifCS9j72ZOQcy54o3uxJMuF19y4bb3IbbhFb46PQmYWdTLrZb9ryxo1DKNBMTCF
qaIoM7sxsFQNKbY884ggodYxAoGBAIbKopXL0HbIe65um5kmZSIPjK+fWGhTb+VW
CxaaaaZSywWzUmyTCd0VesdtjDQ8mJ6HbH4FuMYzB9/hN1+CqWV4hFOsETjeslfO
znxJr+nkIp9osXLfOnUwIsCBD6SyZb5CfDbMucHUDqGFI1osZR7txMpmFHo5ZgnF
Fnu1Sc6dAoGAbgA9Cf6y8JGwr4/zGPDtaemBTLYMD8m/emdqGPgR7yVWXP/jTMqi
o1EEWtpehALZMVZOsmSg7C/1J+nlHbuxPKsjjYK+V7aGsqXGx95lPyemd2cGcJN4
fgoCyCahp2BnVCp7Gm3B/AzeZlH7n23qvbkJOsKGuocDycR9TIud+fk=
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICtDCCAZwCCQCBMhhWPi2g3jANBgkqhkiG9w0BAQUFADAaMRgwFgYDVQQDEw9j
YTMuZXhhbXBsZS5jb20wHhcNMTYwNjE4MTgxNTAwWhcNMjYwNjE2MTgxNTAwWjAe
MRwwGgYDVQQDExNjbGllbnQzLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAyW1BnimfqeE2TFVCVaFSOnKucZMezOUs5CiNAECbgBPU
ehAYNZCpKlD0ejZjc8/x2m0fnfHnmRdmCDprpI1gZV/dUMQCgmsq83pccnk3qFyn
tdDTo0vxTKZhusihipKmVEpvtQP0hMH2De7QjwOpjxnIZwFH8anLr8EyUFNyF8fK
k5emkMh8Xe5ppOTof36v9N/WPBW2/gxM9aj0l47CUSXjAUD8Fy8DeRtq/COywlnG
DK25tnrQcX4RBwU9s8pHrXVrvmgLUEc3pWuxrwGJzQ/iY8l1mDDmhqjmcg1uGYOe
hs/Olnx7pttUbd36mNXSSkjPeTabgpZDFtljMcTJwwIDAQABMA0GCSqGSIb3DQEB
BQUAA4IBAQBUSxHFcGKaTBBj9peCgzr+buhPQ7F72uNe0uYZhcCn91KXECiFM+rh
W13qcfsHDM/PPWN+TXHKzTxCHYv3fGkcAR/bUD206dXbO/T1Oc8UTciJFWXCxMK9
zKlZgn48TcAIEhJodVcqWXr8hZ5Grxw4wB2DnTUTr5FuFS/f4gtlflPJzirxZGe8
LPZb7QZ+LHxGK39QVY/g9LJxlWzbCytPBR0enb8ijjVj2+Sc+NntvQHqXedNFIql
ns6X98nQtwFn9/MgLGbqOYNN36b15HddyDRgfZ6zMO3Aeve7GM5GqnpqhyprN91t
gVaVUIxZCUNmcmtWu+a1QtK/MgLIpX4I
-----END CERTIFICATE-----

View File

@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICYzCCAUsCAQAwHjEcMBoGA1UEAxMTY2xpZW50My5leGFtcGxlLmNvbTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMltQZ4pn6nhNkxVQlWhUjpyrnGT
HszlLOQojQBAm4AT1HoQGDWQqSpQ9Ho2Y3PP8dptH53x55kXZgg6a6SNYGVf3VDE
AoJrKvN6XHJ5N6hcp7XQ06NL8UymYbrIoYqSplRKb7UD9ITB9g3u0I8DqY8ZyGcB
R/Gpy6/BMlBTchfHypOXppDIfF3uaaTk6H9+r/Tf1jwVtv4MTPWo9JeOwlEl4wFA
/BcvA3kbavwjssJZxgytubZ60HF+EQcFPbPKR611a75oC1BHN6Vrsa8Bic0P4mPJ
dZgw5oao5nINbhmDnobPzpZ8e6bbVG3d+pjV0kpIz3k2m4KWQxbZYzHEycMCAwEA
AaAAMA0GCSqGSIb3DQEBBQUAA4IBAQB3ApcHgGHXXYqkNHDp3xaXBrsYNnGSoqQq
PoFqNh2SVVh9D25hcfTrCXbv8Ng+rTEZqb4BMrSPxl5vNRQL78M70NMNE1bXcdW3
XWSh7vLxCAmHx7DKNxQI/96o5lG6FFkmZNYZ4CllqXaW0hV3CTuy4ixGwz4JJ6vI
caS4OEMB/r+kEm0jReGjalS/KWk61+bZnHWKAkvpPxIFKp8YMi84I+GlE1YfbXiC
kYtwCFmEd5T6Ztz/f/DtCF0JuH+S/2+R+7APD1RbaU8lCMDA292zlVP2mro2WRnZ
GAbynaqxaPQIn+2LBD5ytRx9aHALj58vq/PVpUUKb20RwIf74+t1
-----END CERTIFICATE REQUEST-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAyW1BnimfqeE2TFVCVaFSOnKucZMezOUs5CiNAECbgBPUehAY
NZCpKlD0ejZjc8/x2m0fnfHnmRdmCDprpI1gZV/dUMQCgmsq83pccnk3qFyntdDT
o0vxTKZhusihipKmVEpvtQP0hMH2De7QjwOpjxnIZwFH8anLr8EyUFNyF8fKk5em
kMh8Xe5ppOTof36v9N/WPBW2/gxM9aj0l47CUSXjAUD8Fy8DeRtq/COywlnGDK25
tnrQcX4RBwU9s8pHrXVrvmgLUEc3pWuxrwGJzQ/iY8l1mDDmhqjmcg1uGYOehs/O
lnx7pttUbd36mNXSSkjPeTabgpZDFtljMcTJwwIDAQABAoIBAFYbTqG+SXLlw8B9
8g2JGQ3DWK9UpSYSEk62xxAEjnUCBSLpHnBHlwlv8hMMjRdFHa6yV4G9l7PqPMPn
tXxys3KiuIl+QVRfW80Z0ctd5l0ivs8Kpm54WH7b4YtnmScT6ea+q2JGfpECGZ17
Kcz5U9LIwtLFyWuVmm1XuZp9EZj4HI7XgaktPom2f97k7oyQgKvgHVMUU+KYjS0X
KTTf+PkSR/laV9TXNTRlFOwblh8tjrb/CohR/REQ5yTM04oRHhD+TkIV2qIUuM5Z
P2hmIrMGhoJt01eJlZz73vXBBFAd/rzEIuKBroEG+culxje56qBbMKvfhmVJNuMz
6AvkPYECgYEA+yXVRcU7Zqz7rqq7Mw3s3yC6LzE5SNO1hRl9J2N4ldHf4bUeHEuh
ztW8Q7XiiftLcCGJPc3QxWRWwC8omhwefJzEmmVgpDeTMVjI/nNpgdgKSQfM77LX
jZtH8q5JH4cMvJbFMV3JN24LSTrctryt+p/Jps3x0FzbgNGEDik7/y0CgYEAzVGB
UESd09YvBjjW+1CJXHwgTh9Qc4L4VhAt7+O83SxGJEeVMYeTi/YHM2X5ytFBq/1w
M+j9CWpNi7XoHOCVWZfQjOvsrX5R0s7iBrL9ikkmaEy95VEgywsXTaoJE37/l5GR
j9s8P6o3KeCyce++2Qi+dHvAUEspLS1nW8KvAq8CgYAKwrw4mRLKe27tNPOAZIBZ
rxVLIFjL/gYxBb6PCXwJL0zgZto7bCIqso22ePyT3OiGjWlL9J2VV48//MVIlRvZ
Sv5Bf0Z8wsTTwHIcNOW4YoFOT79AJfGGZ7jVdRI8/5RUIEGis9oDPfvNz2/VhJAP
xPjm5LwPqWreQhveX3XqoQKBgQC86JANVYTNotTTabrLspcf5AkpOACit09ciDhr
7uMXsKO8v6wSzUZBUZXggaQqKwy8fUweRvGCFy/QKwesgiqIK3m0H2I9YutQBg/K
0CcddB6Feo6CDnoYt1SynY8KRCBQyZvfe3zcqvVkb5xf3pF/SV9K943DktQJACyI
LgEuewKBgF48AjNyi7zMgH0h5rsVimtjS7p8PhwZXyN70B/poykR3sxhCnChuNmt
47MNOQrNgXCQivrZk1uh4v/itoVCDX/GYz+bqHh8MihALuSX3eRnVPk8oDjoM1lN
Hks5wU7RK8gy14DPtTxfr0ca65v7kzu3nY1UwdFuTjcDwIv8EdGj
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,35 @@
logLevel = "DEBUG"
defaultEntryPoints = ["https"]
[entryPoints]
[entryPoints.https]
address = ":4443"
[entryPoints.https.tls]
ClientCAFiles = ["fixtures/https/clientca/ca1.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"
[file]
[backends]
[backends.backend1]
[backends.backend1.servers.server1]
url = "http://127.0.0.1:9010"
[backends.backend2]
[backends.backend2.servers.server1]
url = "http://127.0.0.1:9020"
[frontends]
[frontends.frontend1]
backend = "backend1"
[frontends.frontend1.routes.test_1]
rule = "Host:snitest.com"
[frontends.frontend2]
backend = "backend2"
[frontends.frontend2.routes.test_2]
rule = "Host:snitest.org"

View File

@@ -0,0 +1,35 @@
logLevel = "DEBUG"
defaultEntryPoints = ["https"]
[entryPoints]
[entryPoints.https]
address = ":4443"
[entryPoints.https.tls]
ClientCAFiles = ["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"
[file]
[backends]
[backends.backend1]
[backends.backend1.servers.server1]
url = "http://127.0.0.1:9010"
[backends.backend2]
[backends.backend2.servers.server1]
url = "http://127.0.0.1:9020"
[frontends]
[frontends.frontend1]
backend = "backend1"
[frontends.frontend1.routes.test_1]
rule = "Host:snitest.com"
[frontends.frontend2]
backend = "backend2"
[frontends.frontend2.routes.test_2]
rule = "Host:snitest.org"

View File

@@ -0,0 +1,35 @@
logLevel = "DEBUG"
defaultEntryPoints = ["https"]
[entryPoints]
[entryPoints.https]
address = ":4443"
[entryPoints.https.tls]
ClientCAFiles = ["fixtures/https/clientca/ca1.crt", "fixtures/https/clientca/ca2.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"
[file]
[backends]
[backends.backend1]
[backends.backend1.servers.server1]
url = "http://127.0.0.1:9010"
[backends.backend2]
[backends.backend2.servers.server1]
url = "http://127.0.0.1:9020"
[frontends]
[frontends.frontend1]
backend = "backend1"
[frontends.frontend1.routes.test_1]
rule = "Host:snitest.com"
[frontends.frontend2]
backend = "backend2"
[frontends.frontend2.routes.test_2]
rule = "Host:snitest.org"

View File

@@ -0,0 +1,9 @@
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[mesos]

View File

@@ -93,6 +93,164 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) {
c.Assert(resp.StatusCode, checker.Equals, 205)
}
// TestWithClientCertificateAuthentication
// The client has to send a certificate signed by a CA trusted by the server
func (s *HTTPSSuite) TestWithClientCertificateAuthentication(c *check.C) {
cmd := exec.Command(traefikBinary, "--configFile=fixtures/https/clientca/https_1ca1config.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
time.Sleep(500 * time.Millisecond)
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
ServerName: "snitest.com",
Certificates: []tls.Certificate{},
}
// Connection without client certificate should fail
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
// Connect with client certificate signed by ca1
cert, err := tls.LoadX509KeyPair("fixtures/https/clientca/client1.crt", "fixtures/https/clientca/client1.key")
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
conn.Close()
// Connect with client signed by ca2 should fail
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
ServerName: "snitest.com",
Certificates: []tls.Certificate{},
}
cert, err = tls.LoadX509KeyPair("fixtures/https/clientca/client2.crt", "fixtures/https/clientca/client2.key")
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
}
// TestWithClientCertificateAuthentication
// Use two CA:s and test that clients with client signed by either of them can connect
func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipeCAs(c *check.C) {
cmd := exec.Command(traefikBinary, "--configFile=fixtures/https/clientca/https_2ca1config.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
time.Sleep(500 * time.Millisecond)
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
ServerName: "snitest.com",
Certificates: []tls.Certificate{},
}
// Connection without client certificate should fail
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
// Connect with client signed by ca1
cert, err := tls.LoadX509KeyPair("fixtures/https/clientca/client1.crt", "fixtures/https/clientca/client1.key")
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
conn.Close()
// Connect with client signed by ca2
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
ServerName: "snitest.com",
Certificates: []tls.Certificate{},
}
cert, err = tls.LoadX509KeyPair("fixtures/https/clientca/client2.crt", "fixtures/https/clientca/client2.key")
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
conn.Close()
// Connect with client signed by ca3 should fail
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
ServerName: "snitest.com",
Certificates: []tls.Certificate{},
}
cert, err = tls.LoadX509KeyPair("fixtures/https/clientca/client3.crt", "fixtures/https/clientca/client3.key")
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
}
// TestWithClientCertificateAuthentication
// Use two CA:s in two different files and test that clients with client signed by either of them can connect
func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipeCAsMultipleFiles(c *check.C) {
cmd := exec.Command(traefikBinary, "--configFile=fixtures/https/clientca/https_2ca2config.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
time.Sleep(500 * time.Millisecond)
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
ServerName: "snitest.com",
Certificates: []tls.Certificate{},
}
// Connection without client certificate should fail
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
// Connect with client signed by ca1
cert, err := tls.LoadX509KeyPair("fixtures/https/clientca/client1.crt", "fixtures/https/clientca/client1.key")
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
conn.Close()
// Connect with client signed by ca2
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
ServerName: "snitest.com",
Certificates: []tls.Certificate{},
}
cert, err = tls.LoadX509KeyPair("fixtures/https/clientca/client2.crt", "fixtures/https/clientca/client2.key")
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
conn.Close()
// Connect with client signed by ca3 should fail
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
ServerName: "snitest.com",
Certificates: []tls.Certificate{},
}
cert, err = tls.LoadX509KeyPair("fixtures/https/clientca/client3.crt", "fixtures/https/clientca/client3.key")
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
}
func startTestServer(port string, statusCode int) (ts *httptest.Server) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(statusCode)

View File

@@ -13,7 +13,7 @@ import (
"github.com/containous/traefik/integration/utils"
"github.com/go-check/check"
"github.com/libkermit/docker-check/compose"
compose "github.com/libkermit/compose/check"
checker "github.com/vdemeester/shakers"
)
@@ -32,6 +32,7 @@ func init() {
check.Suite(&EtcdSuite{})
check.Suite(&MarathonSuite{})
check.Suite(&ConstraintSuite{})
check.Suite(&MesosSuite{})
}
var traefikBinary = "../dist/traefik"

33
integration/mesos_test.go Normal file
View File

@@ -0,0 +1,33 @@
package main
import (
"net/http"
"os/exec"
"time"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
)
// Mesos test suites (using libcompose)
type MesosSuite struct{ BaseSuite }
func (s *MesosSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "mesos")
}
func (s *MesosSuite) TestSimpleConfiguration(c *check.C) {
cmd := exec.Command(traefikBinary, "--configFile=fixtures/mesos/simple.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
time.Sleep(500 * time.Millisecond)
// TODO validate : run on 80
resp, err := http.Get("http://127.0.0.1:8000/")
// Expected a 404 as we did not configure anything
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 404)
}

View File

@@ -0,0 +1,14 @@
consul:
image: progrium/consul
command: -server -bootstrap -log-level debug -ui-dir /ui -config-dir /configs
ports:
- "8500:8500"
- "8585:8585"
expose:
- "8300"
- "8301"
- "8301/udp"
- "8302"
- "8302/udp"
volumes:
- ../tls:/configs

View File

@@ -0,0 +1,34 @@
zk:
image: bobrik/zookeeper
net: host
environment:
ZK_CONFIG: tickTime=2000,initLimit=10,syncLimit=5,maxClientCnxns=128,forceSync=no,clientPort=2181
ZK_ID: " 1"
master:
image: mesosphere/mesos-master:0.28.1-2.0.20.ubuntu1404
net: host
environment:
MESOS_ZK: zk://127.0.0.1:2181/mesos
MESOS_HOSTNAME: 127.0.0.1
MESOS_IP: 127.0.0.1
MESOS_QUORUM: " 1"
MESOS_CLUSTER: docker-compose
MESOS_WORK_DIR: /var/lib/mesos
slave:
image: mesosphere/mesos-slave:0.28.1-2.0.20.ubuntu1404
net: host
pid: host
privileged: true
environment:
MESOS_MASTER: zk://127.0.0.1:2181/mesos
MESOS_HOSTNAME: 127.0.0.1
MESOS_IP: 127.0.0.1
MESOS_CONTAINERIZERS: docker,mesos
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup
- /usr/bin/docker:/usr/bin/docker:ro
- /usr/lib/x86_64-linux-gnu/libapparmor.so.1:/usr/lib/x86_64-linux-gnu/libapparmor.so.1:ro
- /var/run/docker.sock:/var/run/docker.sock
- /lib/x86_64-linux-gnu/libsystemd-journal.so.0:/lib/x86_64-linux-gnu/libsystemd-journal.so.0

View File

@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDrTCCApWgAwIBAgIJAO8QudN/gvGqMA0GCSqGSIb3DQEBCwUAMG0xCzAJBgNV
BAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxDTALBgNVBAcMBEx5b24xDzANBgNVBAoM
BlplbmlrYTENMAsGA1UEAwwEdGVzdDEeMBwGCSqGSIb3DQEJARYPdGVzdEB6ZW5p
a2EuY29tMB4XDTE2MDcwNjA5MTA1MloXDTI2MDcwNDA5MTA1MlowbTELMAkGA1UE
BhMCRlIxDzANBgNVBAgMBkZyYW5jZTENMAsGA1UEBwwETHlvbjEPMA0GA1UECgwG
WmVuaWthMQ0wCwYDVQQDDAR0ZXN0MR4wHAYJKoZIhvcNAQkBFg90ZXN0QHplbmlr
YS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKwrXlde3J8hFY
hEvH1GH5SfA64/yb7pjXOwI3kvdS2dkbLglOL/jsolARAfFWxhhnnyJUy/BBzZtS
rZN/IukuLCypzjnF6I9koVwILU2EkhPcBUzPZWD6gDU42XZH/lgglZyTyLA/pi24
eAag5xVuTBMmBGbRsJJEq8MYgzSOAQLu2K8vFPARZdnvOMXVpfrC5+RxDj1AzyxU
5s7olWWG13cWkkh2PUNdb1gCXsz34ALG3EmD2S92tovkKHUZS5zHnOvFl8bF7bKC
MoXBi4bL2cUQXq815uFl0gfRrBgN4U+uT2UjzhIV9ax/xnkGueXi9wGPYP3Yanu8
dguEtevRAgMBAAGjUDBOMB0GA1UdDgQWBBSxdmZrC6APPhMg73JGRa1sKPB2CDAf
BgNVHSMEGDAWgBSxdmZrC6APPhMg73JGRa1sKPB2CDAMBgNVHRMEBTADAQH/MA0G
CSqGSIb3DQEBCwUAA4IBAQBEZNfJxlKr/hv/cyfbJX6yUKDRG/sIFVD4G9uNEKak
N9Dm5/FZ3pzosq/mBuMjXyY/5kYfiBPpyJfUK7CpWfa/U1RP76dDPm+3aaTNK0XS
rWWxP/n5plfb6bt53cfKrnk9ud9ZqY6jX0vQzbVp6F4+jN3ZZfl4SEwlbK0jnrYV
pbjPKbDS5o0RNgLuk/KN9x/KLb9FdgTYxVrB4orDUzpxx54sjfHRGodUAO9VIlbZ
WteavUhCqbVWvYBB64vxKY695PeX79nmwCMVmsy8luquJYgIn27Czexuei3+2mxX
f1rPZL+iCzi8cuShXqhrxH2dNyxsmYFjiPwFHSVgYtL2
-----END CERTIFICATE-----

View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDBDCCAeygAwIBAgIBEDANBgkqhkiG9w0BAQUFADBtMQswCQYDVQQGEwJGUjEP
MA0GA1UECAwGRnJhbmNlMQ0wCwYDVQQHDARMeW9uMQ8wDQYDVQQKDAZaZW5pa2Ex
DTALBgNVBAMMBHRlc3QxHjAcBgkqhkiG9w0BCQEWD3Rlc3RAemVuaWthLmNvbTAe
Fw0xNjA3MDYxMzMzMTJaFw0yNjA3MDQxMzMzMTJaMBQxEjAQBgNVBAMMCWxvY2Fs
aG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyOvmLF7FLDur27bX7OYo
lh3RF/9Bo0Eq1+uFbqs7/KNVp66njn4QYT+OLcRTovoCIbTqaFT7jeqIKxpJ+DWL
n61BENZvsfSPkxTyF/zekarMHhvrMSpPqEP+NFnfmEVQ4kUELAyREmq6qkZloavV
8X8obRjGbNGuWpNLAlO0g68CAwEAAaOBizCBiDAJBgNVHRMEAjAAMB0GA1UdDgQW
BBQPlr+xQCpVYfksoxb+tsnNkL63EjAfBgNVHSMEGDAWgBSxdmZrC6APPhMg73JG
Ra1sKPB2CDALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF
BwMCMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcNAQEFBQADggEBAEUgwhGh48yJ
JDlR7lT+uWQkPGXuXuATYKOt2fnqYFQ3z3jzP2Og8mR/iZJm85GvsN7CyfVi8hZL
sNJOJ1KPRrGVrOFGD4fd8e1sYYw1wowyEiBQii9f/BGy8khw7rl5RrZotuffTulx
PWXF9EyO+vLhpkPzCXG7CkJdakWfJX/83C7xfC+wOpyeGG89IW2l4W6yofLV88hL
LqBLfuL3J9ZknplSmHDB4W4TFr9aHd2zXdgAUgGd+b0+JfxZtxClivn8RoIkR6Dr
JKhxFO9i714+0MKQMEWAvAFcTw8I9ddkQ4cWs9YoYKYdF/cxowAxGYuykI+H92PO
ABAA2aH8ilE=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMjr5ixexSw7q9u2
1+zmKJYd0Rf/QaNBKtfrhW6rO/yjVaeup45+EGE/ji3EU6L6AiG06mhU+43qiCsa
Sfg1i5+tQRDWb7H0j5MU8hf83pGqzB4b6zEqT6hD/jRZ35hFUOJFBCwMkRJquqpG
ZaGr1fF/KG0YxmzRrlqTSwJTtIOvAgMBAAECgYB2kymK4/8vRKP/DeBOkeI//abJ
p73/79SuCvP7RRko1ugVBrEiGenmypBJGEVXuH4LkG6KViUDMvdboK8oycj0zL6y
4naKuWct6EOcxSLhdFyCFLPY+0ggl3F9oG92D02H/3oU7ORBNFBaigSYRSP8EieT
5LxCkM2L1cElMJ/6cQJBAP901eNQZSvikAPma+9oPySD01e9yr0AE06wBxGNMkHH
OS07WknvIdJAMDKng5Umbp4EG/3UbV5ED/y3NoO22YkCQQDJWVrP5nEx53EXCENb
LWDA7SBxjBX60pqvuguDZSjsONQJUlMlqebZSzf/ezLGRUhkzRek8uOwz8MGKnTV
sf13AkAGvE3ncHc6cP7bG3g9F8KSc+deqOJvmVDpAjste0uX8GjRiH8Y8/UwVgDv
VPtjM2A3SmRyjOdVVPYW8728O1YBAkEAl4aPOPYLKasrCFJHnk5ACfBqAgmSYPgt
QSGZmICAk4UQzRMPT8DU4aIhujpUs7FgEbvml1PS1jUEZ5d75XXVcQJAZI50y14r
LJ4H+Q2NvvmeyuI8csX+63IGGd/Zt9/EYj4TQnKISnTV3cr/vkmsdoCevC4dT8rS
0d1rqCvfNzBUPA==
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,9 @@
{
"ports": {
"https": 8585
},
"ca_file": "/configs/ca.cert",
"cert_file": "/configs/consul.cert",
"key_file": "/configs/consul.key",
"verify_outgoing": true
}

View File

@@ -2,7 +2,7 @@ package utils
import (
"errors"
"github.com/cenkalti/backoff"
"github.com/cenk/backoff"
"net/http"
"strconv"
"time"

39
job/job.go Normal file
View File

@@ -0,0 +1,39 @@
package job
import (
"github.com/cenk/backoff"
"time"
)
var (
_ backoff.BackOff = (*BackOff)(nil)
)
const (
defaultMinJobInterval = 30 * time.Second
)
// BackOff is an exponential backoff implementation for long running jobs.
// In long running jobs, an operation() that fails after a long Duration should not increments the backoff period.
// If operation() takes more than MinJobInterval, Reset() is called in NextBackOff().
type BackOff struct {
*backoff.ExponentialBackOff
MinJobInterval time.Duration
}
// NewBackOff creates an instance of BackOff using default values.
func NewBackOff(backOff *backoff.ExponentialBackOff) *BackOff {
backOff.MaxElapsedTime = 0
return &BackOff{
ExponentialBackOff: backOff,
MinJobInterval: defaultMinJobInterval,
}
}
// NextBackOff calculates the next backoff interval.
func (b *BackOff) NextBackOff() time.Duration {
if b.GetElapsedTime() >= b.MinJobInterval {
b.Reset()
}
return b.ExponentialBackOff.NextBackOff()
}

44
job/job_test.go Normal file
View File

@@ -0,0 +1,44 @@
package job
import (
"github.com/cenk/backoff"
"testing"
"time"
)
func TestJobBackOff(t *testing.T) {
var (
testInitialInterval = 500 * time.Millisecond
testRandomizationFactor = 0.1
testMultiplier = 2.0
testMaxInterval = 5 * time.Second
testMinJobInterval = 1 * time.Second
)
exp := NewBackOff(backoff.NewExponentialBackOff())
exp.InitialInterval = testInitialInterval
exp.RandomizationFactor = testRandomizationFactor
exp.Multiplier = testMultiplier
exp.MaxInterval = testMaxInterval
exp.MinJobInterval = testMinJobInterval
exp.Reset()
var expectedResults = []time.Duration{500, 500, 500, 1000, 2000, 4000, 5000, 5000, 500, 1000, 2000, 4000, 5000, 5000}
for i, d := range expectedResults {
expectedResults[i] = d * time.Millisecond
}
for i, expected := range expectedResults {
// Assert that the next backoff falls in the expected range.
var minInterval = expected - time.Duration(testRandomizationFactor*float64(expected))
var maxInterval = expected + time.Duration(testRandomizationFactor*float64(expected))
if i < 3 || i == 8 {
time.Sleep(2 * time.Second)
}
var actualInterval = exp.NextBackOff()
if !(minInterval <= actualInterval && actualInterval <= maxInterval) {
t.Error("error")
}
// assertEquals(t, expected, exp.currentInterval)
}
}

188
log/logger.go Normal file
View File

@@ -0,0 +1,188 @@
package log
import (
"github.com/Sirupsen/logrus"
"io"
)
var (
logger *logrus.Entry
)
func init() {
logger = logrus.StandardLogger().WithFields(logrus.Fields{})
}
// Context sets the Context of the logger
func Context(context interface{}) *logrus.Entry {
return logger.WithField("context", context)
}
// SetOutput sets the standard logger output.
func SetOutput(out io.Writer) {
logrus.SetOutput(out)
}
// SetFormatter sets the standard logger formatter.
func SetFormatter(formatter logrus.Formatter) {
logrus.SetFormatter(formatter)
}
// SetLevel sets the standard logger level.
func SetLevel(level logrus.Level) {
logrus.SetLevel(level)
}
// GetLevel returns the standard logger level.
func GetLevel() logrus.Level {
return logrus.GetLevel()
}
// AddHook adds a hook to the standard logger hooks.
func AddHook(hook logrus.Hook) {
logrus.AddHook(hook)
}
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
func WithError(err error) *logrus.Entry {
return logger.WithError(err)
}
// WithField creates an entry from the standard logger and adds a field to
// it. If you want multiple fields, use `WithFields`.
//
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
// or Panic on the Entry it returns.
func WithField(key string, value interface{}) *logrus.Entry {
return logger.WithField(key, value)
}
// WithFields creates an entry from the standard logger and adds multiple
// fields to it. This is simply a helper for `WithField`, invoking it
// once for each field.
//
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
// or Panic on the Entry it returns.
func WithFields(fields logrus.Fields) *logrus.Entry {
return logger.WithFields(fields)
}
// Debug logs a message at level Debug on the standard logger.
func Debug(args ...interface{}) {
logger.Debug(args...)
}
// Print logs a message at level Info on the standard logger.
func Print(args ...interface{}) {
logger.Print(args...)
}
// Info logs a message at level Info on the standard logger.
func Info(args ...interface{}) {
logger.Info(args...)
}
// Warn logs a message at level Warn on the standard logger.
func Warn(args ...interface{}) {
logger.Warn(args...)
}
// Warning logs a message at level Warn on the standard logger.
func Warning(args ...interface{}) {
logger.Warning(args...)
}
// Error logs a message at level Error on the standard logger.
func Error(args ...interface{}) {
logger.Error(args...)
}
// Panic logs a message at level Panic on the standard logger.
func Panic(args ...interface{}) {
logger.Panic(args...)
}
// Fatal logs a message at level Fatal on the standard logger.
func Fatal(args ...interface{}) {
logger.Fatal(args...)
}
// Debugf logs a message at level Debug on the standard logger.
func Debugf(format string, args ...interface{}) {
logger.Debugf(format, args...)
}
// Printf logs a message at level Info on the standard logger.
func Printf(format string, args ...interface{}) {
logger.Printf(format, args...)
}
// Infof logs a message at level Info on the standard logger.
func Infof(format string, args ...interface{}) {
logger.Infof(format, args...)
}
// Warnf logs a message at level Warn on the standard logger.
func Warnf(format string, args ...interface{}) {
logger.Warnf(format, args...)
}
// Warningf logs a message at level Warn on the standard logger.
func Warningf(format string, args ...interface{}) {
logger.Warningf(format, args...)
}
// Errorf logs a message at level Error on the standard logger.
func Errorf(format string, args ...interface{}) {
logger.Errorf(format, args...)
}
// Panicf logs a message at level Panic on the standard logger.
func Panicf(format string, args ...interface{}) {
logger.Panicf(format, args...)
}
// Fatalf logs a message at level Fatal on the standard logger.
func Fatalf(format string, args ...interface{}) {
logger.Fatalf(format, args...)
}
// Debugln logs a message at level Debug on the standard logger.
func Debugln(args ...interface{}) {
logger.Debugln(args...)
}
// Println logs a message at level Info on the standard logger.
func Println(args ...interface{}) {
logger.Println(args...)
}
// Infoln logs a message at level Info on the standard logger.
func Infoln(args ...interface{}) {
logger.Infoln(args...)
}
// Warnln logs a message at level Warn on the standard logger.
func Warnln(args ...interface{}) {
logger.Warnln(args...)
}
// Warningln logs a message at level Warn on the standard logger.
func Warningln(args ...interface{}) {
logger.Warningln(args...)
}
// Errorln logs a message at level Error on the standard logger.
func Errorln(args ...interface{}) {
logger.Errorln(args...)
}
// Panicln logs a message at level Panic on the standard logger.
func Panicln(args ...interface{}) {
logger.Panicln(args...)
}
// Fatalln logs a message at level Fatal on the standard logger.
func Fatalln(args ...interface{}) {
logger.Fatalln(args...)
}

View File

@@ -0,0 +1,99 @@
package middlewares
import (
"fmt"
"github.com/abbot/go-http-auth"
"github.com/codegangsta/negroni"
"github.com/containous/traefik/log"
"github.com/containous/traefik/types"
"net/http"
"strings"
)
// Authenticator is a middleware that provides HTTP basic and digest authentication
type Authenticator struct {
handler negroni.Handler
users map[string]string
}
// NewAuthenticator builds a new Autenticator given a config
func NewAuthenticator(authConfig *types.Auth) (*Authenticator, error) {
if authConfig == nil {
return nil, fmt.Errorf("Error creating Authenticator: auth is nil")
}
var err error
authenticator := Authenticator{}
if authConfig.Basic != nil {
authenticator.users, err = parserBasicUsers(authConfig.Basic.Users)
if err != nil {
return nil, err
}
basicAuth := auth.NewBasicAuthenticator("traefik", authenticator.secretBasic)
authenticator.handler = negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if username := basicAuth.CheckAuth(r); username == "" {
log.Debugf("Auth failed...")
basicAuth.RequireAuth(w, r)
} else {
next.ServeHTTP(w, r)
}
})
} else if authConfig.Digest != nil {
authenticator.users, err = parserDigestUsers(authConfig.Digest.Users)
if err != nil {
return nil, err
}
digestAuth := auth.NewDigestAuthenticator("traefik", authenticator.secretDigest)
authenticator.handler = negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if username, _ := digestAuth.CheckAuth(r); username == "" {
digestAuth.RequireAuth(w, r)
} else {
next.ServeHTTP(w, r)
}
})
}
return &authenticator, nil
}
func parserBasicUsers(users types.Users) (map[string]string, error) {
userMap := make(map[string]string)
for _, user := range users {
split := strings.Split(user, ":")
if len(split) != 2 {
return nil, fmt.Errorf("Error parsing Authenticator user: %v", user)
}
userMap[split[0]] = split[1]
}
return userMap, nil
}
func parserDigestUsers(users types.Users) (map[string]string, error) {
userMap := make(map[string]string)
for _, user := range users {
split := strings.Split(user, ":")
if len(split) != 3 {
return nil, fmt.Errorf("Error parsing Authenticator user: %v", user)
}
userMap[split[0]+":"+split[1]] = split[2]
}
return userMap, nil
}
func (a *Authenticator) secretBasic(user, realm string) string {
if secret, ok := a.users[user]; ok {
return secret
}
log.Debugf("User not found: %s", user)
return ""
}
func (a *Authenticator) secretDigest(user, realm string) string {
if secret, ok := a.users[user+":"+realm]; ok {
return secret
}
log.Debugf("User not found: %s:%s", user, realm)
return ""
}
func (a *Authenticator) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
a.handler.ServeHTTP(rw, r, next)
}

View File

@@ -0,0 +1,103 @@
package middlewares
import (
"fmt"
"github.com/codegangsta/negroni"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
func TestBasicAuthFail(t *testing.T) {
authMiddleware, err := NewAuthenticator(&types.Auth{
Basic: &types.Basic{
Users: []string{"test"},
},
})
assert.Contains(t, err.Error(), "Error parsing Authenticator user", "should contains")
authMiddleware, err = NewAuthenticator(&types.Auth{
Basic: &types.Basic{
Users: []string{"test:test"},
},
})
assert.NoError(t, err, "there should be no error")
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "traefik")
})
n := negroni.New(authMiddleware)
n.UseHandler(handler)
ts := httptest.NewServer(n)
defer ts.Close()
client := &http.Client{}
req, err := http.NewRequest("GET", ts.URL, nil)
req.SetBasicAuth("test", "test")
res, err := client.Do(req)
assert.NoError(t, err, "there should be no error")
assert.Equal(t, http.StatusUnauthorized, res.StatusCode, "they should be equal")
}
func TestBasicAuthSuccess(t *testing.T) {
authMiddleware, err := NewAuthenticator(&types.Auth{
Basic: &types.Basic{
Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"},
},
})
assert.NoError(t, err, "there should be no error")
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "traefik")
})
n := negroni.New(authMiddleware)
n.UseHandler(handler)
ts := httptest.NewServer(n)
defer ts.Close()
client := &http.Client{}
req, err := http.NewRequest("GET", ts.URL, nil)
req.SetBasicAuth("test", "test")
res, err := client.Do(req)
assert.NoError(t, err, "there should be no error")
assert.Equal(t, http.StatusOK, res.StatusCode, "they should be equal")
body, err := ioutil.ReadAll(res.Body)
assert.NoError(t, err, "there should be no error")
assert.Equal(t, "traefik\n", string(body), "they should be equal")
}
func TestDigestAuthFail(t *testing.T) {
authMiddleware, err := NewAuthenticator(&types.Auth{
Digest: &types.Digest{
Users: []string{"test"},
},
})
assert.Contains(t, err.Error(), "Error parsing Authenticator user", "should contains")
authMiddleware, err = NewAuthenticator(&types.Auth{
Digest: &types.Digest{
Users: []string{"test:traefik:test"},
},
})
assert.NoError(t, err, "there should be no error")
assert.NotNil(t, authMiddleware, "this should not be nil")
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "traefik")
})
n := negroni.New(authMiddleware)
n.UseHandler(handler)
ts := httptest.NewServer(n)
defer ts.Close()
client := &http.Client{}
req, err := http.NewRequest("GET", ts.URL, nil)
req.SetBasicAuth("test", "test")
res, err := client.Do(req)
assert.NoError(t, err, "there should be no error")
assert.Equal(t, http.StatusUnauthorized, res.StatusCode, "they should be equal")
}

View File

@@ -3,7 +3,7 @@ package middlewares
import (
"net/http"
"github.com/containous/oxy/cbreaker"
"github.com/vulcand/oxy/cbreaker"
)
// CircuitBreaker holds the oxy circuit breaker.
@@ -12,9 +12,12 @@ type CircuitBreaker struct {
}
// NewCircuitBreaker returns a new CircuitBreaker.
func NewCircuitBreaker(next http.Handler, expression string, options ...cbreaker.CircuitBreakerOption) *CircuitBreaker {
circuitBreaker, _ := cbreaker.New(next, expression, options...)
return &CircuitBreaker{circuitBreaker}
func NewCircuitBreaker(next http.Handler, expression string, options ...cbreaker.CircuitBreakerOption) (*CircuitBreaker, error) {
circuitBreaker, err := cbreaker.New(next, expression, options...)
if err != nil {
return nil, err
}
return &CircuitBreaker{circuitBreaker}, nil
}
func (cb *CircuitBreaker) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {

16
middlewares/compress.go Normal file
View File

@@ -0,0 +1,16 @@
package middlewares
import (
"github.com/NYTimes/gziphandler"
"net/http"
)
// Compress is a middleware that allows redirections
type Compress struct {
}
// ServerHTTP is a function used by negroni
func (c *Compress) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
newGzipHandler := gziphandler.GzipHandler(next)
newGzipHandler.ServeHTTP(rw, r)
}

View File

@@ -3,8 +3,6 @@ package middlewares
import (
"bufio"
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/streamrail/concurrent-map"
"io"
"net"
"net/http"
@@ -13,6 +11,9 @@ import (
"strings"
"sync/atomic"
"time"
"github.com/containous/traefik/log"
"github.com/streamrail/concurrent-map"
)
const (
@@ -55,7 +56,7 @@ func NewLogger(file string) *Logger {
if len(file) > 0 {
fi, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Fatal("Error opening file", err)
log.Error("Error opening file", err)
}
return &Logger{fi}
}
@@ -138,8 +139,9 @@ func (fblh frontendBackendLoggingHandler) ServeHTTP(rw http.ResponseWriter, req
size := infoRw.GetSize()
elapsed := time.Now().UTC().Sub(startTime.UTC())
fmt.Fprintf(fblh.writer, `%s - %s [%s] "%s %s %s" %d %d "%s" "%s" %s "%s" "%s" %s%s`,
host, username, ts, method, uri, proto, status, size, referer, agent, fblh.reqid, frontend, backend, elapsed, "\n")
elapsedMillis := elapsed.Nanoseconds() / 1000000
fmt.Fprintf(fblh.writer, `%s - %s [%s] "%s %s %s" %d %d "%s" "%s" %s "%s" "%s" %dms%s`,
host, username, ts, method, uri, proto, status, size, referer, agent, fblh.reqid, frontend, backend, elapsedMillis, "\n")
}

92
middlewares/retry.go Normal file
View File

@@ -0,0 +1,92 @@
package middlewares
import (
"bufio"
"bytes"
"github.com/containous/traefik/log"
"github.com/vulcand/oxy/utils"
"net"
"net/http"
)
// Retry is a middleware that retries requests
type Retry struct {
attempts int
next http.Handler
}
// NewRetry returns a new Retry instance
func NewRetry(attempts int, next http.Handler) *Retry {
return &Retry{
attempts: attempts,
next: next,
}
}
func (retry *Retry) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
attempts := 1
for {
recorder := NewRecorder()
recorder.responseWriter = rw
retry.next.ServeHTTP(recorder, r)
if !isNetworkError(recorder.Code) || attempts >= retry.attempts {
utils.CopyHeaders(rw.Header(), recorder.Header())
rw.WriteHeader(recorder.Code)
rw.Write(recorder.Body.Bytes())
break
}
attempts++
log.Debugf("New attempt %d for request: %v", attempts, r.URL)
}
}
func isNetworkError(status int) bool {
return status == http.StatusBadGateway || status == http.StatusGatewayTimeout
}
// ResponseRecorder is an implementation of http.ResponseWriter that
// records its mutations for later inspection in tests.
type ResponseRecorder struct {
Code int // the HTTP response code from WriteHeader
HeaderMap http.Header // the HTTP response headers
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
responseWriter http.ResponseWriter
}
// NewRecorder returns an initialized ResponseRecorder.
func NewRecorder() *ResponseRecorder {
return &ResponseRecorder{
HeaderMap: make(http.Header),
Body: new(bytes.Buffer),
Code: 200,
}
}
// Header returns the response headers.
func (rw *ResponseRecorder) Header() http.Header {
m := rw.HeaderMap
if m == nil {
m = make(http.Header)
rw.HeaderMap = m
}
return m
}
// Write always succeeds and writes to rw.Body, if not nil.
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
if rw.Body != nil {
return rw.Body.Write(buf)
}
return 0, nil
}
// WriteHeader sets rw.Code.
func (rw *ResponseRecorder) WriteHeader(code int) {
rw.Code = code
}
// Hijack hijacks the connection
func (rw *ResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return rw.responseWriter.(http.Hijacker).Hijack()
}

View File

@@ -1,7 +1,7 @@
package middlewares
import (
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/log"
"github.com/vulcand/vulcand/plugin/rewrite"
"net/http"
)

View File

@@ -1,5 +1,5 @@
site_name: Traefik
site_description: Traefik Documentation
site_name: Træfɪk
site_description: Træfɪk Documentation
site_author: containo.us
site_url: https://docs.traefik.io
@@ -16,7 +16,7 @@ theme: united
site_favicon: 'img/traefik.icon.png'
# Copyright
copyright: Copyright (c) 2016 Containous SAS
copyright: "Copyright &copy; 2016 Containous SAS"
# Options
extra:
@@ -49,4 +49,6 @@ pages:
- User Guide:
- 'Configuration examples': 'user-guide/examples.md'
- 'Swarm cluster': 'user-guide/swarm.md'
- Benchmarks: benchmarks.md
- 'Kubernetes': 'user-guide/kubernetes.md'
- 'Key-value store configuration': 'user-guide/kv-config.md'
- 'Clustering/HA': 'user-guide/cluster.md'

View File

@@ -169,13 +169,13 @@ func (_m *Marathon) DeleteApplication(name string) (*marathon.DeploymentID, erro
return r0, r1
}
// UpdateApplication provides a mock function with given fields: application
func (_m *Marathon) UpdateApplication(application *marathon.Application) (*marathon.DeploymentID, error) {
ret := _m.Called(application)
// UpdateApplication provides a mock function with given fields: application, force
func (_m *Marathon) UpdateApplication(application *marathon.Application, force bool) (*marathon.DeploymentID, error) {
ret := _m.Called(application, force)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(*marathon.Application) *marathon.DeploymentID); ok {
r0 = rf(application)
if rf, ok := ret.Get(0).(func(*marathon.Application, bool) *marathon.DeploymentID); ok {
r0 = rf(application, force)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
@@ -183,8 +183,8 @@ func (_m *Marathon) UpdateApplication(application *marathon.Application) (*marat
}
var r1 error
if rf, ok := ret.Get(1).(func(*marathon.Application) error); ok {
r1 = rf(application)
if rf, ok := ret.Get(1).(func(*marathon.Application, bool) error); ok {
r1 = rf(application, force)
} else {
r1 = ret.Error(1)
}
@@ -307,6 +307,29 @@ func (_m *Marathon) Application(name string) (*marathon.Application, error) {
return r0, r1
}
// ApplicationByVersion provides a mock function with given fields: name, version
func (_m *Marathon) ApplicationByVersion(name string, version string) (*marathon.Application, error) {
ret := _m.Called(name, version)
var r0 *marathon.Application
if rf, ok := ret.Get(0).(func(string, string) *marathon.Application); ok {
r0 = rf(name, version)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Application)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(name, version)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// WaitOnApplication provides a mock function with given fields: name, timeout
func (_m *Marathon) WaitOnApplication(name string, timeout time.Duration) error {
ret := _m.Called(name, timeout)

View File

@@ -1,21 +1,34 @@
package provider
import (
"fmt"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/boltdb"
)
var _ Provider = (*BoltDb)(nil)
// BoltDb holds configurations of the BoltDb provider.
type BoltDb struct {
Kv
Kv `mapstructure:",squash"`
}
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
provider.storeType = store.BOLTDB
boltdb.Register()
store, err := provider.CreateStore()
if err != nil {
return fmt.Errorf("Failed to Connect to KV store: %v", err)
}
provider.kvclient = store
return provider.provide(configurationChan, pool, constraints)
}
// CreateStore creates the KV store
func (provider *BoltDb) CreateStore() (store.Store, error) {
provider.storeType = store.BOLTDB
boltdb.Register()
return provider.createStore()
}

View File

@@ -1,21 +1,34 @@
package provider
import (
"fmt"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/consul"
)
var _ Provider = (*Consul)(nil)
// Consul holds configurations of the Consul provider.
type Consul struct {
Kv
Kv `mapstructure:",squash"`
}
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
provider.storeType = store.CONSUL
consul.Register()
store, err := provider.CreateStore()
if err != nil {
return fmt.Errorf("Failed to Connect to KV store: %v", err)
}
provider.kvclient = store
return provider.provide(configurationChan, pool, constraints)
}
// CreateStore creates the KV store
func (provider *Consul) CreateStore() (store.Store, error) {
provider.storeType = store.CONSUL
consul.Register()
return provider.createStore()
}

View File

@@ -2,14 +2,17 @@ package provider
import (
"errors"
"sort"
"strconv"
"strings"
"text/template"
"time"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff"
"github.com/Sirupsen/logrus"
"github.com/cenk/backoff"
"github.com/containous/traefik/job"
"github.com/containous/traefik/log"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/hashicorp/consul/api"
@@ -22,13 +25,15 @@ const (
DefaultConsulCatalogTagPrefix = "traefik"
)
var _ Provider = (*ConsulCatalog)(nil)
// ConsulCatalog holds configurations of the Consul catalog provider.
type ConsulCatalog struct {
BaseProvider
Endpoint string `description:"Consul server endpoint"`
Domain string `description:"Default domain used"`
client *api.Client
Prefix string
BaseProvider `mapstructure:",squash"`
Endpoint string `description:"Consul server endpoint"`
Domain string `description:"Default domain used"`
client *api.Client
Prefix string
}
type serviceUpdate struct {
@@ -41,6 +46,35 @@ type catalogUpdate struct {
Nodes []*api.ServiceEntry
}
type nodeSorter []*api.ServiceEntry
func (a nodeSorter) Len() int {
return len(a)
}
func (a nodeSorter) Swap(i int, j int) {
a[i], a[j] = a[j], a[i]
}
func (a nodeSorter) Less(i int, j int) bool {
lentr := a[i]
rentr := a[j]
ls := strings.ToLower(lentr.Service.Service)
lr := strings.ToLower(rentr.Service.Service)
if ls != lr {
return ls < lr
}
if lentr.Service.Address != rentr.Service.Address {
return lentr.Service.Address < rentr.Service.Address
}
if lentr.Node.Address != rentr.Node.Address {
return lentr.Node.Address < rentr.Node.Address
}
return lentr.Service.Port < rentr.Service.Port
}
func (provider *ConsulCatalog) watchServices(stopCh <-chan struct{}) <-chan map[string][]string {
watchCh := make(chan map[string][]string)
@@ -139,7 +173,7 @@ func (provider *ConsulCatalog) getBackendAddress(node *api.ServiceEntry) string
}
func (provider *ConsulCatalog) getBackendName(node *api.ServiceEntry, index int) string {
serviceName := node.Service.Service + "--" + node.Service.Address + "--" + strconv.Itoa(node.Service.Port)
serviceName := strings.ToLower(node.Service.Service) + "--" + node.Service.Address + "--" + strconv.Itoa(node.Service.Port)
for _, tag := range node.Service.Tags {
serviceName += "--" + normalize(tag)
@@ -179,12 +213,13 @@ func (provider *ConsulCatalog) getContraintTags(tags []string) []string {
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
var FuncMap = template.FuncMap{
"getBackend": provider.getBackend,
"getFrontendRule": provider.getFrontendRule,
"getBackendName": provider.getBackendName,
"getBackendAddress": provider.getBackendAddress,
"getAttribute": provider.getAttribute,
"getEntryPoints": provider.getEntryPoints,
"getBackend": provider.getBackend,
"getFrontendRule": provider.getFrontendRule,
"getBackendName": provider.getBackendName,
"getBackendAddress": provider.getBackendAddress,
"getAttribute": provider.getAttribute,
"getEntryPoints": provider.getEntryPoints,
"hasMaxconnAttributes": provider.hasMaxconnAttributes,
}
allNodes := []*api.ServiceEntry{}
@@ -200,6 +235,8 @@ func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Confi
}
}
// Ensure a stable ordering of nodes so that identical configurations may be detected
sort.Sort(nodeSorter(allNodes))
templateObjects := struct {
Services []*serviceUpdate
@@ -217,6 +254,15 @@ func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Confi
return configuration
}
func (provider *ConsulCatalog) hasMaxconnAttributes(attributes []string) bool {
amount := provider.getAttribute("backend.maxconn.amount", attributes, "")
extractorfunc := provider.getAttribute("backend.maxconn.extractorfunc", attributes, "")
if amount != "" && extractorfunc != "" {
return true
}
return false
}
func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpdate, error) {
visited := make(map[string]bool)
@@ -225,7 +271,7 @@ func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpd
name := strings.ToLower(service)
if !strings.Contains(name, " ") && !visited[name] {
visited[name] = true
log.WithFields(log.Fields{
log.WithFields(logrus.Fields{
"service": name,
}).Debug("Fetching service")
healthy, err := provider.healthyNodes(name)
@@ -285,12 +331,12 @@ func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMess
notify := func(err error, time time.Duration) {
log.Errorf("Consul connection error %+v, retrying in %s", err, time)
}
worker := func() error {
operation := func() error {
return provider.watch(configurationChan, stop)
}
err := backoff.RetryNotify(worker, backoff.NewExponentialBackOff(), notify)
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
if err != nil {
log.Fatalf("Cannot connect to consul server %+v", err)
log.Errorf("Cannot connect to consul server %+v", err)
}
})

View File

@@ -2,6 +2,7 @@ package provider
import (
"reflect"
"sort"
"testing"
"github.com/containous/traefik/types"
@@ -211,6 +212,8 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
"traefik.backend.loadbalancer=drr",
"traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5",
"random.foo=bar",
"traefik.backend.maxconn.amount=1000",
"traefik.backend.maxconn.extractorfunc=client.ip",
},
},
Nodes: []*api.ServiceEntry{
@@ -259,6 +262,10 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
LoadBalancer: &types.LoadBalancer{
Method: "drr",
},
MaxConn: &types.MaxConn{
Amount: 1000,
ExtractorFunc: "client.ip",
},
},
},
},
@@ -274,3 +281,195 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
}
}
}
func TestConsulCatalogNodeSorter(t *testing.T) {
cases := []struct {
nodes []*api.ServiceEntry
expected []*api.ServiceEntry
}{
{
nodes: []*api.ServiceEntry{},
expected: []*api.ServiceEntry{},
},
{
nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.1",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
},
expected: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.1",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
},
},
{
nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.2",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
{
Service: &api.AgentService{
Service: "bar",
Address: "127.0.0.2",
Port: 81,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.1",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
{
Service: &api.AgentService{
Service: "bar",
Address: "127.0.0.2",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
},
expected: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "bar",
Address: "127.0.0.2",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
{
Service: &api.AgentService{
Service: "bar",
Address: "127.0.0.2",
Port: 81,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.1",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.2",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
},
},
{
nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "foo",
Address: "",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
{
Service: &api.AgentService{
Service: "foo",
Address: "",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
},
expected: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "foo",
Address: "",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
{
Service: &api.AgentService{
Service: "foo",
Address: "",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
},
},
}
for _, c := range cases {
sort.Sort(nodeSorter(c.nodes))
actual := c.nodes
if !reflect.DeepEqual(actual, c.expected) {
t.Fatalf("expected %q, got %q", c.expected, actual)
}
}
}

View File

@@ -2,6 +2,8 @@ package provider
import (
"errors"
"math"
"net"
"net/http"
"strconv"
"strings"
@@ -11,52 +13,76 @@ import (
"golang.org/x/net/context"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff"
"github.com/cenk/backoff"
"github.com/containous/traefik/job"
"github.com/containous/traefik/log"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/containous/traefik/version"
"github.com/docker/engine-api/client"
dockertypes "github.com/docker/engine-api/types"
dockercontainertypes "github.com/docker/engine-api/types/container"
eventtypes "github.com/docker/engine-api/types/events"
"github.com/docker/engine-api/types/filters"
"github.com/docker/engine-api/types/swarm"
swarmtypes "github.com/docker/engine-api/types/swarm"
"github.com/docker/go-connections/nat"
"github.com/docker/go-connections/sockets"
"github.com/docker/go-connections/tlsconfig"
"github.com/vdemeester/docker-events"
)
// DockerAPIVersion is a constant holding the version of the Docker API traefik will use
const DockerAPIVersion string = "1.21"
const (
// DockerAPIVersion is a constant holding the version of the Docker API traefik will use
DockerAPIVersion string = "1.21"
// SwarmAPIVersion is a constant holding the version of the Docker API traefik will use
SwarmAPIVersion string = "1.24"
// SwarmDefaultWatchTime is the duration of the interval when polling docker
SwarmDefaultWatchTime = 15 * time.Second
)
var _ Provider = (*Docker)(nil)
// Docker holds configurations of the Docker provider.
type Docker struct {
BaseProvider
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"`
Domain string `description:"Default domain used"`
TLS *DockerTLS `description:"Enable Docker TLS support"`
BaseProvider `mapstructure:",squash"`
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"`
Domain string `description:"Default domain used"`
TLS *ClientTLS `description:"Enable Docker TLS support"`
ExposedByDefault bool `description:"Expose containers by default"`
UseBindPortIP bool `description:"Use the ip address from the bound port, rather than from the inner network"`
SwarmMode bool `description:"Use Docker on Swarm Mode"`
}
// DockerTLS holds TLS specific configurations
type DockerTLS struct {
CA string `description:"TLS CA"`
Cert string `description:"TLS cert"`
Key string `description:"TLS key"`
InsecureSkipVerify bool `description:"TLS insecure skip verify"`
// dockerData holds the need data to the Docker provider
type dockerData struct {
Name string
Labels map[string]string // List of labels set to container or service
NetworkSettings networkSettings
}
// NetworkSettings holds the networks data to the Docker provider
type networkSettings struct {
NetworkMode dockercontainertypes.NetworkMode
Ports nat.PortMap
Networks map[string]*networkData
}
// Network holds the network data to the Docker provider
type networkData struct {
Name string
Addr string
Port int
Protocol string
ID string
}
func (provider *Docker) createClient() (client.APIClient, error) {
var httpClient *http.Client
httpHeaders := map[string]string{
// FIXME(vdemeester) use version here O:)
"User-Agent": "Traefik",
"User-Agent": "Traefik " + version.Version,
}
if provider.TLS != nil {
tlsOptions := tlsconfig.Options{
CAFile: provider.TLS.CA,
CertFile: provider.TLS.Cert,
KeyFile: provider.TLS.Key,
InsecureSkipVerify: provider.TLS.InsecureSkipVerify,
}
config, err := tlsconfig.Client(tlsOptions)
config, err := provider.TLS.CreateTLSConfig()
if err != nil {
return nil, err
}
@@ -73,8 +99,16 @@ func (provider *Docker) createClient() (client.APIClient, error) {
httpClient = &http.Client{
Transport: tr,
}
}
return client.NewClient(provider.Endpoint, DockerAPIVersion, httpClient, httpHeaders)
var version string
if provider.SwarmMode {
version = SwarmAPIVersion
} else {
version = DockerAPIVersion
}
return client.NewClient(provider.Endpoint, version, httpClient, httpHeaders)
}
// Provide allows the provider to provide configurations to traefik
@@ -91,57 +125,100 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
log.Errorf("Failed to create a client for docker, error: %s", err)
return err
}
version, err := dockerClient.ServerVersion(context.Background())
ctx := context.Background()
version, err := dockerClient.ServerVersion(ctx)
log.Debugf("Docker connection established with docker %s (API %s)", version.Version, version.APIVersion)
containers, err := listContainers(dockerClient)
if err != nil {
log.Errorf("Failed to list containers for docker, error %s", err)
return err
var dockerDataList []dockerData
if provider.SwarmMode {
dockerDataList, err = listServices(ctx, dockerClient)
if err != nil {
log.Errorf("Failed to list services for docker swarm mode, error %s", err)
return err
}
} else {
dockerDataList, err = listContainers(ctx, dockerClient)
if err != nil {
log.Errorf("Failed to list containers for docker, error %s", err)
return err
}
}
configuration := provider.loadDockerConfig(containers)
configuration := provider.loadDockerConfig(dockerDataList)
configurationChan <- types.ConfigMessage{
ProviderName: "docker",
Configuration: configuration,
}
if provider.Watch {
ctx, cancel := context.WithCancel(context.Background())
f := filters.NewArgs()
f.Add("type", "container")
options := dockertypes.EventsOptions{
Filters: f,
}
eventHandler := events.NewHandler(events.ByAction)
startStopHandle := func(m eventtypes.Message) {
log.Debugf("Docker event received %+v", m)
containers, err := listContainers(dockerClient)
if err != nil {
log.Errorf("Failed to list containers for docker, error %s", err)
// Call cancel to get out of the monitor
cancel()
}
configuration := provider.loadDockerConfig(containers)
if configuration != nil {
configurationChan <- types.ConfigMessage{
ProviderName: "docker",
Configuration: configuration,
}
}
}
eventHandler.Handle("start", startStopHandle)
eventHandler.Handle("die", startStopHandle)
ctx, cancel := context.WithCancel(ctx)
if provider.SwarmMode {
// TODO: This need to be change. Linked to Swarm events docker/docker#23827
ticker := time.NewTicker(SwarmDefaultWatchTime)
pool.Go(func(stop chan bool) {
for {
select {
case <-ticker.C:
services, err := listServices(ctx, dockerClient)
if err != nil {
log.Errorf("Failed to list services for docker, error %s", err)
return
}
configuration := provider.loadDockerConfig(services)
if configuration != nil {
configurationChan <- types.ConfigMessage{
ProviderName: "docker",
Configuration: configuration,
}
}
errChan := events.MonitorWithHandler(ctx, dockerClient, options, eventHandler)
pool.Go(func(stop chan bool) {
for {
select {
case <-stop:
case <-stop:
ticker.Stop()
cancel()
return
}
}
})
} else {
pool.Go(func(stop chan bool) {
for {
select {
case <-stop:
cancel()
return
}
}
})
f := filters.NewArgs()
f.Add("type", "container")
options := dockertypes.EventsOptions{
Filters: f,
}
eventHandler := events.NewHandler(events.ByAction)
startStopHandle := func(m eventtypes.Message) {
log.Debugf("Docker event received %+v", m)
containers, err := listContainers(ctx, dockerClient)
if err != nil {
log.Errorf("Failed to list containers for docker, error %s", err)
// Call cancel to get out of the monitor
cancel()
return
}
configuration := provider.loadDockerConfig(containers)
if configuration != nil {
configurationChan <- types.ConfigMessage{
ProviderName: "docker",
Configuration: configuration,
}
}
}
eventHandler.Handle("start", startStopHandle)
eventHandler.Handle("die", startStopHandle)
errChan := events.MonitorWithHandler(ctx, dockerClient, options, eventHandler)
if err := <-errChan; err != nil {
return err
}
})
if err := <-errChan; err != nil {
return err
}
}
return nil
@@ -149,45 +226,65 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
notify := func(err error, time time.Duration) {
log.Errorf("Docker connection error %+v, retrying in %s", err, time)
}
err := backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify)
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
if err != nil {
log.Fatalf("Cannot connect to docker server %+v", err)
log.Errorf("Cannot connect to docker server %+v", err)
}
})
return nil
}
func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.ContainerJSON) *types.Configuration {
func (provider *Docker) loadDockerConfig(containersInspected []dockerData) *types.Configuration {
var DockerFuncMap = template.FuncMap{
"getBackend": provider.getBackend,
"getIPAddress": provider.getIPAddress,
"getPort": provider.getPort,
"getWeight": provider.getWeight,
"getDomain": provider.getDomain,
"getProtocol": provider.getProtocol,
"getPassHostHeader": provider.getPassHostHeader,
"getPriority": provider.getPriority,
"getEntryPoints": provider.getEntryPoints,
"getFrontendRule": provider.getFrontendRule,
"replace": replace,
"getBackend": provider.getBackend,
"getIPAddress": provider.getIPAddress,
"getPort": provider.getPort,
"getWeight": provider.getWeight,
"getDomain": provider.getDomain,
"getProtocol": provider.getProtocol,
"getPassHostHeader": provider.getPassHostHeader,
"getPriority": provider.getPriority,
"getEntryPoints": provider.getEntryPoints,
"getFrontendRule": provider.getFrontendRule,
"hasCircuitBreakerLabel": provider.hasCircuitBreakerLabel,
"getCircuitBreakerExpression": provider.getCircuitBreakerExpression,
"hasLoadBalancerLabel": provider.hasLoadBalancerLabel,
"getLoadBalancerMethod": provider.getLoadBalancerMethod,
"hasMaxConnLabels": provider.hasMaxConnLabels,
"getMaxConnAmount": provider.getMaxConnAmount,
"getMaxConnExtractorFunc": provider.getMaxConnExtractorFunc,
"getSticky": provider.getSticky,
"replace": replace,
}
// filter containers
filteredContainers := fun.Filter(containerFilter, containersInspected).([]dockertypes.ContainerJSON)
filteredContainers := fun.Filter(func(container dockerData) bool {
return provider.containerFilter(container)
}, containersInspected).([]dockerData)
frontends := map[string][]dockertypes.ContainerJSON{}
frontends := map[string][]dockerData{}
backends := map[string]dockerData{}
servers := map[string][]dockerData{}
for _, container := range filteredContainers {
frontends[provider.getFrontendName(container)] = append(frontends[provider.getFrontendName(container)], container)
frontendName := provider.getFrontendName(container)
frontends[frontendName] = append(frontends[frontendName], container)
backendName := provider.getBackend(container)
backends[backendName] = container
servers[backendName] = append(servers[backendName], container)
}
templateObjects := struct {
Containers []dockertypes.ContainerJSON
Frontends map[string][]dockertypes.ContainerJSON
Containers []dockerData
Frontends map[string][]dockerData
Backends map[string]dockerData
Servers map[string][]dockerData
Domain string
}{
filteredContainers,
frontends,
backends,
servers,
provider.Domain,
}
@@ -198,8 +295,67 @@ func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.Conta
return configuration
}
func containerFilter(container dockertypes.ContainerJSON) bool {
_, err := strconv.Atoi(container.Config.Labels["traefik.port"])
func (provider *Docker) hasCircuitBreakerLabel(container dockerData) bool {
if _, err := getLabel(container, "traefik.backend.circuitbreaker.expression"); err != nil {
return false
}
return true
}
func (provider *Docker) hasLoadBalancerLabel(container dockerData) bool {
_, errMethod := getLabel(container, "traefik.backend.loadbalancer.method")
_, errSticky := getLabel(container, "traefik.backend.loadbalancer.sticky")
if errMethod != nil && errSticky != nil {
return false
}
return true
}
func (provider *Docker) hasMaxConnLabels(container dockerData) bool {
if _, err := getLabel(container, "traefik.backend.maxconn.amount"); err != nil {
return false
}
if _, err := getLabel(container, "traefik.backend.maxconn.extractorfunc"); err != nil {
return false
}
return true
}
func (provider *Docker) getCircuitBreakerExpression(container dockerData) string {
if label, err := getLabel(container, "traefik.backend.circuitbreaker.expression"); err == nil {
return label
}
return "NetworkErrorRatio() > 1"
}
func (provider *Docker) getLoadBalancerMethod(container dockerData) string {
if label, err := getLabel(container, "traefik.backend.loadbalancer.method"); err == nil {
return label
}
return "wrr"
}
func (provider *Docker) getMaxConnAmount(container dockerData) int64 {
if label, err := getLabel(container, "traefik.backend.maxconn.amount"); err == nil {
i, errConv := strconv.ParseInt(label, 10, 64)
if errConv != nil {
log.Errorf("Unable to parse traefik.backend.maxconn.amount %s", label)
return math.MaxInt64
}
return i
}
return math.MaxInt64
}
func (provider *Docker) getMaxConnExtractorFunc(container dockerData) string {
if label, err := getLabel(container, "traefik.backend.maxconn.extractorfunc"); err == nil {
return label
}
return "request.host"
}
func (provider *Docker) containerFilter(container dockerData) bool {
_, err := strconv.Atoi(container.Labels["traefik.port"])
if len(container.NetworkSettings.Ports) == 0 && err != nil {
log.Debugf("Filtering container without port and no traefik.port label %s", container.Name)
return false
@@ -209,61 +365,78 @@ func containerFilter(container dockertypes.ContainerJSON) bool {
return false
}
if container.Config.Labels["traefik.enable"] == "false" {
if !isContainerEnabled(container, provider.ExposedByDefault) {
log.Debugf("Filtering disabled container %s", container.Name)
return false
}
constraintTags := strings.Split(container.Labels["traefik.tags"], ",")
if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok {
if failingConstraint != nil {
log.Debugf("Container %v pruned by '%v' constraint", container.Name, failingConstraint.String())
}
return false
}
return true
}
func (provider *Docker) getFrontendName(container dockertypes.ContainerJSON) string {
func (provider *Docker) getFrontendName(container dockerData) string {
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
return normalize(provider.getFrontendRule(container))
}
// GetFrontendRule returns the frontend rule for the specified container, using
// it's label. It returns a default one (Host) if the label is not present.
func (provider *Docker) getFrontendRule(container dockertypes.ContainerJSON) string {
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
// TODO: backwards compatibility with DEPRECATED rule.Value
if value, ok := container.Config.Labels["traefik.frontend.value"]; ok {
log.Warnf("Label traefik.frontend.value=%s is DEPRECATED (will be removed in v1.0.0), please refer to the rule label: https://github.com/containous/traefik/blob/master/docs/index.md#docker", value)
rule, _ := container.Config.Labels["traefik.frontend.rule"]
return rule + ":" + value
}
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
func (provider *Docker) getFrontendRule(container dockerData) string {
if label, err := getLabel(container, "traefik.frontend.rule"); err == nil {
return label
}
return "Host:" + provider.getSubDomain(container.Name) + "." + provider.Domain
}
func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string {
func (provider *Docker) getBackend(container dockerData) string {
if label, err := getLabel(container, "traefik.backend"); err == nil {
return label
}
return normalize(container.Name)
}
func (provider *Docker) getIPAddress(container dockertypes.ContainerJSON) string {
func (provider *Docker) getIPAddress(container dockerData) string {
if label, err := getLabel(container, "traefik.docker.network"); err == nil && label != "" {
networks := container.NetworkSettings.Networks
if networks != nil {
network := networks[label]
networkSettings := container.NetworkSettings
if networkSettings.Networks != nil {
network := networkSettings.Networks[label]
if network != nil {
return network.IPAddress
return network.Addr
}
}
}
// If net==host, quick n' dirty, we return 127.0.0.1
// This will work locally, but will fail with swarm.
if "host" == container.NetworkSettings.NetworkMode {
return "127.0.0.1"
}
if provider.UseBindPortIP {
port := provider.getPort(container)
for netport, portBindings := range container.NetworkSettings.Ports {
if string(netport) == port+"/TCP" || string(netport) == port+"/UDP" {
for _, p := range portBindings {
return p.HostIP
}
}
}
}
for _, network := range container.NetworkSettings.Networks {
return network.IPAddress
return network.Addr
}
return ""
}
func (provider *Docker) getPort(container dockertypes.ContainerJSON) string {
func (provider *Docker) getPort(container dockerData) string {
if label, err := getLabel(container, "traefik.port"); err == nil {
return label
}
@@ -273,50 +446,61 @@ func (provider *Docker) getPort(container dockertypes.ContainerJSON) string {
return ""
}
func (provider *Docker) getWeight(container dockertypes.ContainerJSON) string {
func (provider *Docker) getWeight(container dockerData) string {
if label, err := getLabel(container, "traefik.weight"); err == nil {
return label
}
return "1"
}
func (provider *Docker) getDomain(container dockertypes.ContainerJSON) string {
func (provider *Docker) getSticky(container dockerData) string {
if _, err := getLabel(container, "traefik.backend.loadbalancer.sticky"); err == nil {
return "true"
}
return "false"
}
func (provider *Docker) getDomain(container dockerData) string {
if label, err := getLabel(container, "traefik.domain"); err == nil {
return label
}
return provider.Domain
}
func (provider *Docker) getProtocol(container dockertypes.ContainerJSON) string {
func (provider *Docker) getProtocol(container dockerData) string {
if label, err := getLabel(container, "traefik.protocol"); err == nil {
return label
}
return "http"
}
func (provider *Docker) getPassHostHeader(container dockertypes.ContainerJSON) string {
func (provider *Docker) getPassHostHeader(container dockerData) string {
if passHostHeader, err := getLabel(container, "traefik.frontend.passHostHeader"); err == nil {
return passHostHeader
}
return "true"
}
func (provider *Docker) getPriority(container dockertypes.ContainerJSON) string {
func (provider *Docker) getPriority(container dockerData) string {
if priority, err := getLabel(container, "traefik.frontend.priority"); err == nil {
return priority
}
return "0"
}
func (provider *Docker) getEntryPoints(container dockertypes.ContainerJSON) []string {
func (provider *Docker) getEntryPoints(container dockerData) []string {
if entryPoints, err := getLabel(container, "traefik.frontend.entryPoints"); err == nil {
return strings.Split(entryPoints, ",")
}
return []string{}
}
func getLabel(container dockertypes.ContainerJSON, label string) (string, error) {
for key, value := range container.Config.Labels {
func isContainerEnabled(container dockerData, exposedByDefault bool) bool {
return exposedByDefault && container.Labels["traefik.enable"] != "false" || container.Labels["traefik.enable"] == "true"
}
func getLabel(container dockerData, label string) (string, error) {
for key, value := range container.Labels {
if key == label {
return value, nil
}
@@ -324,7 +508,7 @@ func getLabel(container dockertypes.ContainerJSON, label string) (string, error)
return "", errors.New("Label not found:" + label)
}
func getLabels(container dockertypes.ContainerJSON, labels []string) (map[string]string, error) {
func getLabels(container dockerData, labels []string) (map[string]string, error) {
var globalErr error
foundLabels := map[string]string{}
for _, label := range labels {
@@ -340,25 +524,127 @@ func getLabels(container dockertypes.ContainerJSON, labels []string) (map[string
return foundLabels, globalErr
}
func listContainers(dockerClient client.APIClient) ([]dockertypes.ContainerJSON, error) {
containerList, err := dockerClient.ContainerList(context.Background(), dockertypes.ContainerListOptions{})
func listContainers(ctx context.Context, dockerClient client.ContainerAPIClient) ([]dockerData, error) {
containerList, err := dockerClient.ContainerList(ctx, dockertypes.ContainerListOptions{})
if err != nil {
return []dockertypes.ContainerJSON{}, err
return []dockerData{}, err
}
containersInspected := []dockertypes.ContainerJSON{}
containersInspected := []dockerData{}
// get inspect containers
for _, container := range containerList {
containerInspected, err := dockerClient.ContainerInspect(context.Background(), container.ID)
containerInspected, err := dockerClient.ContainerInspect(ctx, container.ID)
if err != nil {
log.Warnf("Failed to inpsect container %s, error: %s", container.ID, err)
log.Warnf("Failed to inspect container %s, error: %s", container.ID, err)
} else {
dockerData := parseContainer(containerInspected)
containersInspected = append(containersInspected, dockerData)
}
containersInspected = append(containersInspected, containerInspected)
}
return containersInspected, nil
}
func parseContainer(container dockertypes.ContainerJSON) dockerData {
dockerData := dockerData{
NetworkSettings: networkSettings{},
}
if container.ContainerJSONBase != nil {
dockerData.Name = container.ContainerJSONBase.Name
if container.ContainerJSONBase.HostConfig != nil {
dockerData.NetworkSettings.NetworkMode = container.ContainerJSONBase.HostConfig.NetworkMode
}
}
if container.Config != nil && container.Config.Labels != nil {
dockerData.Labels = container.Config.Labels
}
if container.NetworkSettings != nil {
if container.NetworkSettings.Ports != nil {
dockerData.NetworkSettings.Ports = container.NetworkSettings.Ports
}
if container.NetworkSettings.Networks != nil {
dockerData.NetworkSettings.Networks = make(map[string]*networkData)
for name, containerNetwork := range container.NetworkSettings.Networks {
dockerData.NetworkSettings.Networks[name] = &networkData{
ID: containerNetwork.NetworkID,
Name: name,
Addr: containerNetwork.IPAddress,
}
}
}
}
return dockerData
}
// Escape beginning slash "/", convert all others to dash "-"
func (provider *Docker) getSubDomain(name string) string {
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
}
func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerData, error) {
serviceList, err := dockerClient.ServiceList(ctx, dockertypes.ServiceListOptions{})
if err != nil {
return []dockerData{}, err
}
networkListArgs := filters.NewArgs()
networkListArgs.Add("driver", "overlay")
networkList, err := dockerClient.NetworkList(ctx, dockertypes.NetworkListOptions{Filters: networkListArgs})
networkMap := make(map[string]*dockertypes.NetworkResource)
if err != nil {
log.Debug("Failed to network inspect on client for docker, error: %s", err)
return []dockerData{}, err
}
for _, network := range networkList {
networkMap[network.ID] = &network
}
var dockerDataList []dockerData
for _, service := range serviceList {
dockerData := parseService(service, networkMap)
dockerDataList = append(dockerDataList, dockerData)
}
return dockerDataList, err
}
func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes.NetworkResource) dockerData {
dockerData := dockerData{
Name: service.Spec.Annotations.Name,
Labels: service.Spec.Annotations.Labels,
NetworkSettings: networkSettings{},
}
if service.Spec.EndpointSpec != nil {
switch service.Spec.EndpointSpec.Mode {
case swarm.ResolutionModeDNSRR:
log.Debug("Ignored endpoint-mode not supported, service name: %s", dockerData.Name)
case swarm.ResolutionModeVIP:
dockerData.NetworkSettings.Networks = make(map[string]*networkData)
for _, virtualIP := range service.Endpoint.VirtualIPs {
networkService := networkMap[virtualIP.NetworkID]
if networkService != nil {
ip, _, _ := net.ParseCIDR(virtualIP.Addr)
network := &networkData{
Name: networkService.Name,
ID: virtualIP.NetworkID,
Addr: ip.String(),
}
dockerData.NetworkSettings.Networks[network.Name] = network
} else {
log.Debug("Network not found, id: %s", virtualIP.NetworkID)
}
}
}
}
return dockerData
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,34 @@
package provider
import (
"fmt"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/etcd"
)
var _ Provider = (*Etcd)(nil)
// Etcd holds configurations of the Etcd provider.
type Etcd struct {
Kv
Kv `mapstructure:",squash"`
}
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
provider.storeType = store.ETCD
etcd.Register()
store, err := provider.CreateStore()
if err != nil {
return fmt.Errorf("Failed to Connect to KV store: %v", err)
}
provider.kvclient = store
return provider.provide(configurationChan, pool, constraints)
}
// CreateStore creates the KV store
func (provider *Etcd) CreateStore() (store.Store, error) {
provider.storeType = store.ETCD
etcd.Register()
return provider.createStore()
}

View File

@@ -6,15 +6,17 @@ import (
"strings"
"github.com/BurntSushi/toml"
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/log"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"gopkg.in/fsnotify.v1"
)
var _ Provider = (*File)(nil)
// File holds configurations of the File provider.
type File struct {
BaseProvider
BaseProvider `mapstructure:",squash"`
}
// Provide allows the provider to provide configurations to traefik

Some files were not shown because too many files have changed in this diff Show More