Compare commits
708 Commits
v1.4.0-rc2
...
v1.6.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
446d73fcf5 | ||
|
|
e299775d67 | ||
|
|
2c18750537 | ||
|
|
f317e50136 | ||
|
|
1d84bda7ca | ||
|
|
ae7c947ba5 | ||
|
|
6d07729c55 | ||
|
|
1d7bf200a8 | ||
|
|
6bc59f8b33 | ||
|
|
b2cf03fa5c | ||
|
|
36e273714d | ||
|
|
6be77b7fb9 | ||
|
|
6bcf45f136 | ||
|
|
8bca8236db | ||
|
|
fb5aa4c9c1 | ||
|
|
3f5772c62a | ||
|
|
2d946d7ee7 | ||
|
|
10ca35dccd | ||
|
|
e3671cbb04 | ||
|
|
a525d02cc5 | ||
|
|
1cc1a4e6e2 | ||
|
|
3f0af3fe09 | ||
|
|
bc26d9f0de | ||
|
|
0ba28bbc8b | ||
|
|
550184275a | ||
|
|
c376083ecb | ||
|
|
1db5fcf200 | ||
|
|
16b2555ab3 | ||
|
|
d9a5258f40 | ||
|
|
190ebbed27 | ||
|
|
a0872c9e31 | ||
|
|
f5b306e7ff | ||
|
|
7a1feb3c51 | ||
|
|
1e8df9f245 | ||
|
|
b72937e8fb | ||
|
|
67847c3117 | ||
|
|
a2a0c80acb | ||
|
|
b3fd06fb45 | ||
|
|
c5db8d903c | ||
|
|
8fcd242494 | ||
|
|
ebd9af900e | ||
|
|
b02381c2d5 | ||
|
|
9b199ea756 | ||
|
|
ec3b913ee4 | ||
|
|
c210ab31d9 | ||
|
|
6c1fa91c70 | ||
|
|
04bab185f6 | ||
|
|
2213b4cf37 | ||
|
|
1d770e5636 | ||
|
|
b7e15e0a2c | ||
|
|
9c651ae913 | ||
|
|
e09d5cb4ec | ||
|
|
cae353b9f6 | ||
|
|
edb5b3d711 | ||
|
|
667a0c41ed | ||
|
|
2975acdc82 | ||
|
|
76dcbe3429 | ||
|
|
d8e2d464ad | ||
|
|
5f8bcb0c26 | ||
|
|
7ef8d6fa10 | ||
|
|
5924a40222 | ||
|
|
95ce4f5c1e | ||
|
|
f258f20b04 | ||
|
|
7e2ad827aa | ||
|
|
3df588047d | ||
|
|
ac0e5cbb29 | ||
|
|
5ab584bc6a | ||
|
|
a2e03e3bd0 | ||
|
|
f0589b310f | ||
|
|
8519b0d353 | ||
|
|
0e3d1e1503 | ||
|
|
ebd77f314d | ||
|
|
749d833f65 | ||
|
|
0373cd6f97 | ||
|
|
1f3fc8a366 | ||
|
|
89c3930b28 | ||
|
|
29e1e9eef2 | ||
|
|
85aa1a444a | ||
|
|
702876ae7f | ||
|
|
7109910f46 | ||
|
|
8168d2fdc1 | ||
|
|
edbcd01fbc | ||
|
|
c99266e961 | ||
|
|
f804053736 | ||
|
|
2641832304 | ||
|
|
21f6f81914 | ||
|
|
ccd919aba3 | ||
|
|
2387010556 | ||
|
|
f35d574759 | ||
|
|
3be74bb275 | ||
|
|
b1be062437 | ||
|
|
2d0d320d05 | ||
|
|
1de5111ab5 | ||
|
|
3d530e4747 | ||
|
|
0ef1b7b683 | ||
|
|
66485e81b4 | ||
|
|
e74e7cf734 | ||
|
|
a19b93c966 | ||
|
|
f7fd1f2a63 | ||
|
|
88b71d23db | ||
|
|
6845068b82 | ||
|
|
5c0b18efe4 | ||
|
|
4b93d040b3 | ||
|
|
ff61cc971e | ||
|
|
46db91ce73 | ||
|
|
5921909ef5 | ||
|
|
1537861c61 | ||
|
|
1b93551572 | ||
|
|
197a5fbcf4 | ||
|
|
ff32529345 | ||
|
|
a179c3b399 | ||
|
|
a820585f56 | ||
|
|
bfb12f415c | ||
|
|
a731b43b52 | ||
|
|
118b4eb07a | ||
|
|
f1a05ab73c | ||
|
|
4c85a41bfb | ||
|
|
30e048d4ab | ||
|
|
aa0ab6d387 | ||
|
|
30b87985b7 | ||
|
|
df73211d56 | ||
|
|
e3a4ddcd08 | ||
|
|
0ea007b26f | ||
|
|
16bb9b6836 | ||
|
|
d2766b1b4f | ||
|
|
4802484729 | ||
|
|
c762b9bb2e | ||
|
|
5792a19b97 | ||
|
|
9699dc2a85 | ||
|
|
0fa0c2256a | ||
|
|
1b410980ca | ||
|
|
be0dbd62c1 | ||
|
|
1a411b658b | ||
|
|
d2e84a700f | ||
|
|
b9af55fc49 | ||
|
|
e0d92aed6d | ||
|
|
f94fa78565 | ||
|
|
007a1fc7f2 | ||
|
|
a3372acb6d | ||
|
|
af7c9b520f | ||
|
|
43a510c046 | ||
|
|
329c576f44 | ||
|
|
7afa33dfa1 | ||
|
|
73c6007730 | ||
|
|
526c19181e | ||
|
|
79cd306ac2 | ||
|
|
35b83678bd | ||
|
|
eacb6ea15a | ||
|
|
d88263dbf9 | ||
|
|
b1e3444798 | ||
|
|
f6c6d2bcd0 | ||
|
|
8d468925d3 | ||
|
|
f99363674b | ||
|
|
526a04d4c8 | ||
|
|
593c0e7ce2 | ||
|
|
e2b42ca57b | ||
|
|
7860534f0c | ||
|
|
fc81d92c88 | ||
|
|
8fbac2e39e | ||
|
|
b91ae71241 | ||
|
|
0a41cd43a5 | ||
|
|
59f7b2ea98 | ||
|
|
862957c30c | ||
|
|
4831890232 | ||
|
|
546f0173ab | ||
|
|
b001b0da86 | ||
|
|
04e3f2f401 | ||
|
|
3a2b421566 | ||
|
|
acc432b5a8 | ||
|
|
c4529820f2 | ||
|
|
d3edccb839 | ||
|
|
8380de1bd9 | ||
|
|
bf43149d7e | ||
|
|
13e2358815 | ||
|
|
1f6f8d5e0f | ||
|
|
716eca5976 | ||
|
|
9ae808aac4 | ||
|
|
c77fe6b434 | ||
|
|
f149b56063 | ||
|
|
831a3e384b | ||
|
|
49a9e2a9e0 | ||
|
|
a2db3e0499 | ||
|
|
422109b82f | ||
|
|
c864a7297b | ||
|
|
8da038041d | ||
|
|
dd954f3c0a | ||
|
|
6f81e3479a | ||
|
|
db483e9d34 | ||
|
|
700b7a1b51 | ||
|
|
ed65d00574 | ||
|
|
0306b5e8f7 | ||
|
|
cb54e414ed | ||
|
|
bad71d1a36 | ||
|
|
088b8fb348 | ||
|
|
e28ebf1c62 | ||
|
|
39eeb67d91 | ||
|
|
f460c1990e | ||
|
|
0c0949679f | ||
|
|
58d4481118 | ||
|
|
83381e99cf | ||
|
|
21e28ae848 | ||
|
|
31550fd2c9 | ||
|
|
7c7ee2ca61 | ||
|
|
ba046b4d3a | ||
|
|
d675d46930 | ||
|
|
7ea76929d4 | ||
|
|
5ef55dd8b4 | ||
|
|
d47c1a7975 | ||
|
|
8068057040 | ||
|
|
fcdeec0bfa | ||
|
|
b9d8eff994 | ||
|
|
529e34d2ae | ||
|
|
26b3fe201b | ||
|
|
f98c537ec2 | ||
|
|
083bde64ee | ||
|
|
462dcbcf03 | ||
|
|
45fe218ee2 | ||
|
|
d54777236c | ||
|
|
dafdaa4208 | ||
|
|
5212b7d3bd | ||
|
|
83a92596c3 | ||
|
|
4f3b06472b | ||
|
|
029fa83690 | ||
|
|
abdcb9e332 | ||
|
|
17e85e31cd | ||
|
|
7d3dd5a0e4 | ||
|
|
dd873fbeee | ||
|
|
38a4c80995 | ||
|
|
91fa727c74 | ||
|
|
794c0206f3 | ||
|
|
52bad03c8d | ||
|
|
2fde3e8679 | ||
|
|
1e71f52b72 | ||
|
|
2b1d2853cd | ||
|
|
6a92ac0b7b | ||
|
|
f07e8f58e6 | ||
|
|
7b19cb5631 | ||
|
|
f5adea1061 | ||
|
|
dbd173b4e4 | ||
|
|
85cfd87c44 | ||
|
|
c867f48f11 | ||
|
|
514f9a7215 | ||
|
|
0b0380b690 | ||
|
|
4d0c8c189a | ||
|
|
afe4c307f9 | ||
|
|
c0563f1a39 | ||
|
|
ce3a0fdd46 | ||
|
|
ce3c72e9d9 | ||
|
|
dcba74deb9 | ||
|
|
203a5c5c48 | ||
|
|
be4aeaacde | ||
|
|
04ebd9d46a | ||
|
|
52b4e93c38 | ||
|
|
58d6681824 | ||
|
|
c944d203fb | ||
|
|
62df067fac | ||
|
|
7c80b9a692 | ||
|
|
a4a8345a33 | ||
|
|
742dde72bb | ||
|
|
4497ddbb0e | ||
|
|
53388a3570 | ||
|
|
1c495d7ea4 | ||
|
|
4c0d6e211b | ||
|
|
5bfd6acd52 | ||
|
|
0b49de94c6 | ||
|
|
7c0e557f84 | ||
|
|
a81171d5f1 | ||
|
|
26dc2f4d61 | ||
|
|
d426126a92 | ||
|
|
6aac78fc36 | ||
|
|
f6c53f0450 | ||
|
|
54e09b98c7 | ||
|
|
395b1702de | ||
|
|
4eebaa1a80 | ||
|
|
cb9bf3ce68 | ||
|
|
ef4aa202d0 | ||
|
|
cc5ee00b89 | ||
|
|
49a8cb76f5 | ||
|
|
bf12306f17 | ||
|
|
fa1f4f761d | ||
|
|
b50aebd2ed | ||
|
|
323b8237a0 | ||
|
|
9f741abd84 | ||
|
|
32ccc26712 | ||
|
|
563a0bd274 | ||
|
|
a91080b060 | ||
|
|
039ccaf4f1 | ||
|
|
c878d262bf | ||
|
|
c8446c2dc8 | ||
|
|
4afb39778a | ||
|
|
751781a3b7 | ||
|
|
f5d150c3b4 | ||
|
|
ae9342208e | ||
|
|
3040d9df0d | ||
|
|
00e0571811 | ||
|
|
bfb07746fe | ||
|
|
171cda6186 | ||
|
|
4cc17e112f | ||
|
|
b6af61fa6e | ||
|
|
4e07d92190 | ||
|
|
fb4ba7af2b | ||
|
|
c134dcd6fe | ||
|
|
fc00e1c228 | ||
|
|
ae34486b57 | ||
|
|
d7b513e9aa | ||
|
|
d8297a055a | ||
|
|
5140bbe99a | ||
|
|
0c33d110f4 | ||
|
|
5b37fb83fd | ||
|
|
bc6879ecc1 | ||
|
|
17137ba3e7 | ||
|
|
e9d2124885 | ||
|
|
f1f2e1bf64 | ||
|
|
ced5aa5dc6 | ||
|
|
72bc74001f | ||
|
|
adfa3f795c | ||
|
|
89d90de7d8 | ||
|
|
fe426f6fb2 | ||
|
|
3e439cc39b | ||
|
|
56c0634918 | ||
|
|
bcadd68904 | ||
|
|
9790aa91fe | ||
|
|
5316b412d2 | ||
|
|
b5ee5c34f2 | ||
|
|
2618aef008 | ||
|
|
8239e04a19 | ||
|
|
709d50836b | ||
|
|
e2c5f3712f | ||
|
|
ee71b4bfef | ||
|
|
0d57e2aed9 | ||
|
|
30ffba78e6 | ||
|
|
8394549857 | ||
|
|
d0f3ad6024 | ||
|
|
870c0b5cf4 | ||
|
|
044d87d96d | ||
|
|
b60edd9ee9 | ||
|
|
b1ea36793b | ||
|
|
750878d668 | ||
|
|
617b8b20f0 | ||
|
|
d88554fa92 | ||
|
|
e74a20de24 | ||
|
|
7c227392fa | ||
|
|
8a697f7a39 | ||
|
|
8327dd0c0b | ||
|
|
60fd26e0b7 | ||
|
|
acd0c1bcd5 | ||
|
|
9b3750320b | ||
|
|
b9f1f7752d | ||
|
|
944008661f | ||
|
|
79ae52aca7 | ||
|
|
51390aa874 | ||
|
|
cfa1f47226 | ||
|
|
40b59da224 | ||
|
|
f7ed4a5805 | ||
|
|
3d47030349 | ||
|
|
34eb2e371e | ||
|
|
6573634012 | ||
|
|
61ecb4cd18 | ||
|
|
22bdbd2498 | ||
|
|
287fb78654 | ||
|
|
06d528a2bd | ||
|
|
1fe6a8b04d | ||
|
|
bd5cab6e87 | ||
|
|
238acd9330 | ||
|
|
8e7ac513b6 | ||
|
|
e56551d047 | ||
|
|
170fc13e02 | ||
|
|
97ce77169a | ||
|
|
c9b871a03a | ||
|
|
2fdefa258e | ||
|
|
f0a733d6d6 | ||
|
|
586b5714a7 | ||
|
|
6e23454202 | ||
|
|
5b24403c8e | ||
|
|
e83599dd08 | ||
|
|
de7dd068d9 | ||
|
|
a33476dea8 | ||
|
|
dceccbdb92 | ||
|
|
393651f5e2 | ||
|
|
5acee9e11d | ||
|
|
81626eef38 | ||
|
|
e60fbbbebe | ||
|
|
e45e63dc37 | ||
|
|
c3d5ad2eeb | ||
|
|
7c64f5d31e | ||
|
|
66f46c5b96 | ||
|
|
07a6d48a27 | ||
|
|
722ea28e3a | ||
|
|
f195ef27f3 | ||
|
|
7e5c258266 | ||
|
|
38b5aef208 | ||
|
|
a7e4ded722 | ||
|
|
22405a1259 | ||
|
|
d0a6689413 | ||
|
|
a1f47cb4db | ||
|
|
c884c7bb8a | ||
|
|
c042098889 | ||
|
|
571f41dcf0 | ||
|
|
f30ad20c9b | ||
|
|
cbd54470ba | ||
|
|
01e17b6c3e | ||
|
|
3e13ebec93 | ||
|
|
c84fb9895e | ||
|
|
23c1a9ca8e | ||
|
|
741c739ef1 | ||
|
|
52f16e11a8 | ||
|
|
5623a53464 | ||
|
|
c95393b238 | ||
|
|
be0dd71bb4 | ||
|
|
0ee6973e2f | ||
|
|
4819974a1c | ||
|
|
e8e8b41eed | ||
|
|
7d23d3c0a4 | ||
|
|
718fc7a79d | ||
|
|
bfd142b13b | ||
|
|
75533b2beb | ||
|
|
e3d1201b46 | ||
|
|
8f982ff1f2 | ||
|
|
0391e21c84 | ||
|
|
b8a1cb5c68 | ||
|
|
7a71cd3012 | ||
|
|
26bedced35 | ||
|
|
c1aefb8ad8 | ||
|
|
576e87f398 | ||
|
|
b4f6bf0f6a | ||
|
|
edc55aad3c | ||
|
|
38a3fe4316 | ||
|
|
81e3b2dd4c | ||
|
|
4524cdc151 | ||
|
|
9a7821b8fa | ||
|
|
e8333883df | ||
|
|
aeffe1036d | ||
|
|
987e8a93bd | ||
|
|
2cb4acd6cc | ||
|
|
1e44e339ad | ||
|
|
59549d5f39 | ||
|
|
4a7297d05c | ||
|
|
a5335667bb | ||
|
|
498b806ca9 | ||
|
|
dd7a8a9a87 | ||
|
|
133aa77c21 | ||
|
|
942614dd23 | ||
|
|
c30ebe5f90 | ||
|
|
50757b5e99 | ||
|
|
42b900b9b2 | ||
|
|
c26b9b1a5d | ||
|
|
9ee642a7db | ||
|
|
423385bca0 | ||
|
|
6e5f7650a5 | ||
|
|
89a79d0f1b | ||
|
|
9e41485ff1 | ||
|
|
3c7c6c4d9f | ||
|
|
cd1b3904da | ||
|
|
b23b2611b3 | ||
|
|
877770f7cf | ||
|
|
3142a4f4b3 | ||
|
|
b4dc96527d | ||
|
|
35b5ca4c63 | ||
|
|
daf3023b02 | ||
|
|
705f3f1372 | ||
|
|
f6520727a3 | ||
|
|
b17d5b80b8 | ||
|
|
48b4eb5c0d | ||
|
|
7ecd6d20ba | ||
|
|
bddad57a7b | ||
|
|
799136a714 | ||
|
|
350d61b4a6 | ||
|
|
b6f5a66fab | ||
|
|
b0c12e2422 | ||
|
|
623a7dc7e6 | ||
|
|
709c7e5707 | ||
|
|
5f6c5025d5 | ||
|
|
328be161d6 | ||
|
|
ee04f52a16 | ||
|
|
c446c291d9 | ||
|
|
c66d9de759 | ||
|
|
260ee980e0 | ||
|
|
7d98c1c4e0 | ||
|
|
4387cf38d7 | ||
|
|
a9d38570ab | ||
|
|
0e619369fd | ||
|
|
6890dc1844 | ||
|
|
cda09c843a | ||
|
|
e2190bd9d5 | ||
|
|
0472d19bd4 | ||
|
|
07524f5c99 | ||
|
|
1710800cc0 | ||
|
|
c705d6f9b3 | ||
|
|
be718aea11 | ||
|
|
ca680710a2 | ||
|
|
5f71a43758 | ||
|
|
04dd63da1c | ||
|
|
cee022b935 | ||
|
|
ae2ae85070 | ||
|
|
ce6bbbaa33 | ||
|
|
6333bfe6e8 | ||
|
|
41d8863d2f | ||
|
|
523b7f96f8 | ||
|
|
ab1a930705 | ||
|
|
dc74f76a03 | ||
|
|
3a99c86cb3 | ||
|
|
d6ad7e2e64 | ||
|
|
aaf120f263 | ||
|
|
c228e73b26 | ||
|
|
e27e65eb76 | ||
|
|
1c8acf3929 | ||
|
|
40b3c17703 | ||
|
|
e042ef3f27 | ||
|
|
313357a6b3 | ||
|
|
37a1aaad64 | ||
|
|
f084d2a28b | ||
|
|
077b39d7c6 | ||
|
|
7081f3df58 | ||
|
|
9fe6a0a894 | ||
|
|
3d452fd5b9 | ||
|
|
47a5cfbd3e | ||
|
|
4cb6241e93 | ||
|
|
b572879691 | ||
|
|
ad07a6ab2b | ||
|
|
4bdeb33ac1 | ||
|
|
101a4d0d8d | ||
|
|
89e07d0c55 | ||
|
|
39c1cc1b3c | ||
|
|
9f6f637527 | ||
|
|
0f09551a76 | ||
|
|
8cd72cfc1b | ||
|
|
7a141c8616 | ||
|
|
0ca65f955d | ||
|
|
011b748a55 | ||
|
|
f6181ef3e2 | ||
|
|
24368747ab | ||
|
|
66591cf216 | ||
|
|
1feeeb2eec | ||
|
|
419d46c958 | ||
|
|
7063da1c7d | ||
|
|
bee8ebb00b | ||
|
|
da5e4a13bf | ||
|
|
5dc1ec68a3 | ||
|
|
3d2e5ebe39 | ||
|
|
f5130db6b0 | ||
|
|
676b79db42 | ||
|
|
6d2f4a0813 | ||
|
|
4b91204686 | ||
|
|
7ddefcef72 | ||
|
|
0f3e42d463 | ||
|
|
c9129b8ecf | ||
|
|
a6955ecf59 | ||
|
|
6619a787a3 | ||
|
|
aae17c817b | ||
|
|
ab87bad952 | ||
|
|
be306d651e | ||
|
|
8fe5c22075 | ||
|
|
05a9350e57 | ||
|
|
7ed4ae2f8c | ||
|
|
5d6384e101 | ||
|
|
1a4564d998 | ||
|
|
66e489addb | ||
|
|
cdab6b1796 | ||
|
|
722f299306 | ||
|
|
66be04f39e | ||
|
|
8719f2836e | ||
|
|
0c702b0b6b | ||
|
|
6fcab72ec7 | ||
|
|
77b111702b | ||
|
|
96a7cc483f | ||
|
|
1e3506848a | ||
|
|
5ee2cae85c | ||
|
|
5c119fe2d6 | ||
|
|
d55115844a | ||
|
|
4f4491c247 | ||
|
|
1691f586d7 | ||
|
|
04dfe0de84 | ||
|
|
27d1b46835 | ||
|
|
2f62ec3632 | ||
|
|
384488ac02 | ||
|
|
c469e669fd | ||
|
|
56affb90ae | ||
|
|
f6aa147c78 | ||
|
|
9bd0fff319 | ||
|
|
00d7c5972f | ||
|
|
58a438167b | ||
|
|
e3131481e9 | ||
|
|
bc8d68bd31 | ||
|
|
07c6e33598 | ||
|
|
70812c70fc | ||
|
|
d89b234cad | ||
|
|
2070aa9443 | ||
|
|
91ff94ea56 | ||
|
|
0347537f43 | ||
|
|
db9b18f121 | ||
|
|
ee70001be3 | ||
|
|
972eea97fe | ||
|
|
2b4d33e919 | ||
|
|
fc4d670c88 | ||
|
|
02035d4942 | ||
|
|
93a46089ce | ||
|
|
e8d63b2a3b | ||
|
|
d3c7681bc5 | ||
|
|
dc66db4abe | ||
|
|
a0e1cf8376 | ||
|
|
5292b84f4f | ||
|
|
b27455a36f | ||
|
|
5042c5bf40 | ||
|
|
da7b6f0baf | ||
|
|
9b5845f1cb | ||
|
|
e8633d17e8 | ||
|
|
d1d8b01dfb | ||
|
|
7c4353a0ac | ||
|
|
1b2cb53d4f | ||
|
|
3158e51c62 | ||
|
|
a0c72cdf00 | ||
|
|
f0371da838 | ||
|
|
44b82e6231 | ||
|
|
04f0bf3070 | ||
|
|
7400c39511 | ||
|
|
008a5af6d6 | ||
|
|
35ca40c3de | ||
|
|
de821fc305 | ||
|
|
e3cac7d0e5 | ||
|
|
81f7aa9df2 | ||
|
|
6bce298d90 | ||
|
|
afbad56012 | ||
|
|
d973096464 | ||
|
|
7192aa86b5 | ||
|
|
9c8df8b9ce | ||
|
|
ff4c7b82bc | ||
|
|
47ff51e640 | ||
|
|
08503655d9 | ||
|
|
3afd6024b5 | ||
|
|
aa308b7a3a | ||
|
|
9598f646f5 | ||
|
|
8af39bdaf7 | ||
|
|
914f3d1fa3 | ||
|
|
8cb3f0835a | ||
|
|
cba0898e4f | ||
|
|
8d158402f3 | ||
|
|
7f2582e3b6 | ||
|
|
dbc796359f | ||
|
|
4d1285d8e5 | ||
|
|
871d097b30 | ||
|
|
1532033a7f | ||
|
|
9faae7387e | ||
|
|
a5c644e719 | ||
|
|
7a2ce59563 | ||
|
|
14cec7e610 | ||
|
|
6287a3dd53 | ||
|
|
93a1db77c5 | ||
|
|
a9d4b09bdb | ||
|
|
ed2eb7b5a6 | ||
|
|
18d8537d29 | ||
|
|
72f3b1ed39 | ||
|
|
fd70e6edb1 | ||
|
|
5a578c5375 | ||
|
|
9db8773055 | ||
|
|
8a67434380 | ||
|
|
c94e5f3589 | ||
|
|
adef7200f6 | ||
|
|
cf508b6d48 | ||
|
|
f8d36fda28 | ||
|
|
4fe9cc7730 | ||
|
|
758b7f875b | ||
|
|
0b97a67cfa | ||
|
|
ec5976bbc9 | ||
|
|
5cc49e2931 | ||
|
|
b6752a2c02 | ||
|
|
d41e28fc36 | ||
|
|
64c52a6921 | ||
|
|
691a678b19 | ||
|
|
1ba7fd91ff | ||
|
|
1c98a9ad3e | ||
|
|
dd23ceeead | ||
|
|
058fa1367b | ||
|
|
9db12374ea | ||
|
|
fc550ac1fc | ||
|
|
d6ef8ec3d1 | ||
|
|
837db9a2d9 | ||
|
|
a941739f8a | ||
|
|
795a346006 | ||
|
|
9d00da7285 | ||
|
|
52c1909f24 | ||
|
|
2cbf9cae71 | ||
|
|
f9225c54ff | ||
|
|
cb05f36976 | ||
|
|
49e0e20ce2 | ||
|
|
7c35337999 | ||
|
|
2296aab5a8 | ||
|
|
ce3b255f1a | ||
|
|
3942f3366d | ||
|
|
df76cc33a5 | ||
|
|
cf387d5a6d | ||
|
|
0a0cf87625 | ||
|
|
1a2544610d | ||
|
|
5229b7cfba | ||
|
|
243b45881d | ||
|
|
883028d981 | ||
|
|
bdeb7bfb9f | ||
|
|
808ffb0491 | ||
|
|
5305a16350 | ||
|
|
63b581935d | ||
|
|
c7c9349b00 | ||
|
|
d54417acfe | ||
|
|
9fba37b409 | ||
|
|
6d28c52f59 | ||
|
|
03eb5139a2 | ||
|
|
5c4931e235 | ||
|
|
7fd1eb3780 | ||
|
|
2b863d9bc2 | ||
|
|
9ce4f94818 |
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
# vendor/github.com/xenolf/lego/providers/dns/cloudxns/cloudxns.go eol=crlf
|
||||
8
.github/ISSUE_TEMPLATE.md
vendored
@@ -22,7 +22,7 @@ If you intend to ask a support question: DO NOT FILE AN ISSUE.
|
||||
|
||||
HOW TO WRITE A GOOD ISSUE?
|
||||
|
||||
- Respect the issue template as more as possible.
|
||||
- Respect the issue template as much as possible.
|
||||
- If it's possible use the command `traefik bug`. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||
- The title must be short and descriptive.
|
||||
- Explain the conditions which led you to write this issue: the context.
|
||||
@@ -46,6 +46,10 @@ HOW TO WRITE A GOOD ISSUE?
|
||||
For the Traefik Docker image:
|
||||
docker run [IMAGE] version
|
||||
ex: docker run traefik version
|
||||
|
||||
For the alpine Traefik Docker image:
|
||||
docker run [IMAGE] traefik version
|
||||
ex: docker run traefik traefik version
|
||||
-->
|
||||
|
||||
```
|
||||
@@ -62,7 +66,7 @@ Add more configuration information here.
|
||||
-->
|
||||
|
||||
|
||||
### If applicable, please paste the log output in debug mode (`--debug` switch)
|
||||
### If applicable, please paste the log output at DEBUG level (`--logLevel=DEBUG` switch)
|
||||
|
||||
```
|
||||
(paste your output here)
|
||||
|
||||
72
.github/ISSUE_TEMPLATE/bugs.md
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
<!--
|
||||
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
|
||||
The issue tracker is for reporting bugs and feature requests only.
|
||||
For end-user related support questions, refer to one of the following:
|
||||
|
||||
- Stack Overflow (using the "traefik" tag): https://stackoverflow.com/questions/tagged/traefik
|
||||
- the Traefik community Slack channel: https://traefik.herokuapp.com
|
||||
|
||||
-->
|
||||
|
||||
|
||||
### Do you want to request a *feature* or report a *bug*?
|
||||
|
||||
Bug
|
||||
|
||||
### What did you do?
|
||||
|
||||
<!--
|
||||
|
||||
HOW TO WRITE A GOOD ISSUE?
|
||||
|
||||
- Respect the issue template as much as possible.
|
||||
- If it's possible use the command `traefik bug`. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||
- The title must be short and descriptive.
|
||||
- Explain the conditions which led you to write this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||
|
||||
-->
|
||||
|
||||
### What did you expect to see?
|
||||
|
||||
|
||||
|
||||
### What did you see instead?
|
||||
|
||||
|
||||
|
||||
### Output of `traefik version`: (_What version of Traefik are you using?_)
|
||||
|
||||
<!--
|
||||
For the Traefik Docker image:
|
||||
docker run [IMAGE] version
|
||||
ex: docker run traefik version
|
||||
|
||||
For the alpine Traefik Docker image:
|
||||
docker run [IMAGE] traefik version
|
||||
ex: docker run traefik traefik version
|
||||
-->
|
||||
|
||||
```
|
||||
(paste your output here)
|
||||
```
|
||||
|
||||
### What is your environment & configuration (arguments, toml, provider, platform, ...)?
|
||||
|
||||
```toml
|
||||
# (paste your configuration here)
|
||||
```
|
||||
|
||||
<!--
|
||||
Add more configuration information here.
|
||||
-->
|
||||
|
||||
|
||||
### If applicable, please paste the log output in DEBUG level (`--logLevel=DEBUG` switch)
|
||||
|
||||
```
|
||||
(paste your output here)
|
||||
```
|
||||
32
.github/ISSUE_TEMPLATE/features.md
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
<!--
|
||||
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
|
||||
The issue tracker is for reporting bugs and feature requests only.
|
||||
For end-user related support questions, refer to one of the following:
|
||||
|
||||
- Stack Overflow (using the "traefik" tag): https://stackoverflow.com/questions/tagged/traefik
|
||||
- the Traefik community Slack channel: https://traefik.herokuapp.com
|
||||
|
||||
-->
|
||||
|
||||
|
||||
### Do you want to request a *feature* or report a *bug*?
|
||||
|
||||
Feature
|
||||
|
||||
### What did you expect to see?
|
||||
|
||||
<!--
|
||||
|
||||
HOW TO WRITE A GOOD ISSUE?
|
||||
|
||||
- Respect the issue template as much as possible.
|
||||
- If it's possible use the command `traefik bug`. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||
- The title must be short and descriptive.
|
||||
- Explain the conditions which led you to write this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||
|
||||
-->
|
||||
|
||||
21
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -16,8 +16,21 @@ HOW TO WRITE A GOOD PULL REQUEST?
|
||||
|
||||
-->
|
||||
|
||||
### Description
|
||||
### What does this PR do?
|
||||
|
||||
<!--
|
||||
Briefly describe the pull request in a few paragraphs.
|
||||
-->
|
||||
<!-- A brief description of the change being made with this pull request. -->
|
||||
|
||||
|
||||
### Motivation
|
||||
|
||||
<!-- What inspired you to submit this pull request? -->
|
||||
|
||||
|
||||
### More
|
||||
|
||||
- [ ] Added/updated tests
|
||||
- [ ] Added/updated documentation
|
||||
|
||||
### Additional Notes
|
||||
|
||||
<!-- Anything else we should know when reviewing? -->
|
||||
|
||||
7
.github/PULL_REQUEST_TEMPLATE/mergeback.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
### What does this PR do?
|
||||
|
||||
Merge v{{.Version}} into master
|
||||
|
||||
### Motivation
|
||||
|
||||
Be sync.
|
||||
7
.github/PULL_REQUEST_TEMPLATE/release.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
### What does this PR do?
|
||||
|
||||
Prepare release v{{.Version}}.
|
||||
|
||||
### Motivation
|
||||
|
||||
Create a new release.
|
||||
9
.gitignore
vendored
@@ -1,14 +1,15 @@
|
||||
/dist
|
||||
/autogen/gen.go
|
||||
.idea
|
||||
.intellij
|
||||
/autogen/genstatic/gen.go
|
||||
.idea/
|
||||
.intellij/
|
||||
*.iml
|
||||
/traefik
|
||||
/traefik.toml
|
||||
/static/
|
||||
/webui/.tmp/
|
||||
.vscode/
|
||||
/site/
|
||||
*.log
|
||||
*.exe
|
||||
.DS_Store
|
||||
/example/acme/acme.json
|
||||
/examples/acme/acme.json
|
||||
|
||||
42
.gometalinter.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"Vendor": true,
|
||||
"Sort": [
|
||||
"path",
|
||||
"line",
|
||||
"column",
|
||||
"severity",
|
||||
"linter"
|
||||
],
|
||||
"Test": true,
|
||||
"Cyclo": 15,
|
||||
"Enable": [
|
||||
"gotypex",
|
||||
"nakedret",
|
||||
"vet",
|
||||
"goimports",
|
||||
"golint",
|
||||
"ineffassign",
|
||||
"gotype",
|
||||
"misspell",
|
||||
"structcheck",
|
||||
"gosimple",
|
||||
"unconvert",
|
||||
"varcheck",
|
||||
"errcheck",
|
||||
"unused",
|
||||
"deadcode",
|
||||
"staticcheck"
|
||||
],
|
||||
"Disable": [
|
||||
"gas",
|
||||
"maligned",
|
||||
"interfacer",
|
||||
"goconst",
|
||||
"gocyclo",
|
||||
"vetshadow"
|
||||
],
|
||||
"Exclude": [
|
||||
"autogen/.*"
|
||||
],
|
||||
"Deadline": "5m"
|
||||
}
|
||||
@@ -10,7 +10,7 @@ else
|
||||
export VERSION=''
|
||||
fi
|
||||
|
||||
export CODENAME=roquefort
|
||||
export CODENAME=tetedemoine
|
||||
|
||||
export N_MAKE_JOBS=2
|
||||
|
||||
|
||||
34
.travis.yml
@@ -1,6 +1,9 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
git:
|
||||
depth: false
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
@@ -8,7 +11,7 @@ env:
|
||||
global:
|
||||
- REPO: $TRAVIS_REPO_SLUG
|
||||
- VERSION: $TRAVIS_TAG
|
||||
- CODENAME: roquefort
|
||||
- CODENAME: tetedemoine
|
||||
- N_MAKE_JOBS: 2
|
||||
|
||||
script:
|
||||
@@ -21,21 +24,16 @@ before_deploy:
|
||||
sudo -E apt-get -yq update;
|
||||
sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install docker-ce=${DOCKER_VERSION}*;
|
||||
docker version;
|
||||
pip install --user -r requirements.txt;
|
||||
make -j${N_MAKE_JOBS} crossbinary-parallel;
|
||||
make image-dirty;
|
||||
mkdocs build --clean;
|
||||
tar cfz dist/traefik-${VERSION}.src.tar.gz --exclude-vcs --exclude dist .;
|
||||
make image;
|
||||
if [ "$TRAVIS_TAG" ]; then
|
||||
make -j${N_MAKE_JOBS} crossbinary-parallel;
|
||||
tar cfz dist/traefik-${VERSION}.src.tar.gz --exclude-vcs --exclude dist .;
|
||||
fi;
|
||||
curl -sI https://github.com/containous/structor/releases/latest | grep -Fi Location | tr -d '\r' | sed "s/tag/download/g" | awk -F " " '{ print $2 "/structor_linux-amd64"}' | wget --output-document=$GOPATH/bin/structor -i -;
|
||||
chmod +x $GOPATH/bin/structor;
|
||||
structor -o containous -r traefik --dockerfile-url="https://raw.githubusercontent.com/containous/traefik/master/docs.Dockerfile" --menu.js-url="https://raw.githubusercontent.com/containous/structor/master/traefik-menu.js.gotmpl" --rqts-url="https://raw.githubusercontent.com/containous/structor/master/requirements-override.txt" --exp-branch=master --debug;
|
||||
fi
|
||||
deploy:
|
||||
- provider: pages
|
||||
edge: true
|
||||
github_token: ${GITHUB_TOKEN}
|
||||
local_dir: site
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: containous/traefik
|
||||
tags: true
|
||||
- provider: releases
|
||||
api_key: ${GITHUB_TOKEN}
|
||||
file: dist/traefik*
|
||||
@@ -55,3 +53,11 @@ deploy:
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: containous/traefik
|
||||
- provider: pages
|
||||
edge: false
|
||||
github_token: ${GITHUB_TOKEN}
|
||||
local_dir: site
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: containous/traefik
|
||||
all_branches: true
|
||||
|
||||
1280
CHANGELOG.md
@@ -2,7 +2,8 @@
|
||||
|
||||
## Building
|
||||
|
||||
You need either [Docker](https://github.com/docker/docker) and `make` (Method 1), or `go` (Method 2) in order to build traefik. For changes to its dependencies, the `glide` dependency management tool and `glide-vc` plugin are required.
|
||||
You need either [Docker](https://github.com/docker/docker) and `make` (Method 1), or `go` (Method 2) in order to build Traefik.
|
||||
For changes to its dependencies, the `dep` dependency management tool is required.
|
||||
|
||||
### Method 1: Using `Docker` and `Makefile`
|
||||
|
||||
@@ -12,11 +13,11 @@ You need to run the `binary` target. This will create binaries for Linux platfor
|
||||
$ make binary
|
||||
docker build -t "traefik-dev:no-more-godep-ever" -f build.Dockerfile .
|
||||
Sending build context to Docker daemon 295.3 MB
|
||||
Step 0 : FROM golang:1.9-alpine
|
||||
Step 0 : FROM golang:1.10-alpine
|
||||
---> 8c6473912976
|
||||
Step 1 : RUN go get github.com/Masterminds/glide
|
||||
Step 1 : RUN go get github.com/golang/dep/cmd/dep
|
||||
[...]
|
||||
docker run --rm -v "/var/run/docker.sock:/var/run/docker.sock" -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/emile/dev/go/src/github.com/containous/traefik/"dist":/go/src/github.com/containous/traefik/"dist"" "traefik-dev:no-more-godep-ever" ./script/make.sh generate binary
|
||||
docker run --rm -v "/var/run/docker.sock:/var/run/docker.sock" -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/user/go/src/github.com/containous/traefik/"dist":/go/src/github.com/containous/traefik/"dist"" "traefik-dev:no-more-godep-ever" ./script/make.sh generate binary
|
||||
---> Making bundle: generate (in .)
|
||||
removed 'gen.go'
|
||||
|
||||
@@ -63,9 +64,12 @@ Once your environment is set up and the Træfik repository cloned you can build
|
||||
cd ~/go/src/github.com/containous/traefik
|
||||
|
||||
# Get go-bindata. Please note, the ellipses are required
|
||||
go get github.com/jteeuwen/go-bindata/...
|
||||
go get github.com/containous/go-bindata/...
|
||||
|
||||
# Start build
|
||||
|
||||
# generate
|
||||
# (required to merge non-code components into the final binary, such as the web dashboard and provider's Go templates)
|
||||
go generate
|
||||
|
||||
# Standard go build
|
||||
@@ -75,21 +79,26 @@ go build ./cmd/traefik
|
||||
|
||||
You will find the Træfik executable in the `~/go/src/github.com/containous/traefik` folder as `traefik`.
|
||||
|
||||
### Setting up `glide` and `glide-vc` for dependency management
|
||||
### Updating the templates
|
||||
|
||||
- Glide is not required for building; however, it is necessary to modify dependencies (i.e., add, update, or remove third-party packages)
|
||||
- Glide can be installed either via homebrew: `$ brew install glide` or via the official glide script: `$ curl https://glide.sh/get | sh`
|
||||
- The glide plugin `glide-vc` must be installed from source: `go get github.com/sgotti/glide-vc`
|
||||
If you happen to update the provider templates (in `/templates`), you need to run `go generate` to update the `autogen` package.
|
||||
|
||||
If you want to add a dependency, use `$ glide get` to have glide put it into the vendor folder and update the glide manifest/lock files (`glide.yaml` and `glide.lock`, respectively). A following `glide-vc` run should be triggered to trim down the size of the vendor folder. The final result must be committed into VCS.
|
||||
### Setting up dependency management
|
||||
|
||||
Care must be taken to choose the right arguments to `glide` when dealing with dependencies, or otherwise risk ending up with a broken build. For that reason, the helper script `script/glide.sh` encapsulates the gory details and conveniently calls `glide-vc` as well. Call it without parameters for basic usage instructions.
|
||||
[dep](https://github.com/golang/dep) is not required for building; however, it is necessary to modify dependencies (i.e., add, update, or remove third-party packages)
|
||||
|
||||
Here's a full example using glide to add a new dependency:
|
||||
You need to use [dep](https://github.com/golang/dep) >= O.4.1.
|
||||
|
||||
If you want to add a dependency, use `dep ensure -add` to have [dep](https://github.com/golang/dep) put it into the vendor folder and update the dep manifest/lock files (`Gopkg.toml` and `Gopkg.lock`, respectively).
|
||||
|
||||
A following `make dep-prune` run should be triggered to trim down the size of the vendor folder.
|
||||
The final result must be committed into VCS.
|
||||
|
||||
Here's a full example using dep to add a new dependency:
|
||||
|
||||
```bash
|
||||
# install the new main dependency github.com/foo/bar and minimize vendor size
|
||||
$ ./script/glide.sh get github.com/foo/bar
|
||||
$ dep ensure -add github.com/foo/bar
|
||||
# generate (Only required to integrate other components such as web dashboard)
|
||||
$ go generate
|
||||
# Standard go build
|
||||
@@ -108,7 +117,7 @@ integration test using the `test-integration` target.
|
||||
$ make test-unit
|
||||
docker build -t "traefik-dev:your-feature-branch" -f build.Dockerfile .
|
||||
# […]
|
||||
docker run --rm -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/vincent/src/github/vdemeester/traefik/dist:/go/src/github.com/containous/traefik/dist" "traefik-dev:your-feature-branch" ./script/make.sh generate test-unit
|
||||
docker run --rm -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/user/go/src/github/containous/traefik/dist:/go/src/github.com/containous/traefik/dist" "traefik-dev:your-feature-branch" ./script/make.sh generate test-unit
|
||||
---> Making bundle: generate (in .)
|
||||
removed 'gen.go'
|
||||
|
||||
@@ -120,6 +129,7 @@ Test success
|
||||
```
|
||||
|
||||
For development purposes, you can specify which tests to run by using:
|
||||
|
||||
```bash
|
||||
# Run every tests in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite" make test-integration
|
||||
@@ -138,15 +148,37 @@ More: https://labix.org/gocheck
|
||||
|
||||
#### Method 2: `go`
|
||||
|
||||
- Tests can be run from the cloned directory, by `$ go test ./...` which should return `ok` similar to:
|
||||
Unit 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
|
||||
ok _/home/user/go/src/github/containous/traefik 0.004s
|
||||
```
|
||||
|
||||
Integration tests must be run from the `integration/` directory and require the `-integration` switch to be passed like this: `$ cd integration && go test -integration ./...`.
|
||||
|
||||
## Documentation
|
||||
|
||||
The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/)
|
||||
|
||||
### Method 1: `Docker` and `make`
|
||||
|
||||
You can test documentation using the `docs` target.
|
||||
|
||||
```bash
|
||||
$ make docs
|
||||
docker build -t traefik-docs -f docs.Dockerfile .
|
||||
# […]
|
||||
docker run --rm -v /home/user/go/github/containous/traefik:/mkdocs -p 8000:8000 traefik-docs mkdocs serve
|
||||
# […]
|
||||
[I 170828 20:47:48 server:283] Serving on http://0.0.0.0:8000
|
||||
[I 170828 20:47:48 handlers:60] Start watching changes
|
||||
[I 170828 20:47:48 handlers:62] Start detecting changes
|
||||
```
|
||||
|
||||
And go to [http://127.0.0.1:8000](http://127.0.0.1:8000).
|
||||
|
||||
### Method 2: `mkdocs`
|
||||
|
||||
First make sure you have python and pip installed
|
||||
|
||||
```shell
|
||||
@@ -159,7 +191,7 @@ pip 1.5.2
|
||||
Then install mkdocs with pip
|
||||
|
||||
```shell
|
||||
$ pip install mkdocs
|
||||
pip install --user -r requirements.txt
|
||||
```
|
||||
|
||||
To test documentation locally run `mkdocs serve` in the root directory, this should start a server locally to preview your changes.
|
||||
|
||||
1684
Gopkg.lock
generated
Normal file
258
Gopkg.toml
Normal file
@@ -0,0 +1,258 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/ArthurHlt/go-eureka-client"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/BurntSushi/toml"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/BurntSushi/ty"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/NYTimes/gziphandler"
|
||||
|
||||
[[constraint]]
|
||||
branch = "containous-fork"
|
||||
name = "github.com/abbot/go-http-auth"
|
||||
source = "github.com/containous/go-http-auth"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/armon/go-proxyproto"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/aws/aws-sdk-go"
|
||||
version = "1.13.1"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/cenk/backoff"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/containous/flaeg"
|
||||
version = "1.0.1"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/containous/mux"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/containous/staert"
|
||||
version = "3.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/containous/traefik-extra-service-fabric"
|
||||
version = "1.1.5"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/coreos/go-systemd"
|
||||
version = "14.0.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/docker/leadership"
|
||||
source = "github.com/containous/leadership"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/eapache/channels"
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/elazarl/go-bindata-assetfs"
|
||||
|
||||
[[constraint]]
|
||||
branch = "fork-containous"
|
||||
name = "github.com/go-check/check"
|
||||
source = "github.com/containous/check"
|
||||
|
||||
[[override]]
|
||||
branch = "fork-containous"
|
||||
name = "github.com/go-check/check"
|
||||
source = "github.com/containous/check"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-kit/kit"
|
||||
version = "0.7.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/gorilla/websocket"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/hashicorp/consul"
|
||||
version = "1.0.6"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/influxdata/influxdb"
|
||||
version = "1.3.7"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/jjcollinge/servicefabric"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/abronan/valkeyrie"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mesosphere/mesos-dns"
|
||||
source = "https://github.com/containous/mesos-dns.git"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mitchellh/copystructure"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mitchellh/hashstructure"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mitchellh/mapstructure"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/opentracing/opentracing-go"
|
||||
version = "1.0.2"
|
||||
|
||||
[[constraint]]
|
||||
branch = "containous-fork"
|
||||
name = "github.com/rancher/go-rancher-metadata"
|
||||
source = "github.com/containous/go-rancher-metadata"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/ryanuber/go-glob"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/satori/go.uuid"
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/stvp/go-udp-testing"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "1.2.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/uber/jaeger-client-go"
|
||||
version = "2.9.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/uber/jaeger-lib"
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "v1"
|
||||
name = "github.com/unrolled/secure"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/vdemeester/shakers"
|
||||
version = "0.1.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/vulcand/oxy"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/xenolf/lego"
|
||||
# version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "google.golang.org/grpc"
|
||||
version = "1.5.2"
|
||||
|
||||
[[constraint]]
|
||||
name = "gopkg.in/fsnotify.v1"
|
||||
source = "github.com/fsnotify/fsnotify"
|
||||
version = "1.4.2"
|
||||
|
||||
[[constraint]]
|
||||
name = "k8s.io/client-go"
|
||||
version = "6.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "k8s.io/api"
|
||||
version = "kubernetes-1.9.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "k8s.io/apimachinery"
|
||||
version = "kubernetes-1.9.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/libkermit/docker"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/libkermit/docker-check"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/libkermit/compose"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/docker/docker"
|
||||
revision = "7848b8beb9d38a98a78b75f78e05f8d2255f9dfe"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/docker/docker"
|
||||
revision = "7848b8beb9d38a98a78b75f78e05f8d2255f9dfe"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/docker/cli"
|
||||
revision = "6b63d7b96a41055baddc3fa71f381c7f60bd5d8e"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/docker/distribution"
|
||||
revision = "edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c"
|
||||
|
||||
[[override]]
|
||||
branch = "master"
|
||||
name = "github.com/docker/libcompose"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/Nvveen/Gotty"
|
||||
revision = "a8b993ba6abdb0e0c12b0125c603323a71c7790c"
|
||||
source = "github.com/ijc25/Gotty"
|
||||
|
||||
[[override]]
|
||||
# ALWAYS keep this override
|
||||
name = "github.com/mailgun/timetools"
|
||||
revision = "7e6055773c5137efbeb3bd2410d705fe10ab6bfd"
|
||||
|
||||
[[override]]
|
||||
branch = "master"
|
||||
name = "github.com/miekg/dns"
|
||||
|
||||
[prune]
|
||||
non-go = true
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2017 Containous SAS
|
||||
Copyright (c) 2016-2018 Containous SAS
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
* Julien Salleyron [@juliens](https://github.com/juliens)
|
||||
* Nicolas Mengin [@nmengin](https://github.com/nmengin)
|
||||
* Marco Jantke [@marco-jantke](https://github.com/marco-jantke)
|
||||
* Michaël Matur [@mmatur](https://github.com/mmatur)
|
||||
|
||||
|
||||
## PR review process:
|
||||
@@ -40,6 +41,14 @@ The status `status/4-merge-in-progress` is only for the bot.
|
||||
If the bot is not able to perform the merge, the label `bot/need-human-merge` is added.
|
||||
In this case you must solve conflicts/CI/... and after you only need to remove `bot/need-human-merge`.
|
||||
|
||||
A maintainer can add `bot/no-merge` on a PR if he want (temporarily) prevent a merge by the bot.
|
||||
|
||||
`bot/light-review` can be used to decrease required LGTM from 3 to 1 when:
|
||||
|
||||
- vendor updates from previously reviewed PRs
|
||||
- merges branches into master
|
||||
- prepare release
|
||||
|
||||
|
||||
### [Myrmica Bibikoffi](https://github.com/containous/bibikoffi/)
|
||||
|
||||
@@ -52,19 +61,20 @@ In this case you must solve conflicts/CI/... and after you only need to remove `
|
||||
**Manage GitHub labels**
|
||||
|
||||
* Add labels on new PR [GitHub WebHook]
|
||||
* Add milestone to a new PR based on a branch version (1.4, 1.3, ...) [GitHub WebHook]
|
||||
* Add and remove `contributor/waiting-for-corrections` label when a review request changes [GitHub WebHook]
|
||||
* Weekly report of PR status on Slack (CaptainPR) [cron]
|
||||
|
||||
|
||||
## Labels
|
||||
|
||||
If we open/look an issue/PR, we must add a `kind/*` and an `area/*`.
|
||||
If we open/look an issue/PR, we must add a `kind/*`, an `area/*` and a `status/*`.
|
||||
|
||||
### Contributor
|
||||
|
||||
* `contributor/need-more-information`: we need more information from the contributor in order to analyze a problem.
|
||||
* `contributor/waiting-for-feedback`: we need the contributor to give us feedback.
|
||||
* `contributor/waiting-for-corrections`: we need the contributor to take actions in order to move forward with a PR. **(only for PR)**
|
||||
* `contributor/waiting-for-corrections`: we need the contributor to take actions in order to move forward with a PR. **(only for PR)** _[bot, humans]_
|
||||
* `contributor/needs-resolve-conflicts`: use it only when there is some conflicts (and an automatic rebase is not possible). **(only for PR)** _[bot, humans]_
|
||||
|
||||
### Kind
|
||||
@@ -75,7 +85,7 @@ If we open/look an issue/PR, we must add a `kind/*` and an `area/*`.
|
||||
* _Proposal issues_ are design proposal that need to be refined with multiple contributors.
|
||||
* _Proposal PRs_ are technical prototypes that need to be refined with multiple contributors.
|
||||
|
||||
* `kind/bug/possible`: if we need to analyze to understand if it's a bug or not. **(only for issues)** _[bot only]_
|
||||
* `kind/bug/possible`: if we need to analyze to understand if it's a bug or not. **(only for issues)**
|
||||
* `kind/bug/confirmed`: we are sure, it's a bug. **(only for issues)**
|
||||
* `kind/bug/fix`: it's a bug fix. **(only for PR)**
|
||||
|
||||
|
||||
30
Makefile
@@ -20,12 +20,16 @@ GIT_BRANCH := $(subst heads/,,$(shell git rev-parse --abbrev-ref HEAD 2>/dev/nul
|
||||
TRAEFIK_DEV_IMAGE := traefik-dev$(if $(GIT_BRANCH),:$(subst /,-,$(GIT_BRANCH)))
|
||||
REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
|
||||
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik")
|
||||
INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -v "/var/run/docker.sock:/var/run/docker.sock")
|
||||
INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -e "TEST_CONTAINER=1" -v "/var/run/docker.sock:/var/run/docker.sock")
|
||||
TRAEFIK_DOC_IMAGE := traefik-docs
|
||||
|
||||
DOCKER_BUILD_ARGS := $(if $(DOCKER_VERSION), "--build-arg=DOCKER_VERSION=$(DOCKER_VERSION)",)
|
||||
DOCKER_RUN_OPTS := $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"
|
||||
DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(DOCKER_RUN_OPTS)
|
||||
DOCKER_RUN_TRAEFIK_NOTTY := docker run $(INTEGRATION_OPTS) -i $(DOCKER_RUN_OPTS)
|
||||
DOCKER_RUN_DOC_PORT := 8000
|
||||
DOCKER_RUN_DOC_MOUNT := -v $(CURDIR):/mkdocs
|
||||
DOCKER_RUN_DOC_OPTS := --rm $(DOCKER_RUN_DOC_MOUNT) -p $(DOCKER_RUN_DOC_PORT):8000
|
||||
|
||||
|
||||
print-%: ; @echo $*=$($*)
|
||||
@@ -67,9 +71,10 @@ test-unit: build ## run the unit tests
|
||||
|
||||
test-integration: build ## run the integration tests
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate binary test-integration
|
||||
TEST_HOST=1 ./script/make.sh test-integration
|
||||
|
||||
validate: build ## validate gofmt, golint and go vet
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-glide validate-gofmt validate-govet validate-golint validate-misspell validate-vendor
|
||||
validate: build ## validate code, vendor and autogen
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint validate-misspell validate-vendor validate-autogen
|
||||
|
||||
build: dist
|
||||
docker build $(DOCKER_BUILD_ARGS) -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
|
||||
@@ -89,6 +94,12 @@ image-dirty: binary ## build a docker traefik image
|
||||
image: clear-static binary ## clean up static directory and build a docker traefik image
|
||||
docker build -t $(TRAEFIK_IMAGE) .
|
||||
|
||||
docs: docs-image
|
||||
docker run $(DOCKER_RUN_DOC_OPTS) $(TRAEFIK_DOC_IMAGE) mkdocs serve
|
||||
|
||||
docs-image:
|
||||
docker build -t $(TRAEFIK_DOC_IMAGE) -f docs.Dockerfile .
|
||||
|
||||
clear-static:
|
||||
rm -rf static
|
||||
|
||||
@@ -97,7 +108,7 @@ dist:
|
||||
|
||||
run-dev:
|
||||
go generate
|
||||
go build
|
||||
go build ./cmd/traefik
|
||||
./traefik
|
||||
|
||||
generate-webui: build-webui
|
||||
@@ -114,9 +125,14 @@ fmt:
|
||||
gofmt -s -l -w $(SRCS)
|
||||
|
||||
pull-images:
|
||||
for f in $(shell find ./integration/resources/compose/ -type f); do \
|
||||
docker-compose -f $$f pull; \
|
||||
done
|
||||
grep --no-filename -E '^\s+image:' ./integration/resources/compose/*.yml | awk '{print $$2}' | sort | uniq | xargs -P 6 -n 1 docker pull
|
||||
|
||||
dep-ensure:
|
||||
dep ensure -v
|
||||
./script/prune-dep.sh
|
||||
|
||||
dep-prune:
|
||||
./script/prune-dep.sh
|
||||
|
||||
help: ## this help
|
||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
|
||||
241
README.md
@@ -12,84 +12,126 @@
|
||||
[](https://twitter.com/intent/follow?screen_name=traefikproxy)
|
||||
|
||||
|
||||
Træfik (pronounced like [traffic](https://speak-ipa.bearbin.net/speak.cgi?speak=%CB%88tr%C3%A6f%C9%AAk)) 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 mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), and a lot more) to manage its configuration automatically and dynamically.
|
||||
Træfik is a modern HTTP reverse proxy and load balancer that makes deploying microservices easy.
|
||||
Træfik integrates with your existing infrastructure components ([Docker](https://www.docker.com/), [Swarm mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), ...) and configures itself automatically and dynamically.
|
||||
Pointing Træfik at your orchestrator should be the _only_ configuration step you need.
|
||||
|
||||
---
|
||||
|
||||
| **[Overview](#overview)** |
|
||||
**[Features](#features)** |
|
||||
**[Supported backends](#supported-backends)** |
|
||||
**[Quickstart](#quickstart)** |
|
||||
**[Web UI](#web-ui)** |
|
||||
**[Test it](#test-it)** |
|
||||
**[Documentation](#documentation)** |
|
||||
**[Support](#support)** |
|
||||
**[Release cycle](#release-cycle)** |
|
||||
. **[Overview](#overview)** .
|
||||
**[Features](#features)** .
|
||||
**[Supported backends](#supported-backends)** .
|
||||
**[Quickstart](#quickstart)** .
|
||||
**[Web UI](#web-ui)** .
|
||||
**[Test it](#test-it)** .
|
||||
**[Documentation](#documentation)** .
|
||||
|
||||
| **[Contributing](#contributing)** |
|
||||
**[Maintainers](#maintainers)** |
|
||||
**[Plumbing](#plumbing)** |
|
||||
**[Credits](#credits)** |
|
||||
. **[Support](#support)** .
|
||||
**[Release cycle](#release-cycle)** .
|
||||
**[Contributing](#contributing)** .
|
||||
**[Maintainers](#maintainers)** .
|
||||
**[Plumbing](#plumbing)** .
|
||||
**[Credits](#credits)** .
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Imagine that you have deployed a bunch of microservices on your infrastructure. You probably used a service registry (like etcd or consul) and/or an orchestrator (swarm, Mesos/Marathon) to manage all these services.
|
||||
If you want your users to access some of your microservices from the Internet, you will have to use a reverse proxy and configure it using virtual hosts or prefix paths:
|
||||
Imagine that you have deployed a bunch of microservices with the help of an orchestrator (like Swarm or Kubernetes) or a service registry (like etcd or consul).
|
||||
Now you want users to access these microservices, and you need a reverse proxy.
|
||||
|
||||
- domain `api.domain.com` will point the microservice `api` in your private network
|
||||
- path `domain.com/web` will point the microservice `web` in your private network
|
||||
- domain `backoffice.domain.com` will point the microservices `backoffice` in your private network, load-balancing between your multiple instances
|
||||
Traditional reverse-proxies require that you configure _each_ route that will connect paths and subdomains to _each_ microservice.
|
||||
In an environment where you add, remove, kill, upgrade, or scale your services _many_ times a day, the task of keeping the routes up to date becomes tedious.
|
||||
|
||||
But a microservices architecture is dynamic... Services are added, removed, killed or upgraded often, eventually several times a day.
|
||||
**This is when Træfik can help you!**
|
||||
|
||||
Traditional reverse-proxies are not natively dynamic. You can't change their configuration and hot-reload easily.
|
||||
Træfik listens to your service registry/orchestrator API and instantly generates the routes so your microservices are connected to the outside world -- without further intervention from your part.
|
||||
|
||||
Here enters Træfik.
|
||||
**Run Træfik and let it do the work for you!**
|
||||
_(But if you'd rather configure some of your routes manually, Træfik supports that too!)_
|
||||
|
||||

|
||||
|
||||
Træfik can listen to your service registry/orchestrator API, and knows each time a microservice is added, removed, killed or upgraded, and can generate its configuration automatically.
|
||||
Routes to your services will be created instantly.
|
||||
|
||||
Run it and forget it!
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- [It's fast](https://docs.traefik.io/benchmarks)
|
||||
- No dependency hell, single binary made with go
|
||||
- [Tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) official docker image
|
||||
- Rest API
|
||||
- Hot-reloading of configuration. No need to restart the process
|
||||
- Continuously updates its configuration (No restarts!)
|
||||
- Supports multiple load balancing algorithms
|
||||
- Provides HTTPS to your microservices by leveraging [Let's Encrypt](https://letsencrypt.org) (wildcard certificates support)
|
||||
- Circuit breakers, retry
|
||||
- Round Robin, rebalancer load-balancers
|
||||
- Metrics (Rest, Prometheus, Datadog, Statd)
|
||||
- Clean AngularJS Web UI
|
||||
- Websocket, HTTP/2, GRPC ready
|
||||
- Access Logs (JSON, CLF)
|
||||
- [Let's Encrypt](https://letsencrypt.org) support (Automatic HTTPS with renewal)
|
||||
- [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) support
|
||||
- High Availability with cluster mode (beta)
|
||||
- See the magic through its clean web UI
|
||||
- Websocket, HTTP/2, GRPC ready
|
||||
- Provides metrics (Rest, Prometheus, Datadog, Statsd, InfluxDB)
|
||||
- Keeps access logs (JSON, CLF)
|
||||
- [Fast](https://docs.traefik.io/benchmarks) ... which is nice
|
||||
- Exposes a Rest API
|
||||
- Packaged as a single binary file (made with :heart: with go) and available as a [tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image
|
||||
|
||||
## Supported backends
|
||||
|
||||
- [Docker](https://www.docker.com/) / [Swarm mode](https://docs.docker.com/engine/swarm/)
|
||||
- [Kubernetes](https://kubernetes.io)
|
||||
- [Mesos](https://github.com/apache/mesos) / [Marathon](https://mesosphere.github.io/marathon/)
|
||||
- [Rancher](https://rancher.com) (API, Metadata)
|
||||
- [Consul](https://www.consul.io/) / [Etcd](https://coreos.com/etcd/) / [Zookeeper](https://zookeeper.apache.org) / [BoltDB](https://github.com/boltdb/bolt)
|
||||
- [Eureka](https://github.com/Netflix/eureka)
|
||||
- [Amazon ECS](https://aws.amazon.com/ecs)
|
||||
- [Amazon DynamoDB](https://aws.amazon.com/dynamodb)
|
||||
- File
|
||||
- Rest API
|
||||
## Supported Backends
|
||||
|
||||
- [Docker](https://docs.traefik.io/configuration/backends/docker) / [Swarm mode](https://docs.traefik.io/configuration/backends/docker#docker-swarm-mode)
|
||||
- [Kubernetes](https://docs.traefik.io/configuration/backends/kubernetes)
|
||||
- [Mesos](https://docs.traefik.io/configuration/backends/mesos) / [Marathon](https://docs.traefik.io/configuration/backends/marathon)
|
||||
- [Rancher](https://docs.traefik.io/configuration/backends/rancher) (API, Metadata)
|
||||
- [Azure Service Fabric](https://docs.traefik.io/configuration/backends/servicefabric)
|
||||
- [Consul Catalog](https://docs.traefik.io/configuration/backends/consulcatalog)
|
||||
- [Consul](https://docs.traefik.io/configuration/backends/consul) / [Etcd](https://docs.traefik.io/configuration/backends/etcd) / [Zookeeper](https://docs.traefik.io/configuration/backends/zookeeper) / [BoltDB](https://docs.traefik.io/configuration/backends/boltdb)
|
||||
- [Eureka](https://docs.traefik.io/configuration/backends/eureka)
|
||||
- [Amazon ECS](https://docs.traefik.io/configuration/backends/ecs)
|
||||
- [Amazon DynamoDB](https://docs.traefik.io/configuration/backends/dynamodb)
|
||||
- [File](https://docs.traefik.io/configuration/backends/file)
|
||||
- [Rest](https://docs.traefik.io/configuration/backends/rest)
|
||||
|
||||
## Quickstart
|
||||
|
||||
You can have a quick look at Træfik in this [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers. If you are looking for a more comprehensive and real use-case example, you can also check [Play-With-Docker](http://training.play-with-docker.com/traefik-load-balancing/) to see how to load balance between multiple nodes.
|
||||
To get your hands on Træfik, you can use the [5-Minute Quickstart](http://docs.traefik.io/#the-trfik-quickstart-using-docker) in our documentation (you will need Docker).
|
||||
|
||||
Alternatively, if you don't want to install anything on your computer, you can try Træfik online in this great [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers.
|
||||
|
||||
If you are looking for a more comprehensive and real use-case example, you can also check [Play-With-Docker](http://training.play-with-docker.com/traefik-load-balancing/) to see how to load balance between multiple nodes.
|
||||
|
||||
## Web UI
|
||||
|
||||
You can access the simple HTML frontend of Træfik.
|
||||
|
||||

|
||||

|
||||
|
||||
## Documentation
|
||||
|
||||
You can find the complete documentation at [https://docs.traefik.io](https://docs.traefik.io).
|
||||
A collection of contributions around Træfik can be found at [https://awesome.traefik.io](https://awesome.traefik.io).
|
||||
|
||||
## Support
|
||||
|
||||
To get community support, you can:
|
||||
- join the Træfik community Slack channel: [](https://traefik.herokuapp.com)
|
||||
- use [Stack Overflow](https://stackoverflow.com/questions/tagged/traefik) (using the `traefik` tag)
|
||||
|
||||
If you need commercial support, please contact [Containo.us](https://containo.us) by mail: <mailto:support@containo.us>.
|
||||
|
||||
## Download
|
||||
|
||||
- Grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
||||
|
||||
```shell
|
||||
./traefik --configFile=traefik.toml
|
||||
```
|
||||
|
||||
- Or use the official tiny Docker image and run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
||||
|
||||
```shell
|
||||
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik
|
||||
```
|
||||
|
||||
- Or get the sources:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/containous/traefik
|
||||
```
|
||||
|
||||
## Introductory Videos
|
||||
|
||||
Here is a talk given by [Emile Vauge](https://github.com/emilevauge) at [GopherCon 2017](https://gophercon.com/).
|
||||
You will learn Træfik basics in less than 10 minutes.
|
||||
@@ -101,81 +143,26 @@ You will learn fundamental Træfik features and see some demos with Kubernetes.
|
||||
|
||||
[](https://www.youtube.com/watch?v=aFtpIShV60I)
|
||||
|
||||
|
||||
## Web UI
|
||||
|
||||
You can access the simple HTML frontend of Træfik.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## Test it
|
||||
|
||||
- The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
||||
|
||||
```shell
|
||||
./traefik --configFile=traefik.toml
|
||||
```
|
||||
|
||||
- Use the tiny Docker image and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
||||
|
||||
```shell
|
||||
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik
|
||||
```
|
||||
|
||||
- From sources:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/containous/traefik
|
||||
```
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
You can find the complete documentation at [https://docs.traefik.io](https://docs.traefik.io).
|
||||
A collection of contributions around Træfik can be found at [https://awesome.traefik.io](https://awesome.traefik.io).
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
To get basic support, you can:
|
||||
- join the Træfik community Slack channel: [](https://traefik.herokuapp.com)
|
||||
- use [Stack Overflow](https://stackoverflow.com/questions/tagged/traefik) (using the `traefik` tag)
|
||||
|
||||
If you prefer commercial support, please contact [containo.us](https://containo.us) by mail: <mailto:support@containo.us>.
|
||||
|
||||
|
||||
## Release cycle
|
||||
|
||||
- Release: We try to release a new version every 2 months
|
||||
- i.e.: 1.3.0, 1.4.0, 1.5.0
|
||||
- Release candidate: we do RC (1.**x**.0-rc**y**) before the final release (1.**x**.0)
|
||||
- i.e.: 1.1.0-rc1 -> 1.1.0-rc2 -> 1.1.0-rc3 -> 1.1.0-rc4 -> 1.1.0
|
||||
- Bug-fixes: For each version we release bug fixes
|
||||
- i.e.: 1.1.1, 1.1.2, 1.1.3
|
||||
- those versions contain only bug-fixes
|
||||
- no additional features are delivered in those versions
|
||||
- Each version is supported until the next one is released
|
||||
- i.e.: 1.1.x will be supported until 1.2.0 is out
|
||||
- We use [Semantic Versioning](http://semver.org/)
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
Please refer to [contributing documentation](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.
|
||||
|
||||
|
||||
## Maintainers
|
||||
|
||||
[Information about process and maintainers](MAINTAINER.md)
|
||||
|
||||
## Contributing
|
||||
|
||||
If you'd like to contribute to the project, refer to the [contributing documentation](CONTRIBUTING.md).
|
||||
|
||||
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.
|
||||
|
||||
## Release Cycle
|
||||
|
||||
- We release a new version (e.g. 1.1.0, 1.2.0, 1.3.0) every other month.
|
||||
- Release Candidates are available before the release (e.g. 1.1.0-rc1, 1.1.0-rc2, 1.1.0-rc3, 1.1.0-rc4, before 1.1.0)
|
||||
- Bug-fixes (e.g. 1.1.1, 1.1.2, 1.2.1, 1.2.3) are released as needed (no additional features are delivered in those versions, bug-fixes only)
|
||||
|
||||
Each version is supported until the next one is released (e.g. 1.1.x will be supported until 1.2.0 is out)
|
||||
|
||||
We use [Semantic Versioning](http://semver.org/)
|
||||
|
||||
## Plumbing
|
||||
|
||||
@@ -184,11 +171,11 @@ By participating in this project you agree to abide by its terms.
|
||||
- [Negroni](https://github.com/urfave/negroni): web middlewares made simple
|
||||
- [Lego](https://github.com/xenolf/lego): the best [Let's Encrypt](https://letsencrypt.org) library in go
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
Kudos to [Peka](http://peka.byethost11.com/photoblog/) for his awesome work on the logo .
|
||||
Traefik's logo licensed under the Creative Commons 3.0 Attributions license.
|
||||
|
||||
Traefik's logo is licensed under the Creative Commons 3.0 Attributions license.
|
||||
|
||||
Traefik's logo was inspired by the gopher stickers made by Takuya Ueda (https://twitter.com/tenntenn).
|
||||
The original Go gopher was designed by Renee French (http://reneefrench.blogspot.com/).
|
||||
The original Go gopher was designed by Renee French (http://reneefrench.blogspot.com/).
|
||||
114
acme/account.go
@@ -8,12 +8,15 @@ import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
@@ -24,6 +27,7 @@ type Account struct {
|
||||
PrivateKey []byte
|
||||
DomainsCertificate DomainsCertificates
|
||||
ChallengeCerts map[string]*ChallengeCert
|
||||
HTTPChallenge map[string]map[string][]byte
|
||||
}
|
||||
|
||||
// ChallengeCert stores a challenge certificate
|
||||
@@ -33,13 +37,18 @@ type ChallengeCert struct {
|
||||
certificate *tls.Certificate
|
||||
}
|
||||
|
||||
// Init inits account struct
|
||||
// Init account struct
|
||||
func (a *Account) Init() error {
|
||||
err := a.DomainsCertificate.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = a.RemoveAccountV1Values()
|
||||
if err != nil {
|
||||
log.Errorf("Unable to remove ACME Account V1 values during account initialization: %v", err)
|
||||
}
|
||||
|
||||
for _, cert := range a.ChallengeCerts {
|
||||
if cert.certificate == nil {
|
||||
certificate, err := tls.X509KeyPair(cert.Certificate, cert.PrivateKey)
|
||||
@@ -48,6 +57,7 @@ func (a *Account) Init() error {
|
||||
}
|
||||
cert.certificate = &certificate
|
||||
}
|
||||
|
||||
if cert.certificate.Leaf == nil {
|
||||
leaf, err := x509.ParseCertificate(cert.certificate.Certificate[0])
|
||||
if err != nil {
|
||||
@@ -60,14 +70,19 @@ func (a *Account) Init() error {
|
||||
}
|
||||
|
||||
// NewAccount creates an account
|
||||
func NewAccount(email string) (*Account, error) {
|
||||
func NewAccount(email string, certs []*DomainsCertificate) (*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()
|
||||
|
||||
domainsCerts := DomainsCertificates{Certs: certs}
|
||||
err = domainsCerts.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Account{
|
||||
Email: email,
|
||||
PrivateKey: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||
@@ -90,10 +105,34 @@ 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
|
||||
}
|
||||
|
||||
// RemoveAccountV1Values removes ACME account V1 values
|
||||
func (a *Account) RemoveAccountV1Values() error {
|
||||
// Check if ACME Account is in ACME V1 format
|
||||
if a.Registration != nil {
|
||||
isOldRegistration, err := regexp.MatchString(acmeprovider.RegistrationURLPathV1Regexp, a.Registration.URI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isOldRegistration {
|
||||
a.reset()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Account) reset() {
|
||||
log.Debug("Reset ACME account object.")
|
||||
a.Email = ""
|
||||
a.Registration = nil
|
||||
a.PrivateKey = nil
|
||||
}
|
||||
|
||||
// Certificate is used to store certificate info
|
||||
type Certificate struct {
|
||||
Domain string
|
||||
@@ -121,9 +160,11 @@ func (dc *DomainsCertificates) Less(i, j int) bool {
|
||||
if reflect.DeepEqual(dc.Certs[i].Domains, dc.Certs[j].Domains) {
|
||||
return dc.Certs[i].tlsCert.Leaf.NotAfter.After(dc.Certs[j].tlsCert.Leaf.NotAfter)
|
||||
}
|
||||
|
||||
if dc.Certs[i].Domains.Main == dc.Certs[j].Domains.Main {
|
||||
return strings.Join(dc.Certs[i].Domains.SANs, ",") < strings.Join(dc.Certs[j].Domains.SANs, ",")
|
||||
}
|
||||
|
||||
return dc.Certs[i].Domains.Main < dc.Certs[j].Domains.Main
|
||||
}
|
||||
|
||||
@@ -141,29 +182,46 @@ func (dc *DomainsCertificates) removeDuplicates() {
|
||||
}
|
||||
}
|
||||
|
||||
// Init inits DomainsCertificates
|
||||
func (dc *DomainsCertificates) removeEmpty() {
|
||||
certs := []*DomainsCertificate{}
|
||||
for _, cert := range dc.Certs {
|
||||
if cert.Certificate != nil && len(cert.Certificate.Certificate) > 0 && len(cert.Certificate.PrivateKey) > 0 {
|
||||
certs = append(certs, cert)
|
||||
}
|
||||
}
|
||||
dc.Certs = certs
|
||||
}
|
||||
|
||||
// Init DomainsCertificates
|
||||
func (dc *DomainsCertificates) Init() error {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
dc.removeEmpty()
|
||||
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
domainsCertificate.tlsCert = &tlsCert
|
||||
|
||||
if domainsCertificate.tlsCert.Leaf == nil {
|
||||
leaf, err := x509.ParseCertificate(domainsCertificate.tlsCert.Certificate[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
domainsCertificate.tlsCert.Leaf = leaf
|
||||
}
|
||||
}
|
||||
|
||||
dc.removeDuplicates()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain Domain) error {
|
||||
func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain types.Domain) error {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
@@ -173,15 +231,17 @@ func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain D
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
domainsCertificate.Certificate = acmeCert
|
||||
domainsCertificate.tlsCert = &tlsCert
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("Certificate to renew not found for domain %s", domain.Main)
|
||||
|
||||
return fmt.Errorf("certificate to renew not found for domain %s", domain.Main)
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, domain Domain) (*DomainsCertificate, error) {
|
||||
func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, domain types.Domain) (*DomainsCertificate, error) {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
@@ -189,6 +249,7 @@ func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, d
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert := DomainsCertificate{Domains: domain, Certificate: acmeCert, tlsCert: &tlsCert}
|
||||
dc.Certs = append(dc.Certs, &cert)
|
||||
return &cert, nil
|
||||
@@ -197,11 +258,12 @@ func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, d
|
||||
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 {
|
||||
for _, domain := range domainsCertificate.Domains.ToStrArray() {
|
||||
if strings.HasPrefix(domain, "*.") && types.MatchDomain(domainToFind, domain) {
|
||||
return domainsCertificate, true
|
||||
}
|
||||
if domain == domainToFind {
|
||||
return domainsCertificate, true
|
||||
}
|
||||
@@ -210,9 +272,10 @@ func (dc *DomainsCertificates) getCertificateForDomain(domainToFind string) (*Do
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) exists(domainToFind Domain) (*DomainsCertificate, bool) {
|
||||
func (dc *DomainsCertificates) exists(domainToFind types.Domain) (*DomainsCertificate, bool) {
|
||||
dc.lock.RLock()
|
||||
defer dc.lock.RUnlock()
|
||||
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
if reflect.DeepEqual(domainToFind, domainsCertificate.Domains) {
|
||||
return domainsCertificate, true
|
||||
@@ -221,9 +284,29 @@ func (dc *DomainsCertificates) exists(domainToFind Domain) (*DomainsCertificate,
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) toDomainsMap() map[string]*tls.Certificate {
|
||||
domainsCertificatesMap := make(map[string]*tls.Certificate)
|
||||
|
||||
for _, domainCertificate := range dc.Certs {
|
||||
certKey := domainCertificate.Domains.Main
|
||||
|
||||
if domainCertificate.Domains.SANs != nil {
|
||||
sort.Strings(domainCertificate.Domains.SANs)
|
||||
|
||||
for _, dnsName := range domainCertificate.Domains.SANs {
|
||||
if dnsName != domainCertificate.Domains.Main {
|
||||
certKey += fmt.Sprintf(",%s", dnsName)
|
||||
}
|
||||
}
|
||||
}
|
||||
domainsCertificatesMap[certKey] = domainCertificate.tlsCert
|
||||
}
|
||||
return domainsCertificatesMap
|
||||
}
|
||||
|
||||
// DomainsCertificate contains a certificate for multiple domains
|
||||
type DomainsCertificate struct {
|
||||
Domains Domain
|
||||
Domains types.Domain
|
||||
Certificate *Certificate
|
||||
tlsCert *tls.Certificate
|
||||
}
|
||||
@@ -235,8 +318,9 @@ func (dc *DomainsCertificate) needRenew() bool {
|
||||
// If there's an error, we assume the cert is broken, and needs update
|
||||
return true
|
||||
}
|
||||
|
||||
// <= 30 days left, renew certificate
|
||||
if crt.NotAfter.Before(time.Now().Add(time.Duration(24 * 30 * time.Hour))) {
|
||||
if crt.NotAfter.Before(time.Now().Add(24 * 30 * time.Hour)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
713
acme/acme.go
@@ -7,20 +7,28 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
fmtlog "log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/tls/generate"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/containous/traefik/version"
|
||||
"github.com/eapache/channels"
|
||||
"github.com/xenolf/lego/acme"
|
||||
legolog "github.com/xenolf/lego/log"
|
||||
"github.com/xenolf/lego/providers/dns"
|
||||
)
|
||||
|
||||
@@ -30,91 +38,79 @@ var (
|
||||
)
|
||||
|
||||
// ACME allows to connect to lets encrypt and retrieve certs
|
||||
// Deprecated Please use provider/acme/Provider
|
||||
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'"`
|
||||
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."`
|
||||
DNSProvider string `description:"Use a DNS based challenge provider rather than HTTPS."`
|
||||
DelayDontCheckDNS int `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
|
||||
ACMELogging bool `description:"Enable debug logging of ACME actions."`
|
||||
client *acme.Client
|
||||
defaultCertificate *tls.Certificate
|
||||
store cluster.Store
|
||||
challengeProvider *challengeProvider
|
||||
checkOnDemandDomain func(domain string) bool
|
||||
jobs *channels.InfiniteChannel
|
||||
TLSConfig *tls.Config `description:"TLS config in case wildcard certs are used"`
|
||||
}
|
||||
|
||||
//Domains parse []Domain
|
||||
type Domains []Domain
|
||||
|
||||
//Set []Domain
|
||||
func (ds *Domains) Set(str string) error {
|
||||
fargs := func(c rune) bool {
|
||||
return c == ',' || c == ';'
|
||||
}
|
||||
// get function
|
||||
slice := strings.FieldsFunc(str, fargs)
|
||||
if len(slice) < 1 {
|
||||
return fmt.Errorf("Parse error ACME.Domain. Imposible to parse %s", str)
|
||||
}
|
||||
d := Domain{
|
||||
Main: slice[0],
|
||||
SANs: []string{},
|
||||
}
|
||||
if len(slice) > 1 {
|
||||
d.SANs = slice[1:]
|
||||
}
|
||||
*ds = append(*ds, d)
|
||||
return nil
|
||||
}
|
||||
|
||||
//Get []Domain
|
||||
func (ds *Domains) Get() interface{} { return []Domain(*ds) }
|
||||
|
||||
//String returns []Domain in string
|
||||
func (ds *Domains) String() string { return fmt.Sprintf("%+v", *ds) }
|
||||
|
||||
//SetValue sets []Domain into the parser
|
||||
func (ds *Domains) SetValue(val interface{}) {
|
||||
*ds = Domains(val.([]Domain))
|
||||
}
|
||||
|
||||
// Domain holds a domain name with SANs
|
||||
type Domain struct {
|
||||
Main string
|
||||
SANs []string
|
||||
Email string `description:"Email address used for registration"`
|
||||
Domains []types.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:"(Deprecated) Enable on demand certificate generation. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` // Deprecated
|
||||
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."`
|
||||
DNSChallenge *acmeprovider.DNSChallenge `description:"Activate DNS-01 Challenge"`
|
||||
HTTPChallenge *acmeprovider.HTTPChallenge `description:"Activate HTTP-01 Challenge"`
|
||||
DNSProvider string `description:"(Deprecated) Activate DNS-01 Challenge"` // Deprecated
|
||||
DelayDontCheckDNS flaeg.Duration `description:"(Deprecated) Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."` // Deprecated
|
||||
ACMELogging bool `description:"Enable debug logging of ACME actions."`
|
||||
client *acme.Client
|
||||
defaultCertificate *tls.Certificate
|
||||
store cluster.Store
|
||||
challengeHTTPProvider *challengeHTTPProvider
|
||||
checkOnDemandDomain func(domain string) bool
|
||||
jobs *channels.InfiniteChannel
|
||||
TLSConfig *tls.Config `description:"TLS config in case wildcard certs are used"`
|
||||
dynamicCerts *safe.Safe
|
||||
}
|
||||
|
||||
func (a *ACME) init() error {
|
||||
acme.UserAgent = fmt.Sprintf("containous-traefik/%s", version.Version)
|
||||
if a.ACMELogging {
|
||||
acme.Logger = fmtlog.New(os.Stderr, "legolog: ", fmtlog.LstdFlags)
|
||||
legolog.Logger = fmtlog.New(os.Stderr, "legolog: ", fmtlog.LstdFlags)
|
||||
} else {
|
||||
acme.Logger = fmtlog.New(ioutil.Discard, "", 0)
|
||||
legolog.Logger = fmtlog.New(ioutil.Discard, "", 0)
|
||||
}
|
||||
// no certificates in TLS config, so we add a default one
|
||||
cert, err := generateDefaultCertificate()
|
||||
cert, err := generate.DefaultCertificate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.defaultCertificate = cert
|
||||
// TODO: to remove in the futurs
|
||||
if len(a.StorageFile) > 0 && len(a.Storage) == 0 {
|
||||
log.Warn("ACME.StorageFile is deprecated, use ACME.Storage instead")
|
||||
a.Storage = a.StorageFile
|
||||
}
|
||||
|
||||
a.jobs = channels.NewInfiniteChannel()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddRoutes add routes on internal router
|
||||
func (a *ACME) AddRoutes(router *mux.Router) {
|
||||
router.Methods(http.MethodGet).
|
||||
Path(acme.HTTP01ChallengePath("{token}")).
|
||||
Handler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if a.challengeHTTPProvider == nil {
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(req)
|
||||
if token, ok := vars["token"]; ok {
|
||||
domain, _, err := net.SplitHostPort(req.Host)
|
||||
if err != nil {
|
||||
log.Debugf("Unable to split host and port: %v. Fallback to request host.", err)
|
||||
domain = req.Host
|
||||
}
|
||||
tokenValue := a.challengeHTTPProvider.getTokenValue(token, domain)
|
||||
if len(tokenValue) > 0 {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.Write(tokenValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tls.Config, certs *safe.Safe, checkOnDemandDomain func(domain string) bool) error {
|
||||
err := a.init()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -123,6 +119,7 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
|
||||
return errors.New("Empty Store, please provide a key for certs storage")
|
||||
}
|
||||
a.checkOnDemandDomain = checkOnDemandDomain
|
||||
a.dynamicCerts = certs
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, *a.defaultCertificate)
|
||||
tlsConfig.GetCertificate = a.getCertificate
|
||||
a.TLSConfig = tlsConfig
|
||||
@@ -151,7 +148,6 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
|
||||
}
|
||||
|
||||
a.store = datastore
|
||||
a.challengeProvider = &challengeProvider{store: a.store}
|
||||
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
leadership.Pool.AddGoCtx(func(ctx context.Context) {
|
||||
@@ -167,160 +163,69 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
|
||||
}
|
||||
})
|
||||
|
||||
leadership.AddListener(func(elected bool) error {
|
||||
if elected {
|
||||
_, 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.Debug("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.Debug("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
|
||||
}
|
||||
|
||||
a.retrieveCertificates()
|
||||
a.renewCertificates()
|
||||
a.runJobs()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
leadership.AddListener(a.leadershipListener)
|
||||
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
|
||||
a.TLSConfig = tlsConfig
|
||||
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.Info("Loading ACME Account...")
|
||||
// load account
|
||||
object, err := localStore.Load()
|
||||
func (a *ACME) leadershipListener(elected bool) error {
|
||||
if elected {
|
||||
_, err := a.store.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account = object.(*Account)
|
||||
} else {
|
||||
log.Info("Generating ACME Account...")
|
||||
account, err = NewAccount(a.Email)
|
||||
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
needRegister = true
|
||||
}
|
||||
|
||||
a.client, err = a.buildACMEClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account := object.(*Account)
|
||||
account.Init()
|
||||
// Reset Account values if caServer changed, thus registration URI can be updated
|
||||
if account != nil && account.Registration != nil && !strings.HasPrefix(account.Registration.URI, a.CAServer) {
|
||||
account.reset()
|
||||
}
|
||||
|
||||
if needRegister {
|
||||
// New users will need to register; be sure to save it
|
||||
log.Info("Register...")
|
||||
reg, err := a.client.Register()
|
||||
var needRegister bool
|
||||
if account == nil || len(account.Email) == 0 {
|
||||
domainsCerts := DomainsCertificates{Certs: []*DomainsCertificate{}}
|
||||
if account != nil {
|
||||
domainsCerts = account.DomainsCertificate
|
||||
}
|
||||
|
||||
account, err = NewAccount(a.Email, domainsCerts.Certs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
needRegister = true
|
||||
}
|
||||
|
||||
a.client, err = a.buildACMEClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account.Registration = reg
|
||||
}
|
||||
if needRegister {
|
||||
// New users will need to register; be sure to save it
|
||||
log.Debug("Register...")
|
||||
|
||||
// The client has a URL to the current Let's Encrypt Subscriber
|
||||
// Agreement. The user will need to agree to it.
|
||||
log.Debug("AgreeToTOS...")
|
||||
err = a.client.AgreeToTOS()
|
||||
if err != nil {
|
||||
// Let's Encrypt Subscriber Agreement renew ?
|
||||
reg, err := a.client.QueryRegistration()
|
||||
reg, err := a.client.Register(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account.Registration = reg
|
||||
}
|
||||
|
||||
err = transaction.Commit(account)
|
||||
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
|
||||
}
|
||||
|
||||
a.retrieveCertificates()
|
||||
a.renewCertificates()
|
||||
a.runJobs()
|
||||
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
safe.Go(func() {
|
||||
for range ticker.C {
|
||||
a.renewCertificates()
|
||||
}
|
||||
})
|
||||
a.retrieveCertificates()
|
||||
a.renewCertificates()
|
||||
a.runJobs()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -328,14 +233,10 @@ func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificat
|
||||
domain := types.CanonicalDomain(clientHello.ServerName)
|
||||
account := a.store.Get().(*Account)
|
||||
|
||||
if providedCertificate := a.getProvidedCertificate([]string{domain}); providedCertificate != nil {
|
||||
if providedCertificate := a.getProvidedCertificate(domain); providedCertificate != nil {
|
||||
return providedCertificate, nil
|
||||
}
|
||||
|
||||
if challengeCert, ok := a.challengeProvider.getCertificate(domain); ok {
|
||||
log.Debugf("ACME got challenge %s", domain)
|
||||
return challengeCert, nil
|
||||
}
|
||||
if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(domain); ok {
|
||||
log.Debugf("ACME got domain cert %s", domain)
|
||||
return domainCert.tlsCert, nil
|
||||
@@ -346,87 +247,84 @@ func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificat
|
||||
}
|
||||
return a.loadCertificateOnDemand(clientHello)
|
||||
}
|
||||
log.Debugf("ACME got nothing %s", domain)
|
||||
log.Debugf("No certificate found or generated for %s", domain)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (a *ACME) retrieveCertificates() {
|
||||
a.jobs.In() <- func() {
|
||||
log.Info("Retrieving ACME certificates...")
|
||||
for _, domain := range a.Domains {
|
||||
|
||||
a.deleteUnnecessaryDomains()
|
||||
|
||||
for i := 0; i < len(a.Domains); i++ {
|
||||
domain := a.Domains[i]
|
||||
|
||||
// check if cert isn't already loaded
|
||||
account := a.store.Get().(*Account)
|
||||
if _, exists := account.DomainsCertificate.exists(domain); !exists {
|
||||
domains := []string{}
|
||||
var domains []string
|
||||
domains = append(domains, domain.Main)
|
||||
domains = append(domains, domain.SANs...)
|
||||
domains, err := a.getValidDomains(domains, true)
|
||||
if err != nil {
|
||||
log.Errorf("Error validating ACME certificate for domain %q: %s", domains, err)
|
||||
continue
|
||||
}
|
||||
|
||||
certificateResource, err := a.getDomainsCertificates(domains)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting ACME certificate for domain %s: %s", domains, err.Error())
|
||||
log.Errorf("Error getting ACME certificate for domain %q: %s", domains, err)
|
||||
continue
|
||||
}
|
||||
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
log.Errorf("Error creating ACME store transaction from domain %s: %s", domain, err.Error())
|
||||
log.Errorf("Error creating ACME store transaction from domain %q: %s", domain, err)
|
||||
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())
|
||||
log.Errorf("Error adding ACME certificate for domain %q: %s", domains, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
|
||||
log.Errorf("Error Saving ACME account %+v: %s", account, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Retrieved ACME certificates")
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ACME) renewCertificates() {
|
||||
a.jobs.In() <- func() {
|
||||
log.Debug("Testing certificate renew...")
|
||||
log.Info("Testing certificate renew...")
|
||||
account := a.store.Get().(*Account)
|
||||
for _, certificateResource := range account.DomainsCertificate.Certs {
|
||||
if certificateResource.needRenew() {
|
||||
log.Debugf("Renewing certificate %+v", certificateResource.Domains)
|
||||
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,
|
||||
}, true, OSCPMustStaple)
|
||||
log.Infof("Renewing certificate from LE : %+v", certificateResource.Domains)
|
||||
renewedACMECert, err := a.renewACMECertificate(certificateResource)
|
||||
if err != nil {
|
||||
log.Errorf("Error renewing certificate: %v", err)
|
||||
log.Errorf("Error renewing certificate from LE: %v", err)
|
||||
continue
|
||||
}
|
||||
log.Debugf("Renewed certificate %+v", certificateResource.Domains)
|
||||
renewedACMECert := &Certificate{
|
||||
Domain: renewedCert.Domain,
|
||||
CertURL: renewedCert.CertURL,
|
||||
CertStableURL: renewedCert.CertStableURL,
|
||||
PrivateKey: renewedCert.PrivateKey,
|
||||
Certificate: renewedCert.Certificate,
|
||||
operation := func() error {
|
||||
return a.storeRenewedCertificate(certificateResource, renewedACMECert)
|
||||
}
|
||||
transaction, object, err := a.store.Begin()
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Warnf("Renewed certificate storage error: %v, retrying in %s", err, time)
|
||||
}
|
||||
ebo := backoff.NewExponentialBackOff()
|
||||
ebo.MaxElapsedTime = 60 * time.Second
|
||||
err = backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
|
||||
if err != nil {
|
||||
log.Errorf("Error renewing certificate: %v", err)
|
||||
continue
|
||||
}
|
||||
account = object.(*Account)
|
||||
err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
|
||||
if err != nil {
|
||||
log.Errorf("Error renewing certificate: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
|
||||
log.Errorf("Datastore cannot sync: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -434,23 +332,73 @@ func (a *ACME) renewCertificates() {
|
||||
}
|
||||
}
|
||||
|
||||
func dnsOverrideDelay(delay int) error {
|
||||
func (a *ACME) renewACMECertificate(certificateResource *DomainsCertificate) (*Certificate, error) {
|
||||
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,
|
||||
}, true, OSCPMustStaple)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("Renewed certificate from LE: %+v", certificateResource.Domains)
|
||||
return &Certificate{
|
||||
Domain: renewedCert.Domain,
|
||||
CertURL: renewedCert.CertURL,
|
||||
CertStableURL: renewedCert.CertStableURL,
|
||||
PrivateKey: renewedCert.PrivateKey,
|
||||
Certificate: renewedCert.Certificate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *ACME) storeRenewedCertificate(certificateResource *DomainsCertificate, renewedACMECert *Certificate) error {
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error during transaction initialization for renewing certificate: %v", err)
|
||||
}
|
||||
|
||||
log.Infof("Renewing certificate in data store : %+v ", certificateResource.Domains)
|
||||
account := object.(*Account)
|
||||
err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error renewing certificate in datastore: %v ", err)
|
||||
}
|
||||
|
||||
log.Infof("Commit certificate renewed in data store : %+v", certificateResource.Domains)
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
return fmt.Errorf("error saving ACME account %+v: %v", account, err)
|
||||
}
|
||||
|
||||
oldAccount := a.store.Get().(*Account)
|
||||
for _, oldCertificateResource := range oldAccount.DomainsCertificate.Certs {
|
||||
if oldCertificateResource.Domains.Main == certificateResource.Domains.Main && strings.Join(oldCertificateResource.Domains.SANs, ",") == strings.Join(certificateResource.Domains.SANs, ",") && certificateResource.Certificate != renewedACMECert {
|
||||
return fmt.Errorf("renewed certificate not stored: %+v", certificateResource.Domains)
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("Certificate successfully renewed in data store: %+v", certificateResource.Domains)
|
||||
return nil
|
||||
}
|
||||
|
||||
func dnsOverrideDelay(delay flaeg.Duration) error {
|
||||
var err error
|
||||
if delay > 0 {
|
||||
log.Debugf("Delaying %d seconds rather than validating DNS propagation", delay)
|
||||
log.Debugf("Delaying %d rather than validating DNS propagation", delay)
|
||||
acme.PreCheckDNS = func(_, _ string) (bool, error) {
|
||||
time.Sleep(time.Duration(delay) * time.Second)
|
||||
time.Sleep(time.Duration(delay))
|
||||
return true, nil
|
||||
}
|
||||
} else if delay < 0 {
|
||||
err = fmt.Errorf("Invalid negative DelayDontCheckDNS: %d", delay)
|
||||
err = fmt.Errorf("invalid negative DelayBeforeCheck: %d", delay)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
|
||||
log.Debug("Building ACME client...")
|
||||
caServer := "https://acme-v01.api.letsencrypt.org/directory"
|
||||
caServer := "https://acme-v02.api.letsencrypt.org/directory"
|
||||
if len(a.CAServer) > 0 {
|
||||
caServer = a.CAServer
|
||||
}
|
||||
@@ -459,25 +407,29 @@ func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(a.DNSProvider) > 0 {
|
||||
log.Debugf("Using DNS Challenge provider: %s", a.DNSProvider)
|
||||
if a.DNSChallenge != nil && len(a.DNSChallenge.Provider) > 0 {
|
||||
log.Debugf("Using DNS Challenge provider: %s", a.DNSChallenge.Provider)
|
||||
|
||||
err = dnsOverrideDelay(a.DelayDontCheckDNS)
|
||||
err = dnsOverrideDelay(a.DNSChallenge.DelayBeforeCheck)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var provider acme.ChallengeProvider
|
||||
provider, err = dns.NewDNSChallengeProviderByName(a.DNSProvider)
|
||||
provider, err = dns.NewDNSChallengeProviderByName(a.DNSChallenge.Provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01})
|
||||
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01})
|
||||
err = client.SetChallengeProvider(acme.DNS01, provider)
|
||||
} else if a.HTTPChallenge != nil && len(a.HTTPChallenge.EntryPoint) > 0 {
|
||||
log.Debug("Using HTTP Challenge provider.")
|
||||
client.ExcludeChallenges([]acme.Challenge{acme.DNS01})
|
||||
a.challengeHTTPProvider = &challengeHTTPProvider{store: a.store}
|
||||
err = client.SetChallengeProvider(acme.HTTP01, a.challengeHTTPProvider)
|
||||
} else {
|
||||
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
|
||||
err = client.SetChallengeProvider(acme.TLSSNI01, a.challengeProvider)
|
||||
return nil, errors.New("ACME challenge not specified, please select HTTP or DNS Challenge")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -503,7 +455,7 @@ func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.C
|
||||
return nil, err
|
||||
}
|
||||
account = object.(*Account)
|
||||
cert, err := account.DomainsCertificate.addCertificateForDomains(certificate, Domain{Main: domain})
|
||||
cert, err := account.DomainsCertificate.addCertificateForDomains(certificate, types.Domain{Main: domain})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -518,15 +470,9 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||
a.jobs.In() <- func() {
|
||||
log.Debugf("LoadCertificateForDomains %v...", domains)
|
||||
|
||||
if len(domains) == 0 {
|
||||
// no domain
|
||||
return
|
||||
}
|
||||
|
||||
domains = fun.Map(types.CanonicalDomain, domains).([]string)
|
||||
|
||||
// Check provided certificates
|
||||
if a.getProvidedCertificate(domains) != nil {
|
||||
domains, err := a.getValidDomains(domains, false)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting valid domain: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -541,38 +487,40 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||
}
|
||||
ebo := backoff.NewExponentialBackOff()
|
||||
ebo.MaxElapsedTime = 30 * time.Second
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
|
||||
err = backoff.RetryNotify(safe.OperationWithRecover(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) > 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
|
||||
|
||||
// Check provided certificates
|
||||
uncheckedDomains := a.getUncheckedDomains(domains, account)
|
||||
if len(uncheckedDomains) == 0 {
|
||||
return
|
||||
}
|
||||
certificate, err := a.getDomainsCertificates(domains)
|
||||
certificate, err := a.getDomainsCertificates(uncheckedDomains)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting ACME certificates %+v : %v", domains, err)
|
||||
log.Errorf("Error getting ACME certificates %+v : %v", uncheckedDomains, err)
|
||||
return
|
||||
}
|
||||
log.Debugf("Got certificate for domains %+v", domains)
|
||||
log.Debugf("Got certificate for domains %+v", uncheckedDomains)
|
||||
transaction, object, err := a.store.Begin()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error creating transaction %+v : %v", domains, err)
|
||||
log.Errorf("Error creating transaction %+v : %v", uncheckedDomains, err)
|
||||
return
|
||||
}
|
||||
var domain types.Domain
|
||||
if len(uncheckedDomains) > 1 {
|
||||
domain = types.Domain{Main: uncheckedDomains[0], SANs: uncheckedDomains[1:]}
|
||||
} else {
|
||||
domain = types.Domain{Main: uncheckedDomains[0]}
|
||||
}
|
||||
account = object.(*Account)
|
||||
_, err = account.DomainsCertificate.addCertificateForDomains(certificate, domain)
|
||||
if err != nil {
|
||||
log.Errorf("Error adding ACME certificates %+v : %v", domains, err)
|
||||
log.Errorf("Error adding ACME certificates %+v : %v", uncheckedDomains, err)
|
||||
return
|
||||
}
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
@@ -583,37 +531,100 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||
}
|
||||
|
||||
// Get provided certificate which check a domains list (Main and SANs)
|
||||
func (a *ACME) getProvidedCertificate(domains []string) *tls.Certificate {
|
||||
// from static and dynamic provided certificates
|
||||
func (a *ACME) getProvidedCertificate(domains string) *tls.Certificate {
|
||||
log.Debugf("Looking for provided certificate to validate %s...", domains)
|
||||
cert := searchProvidedCertificateForDomains(domains, a.TLSConfig.NameToCertificate)
|
||||
if cert == nil && a.dynamicCerts != nil && a.dynamicCerts.Get() != nil {
|
||||
cert = searchProvidedCertificateForDomains(domains, a.dynamicCerts.Get().(map[string]*tls.Certificate))
|
||||
}
|
||||
if cert == nil {
|
||||
log.Debugf("No provided certificate found for domains %s, get ACME certificate.", domains)
|
||||
}
|
||||
return cert
|
||||
}
|
||||
|
||||
func searchProvidedCertificateForDomains(domain string, certs map[string]*tls.Certificate) *tls.Certificate {
|
||||
// Use regex to test for provided certs that might have been added into TLSConfig
|
||||
providedCertMatch := false
|
||||
log.Debugf("Look for provided certificate to validate %s...", domains)
|
||||
for k := range a.TLSConfig.NameToCertificate {
|
||||
selector := "^" + strings.Replace(k, "*.", "[^\\.]*\\.?", -1) + "$"
|
||||
for _, domainToCheck := range domains {
|
||||
providedCertMatch, _ = regexp.MatchString(selector, domainToCheck)
|
||||
if !providedCertMatch {
|
||||
for certDomains := range certs {
|
||||
domainChecked := false
|
||||
for _, certDomain := range strings.Split(certDomains, ",") {
|
||||
domainChecked = types.MatchDomain(domain, certDomain)
|
||||
if domainChecked {
|
||||
break
|
||||
}
|
||||
}
|
||||
if providedCertMatch {
|
||||
log.Debugf("Got provided certificate for domains %s", domains)
|
||||
return a.TLSConfig.NameToCertificate[k]
|
||||
|
||||
if domainChecked {
|
||||
log.Debugf("Domain %q checked by provided certificate %q", domain, certDomains)
|
||||
return certs[certDomains]
|
||||
}
|
||||
}
|
||||
log.Debugf("No provided certificate found for domains %s, get ACME certificate.", domains)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get provided certificate which check a domains list (Main and SANs)
|
||||
// from static and dynamic provided certificates
|
||||
func (a *ACME) getUncheckedDomains(domains []string, account *Account) []string {
|
||||
log.Debugf("Looking for provided certificate to validate %s...", domains)
|
||||
allCerts := make(map[string]*tls.Certificate)
|
||||
|
||||
// Get static certificates
|
||||
for domains, certificate := range a.TLSConfig.NameToCertificate {
|
||||
allCerts[domains] = certificate
|
||||
}
|
||||
|
||||
// Get dynamic certificates
|
||||
if a.dynamicCerts != nil && a.dynamicCerts.Get() != nil {
|
||||
for domains, certificate := range a.dynamicCerts.Get().(map[string]*tls.Certificate) {
|
||||
allCerts[domains] = certificate
|
||||
}
|
||||
}
|
||||
|
||||
// Get ACME certificates
|
||||
if account != nil {
|
||||
for domains, certificate := range account.DomainsCertificate.toDomainsMap() {
|
||||
allCerts[domains] = certificate
|
||||
}
|
||||
}
|
||||
|
||||
// Get Configuration Domains
|
||||
for i := 0; i < len(a.Domains); i++ {
|
||||
allCerts[a.Domains[i].Main] = &tls.Certificate{}
|
||||
for _, san := range a.Domains[i].SANs {
|
||||
allCerts[san] = &tls.Certificate{}
|
||||
}
|
||||
}
|
||||
|
||||
return searchUncheckedDomains(domains, allCerts)
|
||||
}
|
||||
|
||||
func searchUncheckedDomains(domains []string, certs map[string]*tls.Certificate) []string {
|
||||
var uncheckedDomains []string
|
||||
for _, domainToCheck := range domains {
|
||||
if !isDomainAlreadyChecked(domainToCheck, certs) {
|
||||
uncheckedDomains = append(uncheckedDomains, domainToCheck)
|
||||
}
|
||||
}
|
||||
|
||||
if len(uncheckedDomains) == 0 {
|
||||
log.Debugf("No ACME certificate to generate for domains %q.", domains)
|
||||
} else {
|
||||
log.Debugf("Domains %q need ACME certificates generation for domains %q.", domains, strings.Join(uncheckedDomains, ","))
|
||||
}
|
||||
return uncheckedDomains
|
||||
}
|
||||
|
||||
func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
|
||||
domains = fun.Map(types.CanonicalDomain, domains).([]string)
|
||||
log.Debugf("Loading ACME certificates %s...", domains)
|
||||
bundle := true
|
||||
certificate, failures := a.client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple)
|
||||
if len(failures) > 0 {
|
||||
log.Error(failures)
|
||||
return nil, fmt.Errorf("Cannot obtain certificates %s+v", failures)
|
||||
|
||||
certificate, err := a.client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, fmt.Errorf("cannot obtain certificates: %+v", err)
|
||||
}
|
||||
|
||||
log.Debugf("Loaded ACME certificates %s", domains)
|
||||
return &Certificate{
|
||||
Domain: certificate.Domain,
|
||||
@@ -632,3 +643,105 @@ func (a *ACME) runJobs() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// getValidDomains checks if given domain is allowed to generate a ACME certificate and return it
|
||||
func (a *ACME) getValidDomains(domains []string, wildcardAllowed bool) ([]string, error) {
|
||||
// Check if the domains array is empty or contains only one empty value
|
||||
if len(domains) == 0 || (len(domains) == 1 && len(domains[0]) == 0) {
|
||||
return nil, errors.New("unable to generate a certificate when no domain is given")
|
||||
}
|
||||
|
||||
if strings.HasPrefix(domains[0], "*") {
|
||||
if !wildcardAllowed {
|
||||
return nil, fmt.Errorf("unable to generate a wildcard certificate for domain %q from a 'Host' rule", strings.Join(domains, ","))
|
||||
}
|
||||
|
||||
if a.DNSChallenge == nil && len(a.DNSProvider) == 0 {
|
||||
return nil, fmt.Errorf("unable to generate a wildcard certificate for domain %q : ACME needs a DNSChallenge", strings.Join(domains, ","))
|
||||
}
|
||||
if strings.HasPrefix(domains[0], "*.*") {
|
||||
return nil, fmt.Errorf("unable to generate a wildcard certificate for domain %q : ACME does not allow '*.*' wildcard domain", strings.Join(domains, ","))
|
||||
}
|
||||
}
|
||||
for _, san := range domains[1:] {
|
||||
if strings.HasPrefix(san, "*") {
|
||||
return nil, fmt.Errorf("unable to generate a certificate for domains %q: SANs can not be a wildcard domain", strings.Join(domains, ","))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
domains = fun.Map(types.CanonicalDomain, domains).([]string)
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
func isDomainAlreadyChecked(domainToCheck string, existentDomains map[string]*tls.Certificate) bool {
|
||||
for certDomains := range existentDomains {
|
||||
for _, certDomain := range strings.Split(certDomains, ",") {
|
||||
if types.MatchDomain(domainToCheck, certDomain) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// deleteUnnecessaryDomains deletes from the configuration :
|
||||
// - Duplicated domains
|
||||
// - Domains which are checked by wildcard domain
|
||||
func (a *ACME) deleteUnnecessaryDomains() {
|
||||
var newDomains []types.Domain
|
||||
|
||||
for idxDomainToCheck, domainToCheck := range a.Domains {
|
||||
keepDomain := true
|
||||
|
||||
for idxDomain, domain := range a.Domains {
|
||||
if idxDomainToCheck == idxDomain {
|
||||
continue
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(domain, domainToCheck) {
|
||||
if idxDomainToCheck > idxDomain {
|
||||
log.Warnf("The domain %v is duplicated in the configuration but will be process by ACME only once.", domainToCheck)
|
||||
keepDomain = false
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
var newDomainsToCheck []string
|
||||
|
||||
// Check if domains can be validated by the wildcard domain
|
||||
domainsMap := make(map[string]*tls.Certificate)
|
||||
domainsMap[domain.Main] = &tls.Certificate{}
|
||||
if len(domain.SANs) > 0 {
|
||||
domainsMap[strings.Join(domain.SANs, ",")] = &tls.Certificate{}
|
||||
}
|
||||
|
||||
for _, domainProcessed := range domainToCheck.ToStrArray() {
|
||||
if idxDomain < idxDomainToCheck && isDomainAlreadyChecked(domainProcessed, domainsMap) {
|
||||
// The domain is duplicated in a CN
|
||||
log.Warnf("Domain %q is duplicated in the configuration or validated by the domain %v. It will be processed once.", domainProcessed, domain)
|
||||
continue
|
||||
} else if domain.Main != domainProcessed && strings.HasPrefix(domain.Main, "*") && types.MatchDomain(domainProcessed, domain.Main) {
|
||||
// Check if a wildcard can validate the domain
|
||||
log.Warnf("Domain %q will not be processed by ACME provider because it is validated by the wildcard %q", domainProcessed, domain.Main)
|
||||
continue
|
||||
}
|
||||
newDomainsToCheck = append(newDomainsToCheck, domainProcessed)
|
||||
}
|
||||
|
||||
// Delete the domain if both Main and SANs can be validated by the wildcard domain
|
||||
// otherwise keep the unchecked values
|
||||
if newDomainsToCheck == nil {
|
||||
keepDomain = false
|
||||
break
|
||||
}
|
||||
domainToCheck.Set(newDomainsToCheck)
|
||||
}
|
||||
|
||||
if keepDomain {
|
||||
newDomains = append(newDomains, domainToCheck)
|
||||
}
|
||||
}
|
||||
|
||||
a.Domains = newDomains
|
||||
}
|
||||
|
||||
43
acme/acme_example.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"Email": "test@traefik.io",
|
||||
"Registration": {
|
||||
"body": {
|
||||
"resource": "reg",
|
||||
"id": 3,
|
||||
"key": {
|
||||
"kty": "RSA",
|
||||
"n": "y5a71suIqvEtovDmDVQ3SSNagk5IVCFI_TvqWpEXSrdbcDE2C-PTEtEUJuLkYwygcpiWYbPmXgdS628vQCw5Uo4DeDyHiuysJOWBLaWow3p9goOdhnPbGBq0liIR9xXyRoctdipVk8UiO9scWsu4jMBM3sMr7_yBWPfYYiLEQmZGFO3iE7Oqr55h_kncHIj5lUQY1j_jkftqxlxUB5_0quyJ7l915j5QY--eY7h4GEhRvx0TlUpi-CnRtRblGeDDDilXZD6bQN2962WdKecsmRaYx-ttLz6jCPXz2VDJRWNcIS501ne2Zh3hzw_DS6IRd2GIia1Wg4sisi9epC9sumXPHi6xzR6-_i_nsFjdtTkUcV8HmorOYoc820KQVZaLScxa8e7-ixpOd6mr6AIbEf7dBAkb9f_iK3GwpqKD8yNcaj1EQgNSyJSjnKSulXI_GwkGnuXe00Qpb1a8ha5Z8yWg7XmZZnJyAZrmK60RfwRNQ1rO5ioerNUBJ2KYTYNzVjBdob9Ug6Cjh4bEKNNjqcbjQ50_Z97Vw40xzpDQ_fYllc6n92eSuv6olxFJTmK7EhHuanDzITngaqei3zL9RwQ7P-1jfEZ03qmGrQYYqXcsS46PQ8cE-frzY2mKp16pRNCG7-03gKVGV0JHyW1aYbevNUk7OumCAXhC2YOigBk",
|
||||
"e": "AQAB"
|
||||
},
|
||||
"contact": [
|
||||
"mailto:test@traefik.io"
|
||||
],
|
||||
"agreement": "http://boulder:4000/terms/v1"
|
||||
},
|
||||
"uri": "http://127.0.0.1:4000/acme/reg/3",
|
||||
"new_authzr_uri": "http://127.0.0.1:4000/acme/new-authz",
|
||||
"terms_of_service": "http://boulder:4000/terms/v1"
|
||||
},
|
||||
"PrivateKey": "MIIJJwIBAAKCAgEAy5a71suIqvEtovDmDVQ3SSNagk5IVCFI/TvqWpEXSrdbcDE2C+PTEtEUJuLkYwygcpiWYbPmXgdS628vQCw5Uo4DeDyHiuysJOWBLaWow3p9goOdhnPbGBq0liIR9xXyRoctdipVk8UiO9scWsu4jMBM3sMr7/yBWPfYYiLEQmZGFO3iE7Oqr55h/kncHIj5lUQY1j/jkftqxlxUB5/0quyJ7l915j5QY++eY7h4GEhRvx0TlUpi+CnRtRblGeDDDilXZD6bQN2962WdKecsmRaYx+ttLz6jCPXz2VDJRWNcIS501ne2Zh3hzw/DS6IRd2GIia1Wg4sisi9epC9sumXPHi6xzR6+/i/nsFjdtTkUcV8HmorOYoc820KQVZaLScxa8e7+ixpOd6mr6AIbEf7dBAkb9f/iK3GwpqKD8yNcaj1EQgNSyJSjnKSulXI/GwkGnuXe00Qpb1a8ha5Z8yWg7XmZZnJyAZrmK60RfwRNQ1rO5ioerNUBJ2KYTYNzVjBdob9Ug6Cjh4bEKNNjqcbjQ50/Z97Vw40xzpDQ/fYllc6n92eSuv6olxFJTmK7EhHuanDzITngaqei3zL9RwQ7P+1jfEZ03qmGrQYYqXcsS46PQ8cE+frzY2mKp16pRNCG7+03gKVGV0JHyW1aYbevNUk7OumCAXhC2YOigBkCAwEAAQKCAgA8XW1EuwTC6tAFSDhuK1JZNUpY6K05hMUHkQRj5jFpzgQmt/C2hc7H/YZkIVJmrA/G6sdsINNlffZwKH9yH6q/d6w/snLeFl7UcdhjmIL5sxAT6sKCY0fLVd/FxERfZvp3Pw2Tw+mr7v+/j7BQm6cU1M/2HRiiB9SydIqMTpKyvXB6NC6ceOFbQTL9GxlQvKyEPbS/kiH/3vRB7I5d1GfPZmNfcp6ark9X0my8VK4HRSo36H8t/OhrfLrZXvh/O82aHVf0OTv/d8AgU/jNu+XVXoXegUfWglQFDChJf1KuaE+g5w1tqgFDNgkGRD475soXA6xgZi0Iw/B9tN3zALzT4IiAW1q72feeTgKOMA2zGtKXxQZZSOV+DuWFZNz/tT7XqGQThqxM09CHv2WGOe80vobtegXYTUt90hysrqIZmBW5XYdzQlJh1KWTtfCaTrWd47kbGvhkEPc8aA3Ji4/AqfkVXiqwaLu+MSlgzPpRj7U7UAIDqnpZjgttgLp74Ujnk3bTaUzdyyNqYDBG3IFGr/Sv+2GQDAUn/PYRJKWr0BteqOzX9zvW3zY8g9CYVXfK/AW3RMWLV8ly6vH/gWqa9gEuzRNRlzjUU6/HCVbUx3UT8RMWH2TQ0uuQZr5JX1iTwjeeT0dEIly1NnRQC92wcrE4UUTBEF3krGVpDBf0AQKCAQEA4jB8w+2fwzbF8X+gCODcY7sTeJRunzGy+jbdaLkcThuylga+6W3ZgWx0BD30ql9K2mouCVu86fCTnBeXXEC3QoTdgw/EzJ83+4JU3QSDdzs9Ta9vLHyvrpUkQfZ8UZpeLLmFsmsBMbBbnfw0S1TzXDsgrAc+G4tia8nO/Iqu75kEMGzmHQAvmN3iSqc1aTS4qumbB19g+v+csq9NEht4F9jt39KotG+OD3MxCxtMu7vxAkJRjFFcgcbb2Rtqe/kQEKA1vLEAJg27lV4k8XibCSerVUR6IzT8WZHrNiXmpRguTLl2k8uFUdCOOx6aLGyRVJ6+8SgIsMR540vnxwQzEQKCAQEA5mu2wtWT19mvXopC3easPsXIPzc5oaRkqfWZYT1KHcVQ7NIXsE3vCjcf/3igZ8l/FVQ4G4fpk/GoTqlpV5Aq/JHCpVOR2O69uB+W4kWgliejpHvF9gszzAYnC8lIXqDbWiinBhmm3ii8sDGAoBaSDw5NMUq3mI+nd8zZ+jx1bLBczDafmQ0YKr8k0YaROxIgoBgDOQDdSqG387lwzpza2DKI5Al3HfS42zjT0RmBahPiuT2aEoUZmIYuvFY0fEjfkpbdvLyexHfZCILRUGlG1nAwASFg86lp+mFSBJ3E3cvbP0CpbFGxon5u4Ao3/7htoOh6huh7MQ91h41fv1hsiQKCAQAe7WRR4e7jYVzlbX7zV9Oqq0y5QwpxJ/mB7viNNiphn7Xmf5uhDU0dPjgK0HHgzdDNVpFe5DVLg4KbaDpg+dRU+xfSsNhG5kpgUGzMH67eIbJ7Kc64tX/MDkZ74nkTK1lPIjrer3TlV2jfjDmWR1JTPR51hzP9ziwx8tEjhM7woeqJuIoqUvkvHL+xV3WdIgFSFUkGVAtNpp/FauTN4gWktRupbAN3UH2LLUP6ccwnK0aD+Y9u8T0F3av33qDLvL1umIlgeI89pMkOXmYMwmHoeY0axpcwszECCkqwB7SmxEyoXv+Qq9ZZ3ntkKAYKpvmkKWSQUtoFWYgVBS727mMRAoIBABLdwusU/bPwuPEutObiWjwRiaHTbb6UbUGVQGe70vO5EjUxxorC9s2JUe9i+w9EakleyfFHIZLheHxoVp26yio/7QYIX6q5cYM/4uTH+qwQts9i6wSISkdsQYovguNsnEk3huVy+Dy8bSaoBvYUowTkkOF2Uq4FJRskBLz+ckbh8dcuqcaoUdA+Mk+NixqhE1bIYIssTPItZ5hnGJtyMGD/UkIJnF0ximk4r+8w/W2oDypHpvPZPg1E/1KgZE/Az7166NDpSL6haX3O6ECDPi+Uo/mTuBJ7TpgXm9WQ7WuTo3H8Y2LhFYBOhdmGPKuNeDxyjIW7R0rvDxp4MtzB6rECggEAJIl7/qp1lxUQPQJRTsEYBkOtdRw0IGG1Rcj0emhHaBN05c9opCy+Osb7mVeU5ZiULe5kD02phL+36pEumprz7QzN46Y5pZc8AQ2W/QkeL4Wo9U9QzczvQQzc1EqrBkzvQTZtBhn4DRzz0IuTn1beVyHtBZeNpBFgMQFv9VYQuUNwFoTOkkQrBRnYbXH6KEnhF3c/1Hzi4KHVdHdfZ3LH7KFQJ34xio0q2tWQSQYeybmwOXdd9sxpz/Y4KBS9fqm7UrwnPK8yuOc05HLEaws+1iam5YyJprlQo3mGKe0wRztwn44HDeQr70LlFm0lzigVAv0hSiWO1Q5hJL7nDu8m/Q==",
|
||||
"DomainsCertificate": {
|
||||
"Certs": [
|
||||
{
|
||||
"Domains": {
|
||||
"Main": "local1.com",
|
||||
"SANs": [
|
||||
"test1.local1.com",
|
||||
"test2.local1.com"
|
||||
]
|
||||
},
|
||||
"Certificate": {
|
||||
"Domain": "local1.com",
|
||||
"CertURL": "http://127.0.0.1:4000/acme/cert/ffc4f3f14def9ee6ec6a0522b5c0baa3379d",
|
||||
"CertStableURL": "",
|
||||
"PrivateKey": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS1FJQkFBS0NBZ0VBdVNoTTR4enF6cE5YcFNaNnAvZnQrRmt5VmgyK1BSZXJUelV0OERRSng2UkVjQS9FCnN2RnNIVmNOSkZMS2twYTNlOEd3SUZBakJQNnJPK3hoR1JjWlJrdENON1gyOW5LZFhGbHZkYzJxd0hyTFF5WWkKTTB3ODhTck41VERiNi96TWU2dTB0dERiYWtDbDd6ZEJKUXJ6a1h5ZU1MeVkzTUs3aVkrMHpwL2JqMVhvbk5DdQpaQStkZ3hsMVNrV01DVUYvQk9HNWFyT1hwb0x4S0dQWGdzV3hOTVNLVmJKSHczL3ZqNTViZU92Um5lT3BNWlhvCmMwOWpZT3VBakNka1Z5czBSWHJLNWNCRDRMbVRXdnN4MFdTK2VMVHlGTTdQTHVZM3lEWkNNWEhjVmlqRHhnbFMKYjB1ZVRQcGFUWEQwYkxqZ0RNOUVEdE15ZEJzMUNPWlpPWG9ickN5Q2I1eWxTOFdVd1NzVXM1UldxZnlVbnAvcgpSNGx2c2RZOWRVZjRPdkNMVnJvWWk5NWFGc1Zxa0xLOExuL0Eyc3kxYWlDTnR4RmpKOXRXbWU0V0NhdzRoU0YvCkR4NWVNNWNYR2JSYXduVlZJQlZXeHhzNTBPMFJlUWRvbXBQZEFNS1RDWk9SRmxYaDdOWTdxQVdWRGtpdzhyam8Kekd3Ni9XdjlOR3hTNTliKzc0YVAxcjBxOTZ2RS9Rdi8zTCtjbjhiN0lBLytPYmFKdzhIT3RGbXc4RjBxQkN3MAprYWVVSloxb1JueGFYQUo4RHhHREpFOVdNUzh0QmJtVm16YkxoRkMzeDdVc0xGeTBrSzh1SFBFT3dQb2NKNUFUCkE1UHBvclNEMmFleHA0Z3VqYVp5c1JManpmY0dnaTdva0JFNlZVNWVqRE1iYS9lNERQNEJQUVg5VmtVQ0F3RUEKQVFLQ0FnQmZjMWdYcUp1ZmZMT3REcVlpbXh4UmIrSVVKT2NpWldaSndmZDVvY244NGtEcHFDZFZ2RUZvNnF4NgpzamQ5MURhb2xOUHdCSC9aSGxRMTR3aTNQNEluQzdzS0wwTXVEeTN5SXFUa0RPOWVwSzdPWWdVMWZyTFgvS0lCCjZlc2x2Ny9HYldFTzhhSjdKdktqM0U4NEFtcEg4UDgzenJIYTlJUnJTT3NEcmNNcEpEZHpSOXp1OW1IVDZMYmYKWC9UdC9KYTNkSW42YUxUZ0FSYkRKSjAvN0J3TFFOcXpqT0dUOWdzUWRhbGdMK2x5eEo4L1ViRndhRmVwNmgzdApvbzBHcHQ0ZWgwdTdueDhlNVd3Q2RnWmJsTnpnS3grMC9Gd3dLRHhQZVRFc2ZpOEJONmlkR2NjbVdzd3prTWdtCnJmbERaeGNSWTNRSlZIVHBCL0dTTWZXRFBPQ3dRdGltQk1WN3kxM2hPMTdPWXpSNDBMZnpUalJBbmtna2V2eWYKcFowb3dLR3o4QS9haHhRWWJmYVQ5VEhXV0wrYUpYeUhFanBKckp5aTg3UExVbzhsOFVydU56MDRWNXpLOFJPbgo2cG9EWmVtbm1EYWRlU09pK3hZRWlGT1NwSXNWbzlpcm9jUGFKN2YzYWpiNUU4RHpuN1o1MmhzL2R6akpLcFZJCm5mVDFkUU9SZEowSXRUNlRlQ2RTL0dpS25IS1RtNjR2T21IbmlJcm8rUGRhUmFjV0IrTUJ0VytRd0cyUStyRGkKc3g4NlpQbHRpTVpLMDZ5TVlyVHZUdGk2aFVGaUY5cWh4b3RGazdNQkNrZlIwYUVhaUREQUpKNm1jb1lpRUQ2QgpBVGJhVmpVaGNaUiswYkRST25PN0ozRk5rZmx3K2dMaVhvcXFRRW9pU2ZWb2h5SWY3UUtDQVFFQThjYTM5K0g4CjN3L2Qrcm0yUGNhM0RMQnBYaWU4Z3ZYcGpjazVYSkpvSGVmbnJjZWQrcFpXaTZEYncwYld0MEdtYkxmVjJNSlAKV2I1aTZzSXhmdkN3YlFqbHY0UnExMVA5ZEswT3poMnVpKzZ6cXVBMG5YTVcrN0lJS0cvdDhmS2NJZGRRNnRGcwpFclFVTFBDak56ODA2cHBiSlhPRmVvMW1BK293TGhHNlA3dDhCdlZHSk1NaTNxejNlSUNuVVE2eDNFY01ITXNuClhrM21DUzI1WUZaNk96cytFK254cGVraTAzZmQwblp3UE1jdElHZys1c3hleE9zREsrTHlvb2FqQnc5N0oyUzIKcUNNWXFtT0tLcmxEQ3Y1WmQ4dlZLN3hXVmpKRVhGTTNMZ2pieHBRcCtuVXNVVWxwS01LOVlGS0lRREl0RU9aMApWcWExTXJaOElzN1l5d0tDQVFFQXhBemZIa2pIVGlvTHdZbG5EcEk0MWlOTDh5Y0ZBallrTC94dWhPU2tlVkE4CjdRWDZPZUpDekR3Z0FUYXVqOWR6Y0wwby9yTndWV0xWcnQ3OXk3YnJvVDdFREZKWVNTY25GRXNMTlVWSXRncGkKckNSUXJTL1F2TkVGTmE5K0pRc1dmYkdBNHdIUTFaSjI4MFp1cWMvNlEyUi9kZVh3cUZBQVBHN2NIcEhHWlR6ZQoyRmFRUHFLRkV4WlEyZkpvRys0SVBRNHVQVERybXlGMmVUWXk2T3BaaDBHbWJRYlVTa1dFWDlQRmF1cHJIWVdGCk8wK25DaVVPNVRaMFZoaGR2dUNKMWdPclZHYzhBUlJtUVZ1aUNEWTZCaGlvVTU0ZmZsSXlDTXZ5a3MwcmRXZ3MKWVJ2TmN4TXNlRGJpTDRKSURkMHhiN1d4VUdmVjRVNHZPMks5Vms1N0x3S0NBUUVBMkd1eE1jcXd1RnRUc0tPYwpaaUFDcXZFZTRKRmhSVGtySHlnSW1MelZSaS9ZU3M1c3MycnZmWDA0T3N5bVZ0UUZUVHdoeUMzbktjWXFkVW52ClZGblBFMHJyblV2Qzk0elBUQ205SHZPaTBzK1JORndOdlFMUWgrME5NR1ZBOFZyaU44aXRQZ1RJWU5XaFdianQKNFA1TE45V0QwVHBmT1J4cFBRZmNxT0JsZjdjcmhtNzNvdUNwemZtMmE3OStCaWpKUFF5NzR1cFhDeXRmeHNlUApNSlU0Uk56NjdJaDFMclpKM2xGbDFvYitZT2xKazhDOHpZd1RLT0hWck9zeGxobyt4SXN2Q2t3MDFMelZ6Mi9hCnRmT3Y5NTlHSnQzbXE0ZWpJUFZPQy9iUlpmdTMvMEdSY2dpQTZ5SnpaM0VxWTVaOU1EbTU3VzdjcE5RRlRxZmEKNXEyUmtRS0NBUUErNGhZSzQ3TXg2aUNkTWxKaEJSdS82OUJucktOWm96NFdPalRFNFlXejk3MmpGU0Mrd2tsRQpzeUJjNDBvNGp4WFRHb2wwc04rZU03WndnY3dNTko3OXVHRXZ4cFhVMlA4YTdqc3BHaEVKZXVsTlo5U015R0orCnZkaWE4TEJZZDJiK2FCbjhOay9pd1Rqd0xTNC92NXI1Vk5uaFdpRElDK2tYZVVPWGRwQ1pWbDN3TEV2V0cxRHQKMzJHTmxzZzM5VENsVE5BZUJudjc1VTdYOEQrQ0gvRVpoa0E0aGxFL2hXN0JRZTczclRzd1creHhLc3BjWWFpVwpjdEg3NzVMYUw3Rm1lUVRTYk01OVZpcTZXZ2J0OVY3Rko5R09DSkQzZHF2ZjBITDlEVndjSzQ3WWt3OWlFc3RYCnY5cnEvREhhYUpGNzBGNlFlTTNNbDhSa212WTZJYkEzQW9JQkFRRGt6RmZLeG9HQ3dWUDlua3k4NmFQSjFvd2kKc2FDZEx6RjRWTENRZzkrUXJITzEyY0p5MFFQUnJ2cUQyMGp1cDFlOWJhWVZzbkdYc1FZTFg2NVR6UzJSSCtlSAp6S0NPTTdnMVE3djMxNWpjMDMvN1lQck4rb3RrV0VBOUkyaDZjUE1vY3c0aERTNk02OFlxQVlKTS9RclVhenZhCnhBTFJaZEVkQW1xWDA4VHhuY1hRUEVxYkk0ZnlSZ2pVM1BYR3RRaFFFbERpR2kwbThjQTJNTXdsR1RmbTdOSXgKaENjZ2ZkL296TEp2VUhiMkxLRi82cXEySmJVRHlOMkVoK0xSZUJjdnp6Y1grZE5MdGQxY0Uvcm1SM2hMbWxmNgo3KzRpTVMxK0t1eWV3VlJVUEE1c1F1aUYyVUVoeEs1MUpZK1FpOG9HbERKdGRrOXB3QlZNN1F0WW9KVEwKLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K",
|
||||
"Certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZvakNDQklxZ0F3SUJBZ0lUQVAvRTgvRk43NTdtN0dvRklyWEF1cU0zblRBTkJna3Foa2lHOXcwQkFRc0YKQURBZk1SMHdHd1lEVlFRRERCUm9NbkJ3ZVNCb01tTnJaWElnWm1GclpTQkRRVEFlRncweE9EQXhNVFV3TnpJNQpNREJhRncweE9EQTBNVFV3TnpJNU1EQmFNRVF4RXpBUkJnTlZCQU1UQ214dlkyRnNNUzVqYjIweExUQXJCZ05WCkJBVVRKR1ptWXpSbU0yWXhOR1JsWmpsbFpUWmxZelpoTURVeU1tSTFZekJpWVdFek16YzVaRENDQWlJd0RRWUoKS29aSWh2Y05BUUVCQlFBRGdnSVBBRENDQWdvQ2dnSUJBTGtvVE9NYzZzNlRWNlVtZXFmMzdmaFpNbFlkdmowWApxMDgxTGZBMENjZWtSSEFQeExMeGJCMVhEU1JTeXBLV3QzdkJzQ0JRSXdUK3F6dnNZUmtYR1VaTFFqZTE5dlp5Cm5WeFpiM1hOcXNCNnkwTW1Jak5NUFBFcXplVXcyK3Y4ekh1cnRMYlEyMnBBcGU4M1FTVUs4NUY4bmpDOG1OekMKdTRtUHRNNmYyNDlWNkp6UXJtUVBuWU1aZFVwRmpBbEJmd1RodVdxemw2YUM4U2hqMTRMRnNUVEVpbFd5UjhOLwo3NCtlVzNqcjBaM2pxVEdWNkhOUFkyRHJnSXduWkZjck5FVjZ5dVhBUStDNWsxcjdNZEZrdm5pMDhoVE96eTdtCk44ZzJRakZ4M0ZZb3c4WUpVbTlMbmt6NldrMXc5R3k0NEF6UFJBN1RNblFiTlFqbVdUbDZHNndzZ20rY3BVdkYKbE1FckZMT1VWcW44bEo2ZjYwZUpiN0hXUFhWSCtEcndpMWE2R0l2ZVdoYkZhcEN5dkM1L3dOck10V29namJjUgpZeWZiVnBudUZnbXNPSVVoZnc4ZVhqT1hGeG0wV3NKMVZTQVZWc2NiT2REdEVYa0hhSnFUM1FEQ2t3bVRrUlpWCjRleldPNmdGbFE1SXNQSzQ2TXhzT3Yxci9UUnNVdWZXL3UrR2o5YTlLdmVyeFAwTC85eS9uSi9HK3lBUC9qbTIKaWNQQnpyUlpzUEJkS2dRc05KR25sQ1dkYUVaOFdsd0NmQThSZ3lSUFZqRXZMUVc1bFpzMnk0UlF0OGUxTEN4Ywp0SkN2TGh6eERzRDZIQ2VRRXdPVDZhSzBnOW1uc2FlSUxvMm1jckVTNDgzM0JvSXU2SkFST2xWT1hvd3pHMnYzCnVBeitBVDBGL1ZaRkFnTUJBQUdqZ2dHd01JSUJyREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdIUVlEVlIwbEJCWXcKRkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQndNQ01Bd0dBMVVkRXdFQi93UUNNQUF3SFFZRFZSME9CQllFRk5LZQpBVUZYc2Z2N2lML0lYVVBXdzY2ZU5jQnhNQjhHQTFVZEl3UVlNQmFBRlB0NFR4TDVZQldETEo4WGZ6UVpzeTQyCjZrR0pNR1lHQ0NzR0FRVUZCd0VCQkZvd1dEQWlCZ2dyQmdFRkJRY3dBWVlXYUhSMGNEb3ZMekV5Tnk0d0xqQXUKTVRvME1EQXlMekF5QmdnckJnRUZCUWN3QW9ZbWFIUjBjRG92THpFeU55NHdMakF1TVRvME1EQXdMMkZqYldVdgphWE56ZFdWeUxXTmxjblF3T1FZRFZSMFJCREl3TUlJS2JHOWpZV3d4TG1OdmJZSVFkR1Z6ZERFdWJHOWpZV3d4CkxtTnZiWUlRZEdWemRESXViRzlqWVd3eExtTnZiVEFuQmdOVkhSOEVJREFlTUJ5Z0dxQVloaFpvZEhSd09pOHYKWlhoaGJYQnNaUzVqYjIwdlkzSnNNR0VHQTFVZElBUmFNRmd3Q0FZR1o0RU1BUUlCTUV3R0F5b0RCREJGTUNJRwpDQ3NHQVFVRkJ3SUJGaFpvZEhSd09pOHZaWGhoYlhCc1pTNWpiMjB2WTNCek1COEdDQ3NHQVFVRkJ3SUNNQk1NCkVVUnZJRmRvWVhRZ1ZHaHZkU0JYYVd4ME1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ3A0Q2FxZlR4THNQTzQKS2JueDJZdEc4bTN3MC9keTVVR1VRNjZHbGxPVTk0L2I0MmNhbTRuNUZrTWlpZ01IaUx4c2JZVXh0cDZKQ3R5cQpLKzFNcDFWWEtSTTVKbFBTNWRIaWhxdHk1U3BrTUhjampwQSs3U2YyVWtoNmpKRWYxTUVJY2JnWnpJRk5IT0hYClVUUUppVFhKcno3blJDZnlQWFZtbWErUGtIRlU4R0VEVzJGOVptU1kzVFBiQWhiWkV2UkZubjUrR1lxbkZuancKWWw3Y0I2MXYwRzVpOGQwbnVvbTB4a2hiNTU3Y3BiZHhLblhsaFU4N2RZSTR5SUdPdUFGUWpYcXFXN2NIZCtXUQpWSDB2dFA3cEgrRmt2YnY4WkkxMHMrNU5ZcCtzZjFQZGQxekJsRmdNSGF3dnFFYUg3SU9sejdkajlCdmtVc0dpClhxQWVqQnFPCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVpakNDQTNLZ0F3SUJBZ0lDRWswd0RRWUpLb1pJaHZjTkFRRUxCUUF3S3pFcE1DY0dBMVVFQXd3Z1kyRmoKYTJ4cGJtY2dZM0o1Y0hSdlozSmhjR2hsY2lCbVlXdGxJRkpQVDFRd0hoY05NVFV4TURJeE1qQXhNVFV5V2hjTgpNakF4TURFNU1qQXhNVFV5V2pBZk1SMHdHd1lEVlFRREV4Um9ZWEJ3ZVNCb1lXTnJaWElnWm1GclpTQkRRVENDCkFTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTUlLUjNtYUJjVVNzbmNYWXpRVDEzRDUKTnIrWjNtTHhNTWgzVFVkdDZzQUNtcWJKMGJ0UmxnWGZNdE5MTTJPVTFJNmEzSnUrdElaU2RuMnYyMUpCd3Z4VQp6cFpRNHp5MmNpbUlpTVFEWkNRSEp3ekM5R1puOEhhVzA5MWl6OUgwR28zQTdXRFh3WU5tc2RMTlJpMDBvMTRVCmpvYVZxYVBzWXJaV3ZSS2FJUnFhVTBoSG1TMEFXd1FTdk4vOTNpTUlYdXlpd3l3bWt3S2JXbm54Q1EvZ3NjdEsKRlV0Y05yd0V4OVdnajZLbGh3RFR5STFRV1NCYnhWWU55VWdQRnpLeHJTbXdNTzB5TmZmN2hvK1FUOXg1K1kvNwpYRTU5UzRNYzRaWHhjWEtldy9nU2xOOVU1bXZUK0QyQmhEdGtDdXBkZnNaTkNRV3AyN0ErYi9EbXJGSTlOcXNDCkF3RUFBYU9DQWNJd2dnRytNQklHQTFVZEV3RUIvd1FJTUFZQkFmOENBUUF3UXdZRFZSMGVCRHd3T3FFNE1BYUMKQkM1dGFXd3dDb2NJQUFBQUFBQUFBQUF3SW9jZ0FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQpBQUFBQUFBd0RnWURWUjBQQVFIL0JBUURBZ0dHTUg4R0NDc0dBUVVGQndFQkJITXdjVEF5QmdnckJnRUZCUWN3CkFZWW1hSFIwY0RvdkwybHpjbWN1ZEhKMWMzUnBaQzV2WTNOd0xtbGtaVzUwY25WemRDNWpiMjB3T3dZSUt3WUIKQlFVSE1BS0dMMmgwZEhBNkx5OWhjSEJ6TG1sa1pXNTBjblZ6ZEM1amIyMHZjbTl2ZEhNdlpITjBjbTl2ZEdOaAplRE11Y0Rkak1COEdBMVVkSXdRWU1CYUFGT21rUCs2ZXBlYnkxZGQ1WUR5VHBpNGtqcGVxTUZRR0ExVWRJQVJOCk1Fc3dDQVlHWjRFTUFRSUJNRDhHQ3lzR0FRUUJndDhUQVFFQk1EQXdMZ1lJS3dZQkJRVUhBZ0VXSW1oMGRIQTYKTHk5amNITXVjbTl2ZEMxNE1TNXNaWFJ6Wlc1amNubHdkQzV2Y21jd1BBWURWUjBmQkRVd016QXhvQytnTFlZcgphSFIwY0RvdkwyTnliQzVwWkdWdWRISjFjM1F1WTI5dEwwUlRWRkpQVDFSRFFWZ3pRMUpNTG1OeWJEQWRCZ05WCkhRNEVGZ1FVKzNoUEV2bGdGWU1zbnhkL05CbXpMamJxUVlrd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFBMFkKQWVMWE9rbHg0aGhDaWtVVWwrQmRuRmZuMWcwVzVBaVFMVk5JT0w2UG5xWHUwd2puaE55aHFkd25maFlNbm95NAppZFJoNGxCNnB6OEdmOXBubExkL0RuV1NWM2dTKy9JL21BbDFkQ2tLYnk2SDJWNzkwZTZJSG1JSzJLWW0zam0rClUrK0ZJZEdwQmRzUVRTZG1pWC9yQXl1eE1ETTBhZE1rTkJ3VGZRbVpRQ3o2bkdIdzFRY1NQWk12WnBzQzhTa3YKZWt6eHNqRjFvdE9yTVVQTlBRdnRUV3JWeDhHbFIycWZ4LzR4YlFhMXYyZnJOdkZCQ21PNTlnb3oram5XdmZUdApqMk5qd0RaN3ZsTUJzUG0xNmRiS1lDODQwdXZSb1pqeHFzZGMzQ2hDWmpxaW1GcWxORy94b1BBOCtkVGljWnpDClhFOWlqUEljdlc2eTFhYTNiR3c9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ChallengeCerts": {}
|
||||
}
|
||||
@@ -6,79 +6,127 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/tls/generate"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
func TestDomainsSet(t *testing.T) {
|
||||
checkMap := map[string]Domains{
|
||||
"": {},
|
||||
"foo.com": {Domain{Main: "foo.com", SANs: []string{}}},
|
||||
"foo.com,bar.net": {Domain{Main: "foo.com", SANs: []string{"bar.net"}}},
|
||||
"foo.com,bar1.net,bar2.net,bar3.net": {Domain{Main: "foo.com", SANs: []string{"bar1.net", "bar2.net", "bar3.net"}}},
|
||||
testCases := []struct {
|
||||
input string
|
||||
expected types.Domains
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
expected: types.Domains{},
|
||||
},
|
||||
{
|
||||
input: "foo1.com",
|
||||
expected: types.Domains{
|
||||
types.Domain{Main: "foo1.com"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "foo2.com,bar.net",
|
||||
expected: types.Domains{
|
||||
types.Domain{
|
||||
Main: "foo2.com",
|
||||
SANs: []string{"bar.net"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "foo3.com,bar1.net,bar2.net,bar3.net",
|
||||
expected: types.Domains{
|
||||
types.Domain{
|
||||
Main: "foo3.com",
|
||||
SANs: []string{"bar1.net", "bar2.net", "bar3.net"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for in, check := range checkMap {
|
||||
ds := Domains{}
|
||||
ds.Set(in)
|
||||
if !reflect.DeepEqual(check, ds) {
|
||||
t.Errorf("Expected %+v\nGot %+v", check, ds)
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
domains := types.Domains{}
|
||||
domains.Set(test.input)
|
||||
assert.Exactly(t, test.expected, domains)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainsSetAppend(t *testing.T) {
|
||||
inSlice := []string{
|
||||
"",
|
||||
"foo1.com",
|
||||
"foo2.com,bar.net",
|
||||
"foo3.com,bar1.net,bar2.net,bar3.net",
|
||||
testCases := []struct {
|
||||
input string
|
||||
expected types.Domains
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
expected: types.Domains{},
|
||||
},
|
||||
{
|
||||
input: "foo1.com",
|
||||
expected: types.Domains{
|
||||
types.Domain{Main: "foo1.com"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "foo2.com,bar.net",
|
||||
expected: types.Domains{
|
||||
types.Domain{Main: "foo1.com"},
|
||||
types.Domain{
|
||||
Main: "foo2.com",
|
||||
SANs: []string{"bar.net"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "foo3.com,bar1.net,bar2.net,bar3.net",
|
||||
expected: types.Domains{
|
||||
types.Domain{Main: "foo1.com"},
|
||||
types.Domain{
|
||||
Main: "foo2.com",
|
||||
SANs: []string{"bar.net"},
|
||||
},
|
||||
types.Domain{
|
||||
Main: "foo3.com",
|
||||
SANs: []string{"bar1.net", "bar2.net", "bar3.net"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
checkSlice := []Domains{
|
||||
{},
|
||||
{
|
||||
Domain{
|
||||
Main: "foo1.com",
|
||||
SANs: []string{}}},
|
||||
{
|
||||
Domain{
|
||||
Main: "foo1.com",
|
||||
SANs: []string{}},
|
||||
Domain{
|
||||
Main: "foo2.com",
|
||||
SANs: []string{"bar.net"}}},
|
||||
{
|
||||
Domain{
|
||||
Main: "foo1.com",
|
||||
SANs: []string{}},
|
||||
Domain{
|
||||
Main: "foo2.com",
|
||||
SANs: []string{"bar.net"}},
|
||||
Domain{Main: "foo3.com",
|
||||
SANs: []string{"bar1.net", "bar2.net", "bar3.net"}}},
|
||||
}
|
||||
ds := Domains{}
|
||||
for i, in := range inSlice {
|
||||
ds.Set(in)
|
||||
if !reflect.DeepEqual(checkSlice[i], ds) {
|
||||
t.Errorf("Expected %s %+v\nGot %+v", in, checkSlice[i], ds)
|
||||
}
|
||||
|
||||
// append to
|
||||
domains := types.Domains{}
|
||||
for _, test := range testCases {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
|
||||
domains.Set(test.input)
|
||||
assert.Exactly(t, test.expected, domains)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertificatesRenew(t *testing.T) {
|
||||
foo1Cert, foo1Key, _ := generateKeyPair("foo1.com", time.Now())
|
||||
foo2Cert, foo2Key, _ := generateKeyPair("foo2.com", time.Now())
|
||||
foo1Cert, foo1Key, _ := generate.KeyPair("foo1.com", time.Now())
|
||||
foo2Cert, foo2Key, _ := generate.KeyPair("foo2.com", time.Now())
|
||||
|
||||
domainsCertificates := DomainsCertificates{
|
||||
lock: sync.RWMutex{},
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: Domain{
|
||||
Main: "foo1.com",
|
||||
SANs: []string{}},
|
||||
Domains: types.Domain{
|
||||
Main: "foo1.com"},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo1.com",
|
||||
CertURL: "url",
|
||||
@@ -88,9 +136,8 @@ func TestCertificatesRenew(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: Domain{
|
||||
Main: "foo2.com",
|
||||
SANs: []string{}},
|
||||
Domains: types.Domain{
|
||||
Main: "foo2.com"},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo2.com",
|
||||
CertURL: "url",
|
||||
@@ -101,7 +148,8 @@ func TestCertificatesRenew(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
foo1Cert, foo1Key, _ = generateKeyPair("foo1.com", time.Now())
|
||||
|
||||
foo1Cert, foo1Key, _ = generate.KeyPair("foo1.com", time.Now())
|
||||
newCertificate := &Certificate{
|
||||
Domain: "foo1.com",
|
||||
CertURL: "url",
|
||||
@@ -110,17 +158,15 @@ func TestCertificatesRenew(t *testing.T) {
|
||||
Certificate: foo1Cert,
|
||||
}
|
||||
|
||||
err := domainsCertificates.renewCertificates(
|
||||
newCertificate,
|
||||
Domain{
|
||||
Main: "foo1.com",
|
||||
SANs: []string{}})
|
||||
err := domainsCertificates.renewCertificates(newCertificate, types.Domain{Main: "foo1.com"})
|
||||
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)
|
||||
}
|
||||
@@ -128,17 +174,16 @@ func TestCertificatesRenew(t *testing.T) {
|
||||
|
||||
func TestRemoveDuplicates(t *testing.T) {
|
||||
now := time.Now()
|
||||
fooCert, fooKey, _ := generateKeyPair("foo.com", now)
|
||||
foo24Cert, foo24Key, _ := generateKeyPair("foo.com", now.Add(24*time.Hour))
|
||||
foo48Cert, foo48Key, _ := generateKeyPair("foo.com", now.Add(48*time.Hour))
|
||||
barCert, barKey, _ := generateKeyPair("bar.com", now)
|
||||
fooCert, fooKey, _ := generate.KeyPair("foo.com", now)
|
||||
foo24Cert, foo24Key, _ := generate.KeyPair("foo.com", now.Add(24*time.Hour))
|
||||
foo48Cert, foo48Key, _ := generate.KeyPair("foo.com", now.Add(48*time.Hour))
|
||||
barCert, barKey, _ := generate.KeyPair("bar.com", now)
|
||||
domainsCertificates := DomainsCertificates{
|
||||
lock: sync.RWMutex{},
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: Domain{
|
||||
Main: "foo.com",
|
||||
SANs: []string{}},
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com"},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo.com",
|
||||
CertURL: "url",
|
||||
@@ -148,9 +193,8 @@ func TestRemoveDuplicates(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: Domain{
|
||||
Main: "foo.com",
|
||||
SANs: []string{}},
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com"},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo.com",
|
||||
CertURL: "url",
|
||||
@@ -160,9 +204,8 @@ func TestRemoveDuplicates(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: Domain{
|
||||
Main: "foo.com",
|
||||
SANs: []string{}},
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com"},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo.com",
|
||||
CertURL: "url",
|
||||
@@ -172,9 +215,8 @@ func TestRemoveDuplicates(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: Domain{
|
||||
Main: "bar.com",
|
||||
SANs: []string{}},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com"},
|
||||
Certificate: &Certificate{
|
||||
Domain: "bar.com",
|
||||
CertURL: "url",
|
||||
@@ -184,9 +226,8 @@ func TestRemoveDuplicates(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: Domain{
|
||||
Main: "foo.com",
|
||||
SANs: []string{}},
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com"},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo.com",
|
||||
CertURL: "url",
|
||||
@@ -259,14 +300,19 @@ llJh9MC0svjevGtNlxJoE3lmEQIhAKXy1wfZ32/XtcrnENPvi6lzxI0T94X7s5pP3aCoPPoJAiEAl
|
||||
cijFkALeQp/qyeXdFld2v9gUN3eCgljgcl0QweRoIc=---`)
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{
|
||||
"new-authz": "https://foo/acme/new-authz",
|
||||
"new-cert": "https://foo/acme/new-cert",
|
||||
"new-reg": "https://foo/acme/new-reg",
|
||||
"revoke-cert": "https://foo/acme/revoke-cert"
|
||||
"GPHhmRVEDas": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
|
||||
"keyChange": "https://foo/acme/key-change",
|
||||
"meta": {
|
||||
"termsOfService": "https://boulder:4431/terms/v7"
|
||||
},
|
||||
"newAccount": "https://foo/acme/new-acct",
|
||||
"newNonce": "https://foo/acme/new-nonce",
|
||||
"newOrder": "https://foo/acme/new-order",
|
||||
"revokeCert": "https://foo/acme/revoke-cert"
|
||||
}`))
|
||||
}))
|
||||
defer ts.Close()
|
||||
a := ACME{DNSProvider: "manual", DelayDontCheckDNS: 10, CAServer: ts.URL}
|
||||
a := ACME{DNSChallenge: &acmeprovider.DNSChallenge{Provider: "manual", DelayBeforeCheck: 10}, CAServer: ts.URL}
|
||||
|
||||
client, err := a.buildACMEClient(account)
|
||||
if err != nil {
|
||||
@@ -280,7 +326,7 @@ cijFkALeQp/qyeXdFld2v9gUN3eCgljgcl0QweRoIc=---`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcme_getProvidedCertificate(t *testing.T) {
|
||||
func TestAcme_getUncheckedCertificates(t *testing.T) {
|
||||
mm := make(map[string]*tls.Certificate)
|
||||
mm["*.containo.us"] = &tls.Certificate{}
|
||||
mm["traefik.acme.io"] = &tls.Certificate{}
|
||||
@@ -288,9 +334,485 @@ func TestAcme_getProvidedCertificate(t *testing.T) {
|
||||
a := ACME{TLSConfig: &tls.Config{NameToCertificate: mm}}
|
||||
|
||||
domains := []string{"traefik.containo.us", "trae.containo.us"}
|
||||
certificate := a.getProvidedCertificate(domains)
|
||||
assert.NotNil(t, certificate)
|
||||
uncheckedDomains := a.getUncheckedDomains(domains, nil)
|
||||
assert.Empty(t, uncheckedDomains)
|
||||
domains = []string{"traefik.acme.io", "trae.acme.io"}
|
||||
certificate = a.getProvidedCertificate(domains)
|
||||
uncheckedDomains = a.getUncheckedDomains(domains, nil)
|
||||
assert.Len(t, uncheckedDomains, 1)
|
||||
domainsCertificates := DomainsCertificates{Certs: []*DomainsCertificate{
|
||||
{
|
||||
tlsCert: &tls.Certificate{},
|
||||
Domains: types.Domain{
|
||||
Main: "*.acme.wtf",
|
||||
SANs: []string{"trae.acme.io"},
|
||||
},
|
||||
},
|
||||
}}
|
||||
account := Account{DomainsCertificate: domainsCertificates}
|
||||
uncheckedDomains = a.getUncheckedDomains(domains, &account)
|
||||
assert.Empty(t, uncheckedDomains)
|
||||
}
|
||||
|
||||
func TestAcme_getProvidedCertificate(t *testing.T) {
|
||||
mm := make(map[string]*tls.Certificate)
|
||||
mm["*.containo.us"] = &tls.Certificate{}
|
||||
mm["traefik.acme.io"] = &tls.Certificate{}
|
||||
|
||||
a := ACME{TLSConfig: &tls.Config{NameToCertificate: mm}}
|
||||
|
||||
domain := "traefik.containo.us"
|
||||
certificate := a.getProvidedCertificate(domain)
|
||||
assert.NotNil(t, certificate)
|
||||
domain = "trae.acme.io"
|
||||
certificate = a.getProvidedCertificate(domain)
|
||||
assert.Nil(t, certificate)
|
||||
}
|
||||
|
||||
func TestAcme_getValidDomain(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
domains []string
|
||||
wildcardAllowed bool
|
||||
dnsChallenge *acmeprovider.DNSChallenge
|
||||
expectedErr string
|
||||
expectedDomains []string
|
||||
}{
|
||||
{
|
||||
desc: "valid wildcard",
|
||||
domains: []string{"*.traefik.wtf"},
|
||||
dnsChallenge: &acmeprovider.DNSChallenge{},
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "",
|
||||
expectedDomains: []string{"*.traefik.wtf"},
|
||||
},
|
||||
{
|
||||
desc: "no wildcard",
|
||||
domains: []string{"traefik.wtf", "foo.traefik.wtf"},
|
||||
dnsChallenge: &acmeprovider.DNSChallenge{},
|
||||
expectedErr: "",
|
||||
wildcardAllowed: true,
|
||||
expectedDomains: []string{"traefik.wtf", "foo.traefik.wtf"},
|
||||
},
|
||||
{
|
||||
desc: "unauthorized wildcard",
|
||||
domains: []string{"*.traefik.wtf"},
|
||||
dnsChallenge: &acmeprovider.DNSChallenge{},
|
||||
wildcardAllowed: false,
|
||||
expectedErr: "unable to generate a wildcard certificate for domain \"*.traefik.wtf\" from a 'Host' rule",
|
||||
expectedDomains: nil,
|
||||
},
|
||||
{
|
||||
desc: "no domain",
|
||||
domains: []string{},
|
||||
dnsChallenge: nil,
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "unable to generate a certificate when no domain is given",
|
||||
expectedDomains: nil,
|
||||
},
|
||||
{
|
||||
desc: "no DNSChallenge",
|
||||
domains: []string{"*.traefik.wtf", "foo.traefik.wtf"},
|
||||
dnsChallenge: nil,
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "unable to generate a wildcard certificate for domain \"*.traefik.wtf,foo.traefik.wtf\" : ACME needs a DNSChallenge",
|
||||
expectedDomains: nil,
|
||||
},
|
||||
{
|
||||
desc: "unauthorized wildcard with SAN",
|
||||
domains: []string{"*.*.traefik.wtf", "foo.traefik.wtf"},
|
||||
dnsChallenge: &acmeprovider.DNSChallenge{},
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "unable to generate a wildcard certificate for domain \"*.*.traefik.wtf,foo.traefik.wtf\" : ACME does not allow '*.*' wildcard domain",
|
||||
expectedDomains: nil,
|
||||
},
|
||||
{
|
||||
desc: "wildcard with SANs",
|
||||
domains: []string{"*.traefik.wtf", "traefik.wtf"},
|
||||
dnsChallenge: &acmeprovider.DNSChallenge{},
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "",
|
||||
expectedDomains: []string{"*.traefik.wtf", "traefik.wtf"},
|
||||
},
|
||||
{
|
||||
desc: "unexpected SANs",
|
||||
domains: []string{"*.traefik.wtf", "*.acme.wtf"},
|
||||
dnsChallenge: &acmeprovider.DNSChallenge{},
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "unable to generate a certificate for domains \"*.traefik.wtf,*.acme.wtf\": SANs can not be a wildcard domain",
|
||||
expectedDomains: nil,
|
||||
},
|
||||
}
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
a := ACME{}
|
||||
if test.dnsChallenge != nil {
|
||||
a.DNSChallenge = test.dnsChallenge
|
||||
}
|
||||
domains, err := a.getValidDomains(test.domains, test.wildcardAllowed)
|
||||
|
||||
if len(test.expectedErr) > 0 {
|
||||
assert.EqualError(t, err, test.expectedErr, "Unexpected error.")
|
||||
} else {
|
||||
assert.Equal(t, len(test.expectedDomains), len(domains), "Unexpected domains.")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcme_getCertificateForDomain(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
domain string
|
||||
dc *DomainsCertificates
|
||||
expected *DomainsCertificate
|
||||
expectedFound bool
|
||||
}{
|
||||
{
|
||||
desc: "non-wildcard exact match",
|
||||
domain: "foo.traefik.wtf",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.traefik.wtf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: &DomainsCertificate{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.traefik.wtf",
|
||||
},
|
||||
},
|
||||
expectedFound: true,
|
||||
},
|
||||
{
|
||||
desc: "non-wildcard no match",
|
||||
domain: "bar.traefik.wtf",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.traefik.wtf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
expectedFound: false,
|
||||
},
|
||||
{
|
||||
desc: "wildcard match",
|
||||
domain: "foo.traefik.wtf",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "*.traefik.wtf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: &DomainsCertificate{
|
||||
Domains: types.Domain{
|
||||
Main: "*.traefik.wtf",
|
||||
},
|
||||
},
|
||||
expectedFound: true,
|
||||
},
|
||||
{
|
||||
desc: "wildcard no match",
|
||||
domain: "foo.traefik.wtf",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "*.bar.traefik.wtf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
expectedFound: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, found := test.dc.getCertificateForDomain(test.domain)
|
||||
assert.Equal(t, test.expectedFound, found)
|
||||
assert.Equal(t, test.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveEmptyCertificates(t *testing.T) {
|
||||
now := time.Now()
|
||||
fooCert, fooKey, _ := generate.KeyPair("foo.com", now)
|
||||
acmeCert, acmeKey, _ := generate.KeyPair("acme.wtf", now.Add(24*time.Hour))
|
||||
barCert, barKey, _ := generate.KeyPair("bar.com", now)
|
||||
testCases := []struct {
|
||||
desc string
|
||||
dc *DomainsCertificates
|
||||
expectedDc *DomainsCertificates
|
||||
}{
|
||||
{
|
||||
desc: "No empty certificate",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: fooCert,
|
||||
PrivateKey: fooKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: barCert,
|
||||
PrivateKey: barKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: fooCert,
|
||||
PrivateKey: fooKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: barCert,
|
||||
PrivateKey: barKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "First certificate is nil",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: barCert,
|
||||
PrivateKey: barKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: nil,
|
||||
PrivateKey: barKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Last certificate is empty",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: fooCert,
|
||||
PrivateKey: fooKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: fooCert,
|
||||
PrivateKey: fooKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "First and last certificates are nil or empty",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Certificate: &Certificate{
|
||||
Certificate: acmeCert,
|
||||
PrivateKey: acmeKey,
|
||||
},
|
||||
Domains: types.Domain{
|
||||
Main: "acme.wtf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "All certificates are nil or empty",
|
||||
dc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: types.Domain{
|
||||
Main: "foo24.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Certificate: &Certificate{},
|
||||
Domains: types.Domain{
|
||||
Main: "bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDc: &DomainsCertificates{
|
||||
Certs: []*DomainsCertificate{},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
a := &Account{DomainsCertificate: *test.dc}
|
||||
a.Init()
|
||||
|
||||
assert.Equal(t, len(test.expectedDc.Certs), len(a.DomainsCertificate.Certs))
|
||||
sort.Sort(&a.DomainsCertificate)
|
||||
sort.Sort(test.expectedDc)
|
||||
for key, value := range test.expectedDc.Certs {
|
||||
assert.Equal(t, value.Domains.Main, a.DomainsCertificate.Certs[key].Domains.Main)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
var _ acme.ChallengeProviderTimeout = (*challengeProvider)(nil)
|
||||
|
||||
type challengeProvider struct {
|
||||
store cluster.Store
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
account := c.store.Get().(*Account)
|
||||
if account.ChallengeCerts == nil {
|
||||
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(safe.OperationWithRecover(operation), ebo, notify)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting cert: %v", err)
|
||||
return nil, false
|
||||
}
|
||||
return result, true
|
||||
}
|
||||
|
||||
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()
|
||||
transaction, object, err := c.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account := object.(*Account)
|
||||
if account.ChallengeCerts == nil {
|
||||
account.ChallengeCerts = map[string]*ChallengeCert{}
|
||||
}
|
||||
account.ChallengeCerts[domain] = &cert
|
||||
return transaction.Commit(account)
|
||||
}
|
||||
|
||||
func (c *challengeProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
log.Debugf("Challenge CleanUp %s", domain)
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
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
|
||||
}
|
||||
92
acme/challenge_http_provider.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
var _ acme.ChallengeProviderTimeout = (*challengeHTTPProvider)(nil)
|
||||
|
||||
type challengeHTTPProvider struct {
|
||||
store cluster.Store
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func (c *challengeHTTPProvider) getTokenValue(token, domain string) []byte {
|
||||
log.Debugf("Looking for an existing ACME challenge for token %v...", token)
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
account := c.store.Get().(*Account)
|
||||
if account.HTTPChallenge == nil {
|
||||
return []byte{}
|
||||
}
|
||||
var result []byte
|
||||
operation := func() error {
|
||||
var ok bool
|
||||
if result, ok = account.HTTPChallenge[token][domain]; !ok {
|
||||
return fmt.Errorf("cannot find challenge for token %v", token)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Error getting challenge for token retrying in %s", time)
|
||||
}
|
||||
ebo := backoff.NewExponentialBackOff()
|
||||
ebo.MaxElapsedTime = 60 * time.Second
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting challenge for token: %v", err)
|
||||
return []byte{}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *challengeHTTPProvider) Present(domain, token, keyAuth string) error {
|
||||
log.Debugf("Challenge Present %s", domain)
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
transaction, object, err := c.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account := object.(*Account)
|
||||
if account.HTTPChallenge == nil {
|
||||
account.HTTPChallenge = map[string]map[string][]byte{}
|
||||
}
|
||||
if _, ok := account.HTTPChallenge[token]; !ok {
|
||||
account.HTTPChallenge[token] = map[string][]byte{}
|
||||
}
|
||||
account.HTTPChallenge[token][domain] = []byte(keyAuth)
|
||||
return transaction.Commit(account)
|
||||
}
|
||||
|
||||
func (c *challengeHTTPProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
log.Debugf("Challenge CleanUp %s", domain)
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
transaction, object, err := c.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account := object.(*Account)
|
||||
if _, ok := account.HTTPChallenge[token]; ok {
|
||||
if _, domainOk := account.HTTPChallenge[token][domain]; domainOk {
|
||||
delete(account.HTTPChallenge[token], domain)
|
||||
}
|
||||
if len(account.HTTPChallenge[token]) == 0 {
|
||||
delete(account.HTTPChallenge, token)
|
||||
}
|
||||
}
|
||||
return transaction.Commit(account)
|
||||
}
|
||||
|
||||
func (c *challengeHTTPProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return 60 * time.Second, 5 * time.Second
|
||||
}
|
||||
133
acme/crypto.go
@@ -1,133 +0,0 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
func generateDefaultCertificate() (*tls.Certificate, error) {
|
||||
randomBytes := make([]byte, 100)
|
||||
_, err := rand.Read(randomBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zBytes := sha256.Sum256(randomBytes)
|
||||
z := hex.EncodeToString(zBytes[:sha256.Size])
|
||||
domain := fmt.Sprintf("%s.%s.traefik.default", z[:32], z[32:])
|
||||
|
||||
certPEM, keyPEM, err := generateKeyPair(domain, time.Time{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certificate, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &certificate, nil
|
||||
}
|
||||
|
||||
func generateKeyPair(domain string, expiration time.Time) ([]byte, []byte, error) {
|
||||
rsaPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaPrivKey)})
|
||||
|
||||
certPEM, err := generatePemCert(rsaPrivKey, domain, expiration)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return certPEM, keyPEM, nil
|
||||
}
|
||||
|
||||
func generatePemCert(privKey *rsa.PrivateKey, domain string, expiration time.Time) ([]byte, error) {
|
||||
derBytes, err := generateDerCert(privKey, expiration, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil
|
||||
}
|
||||
|
||||
func generateDerCert(privKey *rsa.PrivateKey, expiration time.Time, domain string) ([]byte, error) {
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if expiration.IsZero() {
|
||||
expiration = time.Now().Add(365)
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "TRAEFIK DEFAULT CERT",
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: expiration,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment,
|
||||
BasicConstraintsValid: true,
|
||||
DNSNames: []string{domain},
|
||||
}
|
||||
|
||||
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, time.Time{})
|
||||
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)}
|
||||
case *x509.CertificateRequest:
|
||||
pemBlock = &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: key.Raw}
|
||||
case []byte:
|
||||
pemBlock = &pem.Block{Type: "CERTIFICATE", Bytes: []byte(data.([]byte))}
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(pemBlock)
|
||||
}
|
||||
@@ -2,22 +2,16 @@ package acme
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/provider/acme"
|
||||
)
|
||||
|
||||
var _ cluster.Store = (*LocalStore)(nil)
|
||||
|
||||
// LocalStore is a store using a file as storage
|
||||
type LocalStore struct {
|
||||
file string
|
||||
storageLock sync.RWMutex
|
||||
account *Account
|
||||
file string
|
||||
}
|
||||
|
||||
// NewLocalStore create a LocalStore
|
||||
@@ -27,71 +21,155 @@ func NewLocalStore(file string) *LocalStore {
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
// Get loads file into store and returns the Account
|
||||
func (s *LocalStore) Get() (*Account, error) {
|
||||
account := &Account{}
|
||||
|
||||
err := checkPermissions(s.file)
|
||||
hasData, err := acme.CheckFile(s.file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := os.Open(s.file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
if hasData {
|
||||
f, err := os.Open(s.file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
file, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(file, &account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
defer f.Close()
|
||||
file, err := ioutil.ReadAll(f)
|
||||
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
|
||||
}
|
||||
// ConvertToNewFormat converts old acme.json format to the new one and store the result into the file (used for the backward compatibility)
|
||||
func ConvertToNewFormat(fileName string) {
|
||||
localStore := acme.NewLocalStore(fileName)
|
||||
|
||||
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")
|
||||
storeAccount, err := localStore.GetAccount()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to read new account, ACME data conversion is not available : %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
storeCertificates, err := localStore.GetCertificates()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to read new certificates, ACME data conversion is not available : %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if storeAccount == nil {
|
||||
localStore := NewLocalStore(fileName)
|
||||
|
||||
account, err := localStore.Get()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to read old account, ACME data conversion is not available : %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert ACME data from old to new format
|
||||
newAccount := &acme.Account{}
|
||||
if account != nil && len(account.Email) > 0 {
|
||||
err = backupACMEFile(fileName, account)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to create a backup for the V1 formatted ACME file: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = account.RemoveAccountV1Values()
|
||||
if err != nil {
|
||||
log.Errorf("Unable to remove ACME Account V1 values during format conversion: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
newAccount = &acme.Account{
|
||||
PrivateKey: account.PrivateKey,
|
||||
Registration: account.Registration,
|
||||
Email: account.Email,
|
||||
}
|
||||
|
||||
var newCertificates []*acme.Certificate
|
||||
for _, cert := range account.DomainsCertificate.Certs {
|
||||
newCertificates = append(newCertificates, &acme.Certificate{
|
||||
Certificate: cert.Certificate.Certificate,
|
||||
Key: cert.Certificate.PrivateKey,
|
||||
Domain: cert.Domains,
|
||||
})
|
||||
}
|
||||
|
||||
// If account is in the old format, storeCertificates is nil or empty and has to be initialized
|
||||
storeCertificates = newCertificates
|
||||
}
|
||||
|
||||
// Store the data in new format into the file even if account is nil
|
||||
// to delete Account in ACME v1 format and keeping the certificates
|
||||
newLocalStore := acme.NewLocalStore(fileName)
|
||||
newLocalStore.SaveDataChan <- &acme.StoredData{Account: newAccount, Certificates: storeCertificates}
|
||||
}
|
||||
}
|
||||
|
||||
func backupACMEFile(originalFileName string, account interface{}) error {
|
||||
// write account to file
|
||||
data, err := json.MarshalIndent(object, "", " ")
|
||||
data, err := json.MarshalIndent(account, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(t.file, data, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.dirty = true
|
||||
return nil
|
||||
return ioutil.WriteFile(originalFileName+".bak", data, 0600)
|
||||
}
|
||||
|
||||
// FromNewToOldFormat converts new acme account to the old one (used for the backward compatibility)
|
||||
func FromNewToOldFormat(fileName string) (*Account, error) {
|
||||
localStore := acme.NewLocalStore(fileName)
|
||||
|
||||
storeAccount, err := localStore.GetAccount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storeCertificates, err := localStore.GetCertificates()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert ACME Account from new to old format
|
||||
// (Needed by the KV stores)
|
||||
var account *Account
|
||||
if storeAccount != nil {
|
||||
account = &Account{
|
||||
Email: storeAccount.Email,
|
||||
PrivateKey: storeAccount.PrivateKey,
|
||||
Registration: storeAccount.Registration,
|
||||
DomainsCertificate: DomainsCertificates{},
|
||||
}
|
||||
}
|
||||
|
||||
// Convert ACME Certificates from new to old format
|
||||
// (Needed by the KV stores)
|
||||
if len(storeCertificates) > 0 {
|
||||
// Account can be nil if data are migrated from new format
|
||||
// with a ACME V1 Account
|
||||
if account == nil {
|
||||
account = &Account{}
|
||||
}
|
||||
for _, cert := range storeCertificates {
|
||||
_, err := account.DomainsCertificate.addCertificateForDomains(&Certificate{
|
||||
Domain: cert.Domain.Main,
|
||||
Certificate: cert.Certificate,
|
||||
PrivateKey: cert.Key,
|
||||
}, cert.Domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
31
acme/localStore_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
acmeFile := "./acme_example.json"
|
||||
|
||||
folder, prefix := filepath.Split(acmeFile)
|
||||
tmpFile, err := ioutil.TempFile(folder, prefix)
|
||||
defer os.Remove(tmpFile.Name())
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
fileContent, err := ioutil.ReadFile(acmeFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
tmpFile.Write(fileContent)
|
||||
|
||||
localStore := NewLocalStore(tmpFile.Name())
|
||||
account, err := localStore.Get()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, account.DomainsCertificate.Certs, 1)
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Check file permissions
|
||||
func checkPermissions(name string) error {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fi.Mode().Perm()&0077 != 0 {
|
||||
return fmt.Errorf("permissions %o for %s are too open, please use 600", fi.Mode().Perm(), name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package acme
|
||||
|
||||
// Do not check file permissions on Windows right now
|
||||
func checkPermissions(name string) error {
|
||||
return nil
|
||||
}
|
||||
136
anonymize/anonymize.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package anonymize
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
|
||||
"github.com/mitchellh/copystructure"
|
||||
"github.com/mvdan/xurls"
|
||||
)
|
||||
|
||||
const (
|
||||
maskShort = "xxxx"
|
||||
maskLarge = maskShort + maskShort + maskShort + maskShort + maskShort + maskShort + maskShort + maskShort
|
||||
)
|
||||
|
||||
// Do configuration.
|
||||
func Do(baseConfig interface{}, indent bool) (string, error) {
|
||||
anomConfig, err := copystructure.Copy(baseConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(anomConfig)
|
||||
|
||||
err = doOnStruct(val)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
configJSON, err := marshal(anomConfig, indent)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return doOnJSON(string(configJSON)), nil
|
||||
}
|
||||
|
||||
func doOnJSON(input string) string {
|
||||
mailExp := regexp.MustCompile(`\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3}"`)
|
||||
return xurls.Relaxed.ReplaceAllString(mailExp.ReplaceAllString(input, maskLarge+"\""), maskLarge)
|
||||
}
|
||||
|
||||
func doOnStruct(field reflect.Value) error {
|
||||
switch field.Kind() {
|
||||
case reflect.Ptr:
|
||||
if !field.IsNil() {
|
||||
if err := doOnStruct(field.Elem()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
for i := 0; i < field.NumField(); i++ {
|
||||
fld := field.Field(i)
|
||||
stField := field.Type().Field(i)
|
||||
if !isExported(stField) {
|
||||
continue
|
||||
}
|
||||
if stField.Tag.Get("export") == "true" {
|
||||
if err := doOnStruct(fld); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := reset(fld, stField.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
for _, key := range field.MapKeys() {
|
||||
if err := doOnStruct(field.MapIndex(key)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
for j := 0; j < field.Len(); j++ {
|
||||
if err := doOnStruct(field.Index(j)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func reset(field reflect.Value, name string) error {
|
||||
if !field.CanSet() {
|
||||
return fmt.Errorf("cannot reset field %s", name)
|
||||
}
|
||||
|
||||
switch field.Kind() {
|
||||
case reflect.Ptr:
|
||||
if !field.IsNil() {
|
||||
field.Set(reflect.Zero(field.Type()))
|
||||
}
|
||||
case reflect.Struct:
|
||||
if field.IsValid() {
|
||||
field.Set(reflect.Zero(field.Type()))
|
||||
}
|
||||
case reflect.String:
|
||||
if field.String() != "" {
|
||||
field.Set(reflect.ValueOf(maskShort))
|
||||
}
|
||||
case reflect.Map:
|
||||
if field.Len() > 0 {
|
||||
field.Set(reflect.MakeMap(field.Type()))
|
||||
}
|
||||
case reflect.Slice:
|
||||
if field.Len() > 0 {
|
||||
field.Set(reflect.MakeSlice(field.Type(), 0, 0))
|
||||
}
|
||||
case reflect.Interface:
|
||||
if !field.IsNil() {
|
||||
return reset(field.Elem(), "")
|
||||
}
|
||||
default:
|
||||
// Primitive type
|
||||
field.Set(reflect.Zero(field.Type()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isExported return true is a struct field is exported, else false
|
||||
func isExported(f reflect.StructField) bool {
|
||||
if f.PkgPath != "" && !f.Anonymous {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func marshal(anomConfig interface{}, indent bool) ([]byte, error) {
|
||||
if indent {
|
||||
return json.MarshalIndent(anomConfig, "", " ")
|
||||
}
|
||||
return json.Marshal(anomConfig)
|
||||
}
|
||||
665
anonymize/anonymize_config_test.go
Normal file
@@ -0,0 +1,665 @@
|
||||
package anonymize
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/provider"
|
||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/provider/boltdb"
|
||||
"github.com/containous/traefik/provider/consul"
|
||||
"github.com/containous/traefik/provider/consulcatalog"
|
||||
"github.com/containous/traefik/provider/docker"
|
||||
"github.com/containous/traefik/provider/dynamodb"
|
||||
"github.com/containous/traefik/provider/ecs"
|
||||
"github.com/containous/traefik/provider/etcd"
|
||||
"github.com/containous/traefik/provider/eureka"
|
||||
"github.com/containous/traefik/provider/file"
|
||||
"github.com/containous/traefik/provider/kubernetes"
|
||||
"github.com/containous/traefik/provider/kv"
|
||||
"github.com/containous/traefik/provider/marathon"
|
||||
"github.com/containous/traefik/provider/mesos"
|
||||
"github.com/containous/traefik/provider/rancher"
|
||||
"github.com/containous/traefik/provider/zk"
|
||||
traefiktls "github.com/containous/traefik/tls"
|
||||
"github.com/containous/traefik/types"
|
||||
)
|
||||
|
||||
func TestDo_globalConfiguration(t *testing.T) {
|
||||
|
||||
config := &configuration.GlobalConfiguration{}
|
||||
|
||||
config.GraceTimeOut = flaeg.Duration(666 * time.Second)
|
||||
config.Debug = true
|
||||
config.CheckNewVersion = true
|
||||
config.AccessLogsFile = "AccessLogsFile"
|
||||
config.AccessLog = &types.AccessLog{
|
||||
FilePath: "AccessLog FilePath",
|
||||
Format: "AccessLog Format",
|
||||
}
|
||||
config.TraefikLogsFile = "TraefikLogsFile"
|
||||
config.LogLevel = "LogLevel"
|
||||
config.EntryPoints = configuration.EntryPoints{
|
||||
"foo": {
|
||||
Address: "foo Address",
|
||||
TLS: &traefiktls.TLS{
|
||||
MinVersion: "foo MinVersion",
|
||||
CipherSuites: []string{"foo CipherSuites 1", "foo CipherSuites 2", "foo CipherSuites 3"},
|
||||
Certificates: traefiktls.Certificates{
|
||||
{CertFile: "CertFile 1", KeyFile: "KeyFile 1"},
|
||||
{CertFile: "CertFile 2", KeyFile: "KeyFile 2"},
|
||||
},
|
||||
ClientCA: traefiktls.ClientCA{
|
||||
Files: []string{"foo ClientCAFiles 1", "foo ClientCAFiles 2", "foo ClientCAFiles 3"},
|
||||
Optional: false,
|
||||
},
|
||||
},
|
||||
Redirect: &types.Redirect{
|
||||
Replacement: "foo Replacement",
|
||||
Regex: "foo Regex",
|
||||
EntryPoint: "foo EntryPoint",
|
||||
},
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
UsersFile: "foo Basic UsersFile",
|
||||
Users: types.Users{"foo Basic Users 1", "foo Basic Users 2", "foo Basic Users 3"},
|
||||
},
|
||||
Digest: &types.Digest{
|
||||
UsersFile: "foo Digest UsersFile",
|
||||
Users: types.Users{"foo Digest Users 1", "foo Digest Users 2", "foo Digest Users 3"},
|
||||
},
|
||||
Forward: &types.Forward{
|
||||
Address: "foo Address",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "foo CA",
|
||||
Cert: "foo Cert",
|
||||
Key: "foo Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
},
|
||||
},
|
||||
WhitelistSourceRange: []string{"foo WhitelistSourceRange 1", "foo WhitelistSourceRange 2", "foo WhitelistSourceRange 3"},
|
||||
Compress: true,
|
||||
ProxyProtocol: &configuration.ProxyProtocol{
|
||||
TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"},
|
||||
},
|
||||
},
|
||||
"fii": {
|
||||
Address: "fii Address",
|
||||
TLS: &traefiktls.TLS{
|
||||
MinVersion: "fii MinVersion",
|
||||
CipherSuites: []string{"fii CipherSuites 1", "fii CipherSuites 2", "fii CipherSuites 3"},
|
||||
Certificates: traefiktls.Certificates{
|
||||
{CertFile: "CertFile 1", KeyFile: "KeyFile 1"},
|
||||
{CertFile: "CertFile 2", KeyFile: "KeyFile 2"},
|
||||
},
|
||||
ClientCA: traefiktls.ClientCA{
|
||||
Files: []string{"fii ClientCAFiles 1", "fii ClientCAFiles 2", "fii ClientCAFiles 3"},
|
||||
Optional: false,
|
||||
},
|
||||
},
|
||||
Redirect: &types.Redirect{
|
||||
Replacement: "fii Replacement",
|
||||
Regex: "fii Regex",
|
||||
EntryPoint: "fii EntryPoint",
|
||||
},
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
UsersFile: "fii Basic UsersFile",
|
||||
Users: types.Users{"fii Basic Users 1", "fii Basic Users 2", "fii Basic Users 3"},
|
||||
},
|
||||
Digest: &types.Digest{
|
||||
UsersFile: "fii Digest UsersFile",
|
||||
Users: types.Users{"fii Digest Users 1", "fii Digest Users 2", "fii Digest Users 3"},
|
||||
},
|
||||
Forward: &types.Forward{
|
||||
Address: "fii Address",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "fii CA",
|
||||
Cert: "fii Cert",
|
||||
Key: "fii Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
},
|
||||
},
|
||||
WhitelistSourceRange: []string{"fii WhitelistSourceRange 1", "fii WhitelistSourceRange 2", "fii WhitelistSourceRange 3"},
|
||||
Compress: true,
|
||||
ProxyProtocol: &configuration.ProxyProtocol{
|
||||
TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
config.Cluster = &types.Cluster{
|
||||
Node: "Cluster Node",
|
||||
Store: &types.Store{
|
||||
Prefix: "Cluster Store Prefix",
|
||||
// ...
|
||||
},
|
||||
}
|
||||
config.Constraints = types.Constraints{
|
||||
{
|
||||
Key: "Constraints Key 1",
|
||||
Regex: "Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "Constraints Key 1",
|
||||
Regex: "Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
}
|
||||
config.ACME = &acme.ACME{
|
||||
Email: "acme Email",
|
||||
Domains: []types.Domain{
|
||||
{
|
||||
Main: "Domains Main",
|
||||
SANs: []string{"Domains acme SANs 1", "Domains acme SANs 2", "Domains acme SANs 3"},
|
||||
},
|
||||
},
|
||||
Storage: "Storage",
|
||||
StorageFile: "StorageFile",
|
||||
OnDemand: true,
|
||||
OnHostRule: true,
|
||||
CAServer: "CAServer",
|
||||
EntryPoint: "EntryPoint",
|
||||
DNSChallenge: &acmeprovider.DNSChallenge{Provider: "DNSProvider"},
|
||||
DelayDontCheckDNS: 666,
|
||||
ACMELogging: true,
|
||||
TLSConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
// ...
|
||||
},
|
||||
}
|
||||
config.DefaultEntryPoints = configuration.DefaultEntryPoints{"DefaultEntryPoints 1", "DefaultEntryPoints 2", "DefaultEntryPoints 3"}
|
||||
config.ProvidersThrottleDuration = flaeg.Duration(666 * time.Second)
|
||||
config.MaxIdleConnsPerHost = 666
|
||||
config.IdleTimeout = flaeg.Duration(666 * time.Second)
|
||||
config.InsecureSkipVerify = true
|
||||
config.RootCAs = traefiktls.RootCAs{"RootCAs 1", "RootCAs 2", "RootCAs 3"}
|
||||
config.Retry = &configuration.Retry{
|
||||
Attempts: 666,
|
||||
}
|
||||
config.HealthCheck = &configuration.HealthCheckConfig{
|
||||
Interval: flaeg.Duration(666 * time.Second),
|
||||
}
|
||||
config.RespondingTimeouts = &configuration.RespondingTimeouts{
|
||||
ReadTimeout: flaeg.Duration(666 * time.Second),
|
||||
WriteTimeout: flaeg.Duration(666 * time.Second),
|
||||
IdleTimeout: flaeg.Duration(666 * time.Second),
|
||||
}
|
||||
config.ForwardingTimeouts = &configuration.ForwardingTimeouts{
|
||||
DialTimeout: flaeg.Duration(666 * time.Second),
|
||||
ResponseHeaderTimeout: flaeg.Duration(666 * time.Second),
|
||||
}
|
||||
config.Docker = &docker.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "docker Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "docker Constraints Key 1",
|
||||
Regex: "docker Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "docker Constraints Key 1",
|
||||
Regex: "docker Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "docker Endpoint",
|
||||
Domain: "docker Domain",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "docker CA",
|
||||
Cert: "docker Cert",
|
||||
Key: "docker Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
ExposedByDefault: true,
|
||||
UseBindPortIP: true,
|
||||
SwarmMode: true,
|
||||
}
|
||||
config.File = &file.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "file Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "file Constraints Key 1",
|
||||
Regex: "file Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "file Constraints Key 1",
|
||||
Regex: "file Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Directory: "file Directory",
|
||||
}
|
||||
config.Web = &configuration.WebCompatibility{
|
||||
Address: "web Address",
|
||||
CertFile: "web CertFile",
|
||||
KeyFile: "web KeyFile",
|
||||
ReadOnly: true,
|
||||
Statistics: &types.Statistics{
|
||||
RecentErrors: 666,
|
||||
},
|
||||
Metrics: &types.Metrics{
|
||||
Prometheus: &types.Prometheus{
|
||||
Buckets: types.Buckets{6.5, 6.6, 6.7},
|
||||
},
|
||||
Datadog: &types.Datadog{
|
||||
Address: "Datadog Address",
|
||||
PushInterval: "Datadog PushInterval",
|
||||
},
|
||||
StatsD: &types.Statsd{
|
||||
Address: "StatsD Address",
|
||||
PushInterval: "StatsD PushInterval",
|
||||
},
|
||||
},
|
||||
Path: "web Path",
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
UsersFile: "web Basic UsersFile",
|
||||
Users: types.Users{"web Basic Users 1", "web Basic Users 2", "web Basic Users 3"},
|
||||
},
|
||||
Digest: &types.Digest{
|
||||
UsersFile: "web Digest UsersFile",
|
||||
Users: types.Users{"web Digest Users 1", "web Digest Users 2", "web Digest Users 3"},
|
||||
},
|
||||
Forward: &types.Forward{
|
||||
Address: "web Address",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "web CA",
|
||||
Cert: "web Cert",
|
||||
Key: "web Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
},
|
||||
},
|
||||
Debug: true,
|
||||
}
|
||||
config.Marathon = &marathon.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "marathon Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "marathon Constraints Key 1",
|
||||
Regex: "marathon Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "marathon Constraints Key 1",
|
||||
Regex: "marathon Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "",
|
||||
Domain: "",
|
||||
ExposedByDefault: true,
|
||||
GroupsAsSubDomains: true,
|
||||
DCOSToken: "",
|
||||
MarathonLBCompatibility: true,
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "marathon CA",
|
||||
Cert: "marathon Cert",
|
||||
Key: "marathon Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
DialerTimeout: flaeg.Duration(666 * time.Second),
|
||||
KeepAlive: flaeg.Duration(666 * time.Second),
|
||||
ForceTaskHostname: true,
|
||||
Basic: &marathon.Basic{
|
||||
HTTPBasicAuthUser: "marathon HTTPBasicAuthUser",
|
||||
HTTPBasicPassword: "marathon HTTPBasicPassword",
|
||||
},
|
||||
RespectReadinessChecks: true,
|
||||
}
|
||||
config.ConsulCatalog = &consulcatalog.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "ConsulCatalog Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "ConsulCatalog Constraints Key 1",
|
||||
Regex: "ConsulCatalog Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "ConsulCatalog Constraints Key 1",
|
||||
Regex: "ConsulCatalog Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "ConsulCatalog Endpoint",
|
||||
Domain: "ConsulCatalog Domain",
|
||||
ExposedByDefault: true,
|
||||
Prefix: "ConsulCatalog Prefix",
|
||||
FrontEndRule: "ConsulCatalog FrontEndRule",
|
||||
}
|
||||
config.Kubernetes = &kubernetes.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "k8s Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "k8s Constraints Key 1",
|
||||
Regex: "k8s Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "k8s Constraints Key 1",
|
||||
Regex: "k8s Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "k8s Endpoint",
|
||||
Token: "k8s Token",
|
||||
CertAuthFilePath: "k8s CertAuthFilePath",
|
||||
DisablePassHostHeaders: true,
|
||||
Namespaces: kubernetes.Namespaces{"k8s Namespaces 1", "k8s Namespaces 2", "k8s Namespaces 3"},
|
||||
LabelSelector: "k8s LabelSelector",
|
||||
}
|
||||
config.Mesos = &mesos.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "mesos Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "mesos Constraints Key 1",
|
||||
Regex: "mesos Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "mesos Constraints Key 1",
|
||||
Regex: "mesos Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "mesos Endpoint",
|
||||
Domain: "mesos Domain",
|
||||
ExposedByDefault: true,
|
||||
GroupsAsSubDomains: true,
|
||||
ZkDetectionTimeout: 666,
|
||||
RefreshSeconds: 666,
|
||||
IPSources: "mesos IPSources",
|
||||
StateTimeoutSecond: 666,
|
||||
Masters: []string{"mesos Masters 1", "mesos Masters 2", "mesos Masters 3"},
|
||||
}
|
||||
config.Eureka = &eureka.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "eureka Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "eureka Constraints Key 1",
|
||||
Regex: "eureka Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "eureka Constraints Key 1",
|
||||
Regex: "eureka Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "eureka Endpoint",
|
||||
Delay: flaeg.Duration(30 * time.Second),
|
||||
RefreshSeconds: flaeg.Duration(30 * time.Second),
|
||||
}
|
||||
config.ECS = &ecs.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "ecs Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "ecs Constraints Key 1",
|
||||
Regex: "ecs Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "ecs Constraints Key 1",
|
||||
Regex: "ecs Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Domain: "ecs Domain",
|
||||
ExposedByDefault: true,
|
||||
RefreshSeconds: 666,
|
||||
Clusters: ecs.Clusters{"ecs Clusters 1", "ecs Clusters 2", "ecs Clusters 3"},
|
||||
Cluster: "ecs Cluster",
|
||||
AutoDiscoverClusters: true,
|
||||
Region: "ecs Region",
|
||||
AccessKeyID: "ecs AccessKeyID",
|
||||
SecretAccessKey: "ecs SecretAccessKey",
|
||||
}
|
||||
config.Rancher = &rancher.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "rancher Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "rancher Constraints Key 1",
|
||||
Regex: "rancher Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "rancher Constraints Key 1",
|
||||
Regex: "rancher Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
APIConfiguration: rancher.APIConfiguration{
|
||||
Endpoint: "rancher Endpoint",
|
||||
AccessKey: "rancher AccessKey",
|
||||
SecretKey: "rancher SecretKey",
|
||||
},
|
||||
API: &rancher.APIConfiguration{
|
||||
Endpoint: "rancher Endpoint",
|
||||
AccessKey: "rancher AccessKey",
|
||||
SecretKey: "rancher SecretKey",
|
||||
},
|
||||
Metadata: &rancher.MetadataConfiguration{
|
||||
IntervalPoll: true,
|
||||
Prefix: "rancher Metadata Prefix",
|
||||
},
|
||||
Domain: "rancher Domain",
|
||||
RefreshSeconds: 666,
|
||||
ExposedByDefault: true,
|
||||
EnableServiceHealthFilter: true,
|
||||
}
|
||||
config.DynamoDB = &dynamodb.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "dynamodb Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "dynamodb Constraints Key 1",
|
||||
Regex: "dynamodb Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "dynamodb Constraints Key 1",
|
||||
Regex: "dynamodb Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
AccessKeyID: "dynamodb AccessKeyID",
|
||||
RefreshSeconds: 666,
|
||||
Region: "dynamodb Region",
|
||||
SecretAccessKey: "dynamodb SecretAccessKey",
|
||||
TableName: "dynamodb TableName",
|
||||
Endpoint: "dynamodb Endpoint",
|
||||
}
|
||||
config.Etcd = &etcd.Provider{
|
||||
Provider: kv.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "etcd Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "etcd Constraints Key 1",
|
||||
Regex: "etcd Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "etcd Constraints Key 1",
|
||||
Regex: "etcd Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "etcd Endpoint",
|
||||
Prefix: "etcd Prefix",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "etcd CA",
|
||||
Cert: "etcd Cert",
|
||||
Key: "etcd Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
Username: "etcd Username",
|
||||
Password: "etcd Password",
|
||||
},
|
||||
}
|
||||
config.Zookeeper = &zk.Provider{
|
||||
Provider: kv.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "zk Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "zk Constraints Key 1",
|
||||
Regex: "zk Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "zk Constraints Key 1",
|
||||
Regex: "zk Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "zk Endpoint",
|
||||
Prefix: "zk Prefix",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "zk CA",
|
||||
Cert: "zk Cert",
|
||||
Key: "zk Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
Username: "zk Username",
|
||||
Password: "zk Password",
|
||||
},
|
||||
}
|
||||
config.Boltdb = &boltdb.Provider{
|
||||
Provider: kv.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "boltdb Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "boltdb Constraints Key 1",
|
||||
Regex: "boltdb Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "boltdb Constraints Key 1",
|
||||
Regex: "boltdb Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "boltdb Endpoint",
|
||||
Prefix: "boltdb Prefix",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "boltdb CA",
|
||||
Cert: "boltdb Cert",
|
||||
Key: "boltdb Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
Username: "boltdb Username",
|
||||
Password: "boltdb Password",
|
||||
},
|
||||
}
|
||||
config.Consul = &consul.Provider{
|
||||
Provider: kv.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "consul Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "consul Constraints Key 1",
|
||||
Regex: "consul Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "consul Constraints Key 1",
|
||||
Regex: "consul Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "consul Endpoint",
|
||||
Prefix: "consul Prefix",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "consul CA",
|
||||
Cert: "consul Cert",
|
||||
Key: "consul Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
Username: "consul Username",
|
||||
Password: "consul Password",
|
||||
},
|
||||
}
|
||||
|
||||
cleanJSON, err := Do(config, true)
|
||||
if err != nil {
|
||||
t.Fatal(err, cleanJSON)
|
||||
}
|
||||
}
|
||||
237
anonymize/anonymize_doOnJSON_test.go
Normal file
@@ -0,0 +1,237 @@
|
||||
package anonymize
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_doOnJSON(t *testing.T) {
|
||||
baseConfiguration := `
|
||||
{
|
||||
"GraceTimeOut": 10000000000,
|
||||
"Debug": false,
|
||||
"CheckNewVersion": true,
|
||||
"AccessLogsFile": "",
|
||||
"TraefikLogsFile": "",
|
||||
"LogLevel": "ERROR",
|
||||
"EntryPoints": {
|
||||
"http": {
|
||||
"Network": "",
|
||||
"Address": ":80",
|
||||
"TLS": null,
|
||||
"Redirect": {
|
||||
"EntryPoint": "https",
|
||||
"Regex": "",
|
||||
"Replacement": ""
|
||||
},
|
||||
"Auth": null,
|
||||
"Compress": false
|
||||
},
|
||||
"https": {
|
||||
"Address": ":443",
|
||||
"TLS": {
|
||||
"MinVersion": "",
|
||||
"CipherSuites": null,
|
||||
"Certificates": null,
|
||||
"ClientCAFiles": null
|
||||
},
|
||||
"Redirect": null,
|
||||
"Auth": null,
|
||||
"Compress": false
|
||||
}
|
||||
},
|
||||
"Cluster": null,
|
||||
"Constraints": [],
|
||||
"ACME": {
|
||||
"Email": "foo@bar.com",
|
||||
"Domains": [
|
||||
{
|
||||
"Main": "foo@bar.com",
|
||||
"SANs": null
|
||||
},
|
||||
{
|
||||
"Main": "foo@bar.com",
|
||||
"SANs": null
|
||||
}
|
||||
],
|
||||
"Storage": "",
|
||||
"StorageFile": "/acme/acme.json",
|
||||
"OnDemand": true,
|
||||
"OnHostRule": true,
|
||||
"CAServer": "",
|
||||
"EntryPoint": "https",
|
||||
"DNSProvider": "",
|
||||
"DelayDontCheckDNS": 0,
|
||||
"ACMELogging": false,
|
||||
"TLSConfig": null
|
||||
},
|
||||
"DefaultEntryPoints": [
|
||||
"https",
|
||||
"http"
|
||||
],
|
||||
"ProvidersThrottleDuration": 2000000000,
|
||||
"MaxIdleConnsPerHost": 200,
|
||||
"IdleTimeout": 180000000000,
|
||||
"InsecureSkipVerify": false,
|
||||
"Retry": null,
|
||||
"HealthCheck": {
|
||||
"Interval": 30000000000
|
||||
},
|
||||
"Docker": null,
|
||||
"File": null,
|
||||
"Web": null,
|
||||
"Marathon": null,
|
||||
"Consul": null,
|
||||
"ConsulCatalog": null,
|
||||
"Etcd": null,
|
||||
"Zookeeper": null,
|
||||
"Boltdb": null,
|
||||
"Kubernetes": null,
|
||||
"Mesos": null,
|
||||
"Eureka": null,
|
||||
"ECS": null,
|
||||
"Rancher": null,
|
||||
"DynamoDB": null,
|
||||
"ConfigFile": "/etc/traefik/traefik.toml"
|
||||
}
|
||||
`
|
||||
expectedConfiguration := `
|
||||
{
|
||||
"GraceTimeOut": 10000000000,
|
||||
"Debug": false,
|
||||
"CheckNewVersion": true,
|
||||
"AccessLogsFile": "",
|
||||
"TraefikLogsFile": "",
|
||||
"LogLevel": "ERROR",
|
||||
"EntryPoints": {
|
||||
"http": {
|
||||
"Network": "",
|
||||
"Address": ":80",
|
||||
"TLS": null,
|
||||
"Redirect": {
|
||||
"EntryPoint": "https",
|
||||
"Regex": "",
|
||||
"Replacement": ""
|
||||
},
|
||||
"Auth": null,
|
||||
"Compress": false
|
||||
},
|
||||
"https": {
|
||||
"Address": ":443",
|
||||
"TLS": {
|
||||
"MinVersion": "",
|
||||
"CipherSuites": null,
|
||||
"Certificates": null,
|
||||
"ClientCAFiles": null
|
||||
},
|
||||
"Redirect": null,
|
||||
"Auth": null,
|
||||
"Compress": false
|
||||
}
|
||||
},
|
||||
"Cluster": null,
|
||||
"Constraints": [],
|
||||
"ACME": {
|
||||
"Email": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"Domains": [
|
||||
{
|
||||
"Main": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"SANs": null
|
||||
},
|
||||
{
|
||||
"Main": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"SANs": null
|
||||
}
|
||||
],
|
||||
"Storage": "",
|
||||
"StorageFile": "/acme/acme.json",
|
||||
"OnDemand": true,
|
||||
"OnHostRule": true,
|
||||
"CAServer": "",
|
||||
"EntryPoint": "https",
|
||||
"DNSProvider": "",
|
||||
"DelayDontCheckDNS": 0,
|
||||
"ACMELogging": false,
|
||||
"TLSConfig": null
|
||||
},
|
||||
"DefaultEntryPoints": [
|
||||
"https",
|
||||
"http"
|
||||
],
|
||||
"ProvidersThrottleDuration": 2000000000,
|
||||
"MaxIdleConnsPerHost": 200,
|
||||
"IdleTimeout": 180000000000,
|
||||
"InsecureSkipVerify": false,
|
||||
"Retry": null,
|
||||
"HealthCheck": {
|
||||
"Interval": 30000000000
|
||||
},
|
||||
"Docker": null,
|
||||
"File": null,
|
||||
"Web": null,
|
||||
"Marathon": null,
|
||||
"Consul": null,
|
||||
"ConsulCatalog": null,
|
||||
"Etcd": null,
|
||||
"Zookeeper": null,
|
||||
"Boltdb": null,
|
||||
"Kubernetes": null,
|
||||
"Mesos": null,
|
||||
"Eureka": null,
|
||||
"ECS": null,
|
||||
"Rancher": null,
|
||||
"DynamoDB": null,
|
||||
"ConfigFile": "/etc/traefik/traefik.toml"
|
||||
}
|
||||
`
|
||||
anomConfiguration := doOnJSON(baseConfiguration)
|
||||
|
||||
if anomConfiguration != expectedConfiguration {
|
||||
t.Errorf("Got %s, want %s.", anomConfiguration, expectedConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_doOnJSON_simple(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input string
|
||||
expectedOutput string
|
||||
}{
|
||||
{
|
||||
name: "email",
|
||||
input: `{
|
||||
"email1": "goo@example.com",
|
||||
"email2": "foo.bargoo@example.com",
|
||||
"email3": "foo.bargoo@example.com.us"
|
||||
}`,
|
||||
expectedOutput: `{
|
||||
"email1": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"email2": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"email3": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "url",
|
||||
input: `{
|
||||
"URL": "foo domain.com foo",
|
||||
"URL": "foo sub.domain.com foo",
|
||||
"URL": "foo sub.sub.domain.com foo",
|
||||
"URL": "foo sub.sub.sub.domain.com.us foo"
|
||||
}`,
|
||||
expectedOutput: `{
|
||||
"URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo",
|
||||
"URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo",
|
||||
"URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo",
|
||||
"URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo"
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
output := doOnJSON(test.input)
|
||||
assert.Equal(t, test.expectedOutput, output)
|
||||
})
|
||||
}
|
||||
}
|
||||
176
anonymize/anonymize_doOnStruct_test.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package anonymize
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type Courgette struct {
|
||||
Ji string
|
||||
Ho string
|
||||
}
|
||||
type Tomate struct {
|
||||
Ji string
|
||||
Ho string
|
||||
}
|
||||
|
||||
type Carotte struct {
|
||||
Name string
|
||||
Value int
|
||||
Courgette Courgette
|
||||
ECourgette Courgette `export:"true"`
|
||||
Pourgette *Courgette
|
||||
EPourgette *Courgette `export:"true"`
|
||||
Aubergine map[string]string
|
||||
EAubergine map[string]string `export:"true"`
|
||||
SAubergine map[string]Tomate
|
||||
ESAubergine map[string]Tomate `export:"true"`
|
||||
PSAubergine map[string]*Tomate
|
||||
EPAubergine map[string]*Tomate `export:"true"`
|
||||
}
|
||||
|
||||
func Test_doOnStruct(t *testing.T) {
|
||||
testCase := []struct {
|
||||
name string
|
||||
base *Carotte
|
||||
expected *Carotte
|
||||
hasError bool
|
||||
}{
|
||||
{
|
||||
name: "primitive",
|
||||
base: &Carotte{
|
||||
Name: "koko",
|
||||
Value: 666,
|
||||
},
|
||||
expected: &Carotte{
|
||||
Name: "xxxx",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "struct",
|
||||
base: &Carotte{
|
||||
Name: "koko",
|
||||
Courgette: Courgette{
|
||||
Ji: "huu",
|
||||
},
|
||||
},
|
||||
expected: &Carotte{
|
||||
Name: "xxxx",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pointer",
|
||||
base: &Carotte{
|
||||
Name: "koko",
|
||||
Pourgette: &Courgette{
|
||||
Ji: "hoo",
|
||||
},
|
||||
},
|
||||
expected: &Carotte{
|
||||
Name: "xxxx",
|
||||
Pourgette: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "export struct",
|
||||
base: &Carotte{
|
||||
Name: "koko",
|
||||
ECourgette: Courgette{
|
||||
Ji: "huu",
|
||||
},
|
||||
},
|
||||
expected: &Carotte{
|
||||
Name: "xxxx",
|
||||
ECourgette: Courgette{
|
||||
Ji: "xxxx",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "export pointer struct",
|
||||
base: &Carotte{
|
||||
Name: "koko",
|
||||
ECourgette: Courgette{
|
||||
Ji: "huu",
|
||||
},
|
||||
},
|
||||
expected: &Carotte{
|
||||
Name: "xxxx",
|
||||
ECourgette: Courgette{
|
||||
Ji: "xxxx",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "export map string/string",
|
||||
base: &Carotte{
|
||||
Name: "koko",
|
||||
EAubergine: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
expected: &Carotte{
|
||||
Name: "xxxx",
|
||||
EAubergine: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "export map string/pointer",
|
||||
base: &Carotte{
|
||||
Name: "koko",
|
||||
EPAubergine: map[string]*Tomate{
|
||||
"foo": {
|
||||
Ji: "fdskljf",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: &Carotte{
|
||||
Name: "xxxx",
|
||||
EPAubergine: map[string]*Tomate{
|
||||
"foo": {
|
||||
Ji: "xxxx",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "export map string/struct (UNSAFE)",
|
||||
base: &Carotte{
|
||||
Name: "koko",
|
||||
ESAubergine: map[string]Tomate{
|
||||
"foo": {
|
||||
Ji: "JiJiJi",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: &Carotte{
|
||||
Name: "xxxx",
|
||||
ESAubergine: map[string]Tomate{
|
||||
"foo": {
|
||||
Ji: "JiJiJi",
|
||||
},
|
||||
},
|
||||
},
|
||||
hasError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCase {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
val := reflect.ValueOf(test.base).Elem()
|
||||
err := doOnStruct(val)
|
||||
if !test.hasError && err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if test.hasError && err == nil {
|
||||
t.Fatal("Got no error but want an error.")
|
||||
}
|
||||
|
||||
assert.EqualValues(t, test.expected, test.base)
|
||||
})
|
||||
}
|
||||
}
|
||||
32
api/dashboard.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/autogen/genstatic"
|
||||
"github.com/elazarl/go-bindata-assetfs"
|
||||
)
|
||||
|
||||
// DashboardHandler expose dashboard routes
|
||||
type DashboardHandler struct{}
|
||||
|
||||
// AddRoutes add dashboard routes on a router
|
||||
func (g DashboardHandler) AddRoutes(router *mux.Router) {
|
||||
// Expose dashboard
|
||||
router.Methods(http.MethodGet).
|
||||
Path("/").
|
||||
HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
http.Redirect(response, request, request.Header.Get("X-Forwarded-Prefix")+"/dashboard/", 302)
|
||||
})
|
||||
|
||||
router.Methods(http.MethodGet).
|
||||
Path("/dashboard/status").
|
||||
HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
http.Redirect(response, request, "/dashboard/", 302)
|
||||
})
|
||||
|
||||
router.Methods(http.MethodGet).
|
||||
PathPrefix("/dashboard/").
|
||||
Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: genstatic.Asset, AssetInfo: genstatic.AssetInfo, AssetDir: genstatic.AssetDir, Prefix: "static"})))
|
||||
}
|
||||
46
api/debug.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"expvar"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"runtime"
|
||||
|
||||
"github.com/containous/mux"
|
||||
)
|
||||
|
||||
func init() {
|
||||
expvar.Publish("Goroutines", expvar.Func(goroutines))
|
||||
}
|
||||
|
||||
func goroutines() interface{} {
|
||||
return runtime.NumGoroutine()
|
||||
}
|
||||
|
||||
// DebugHandler expose debug routes
|
||||
type DebugHandler struct{}
|
||||
|
||||
// AddRoutes add debug routes on a router
|
||||
func (g DebugHandler) AddRoutes(router *mux.Router) {
|
||||
router.Methods(http.MethodGet).Path("/debug/vars").
|
||||
HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
fmt.Fprint(w, "{\n")
|
||||
first := true
|
||||
expvar.Do(func(kv expvar.KeyValue) {
|
||||
if !first {
|
||||
fmt.Fprint(w, ",\n")
|
||||
}
|
||||
first = false
|
||||
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
|
||||
})
|
||||
fmt.Fprint(w, "\n}\n")
|
||||
})
|
||||
|
||||
router.Methods(http.MethodGet).PathPrefix("/debug/pprof/cmdline").HandlerFunc(pprof.Cmdline)
|
||||
router.Methods(http.MethodGet).PathPrefix("/debug/pprof/profile").HandlerFunc(pprof.Profile)
|
||||
router.Methods(http.MethodGet).PathPrefix("/debug/pprof/symbol").HandlerFunc(pprof.Symbol)
|
||||
router.Methods(http.MethodGet).PathPrefix("/debug/pprof/trace").HandlerFunc(pprof.Trace)
|
||||
router.Methods(http.MethodGet).PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
|
||||
}
|
||||
250
api/handler.go
Normal file
@@ -0,0 +1,250 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/containous/traefik/version"
|
||||
thoas_stats "github.com/thoas/stats"
|
||||
"github.com/unrolled/render"
|
||||
)
|
||||
|
||||
// Handler expose api routes
|
||||
type Handler struct {
|
||||
EntryPoint string `description:"EntryPoint" export:"true"`
|
||||
Dashboard bool `description:"Activate dashboard" export:"true"`
|
||||
Debug bool `export:"true"`
|
||||
CurrentConfigurations *safe.Safe
|
||||
Statistics *types.Statistics `description:"Enable more detailed statistics" export:"true"`
|
||||
Stats *thoas_stats.Stats `json:"-"`
|
||||
StatsRecorder *middlewares.StatsRecorder `json:"-"`
|
||||
}
|
||||
|
||||
var (
|
||||
templatesRenderer = render.New(render.Options{
|
||||
Directory: "nowhere",
|
||||
})
|
||||
)
|
||||
|
||||
// AddRoutes add api routes on a router
|
||||
func (p Handler) AddRoutes(router *mux.Router) {
|
||||
if p.Debug {
|
||||
DebugHandler{}.AddRoutes(router)
|
||||
}
|
||||
|
||||
router.Methods(http.MethodGet).Path("/api").HandlerFunc(p.getConfigHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers").HandlerFunc(p.getConfigHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}").HandlerFunc(p.getProviderHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}/backends").HandlerFunc(p.getBackendsHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}/backends/{backend}").HandlerFunc(p.getBackendHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}/backends/{backend}/servers").HandlerFunc(p.getServersHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}/backends/{backend}/servers/{server}").HandlerFunc(p.getServerHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}/frontends").HandlerFunc(p.getFrontendsHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}/frontends/{frontend}").HandlerFunc(p.getFrontendHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}/frontends/{frontend}/routes").HandlerFunc(p.getRoutesHandler)
|
||||
router.Methods(http.MethodGet).Path("/api/providers/{provider}/frontends/{frontend}/routes/{route}").HandlerFunc(p.getRouteHandler)
|
||||
|
||||
// health route
|
||||
router.Methods(http.MethodGet).Path("/health").HandlerFunc(p.getHealthHandler)
|
||||
|
||||
version.Handler{}.AddRoutes(router)
|
||||
|
||||
if p.Dashboard {
|
||||
DashboardHandler{}.AddRoutes(router)
|
||||
}
|
||||
}
|
||||
|
||||
func getProviderIDFromVars(vars map[string]string) string {
|
||||
providerID := vars["provider"]
|
||||
// TODO: Deprecated
|
||||
if providerID == "rest" {
|
||||
providerID = "web"
|
||||
}
|
||||
return providerID
|
||||
}
|
||||
|
||||
func (p Handler) getConfigHandler(response http.ResponseWriter, request *http.Request) {
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, currentConfigurations)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (p Handler) getProviderHandler(response http.ResponseWriter, request *http.Request) {
|
||||
providerID := getProviderIDFromVars(mux.Vars(request))
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, provider)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
} else {
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
}
|
||||
|
||||
func (p Handler) getBackendsHandler(response http.ResponseWriter, request *http.Request) {
|
||||
providerID := getProviderIDFromVars(mux.Vars(request))
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, provider.Backends)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
} else {
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
}
|
||||
|
||||
func (p Handler) getBackendHandler(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
providerID := getProviderIDFromVars(vars)
|
||||
backendID := vars["backend"]
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
if backend, ok := provider.Backends[backendID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, backend)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
|
||||
func (p Handler) getServersHandler(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
providerID := getProviderIDFromVars(vars)
|
||||
backendID := vars["backend"]
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
if backend, ok := provider.Backends[backendID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, backend.Servers)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
|
||||
func (p Handler) getServerHandler(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
providerID := getProviderIDFromVars(vars)
|
||||
backendID := vars["backend"]
|
||||
serverID := vars["server"]
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
if backend, ok := provider.Backends[backendID]; ok {
|
||||
if server, ok := backend.Servers[serverID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, server)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
|
||||
func (p Handler) getFrontendsHandler(response http.ResponseWriter, request *http.Request) {
|
||||
providerID := getProviderIDFromVars(mux.Vars(request))
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, provider.Frontends)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
} else {
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
}
|
||||
|
||||
func (p Handler) getFrontendHandler(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
providerID := getProviderIDFromVars(vars)
|
||||
frontendID := vars["frontend"]
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
if frontend, ok := provider.Frontends[frontendID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, frontend)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
|
||||
func (p Handler) getRoutesHandler(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
providerID := getProviderIDFromVars(vars)
|
||||
frontendID := vars["frontend"]
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
if frontend, ok := provider.Frontends[frontendID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, frontend.Routes)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
|
||||
func (p Handler) getRouteHandler(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
providerID := getProviderIDFromVars(vars)
|
||||
frontendID := vars["frontend"]
|
||||
routeID := vars["route"]
|
||||
|
||||
currentConfigurations := p.CurrentConfigurations.Get().(types.Configurations)
|
||||
if provider, ok := currentConfigurations[providerID]; ok {
|
||||
if frontend, ok := provider.Frontends[frontendID]; ok {
|
||||
if route, ok := frontend.Routes[routeID]; ok {
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, route)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
|
||||
// healthResponse combines data returned by thoas/stats with statistics (if
|
||||
// they are enabled).
|
||||
type healthResponse struct {
|
||||
*thoas_stats.Data
|
||||
*middlewares.Stats
|
||||
}
|
||||
|
||||
func (p *Handler) getHealthHandler(response http.ResponseWriter, request *http.Request) {
|
||||
health := &healthResponse{Data: p.Stats.Data()}
|
||||
if p.StatsRecorder != nil {
|
||||
health.Stats = p.StatsRecorder.Data()
|
||||
}
|
||||
err := templatesRenderer.JSON(response, http.StatusOK, health)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/types"
|
||||
)
|
||||
|
||||
// Forward the authentication to a external server
|
||||
func Forward(forward *types.Forward, w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
httpClient := http.Client{}
|
||||
|
||||
if forward.TLS != nil {
|
||||
tlsConfig, err := forward.TLS.CreateTLSConfig()
|
||||
if err != nil {
|
||||
log.Debugf("Impossible to configure TLS to call %s. Cause %s", forward.Address, err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
httpClient.Transport = &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
forwardReq, err := http.NewRequest(http.MethodGet, forward.Address, nil)
|
||||
if err != nil {
|
||||
log.Debugf("Error calling %s. Cause %s", forward.Address, err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
forwardReq.Header = r.Header
|
||||
|
||||
forwardResponse, forwardErr := httpClient.Do(forwardReq)
|
||||
if forwardErr != nil {
|
||||
log.Debugf("Error calling %s. Cause: %s", forward.Address, forwardErr)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
body, readError := ioutil.ReadAll(forwardResponse.Body)
|
||||
if readError != nil {
|
||||
log.Debugf("Error reading body %s. Cause: %s", forward.Address, readError)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer forwardResponse.Body.Close()
|
||||
|
||||
if forwardResponse.StatusCode < http.StatusOK || forwardResponse.StatusCode >= http.StatusMultipleChoices {
|
||||
log.Debugf("Remote error %s. StatusCode: %d", forward.Address, forwardResponse.StatusCode)
|
||||
w.WriteHeader(forwardResponse.StatusCode)
|
||||
w.Write(body)
|
||||
return
|
||||
}
|
||||
|
||||
r.RequestURI = r.URL.RequestURI()
|
||||
next(w, r)
|
||||
}
|
||||
2390
autogen/gentemplates/gen.go
Normal file
@@ -1,26 +1,22 @@
|
||||
FROM golang:1.9-alpine
|
||||
FROM golang:1.10-alpine
|
||||
|
||||
RUN apk --update upgrade \
|
||||
&& apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
RUN go get github.com/jteeuwen/go-bindata/... \
|
||||
RUN go get github.com/containous/go-bindata/... \
|
||||
&& go get github.com/golang/lint/golint \
|
||||
&& go get github.com/kisielk/errcheck \
|
||||
&& go get github.com/client9/misspell/cmd/misspell \
|
||||
&& go get github.com/mattfarina/glide-hash \
|
||||
&& go get github.com/sgotti/glide-vc
|
||||
&& go get github.com/client9/misspell/cmd/misspell
|
||||
|
||||
# Which docker version to test on
|
||||
ARG DOCKER_VERSION=17.03.2
|
||||
ARG DEP_VERSION=0.4.1
|
||||
|
||||
# Which glide version to test on
|
||||
ARG GLIDE_VERSION=v0.12.3
|
||||
|
||||
# Download glide
|
||||
# Download dep binary to bin folder in $GOPATH
|
||||
RUN mkdir -p /usr/local/bin \
|
||||
&& curl -fL https://github.com/Masterminds/glide/releases/download/${GLIDE_VERSION}/glide-${GLIDE_VERSION}-linux-amd64.tar.gz \
|
||||
| tar -xzC /usr/local/bin --transform 's#^.+/##x'
|
||||
&& curl -fsSL -o /usr/local/bin/dep https://github.com/golang/dep/releases/download/v${DEP_VERSION}/dep-linux-amd64 \
|
||||
&& chmod +x /usr/local/bin/dep
|
||||
|
||||
# Download docker
|
||||
RUN mkdir -p /usr/local/bin \
|
||||
|
||||
@@ -7,12 +7,12 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/abronan/valkeyrie/store"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
@@ -76,7 +76,7 @@ func NewDataStore(ctx context.Context, kvSource staert.KvSource, object Object,
|
||||
|
||||
func (d *Datastore) watchChanges() error {
|
||||
stopCh := make(chan struct{})
|
||||
kvCh, err := d.kv.Watch(d.lockKey, stopCh)
|
||||
kvCh, err := d.kv.Watch(d.lockKey, stopCh, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -119,19 +119,8 @@ func (d *Datastore) watchChanges() error {
|
||||
|
||||
func (d *Datastore) reload() error {
|
||||
log.Debug("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
|
||||
_, err := d.Load()
|
||||
return err
|
||||
}
|
||||
|
||||
// Begin creates a transaction with the KV store.
|
||||
@@ -199,6 +188,10 @@ func (d *Datastore) get() *Metadata {
|
||||
func (d *Datastore) Load() (Object, error) {
|
||||
d.localLock.Lock()
|
||||
defer d.localLock.Unlock()
|
||||
|
||||
// clear Object first, as mapstructure's decoder doesn't have ZeroFields set to true for merging purposes
|
||||
d.meta.Object = d.meta.Object[:0]
|
||||
|
||||
err := d.kv.LoadConfig(d.meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -2,15 +2,22 @@ package cluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/docker/leadership"
|
||||
"github.com/unrolled/render"
|
||||
)
|
||||
|
||||
var templatesRenderer = render.New(render.Options{
|
||||
Directory: "nowhere",
|
||||
})
|
||||
|
||||
// Leadership allows leadership election using a KV store
|
||||
type Leadership struct {
|
||||
*safe.Pool
|
||||
@@ -98,7 +105,32 @@ func (l *Leadership) onElection(elected bool) {
|
||||
}
|
||||
}
|
||||
|
||||
type leaderResponse struct {
|
||||
Leader bool `json:"leader"`
|
||||
}
|
||||
|
||||
func (l *Leadership) getLeaderHandler(response http.ResponseWriter, request *http.Request) {
|
||||
leader := &leaderResponse{Leader: l.IsLeader()}
|
||||
|
||||
status := http.StatusOK
|
||||
if !leader.Leader {
|
||||
// Set status to be `429`, as this will typically cause load balancers to stop sending requests to the instance without removing them from rotation.
|
||||
status = http.StatusTooManyRequests
|
||||
}
|
||||
|
||||
err := templatesRenderer.JSON(response, status, leader)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// IsLeader returns true if current node is leader
|
||||
func (l *Leadership) IsLeader() bool {
|
||||
return l.leader.Get().(bool)
|
||||
}
|
||||
|
||||
// AddRoutes add dashboard routes on a router
|
||||
func (l *Leadership) AddRoutes(router *mux.Router) {
|
||||
// Expose cluster leader
|
||||
router.Methods(http.MethodGet).Path("/api/cluster/leader").HandlerFunc(l.getLeaderHandler)
|
||||
}
|
||||
|
||||
171
cmd/bug/bug.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package bug
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"text/template"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/anonymize"
|
||||
"github.com/containous/traefik/cmd"
|
||||
"github.com/containous/traefik/cmd/version"
|
||||
)
|
||||
|
||||
const (
|
||||
bugTracker = "https://github.com/containous/traefik/issues/new"
|
||||
bugTemplate = `<!--
|
||||
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
|
||||
The issue tracker is for reporting bugs and feature requests only.
|
||||
For end-user related support questions, refer to one of the following:
|
||||
|
||||
- Stack Overflow (using the "traefik" tag): https://stackoverflow.com/questions/tagged/traefik
|
||||
- the Traefik community Slack channel: https://traefik.herokuapp.com
|
||||
|
||||
-->
|
||||
|
||||
### Do you want to request a *feature* or report a *bug*?
|
||||
|
||||
(If you intend to ask a support question: **DO NOT FILE AN ISSUE**.
|
||||
Use [Stack Overflow](https://stackoverflow.com/questions/tagged/traefik)
|
||||
or [Slack](https://traefik.herokuapp.com) instead.)
|
||||
|
||||
|
||||
|
||||
### What did you do?
|
||||
|
||||
<!--
|
||||
|
||||
HOW TO WRITE A GOOD ISSUE?
|
||||
|
||||
- Respect the issue template as more as possible.
|
||||
- If it's possible use the command ` + "`" + "traefik bug" + "`" + `. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||
- The title must be short and descriptive.
|
||||
- Explain the conditions which led you to write this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||
|
||||
-->
|
||||
|
||||
|
||||
### What did you expect to see?
|
||||
|
||||
|
||||
|
||||
### What did you see instead?
|
||||
|
||||
|
||||
|
||||
### Output of ` + "`" + `traefik version` + "`" + `: (_What version of Traefik are you using?_)
|
||||
|
||||
` + "```" + `
|
||||
{{.Version}}
|
||||
` + "```" + `
|
||||
|
||||
### What is your environment & configuration (arguments, toml, provider, platform, ...)?
|
||||
|
||||
` + "```" + `json
|
||||
{{.Configuration}}
|
||||
` + "```" + `
|
||||
|
||||
<!--
|
||||
Add more configuration information here.
|
||||
-->
|
||||
|
||||
### If applicable, please paste the log output at DEBUG level (` + "`" + `--logLevel=DEBUG` + "`" + ` switch)
|
||||
|
||||
` + "```" + `
|
||||
(paste your output here)
|
||||
` + "```" + `
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
// NewCmd builds a new Bug command
|
||||
func NewCmd(traefikConfiguration *cmd.TraefikConfiguration, traefikPointersConfiguration *cmd.TraefikConfiguration) *flaeg.Command {
|
||||
|
||||
//version Command init
|
||||
return &flaeg.Command{
|
||||
Name: "bug",
|
||||
Description: `Report an issue on Traefik bugtracker`,
|
||||
Config: traefikConfiguration,
|
||||
DefaultPointersConfig: traefikPointersConfiguration,
|
||||
Run: runCmd(traefikConfiguration),
|
||||
Metadata: map[string]string{
|
||||
"parseAllSources": "true",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func runCmd(traefikConfiguration *cmd.TraefikConfiguration) func() error {
|
||||
return func() error {
|
||||
|
||||
body, err := createReport(traefikConfiguration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sendReport(body)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func createReport(traefikConfiguration *cmd.TraefikConfiguration) (string, error) {
|
||||
var versionPrint bytes.Buffer
|
||||
if err := version.GetPrint(&versionPrint); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("bug").Parse(bugTemplate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
config, err := anonymize.Do(traefikConfiguration, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
v := struct {
|
||||
Version string
|
||||
Configuration string
|
||||
}{
|
||||
Version: versionPrint.String(),
|
||||
Configuration: config,
|
||||
}
|
||||
|
||||
var bug bytes.Buffer
|
||||
if err := tmpl.Execute(&bug, v); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return bug.String(), nil
|
||||
}
|
||||
|
||||
func sendReport(body string) {
|
||||
URL := bugTracker + "?body=" + url.QueryEscape(body)
|
||||
if err := openBrowser(URL); err != nil {
|
||||
fmt.Printf("Please file a new issue at %s using this template:\n\n", bugTracker)
|
||||
fmt.Print(body)
|
||||
}
|
||||
}
|
||||
|
||||
func openBrowser(URL string) error {
|
||||
var err error
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
err = exec.Command("xdg-open", URL).Start()
|
||||
case "windows":
|
||||
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", URL).Start()
|
||||
case "darwin":
|
||||
err = exec.Command("open", URL).Start()
|
||||
default:
|
||||
err = fmt.Errorf("unsupported platform")
|
||||
}
|
||||
return err
|
||||
}
|
||||
67
cmd/bug/bug_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package bug
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/anonymize"
|
||||
"github.com/containous/traefik/cmd"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/provider/file"
|
||||
"github.com/containous/traefik/tls"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_createReport(t *testing.T) {
|
||||
traefikConfiguration := &cmd.TraefikConfiguration{
|
||||
ConfigFile: "FOO",
|
||||
GlobalConfiguration: configuration.GlobalConfiguration{
|
||||
EntryPoints: configuration.EntryPoints{
|
||||
"goo": &configuration.EntryPoint{
|
||||
Address: "hoo.bar",
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
UsersFile: "foo Basic UsersFile",
|
||||
Users: types.Users{"foo Basic Users 1", "foo Basic Users 2", "foo Basic Users 3"},
|
||||
},
|
||||
Digest: &types.Digest{
|
||||
UsersFile: "foo Digest UsersFile",
|
||||
Users: types.Users{"foo Digest Users 1", "foo Digest Users 2", "foo Digest Users 3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
File: &file.Provider{
|
||||
Directory: "BAR",
|
||||
},
|
||||
RootCAs: tls.RootCAs{"fllf"},
|
||||
},
|
||||
}
|
||||
|
||||
report, err := createReport(traefikConfiguration)
|
||||
assert.NoError(t, err, report)
|
||||
|
||||
// exported anonymous configuration
|
||||
assert.NotContains(t, "web Basic Users ", report)
|
||||
assert.NotContains(t, "foo Digest Users ", report)
|
||||
assert.NotContains(t, "hoo.bar", report)
|
||||
}
|
||||
|
||||
func Test_anonymize_traefikConfiguration(t *testing.T) {
|
||||
traefikConfiguration := &cmd.TraefikConfiguration{
|
||||
ConfigFile: "FOO",
|
||||
GlobalConfiguration: configuration.GlobalConfiguration{
|
||||
EntryPoints: configuration.EntryPoints{
|
||||
"goo": &configuration.EntryPoint{
|
||||
Address: "hoo.bar",
|
||||
},
|
||||
},
|
||||
File: &file.Provider{
|
||||
Directory: "BAR",
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := anonymize.Do(traefikConfiguration, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "hoo.bar", traefikConfiguration.GlobalConfiguration.EntryPoints["goo"].Address)
|
||||
}
|
||||
324
cmd/configuration.go
Normal file
@@ -0,0 +1,324 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik-extra-service-fabric"
|
||||
"github.com/containous/traefik/api"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/middlewares/accesslog"
|
||||
"github.com/containous/traefik/middlewares/tracing"
|
||||
"github.com/containous/traefik/middlewares/tracing/jaeger"
|
||||
"github.com/containous/traefik/middlewares/tracing/zipkin"
|
||||
"github.com/containous/traefik/ping"
|
||||
"github.com/containous/traefik/provider/boltdb"
|
||||
"github.com/containous/traefik/provider/consul"
|
||||
"github.com/containous/traefik/provider/consulcatalog"
|
||||
"github.com/containous/traefik/provider/docker"
|
||||
"github.com/containous/traefik/provider/dynamodb"
|
||||
"github.com/containous/traefik/provider/ecs"
|
||||
"github.com/containous/traefik/provider/etcd"
|
||||
"github.com/containous/traefik/provider/eureka"
|
||||
"github.com/containous/traefik/provider/file"
|
||||
"github.com/containous/traefik/provider/kubernetes"
|
||||
"github.com/containous/traefik/provider/marathon"
|
||||
"github.com/containous/traefik/provider/mesos"
|
||||
"github.com/containous/traefik/provider/rancher"
|
||||
"github.com/containous/traefik/provider/rest"
|
||||
"github.com/containous/traefik/provider/zk"
|
||||
"github.com/containous/traefik/types"
|
||||
sf "github.com/jjcollinge/servicefabric"
|
||||
)
|
||||
|
||||
// TraefikConfiguration holds GlobalConfiguration and other stuff
|
||||
type TraefikConfiguration struct {
|
||||
configuration.GlobalConfiguration `mapstructure:",squash" export:"true"`
|
||||
ConfigFile string `short:"c" description:"Configuration file to use (TOML)." export:"true"`
|
||||
}
|
||||
|
||||
// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
|
||||
func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
// default Docker
|
||||
var defaultDocker docker.Provider
|
||||
defaultDocker.Watch = true
|
||||
defaultDocker.ExposedByDefault = true
|
||||
defaultDocker.Endpoint = "unix:///var/run/docker.sock"
|
||||
defaultDocker.SwarmMode = false
|
||||
|
||||
// default File
|
||||
var defaultFile file.Provider
|
||||
defaultFile.Watch = true
|
||||
defaultFile.Filename = "" // needs equivalent to viper.ConfigFileUsed()
|
||||
|
||||
// default Rest
|
||||
var defaultRest rest.Provider
|
||||
defaultRest.EntryPoint = configuration.DefaultInternalEntryPointName
|
||||
|
||||
// TODO: Deprecated - Web provider, use REST provider instead
|
||||
var defaultWeb configuration.WebCompatibility
|
||||
defaultWeb.Address = ":8080"
|
||||
defaultWeb.Statistics = &types.Statistics{
|
||||
RecentErrors: 10,
|
||||
}
|
||||
|
||||
// TODO: Deprecated - default Metrics
|
||||
defaultWeb.Metrics = &types.Metrics{
|
||||
Prometheus: &types.Prometheus{
|
||||
Buckets: types.Buckets{0.1, 0.3, 1.2, 5},
|
||||
EntryPoint: configuration.DefaultInternalEntryPointName,
|
||||
},
|
||||
Datadog: &types.Datadog{
|
||||
Address: "localhost:8125",
|
||||
PushInterval: "10s",
|
||||
},
|
||||
StatsD: &types.Statsd{
|
||||
Address: "localhost:8125",
|
||||
PushInterval: "10s",
|
||||
},
|
||||
InfluxDB: &types.InfluxDB{
|
||||
Address: "localhost:8089",
|
||||
PushInterval: "10s",
|
||||
},
|
||||
}
|
||||
|
||||
// default Marathon
|
||||
var defaultMarathon marathon.Provider
|
||||
defaultMarathon.Watch = true
|
||||
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
|
||||
defaultMarathon.ExposedByDefault = true
|
||||
defaultMarathon.Constraints = types.Constraints{}
|
||||
defaultMarathon.DialerTimeout = flaeg.Duration(60 * time.Second)
|
||||
defaultMarathon.KeepAlive = flaeg.Duration(10 * time.Second)
|
||||
|
||||
// default Consul
|
||||
var defaultConsul consul.Provider
|
||||
defaultConsul.Watch = true
|
||||
defaultConsul.Endpoint = "127.0.0.1:8500"
|
||||
defaultConsul.Prefix = "traefik"
|
||||
defaultConsul.Constraints = types.Constraints{}
|
||||
|
||||
// default CatalogProvider
|
||||
var defaultConsulCatalog consulcatalog.Provider
|
||||
defaultConsulCatalog.Endpoint = "127.0.0.1:8500"
|
||||
defaultConsulCatalog.ExposedByDefault = true
|
||||
defaultConsulCatalog.Constraints = types.Constraints{}
|
||||
defaultConsulCatalog.Prefix = "traefik"
|
||||
defaultConsulCatalog.FrontEndRule = "Host:{{.ServiceName}}.{{.Domain}}"
|
||||
|
||||
// default Etcd
|
||||
var defaultEtcd etcd.Provider
|
||||
defaultEtcd.Watch = true
|
||||
defaultEtcd.Endpoint = "127.0.0.1:2379"
|
||||
defaultEtcd.Prefix = "/traefik"
|
||||
defaultEtcd.Constraints = types.Constraints{}
|
||||
|
||||
// default Zookeeper
|
||||
var defaultZookeeper zk.Provider
|
||||
defaultZookeeper.Watch = true
|
||||
defaultZookeeper.Endpoint = "127.0.0.1:2181"
|
||||
defaultZookeeper.Prefix = "traefik"
|
||||
defaultZookeeper.Constraints = types.Constraints{}
|
||||
|
||||
// default Boltdb
|
||||
var defaultBoltDb boltdb.Provider
|
||||
defaultBoltDb.Watch = true
|
||||
defaultBoltDb.Endpoint = "127.0.0.1:4001"
|
||||
defaultBoltDb.Prefix = "/traefik"
|
||||
defaultBoltDb.Constraints = types.Constraints{}
|
||||
|
||||
// default Kubernetes
|
||||
var defaultKubernetes kubernetes.Provider
|
||||
defaultKubernetes.Watch = true
|
||||
defaultKubernetes.Constraints = types.Constraints{}
|
||||
|
||||
// default Mesos
|
||||
var defaultMesos mesos.Provider
|
||||
defaultMesos.Watch = true
|
||||
defaultMesos.Endpoint = "http://127.0.0.1:5050"
|
||||
defaultMesos.ExposedByDefault = true
|
||||
defaultMesos.Constraints = types.Constraints{}
|
||||
defaultMesos.RefreshSeconds = 30
|
||||
defaultMesos.ZkDetectionTimeout = 30
|
||||
defaultMesos.StateTimeoutSecond = 30
|
||||
|
||||
// default ECS
|
||||
var defaultECS ecs.Provider
|
||||
defaultECS.Watch = true
|
||||
defaultECS.ExposedByDefault = true
|
||||
defaultECS.AutoDiscoverClusters = false
|
||||
defaultECS.Clusters = ecs.Clusters{"default"}
|
||||
defaultECS.RefreshSeconds = 15
|
||||
defaultECS.Constraints = types.Constraints{}
|
||||
|
||||
// default Rancher
|
||||
var defaultRancher rancher.Provider
|
||||
defaultRancher.Watch = true
|
||||
defaultRancher.ExposedByDefault = true
|
||||
defaultRancher.RefreshSeconds = 15
|
||||
|
||||
// default DynamoDB
|
||||
var defaultDynamoDB dynamodb.Provider
|
||||
defaultDynamoDB.Constraints = types.Constraints{}
|
||||
defaultDynamoDB.RefreshSeconds = 15
|
||||
defaultDynamoDB.TableName = "traefik"
|
||||
defaultDynamoDB.Watch = true
|
||||
|
||||
// default Eureka
|
||||
var defaultEureka eureka.Provider
|
||||
defaultEureka.RefreshSeconds = flaeg.Duration(30 * time.Second)
|
||||
|
||||
// default ServiceFabric
|
||||
var defaultServiceFabric servicefabric.Provider
|
||||
defaultServiceFabric.APIVersion = sf.DefaultAPIVersion
|
||||
defaultServiceFabric.RefreshSeconds = 10
|
||||
|
||||
// default Ping
|
||||
var defaultPing = ping.Handler{
|
||||
EntryPoint: "traefik",
|
||||
}
|
||||
|
||||
// default TraefikLog
|
||||
defaultTraefikLog := types.TraefikLog{
|
||||
Format: "common",
|
||||
FilePath: "",
|
||||
}
|
||||
|
||||
// default AccessLog
|
||||
defaultAccessLog := types.AccessLog{
|
||||
Format: accesslog.CommonFormat,
|
||||
FilePath: "",
|
||||
Filters: &types.AccessLogFilters{},
|
||||
Fields: &types.AccessLogFields{
|
||||
DefaultMode: types.AccessLogKeep,
|
||||
Headers: &types.FieldHeaders{
|
||||
DefaultMode: types.AccessLogKeep,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// default HealthCheckConfig
|
||||
healthCheck := configuration.HealthCheckConfig{
|
||||
Interval: flaeg.Duration(configuration.DefaultHealthCheckInterval),
|
||||
}
|
||||
|
||||
// default RespondingTimeouts
|
||||
respondingTimeouts := configuration.RespondingTimeouts{
|
||||
IdleTimeout: flaeg.Duration(configuration.DefaultIdleTimeout),
|
||||
}
|
||||
|
||||
// default ForwardingTimeouts
|
||||
forwardingTimeouts := configuration.ForwardingTimeouts{
|
||||
DialTimeout: flaeg.Duration(configuration.DefaultDialTimeout),
|
||||
}
|
||||
|
||||
// default Tracing
|
||||
defaultTracing := tracing.Tracing{
|
||||
Backend: "jaeger",
|
||||
ServiceName: "traefik",
|
||||
Jaeger: &jaeger.Config{
|
||||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
},
|
||||
Zipkin: &zipkin.Config{
|
||||
HTTPEndpoint: "http://localhost:9411/api/v1/spans",
|
||||
SameSpan: false,
|
||||
ID128Bit: true,
|
||||
Debug: false,
|
||||
},
|
||||
}
|
||||
|
||||
// default LifeCycle
|
||||
defaultLifeCycle := configuration.LifeCycle{
|
||||
GraceTimeOut: flaeg.Duration(configuration.DefaultGraceTimeout),
|
||||
}
|
||||
|
||||
// default ApiConfiguration
|
||||
defaultAPI := api.Handler{
|
||||
EntryPoint: "traefik",
|
||||
Dashboard: true,
|
||||
}
|
||||
defaultAPI.Statistics = &types.Statistics{
|
||||
RecentErrors: 10,
|
||||
}
|
||||
|
||||
// default Metrics
|
||||
defaultMetrics := types.Metrics{
|
||||
Prometheus: &types.Prometheus{
|
||||
Buckets: types.Buckets{0.1, 0.3, 1.2, 5},
|
||||
EntryPoint: configuration.DefaultInternalEntryPointName,
|
||||
},
|
||||
Datadog: &types.Datadog{
|
||||
Address: "localhost:8125",
|
||||
PushInterval: "10s",
|
||||
},
|
||||
StatsD: &types.Statsd{
|
||||
Address: "localhost:8125",
|
||||
PushInterval: "10s",
|
||||
},
|
||||
InfluxDB: &types.InfluxDB{
|
||||
Address: "localhost:8089",
|
||||
PushInterval: "10s",
|
||||
},
|
||||
}
|
||||
|
||||
defaultConfiguration := configuration.GlobalConfiguration{
|
||||
Docker: &defaultDocker,
|
||||
File: &defaultFile,
|
||||
Web: &defaultWeb,
|
||||
Rest: &defaultRest,
|
||||
Marathon: &defaultMarathon,
|
||||
Consul: &defaultConsul,
|
||||
ConsulCatalog: &defaultConsulCatalog,
|
||||
Etcd: &defaultEtcd,
|
||||
Zookeeper: &defaultZookeeper,
|
||||
Boltdb: &defaultBoltDb,
|
||||
Kubernetes: &defaultKubernetes,
|
||||
Mesos: &defaultMesos,
|
||||
ECS: &defaultECS,
|
||||
Rancher: &defaultRancher,
|
||||
Eureka: &defaultEureka,
|
||||
DynamoDB: &defaultDynamoDB,
|
||||
Retry: &configuration.Retry{},
|
||||
HealthCheck: &healthCheck,
|
||||
RespondingTimeouts: &respondingTimeouts,
|
||||
ForwardingTimeouts: &forwardingTimeouts,
|
||||
TraefikLog: &defaultTraefikLog,
|
||||
AccessLog: &defaultAccessLog,
|
||||
LifeCycle: &defaultLifeCycle,
|
||||
Ping: &defaultPing,
|
||||
API: &defaultAPI,
|
||||
Metrics: &defaultMetrics,
|
||||
Tracing: &defaultTracing,
|
||||
}
|
||||
|
||||
return &TraefikConfiguration{
|
||||
GlobalConfiguration: defaultConfiguration,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTraefikConfiguration creates a TraefikConfiguration with default values
|
||||
func NewTraefikConfiguration() *TraefikConfiguration {
|
||||
return &TraefikConfiguration{
|
||||
GlobalConfiguration: configuration.GlobalConfiguration{
|
||||
AccessLogsFile: "",
|
||||
TraefikLogsFile: "",
|
||||
EntryPoints: map[string]*configuration.EntryPoint{},
|
||||
Constraints: types.Constraints{},
|
||||
DefaultEntryPoints: []string{"http"},
|
||||
ProvidersThrottleDuration: flaeg.Duration(2 * time.Second),
|
||||
MaxIdleConnsPerHost: 200,
|
||||
IdleTimeout: flaeg.Duration(0),
|
||||
HealthCheck: &configuration.HealthCheckConfig{
|
||||
Interval: flaeg.Duration(configuration.DefaultHealthCheckInterval),
|
||||
},
|
||||
LifeCycle: &configuration.LifeCycle{
|
||||
GraceTimeOut: flaeg.Duration(configuration.DefaultGraceTimeout),
|
||||
},
|
||||
CheckNewVersion: true,
|
||||
},
|
||||
ConfigFile: "",
|
||||
}
|
||||
}
|
||||
22
cmd/context.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ContextWithSignal create a context cancelled when SIGINT or SIGTERM are notified
|
||||
func ContextWithSignal(ctx context.Context) context.Context {
|
||||
newCtx, cancel := context.WithCancel(ctx)
|
||||
signals := make(chan os.Signal)
|
||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
select {
|
||||
case <-signals:
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
return newCtx
|
||||
}
|
||||
73
cmd/healthcheck/healthcheck.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/cmd"
|
||||
"github.com/containous/traefik/configuration"
|
||||
)
|
||||
|
||||
// NewCmd builds a new HealthCheck command
|
||||
func NewCmd(traefikConfiguration *cmd.TraefikConfiguration, traefikPointersConfiguration *cmd.TraefikConfiguration) *flaeg.Command {
|
||||
return &flaeg.Command{
|
||||
Name: "healthcheck",
|
||||
Description: `Calls traefik /ping to check health (web provider must be enabled)`,
|
||||
Config: traefikConfiguration,
|
||||
DefaultPointersConfig: traefikPointersConfiguration,
|
||||
Run: runCmd(traefikConfiguration),
|
||||
Metadata: map[string]string{
|
||||
"parseAllSources": "true",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func runCmd(traefikConfiguration *cmd.TraefikConfiguration) func() error {
|
||||
return func() error {
|
||||
traefikConfiguration.GlobalConfiguration.SetEffectiveConfiguration(traefikConfiguration.ConfigFile)
|
||||
|
||||
resp, errPing := Do(traefikConfiguration.GlobalConfiguration)
|
||||
if errPing != nil {
|
||||
fmt.Printf("Error calling healthcheck: %s\n", errPing)
|
||||
os.Exit(1)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
fmt.Printf("Bad healthcheck status: %s\n", resp.Status)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("OK: %s\n", resp.Request.URL)
|
||||
os.Exit(0)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Do try to do a healthcheck
|
||||
func Do(globalConfiguration configuration.GlobalConfiguration) (*http.Response, error) {
|
||||
if globalConfiguration.Ping == nil {
|
||||
return nil, errors.New("please enable `ping` to use health check")
|
||||
}
|
||||
pingEntryPoint, ok := globalConfiguration.EntryPoints[globalConfiguration.Ping.EntryPoint]
|
||||
if !ok {
|
||||
return nil, errors.New("missing `ping` entrypoint")
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
protocol := "http"
|
||||
if pingEntryPoint.TLS != nil {
|
||||
protocol = "https"
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
client.Transport = tr
|
||||
}
|
||||
path := "/"
|
||||
if globalConfiguration.Web != nil {
|
||||
path = globalConfiguration.Web.Path
|
||||
}
|
||||
return client.Head(protocol + "://" + pingEntryPoint.Address + path + "ping")
|
||||
}
|
||||
192
cmd/storeconfig/storeconfig.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package storeconfig
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
stdlog "log"
|
||||
"os"
|
||||
|
||||
"github.com/abronan/valkeyrie/store"
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/cmd"
|
||||
"github.com/containous/traefik/log"
|
||||
)
|
||||
|
||||
// NewCmd builds a new StoreConfig command
|
||||
func NewCmd(traefikConfiguration *cmd.TraefikConfiguration, traefikPointersConfiguration *cmd.TraefikConfiguration) *flaeg.Command {
|
||||
return &flaeg.Command{
|
||||
Name: "storeconfig",
|
||||
Description: `Store the static traefik configuration into a Key-value stores. Traefik will not start.`,
|
||||
Config: traefikConfiguration,
|
||||
DefaultPointersConfig: traefikPointersConfiguration,
|
||||
Metadata: map[string]string{
|
||||
"parseAllSources": "true",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Run store config in KV
|
||||
func Run(kv *staert.KvSource, traefikConfiguration *cmd.TraefikConfiguration) func() error {
|
||||
return func() error {
|
||||
if kv == nil {
|
||||
return fmt.Errorf("error using command storeconfig, no Key-value store defined")
|
||||
}
|
||||
|
||||
fileConfig := traefikConfiguration.GlobalConfiguration.File
|
||||
if fileConfig != nil {
|
||||
traefikConfiguration.GlobalConfiguration.File = nil
|
||||
if len(fileConfig.Filename) == 0 && len(fileConfig.Directory) == 0 {
|
||||
fileConfig.Filename = traefikConfiguration.ConfigFile
|
||||
}
|
||||
}
|
||||
|
||||
jsonConf, err := json.Marshal(traefikConfiguration.GlobalConfiguration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdlog.Printf("Storing configuration: %s\n", jsonConf)
|
||||
|
||||
err = kv.StoreConfig(traefikConfiguration.GlobalConfiguration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fileConfig != nil {
|
||||
jsonConf, err = json.Marshal(fileConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stdlog.Printf("Storing file configuration: %s\n", jsonConf)
|
||||
config, err := fileConfig.BuildConfiguration()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stdlog.Print("Writing config to KV")
|
||||
err = kv.StoreConfig(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if traefikConfiguration.GlobalConfiguration.ACME != nil {
|
||||
account := &acme.Account{}
|
||||
|
||||
// Migrate ACME data from file to KV store if needed
|
||||
if len(traefikConfiguration.GlobalConfiguration.ACME.StorageFile) > 0 {
|
||||
account, err = migrateACMEData(traefikConfiguration.GlobalConfiguration.ACME.StorageFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Store the ACME Account into the KV Store
|
||||
meta := cluster.NewMetadata(account)
|
||||
err = meta.Marshall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source := staert.KvSource{
|
||||
Store: kv,
|
||||
Prefix: traefikConfiguration.GlobalConfiguration.ACME.Storage,
|
||||
}
|
||||
|
||||
err = source.StoreConfig(meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Force to delete storagefile
|
||||
return kv.Delete(kv.Prefix + "/acme/storagefile")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// migrateACMEData allows migrating data from acme.json file to KV store in function of the file format
|
||||
func migrateACMEData(fileName string) (*acme.Account, error) {
|
||||
|
||||
f, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
file, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if the storage file is not empty before to get data
|
||||
account := &acme.Account{}
|
||||
if len(file) > 0 {
|
||||
accountFromNewFormat, err := acme.FromNewToOldFormat(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if accountFromNewFormat == nil {
|
||||
// convert ACME json file to KV store (used for backward compatibility)
|
||||
localStore := acme.NewLocalStore(fileName)
|
||||
|
||||
account, err = localStore.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = account.RemoveAccountV1Values()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
account = accountFromNewFormat
|
||||
}
|
||||
} else {
|
||||
log.Warnf("No data will be imported from the storageFile %q because it is empty.", fileName)
|
||||
}
|
||||
|
||||
err = account.Init()
|
||||
return account, err
|
||||
}
|
||||
|
||||
// CreateKvSource creates KvSource
|
||||
// TLS support is enable for Consul and Etcd backends
|
||||
func CreateKvSource(traefikConfiguration *cmd.TraefikConfiguration) (*staert.KvSource, error) {
|
||||
var kv *staert.KvSource
|
||||
var kvStore store.Store
|
||||
var err error
|
||||
|
||||
switch {
|
||||
case traefikConfiguration.Consul != nil:
|
||||
kvStore, err = traefikConfiguration.Consul.CreateStore()
|
||||
kv = &staert.KvSource{
|
||||
Store: kvStore,
|
||||
Prefix: traefikConfiguration.Consul.Prefix,
|
||||
}
|
||||
case traefikConfiguration.Etcd != nil:
|
||||
kvStore, err = traefikConfiguration.Etcd.CreateStore()
|
||||
kv = &staert.KvSource{
|
||||
Store: kvStore,
|
||||
Prefix: traefikConfiguration.Etcd.Prefix,
|
||||
}
|
||||
case traefikConfiguration.Zookeeper != nil:
|
||||
kvStore, err = traefikConfiguration.Zookeeper.CreateStore()
|
||||
kv = &staert.KvSource{
|
||||
Store: kvStore,
|
||||
Prefix: traefikConfiguration.Zookeeper.Prefix,
|
||||
}
|
||||
case traefikConfiguration.Boltdb != nil:
|
||||
kvStore, err = traefikConfiguration.Boltdb.CreateStore()
|
||||
kv = &staert.KvSource{
|
||||
Store: kvStore,
|
||||
Prefix: traefikConfiguration.Boltdb.Prefix,
|
||||
}
|
||||
}
|
||||
return kv, err
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"text/template"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/mvdan/xurls"
|
||||
)
|
||||
|
||||
var (
|
||||
bugtracker = "https://github.com/containous/traefik/issues/new"
|
||||
bugTemplate = `<!--
|
||||
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
|
||||
The issue tracker is for reporting bugs and feature requests only.
|
||||
For end-user related support questions, refer to one of the following:
|
||||
|
||||
- Stack Overflow (using the "traefik" tag): https://stackoverflow.com/questions/tagged/traefik
|
||||
- the Traefik community Slack channel: https://traefik.herokuapp.com
|
||||
|
||||
-->
|
||||
|
||||
### Do you want to request a *feature* or report a *bug*?
|
||||
|
||||
(If you intend to ask a support question: **DO NOT FILE AN ISSUE**.
|
||||
Use [Stack Overflow](https://stackoverflow.com/questions/tagged/traefik)
|
||||
or [Slack](https://traefik.herokuapp.com) instead.)
|
||||
|
||||
|
||||
|
||||
### What did you do?
|
||||
|
||||
<!--
|
||||
|
||||
HOW TO WRITE A GOOD ISSUE?
|
||||
|
||||
- Respect the issue template as more as possible.
|
||||
- If it's possible use the command ` + "`" + "traefik bug" + "`" + `. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||
- The title must be short and descriptive.
|
||||
- Explain the conditions which led you to write this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||
|
||||
-->
|
||||
|
||||
|
||||
### What did you expect to see?
|
||||
|
||||
|
||||
|
||||
### What did you see instead?
|
||||
|
||||
|
||||
|
||||
### Output of ` + "`" + `traefik version` + "`" + `: (_What version of Traefik are you using?_)
|
||||
|
||||
` + "```" + `
|
||||
{{.Version}}
|
||||
` + "```" + `
|
||||
|
||||
### What is your environment & configuration (arguments, toml, provider, platform, ...)?
|
||||
|
||||
` + "```" + `json
|
||||
{{.Configuration}}
|
||||
` + "```" + `
|
||||
|
||||
<!--
|
||||
Add more configuration information here.
|
||||
-->
|
||||
|
||||
### If applicable, please paste the log output in debug mode (` + "`" + `--debug` + "`" + ` switch)
|
||||
|
||||
` + "```" + `
|
||||
(paste your output here)
|
||||
` + "```" + `
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
// newBugCmd builds a new Bug command
|
||||
func newBugCmd(traefikConfiguration interface{}, traefikPointersConfiguration interface{}) *flaeg.Command {
|
||||
|
||||
//version Command init
|
||||
return &flaeg.Command{
|
||||
Name: "bug",
|
||||
Description: `Report an issue on Traefik bugtracker`,
|
||||
Config: traefikConfiguration,
|
||||
DefaultPointersConfig: traefikPointersConfiguration,
|
||||
Run: func() error {
|
||||
var version bytes.Buffer
|
||||
if err := getVersionPrint(&version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("").Parse(bugTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configJSON, err := json.MarshalIndent(traefikConfiguration, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v := struct {
|
||||
Version string
|
||||
Configuration string
|
||||
}{
|
||||
Version: version.String(),
|
||||
Configuration: anonymize(string(configJSON)),
|
||||
}
|
||||
|
||||
var bug bytes.Buffer
|
||||
if err := tmpl.Execute(&bug, v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := bug.String()
|
||||
URL := bugtracker + "?body=" + url.QueryEscape(body)
|
||||
if err := openBrowser(URL); err != nil {
|
||||
fmt.Printf("Please file a new issue at %s using this template:\n\n", bugtracker)
|
||||
fmt.Print(body)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Metadata: map[string]string{
|
||||
"parseAllSources": "true",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func openBrowser(URL string) error {
|
||||
var err error
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
err = exec.Command("xdg-open", URL).Start()
|
||||
case "windows":
|
||||
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", URL).Start()
|
||||
case "darwin":
|
||||
err = exec.Command("open", URL).Start()
|
||||
default:
|
||||
err = fmt.Errorf("unsupported platform")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func anonymize(input string) string {
|
||||
replace := "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\""
|
||||
mailExp := regexp.MustCompile(`\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3}"`)
|
||||
return xurls.Relaxed.ReplaceAllString(mailExp.ReplaceAllString(input, replace), replace)
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_anonymize(t *testing.T) {
|
||||
baseConfiguration := `
|
||||
{
|
||||
"GraceTimeOut": 10000000000,
|
||||
"Debug": false,
|
||||
"CheckNewVersion": true,
|
||||
"AccessLogsFile": "",
|
||||
"TraefikLogsFile": "",
|
||||
"LogLevel": "ERROR",
|
||||
"EntryPoints": {
|
||||
"http": {
|
||||
"Network": "",
|
||||
"Address": ":80",
|
||||
"TLS": null,
|
||||
"Redirect": {
|
||||
"EntryPoint": "https",
|
||||
"Regex": "",
|
||||
"Replacement": ""
|
||||
},
|
||||
"Auth": null,
|
||||
"Compress": false
|
||||
},
|
||||
"https": {
|
||||
"Network": "",
|
||||
"Address": ":443",
|
||||
"TLS": {
|
||||
"MinVersion": "",
|
||||
"CipherSuites": null,
|
||||
"Certificates": null,
|
||||
"ClientCAFiles": null
|
||||
},
|
||||
"Redirect": null,
|
||||
"Auth": null,
|
||||
"Compress": false
|
||||
}
|
||||
},
|
||||
"Cluster": null,
|
||||
"Constraints": [],
|
||||
"ACME": {
|
||||
"Email": "foo@bar.com",
|
||||
"Domains": [
|
||||
{
|
||||
"Main": "foo@bar.com",
|
||||
"SANs": null
|
||||
},
|
||||
{
|
||||
"Main": "foo@bar.com",
|
||||
"SANs": null
|
||||
}
|
||||
],
|
||||
"Storage": "",
|
||||
"StorageFile": "/acme/acme.json",
|
||||
"OnDemand": true,
|
||||
"OnHostRule": true,
|
||||
"CAServer": "",
|
||||
"EntryPoint": "https",
|
||||
"DNSProvider": "",
|
||||
"DelayDontCheckDNS": 0,
|
||||
"ACMELogging": false,
|
||||
"TLSConfig": null
|
||||
},
|
||||
"DefaultEntryPoints": [
|
||||
"https",
|
||||
"http"
|
||||
],
|
||||
"ProvidersThrottleDuration": 2000000000,
|
||||
"MaxIdleConnsPerHost": 200,
|
||||
"IdleTimeout": 180000000000,
|
||||
"InsecureSkipVerify": false,
|
||||
"Retry": null,
|
||||
"HealthCheck": {
|
||||
"Interval": 30000000000
|
||||
},
|
||||
"Docker": null,
|
||||
"File": null,
|
||||
"Web": null,
|
||||
"Marathon": null,
|
||||
"Consul": null,
|
||||
"ConsulCatalog": null,
|
||||
"Etcd": null,
|
||||
"Zookeeper": null,
|
||||
"Boltdb": null,
|
||||
"Kubernetes": null,
|
||||
"Mesos": null,
|
||||
"Eureka": null,
|
||||
"ECS": null,
|
||||
"Rancher": null,
|
||||
"DynamoDB": null,
|
||||
"ConfigFile": "/etc/traefik/traefik.toml"
|
||||
}
|
||||
`
|
||||
expectedConfiguration := `
|
||||
{
|
||||
"GraceTimeOut": 10000000000,
|
||||
"Debug": false,
|
||||
"CheckNewVersion": true,
|
||||
"AccessLogsFile": "",
|
||||
"TraefikLogsFile": "",
|
||||
"LogLevel": "ERROR",
|
||||
"EntryPoints": {
|
||||
"http": {
|
||||
"Network": "",
|
||||
"Address": ":80",
|
||||
"TLS": null,
|
||||
"Redirect": {
|
||||
"EntryPoint": "https",
|
||||
"Regex": "",
|
||||
"Replacement": ""
|
||||
},
|
||||
"Auth": null,
|
||||
"Compress": false
|
||||
},
|
||||
"https": {
|
||||
"Network": "",
|
||||
"Address": ":443",
|
||||
"TLS": {
|
||||
"MinVersion": "",
|
||||
"CipherSuites": null,
|
||||
"Certificates": null,
|
||||
"ClientCAFiles": null
|
||||
},
|
||||
"Redirect": null,
|
||||
"Auth": null,
|
||||
"Compress": false
|
||||
}
|
||||
},
|
||||
"Cluster": null,
|
||||
"Constraints": [],
|
||||
"ACME": {
|
||||
"Email": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"Domains": [
|
||||
{
|
||||
"Main": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"SANs": null
|
||||
},
|
||||
{
|
||||
"Main": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"SANs": null
|
||||
}
|
||||
],
|
||||
"Storage": "",
|
||||
"StorageFile": "/acme/acme.json",
|
||||
"OnDemand": true,
|
||||
"OnHostRule": true,
|
||||
"CAServer": "",
|
||||
"EntryPoint": "https",
|
||||
"DNSProvider": "",
|
||||
"DelayDontCheckDNS": 0,
|
||||
"ACMELogging": false,
|
||||
"TLSConfig": null
|
||||
},
|
||||
"DefaultEntryPoints": [
|
||||
"https",
|
||||
"http"
|
||||
],
|
||||
"ProvidersThrottleDuration": 2000000000,
|
||||
"MaxIdleConnsPerHost": 200,
|
||||
"IdleTimeout": 180000000000,
|
||||
"InsecureSkipVerify": false,
|
||||
"Retry": null,
|
||||
"HealthCheck": {
|
||||
"Interval": 30000000000
|
||||
},
|
||||
"Docker": null,
|
||||
"File": null,
|
||||
"Web": null,
|
||||
"Marathon": null,
|
||||
"Consul": null,
|
||||
"ConsulCatalog": null,
|
||||
"Etcd": null,
|
||||
"Zookeeper": null,
|
||||
"Boltdb": null,
|
||||
"Kubernetes": null,
|
||||
"Mesos": null,
|
||||
"Eureka": null,
|
||||
"ECS": null,
|
||||
"Rancher": null,
|
||||
"DynamoDB": null,
|
||||
"ConfigFile": "/etc/traefik/traefik.toml"
|
||||
}
|
||||
`
|
||||
anomConfiguration := anonymize(baseConfiguration)
|
||||
|
||||
if anomConfiguration != expectedConfiguration {
|
||||
t.Errorf("Got %s, want %s.", anomConfiguration, expectedConfiguration)
|
||||
}
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/middlewares/accesslog"
|
||||
"github.com/containous/traefik/provider/boltdb"
|
||||
"github.com/containous/traefik/provider/consul"
|
||||
"github.com/containous/traefik/provider/docker"
|
||||
"github.com/containous/traefik/provider/dynamodb"
|
||||
"github.com/containous/traefik/provider/ecs"
|
||||
"github.com/containous/traefik/provider/etcd"
|
||||
"github.com/containous/traefik/provider/file"
|
||||
"github.com/containous/traefik/provider/kubernetes"
|
||||
"github.com/containous/traefik/provider/marathon"
|
||||
"github.com/containous/traefik/provider/mesos"
|
||||
"github.com/containous/traefik/provider/rancher"
|
||||
"github.com/containous/traefik/provider/web"
|
||||
"github.com/containous/traefik/provider/zk"
|
||||
"github.com/containous/traefik/types"
|
||||
)
|
||||
|
||||
// TraefikConfiguration holds GlobalConfiguration and other stuff
|
||||
type TraefikConfiguration struct {
|
||||
configuration.GlobalConfiguration `mapstructure:",squash"`
|
||||
ConfigFile string `short:"c" description:"Configuration file to use (TOML)."`
|
||||
}
|
||||
|
||||
// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
|
||||
func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
//default Docker
|
||||
var defaultDocker docker.Provider
|
||||
defaultDocker.Watch = true
|
||||
defaultDocker.ExposedByDefault = true
|
||||
defaultDocker.Endpoint = "unix:///var/run/docker.sock"
|
||||
defaultDocker.SwarmMode = false
|
||||
|
||||
// default File
|
||||
var defaultFile file.Provider
|
||||
defaultFile.Watch = true
|
||||
defaultFile.Filename = "" //needs equivalent to viper.ConfigFileUsed()
|
||||
|
||||
// default Web
|
||||
var defaultWeb web.Provider
|
||||
defaultWeb.Address = ":8080"
|
||||
defaultWeb.Statistics = &types.Statistics{
|
||||
RecentErrors: 10,
|
||||
}
|
||||
|
||||
// default Metrics
|
||||
defaultWeb.Metrics = &types.Metrics{
|
||||
Prometheus: &types.Prometheus{
|
||||
Buckets: types.Buckets{0.1, 0.3, 1.2, 5},
|
||||
},
|
||||
Datadog: &types.Datadog{
|
||||
Address: "localhost:8125",
|
||||
PushInterval: "10s",
|
||||
},
|
||||
StatsD: &types.Statsd{
|
||||
Address: "localhost:8125",
|
||||
PushInterval: "10s",
|
||||
},
|
||||
}
|
||||
|
||||
// default Marathon
|
||||
var defaultMarathon marathon.Provider
|
||||
defaultMarathon.Watch = true
|
||||
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
|
||||
defaultMarathon.ExposedByDefault = true
|
||||
defaultMarathon.Constraints = types.Constraints{}
|
||||
defaultMarathon.DialerTimeout = flaeg.Duration(60 * time.Second)
|
||||
defaultMarathon.KeepAlive = flaeg.Duration(10 * time.Second)
|
||||
|
||||
// default Consul
|
||||
var defaultConsul consul.Provider
|
||||
defaultConsul.Watch = true
|
||||
defaultConsul.Endpoint = "127.0.0.1:8500"
|
||||
defaultConsul.Prefix = "traefik"
|
||||
defaultConsul.Constraints = types.Constraints{}
|
||||
|
||||
// default CatalogProvider
|
||||
var defaultConsulCatalog consul.CatalogProvider
|
||||
defaultConsulCatalog.Endpoint = "127.0.0.1:8500"
|
||||
defaultConsulCatalog.ExposedByDefault = true
|
||||
defaultConsulCatalog.Constraints = types.Constraints{}
|
||||
defaultConsulCatalog.Prefix = "traefik"
|
||||
defaultConsulCatalog.FrontEndRule = "Host:{{.ServiceName}}.{{.Domain}}"
|
||||
|
||||
// default Etcd
|
||||
var defaultEtcd etcd.Provider
|
||||
defaultEtcd.Watch = true
|
||||
defaultEtcd.Endpoint = "127.0.0.1:2379"
|
||||
defaultEtcd.Prefix = "/traefik"
|
||||
defaultEtcd.Constraints = types.Constraints{}
|
||||
|
||||
//default Zookeeper
|
||||
var defaultZookeeper zk.Provider
|
||||
defaultZookeeper.Watch = true
|
||||
defaultZookeeper.Endpoint = "127.0.0.1:2181"
|
||||
defaultZookeeper.Prefix = "/traefik"
|
||||
defaultZookeeper.Constraints = types.Constraints{}
|
||||
|
||||
//default Boltdb
|
||||
var defaultBoltDb boltdb.Provider
|
||||
defaultBoltDb.Watch = true
|
||||
defaultBoltDb.Endpoint = "127.0.0.1:4001"
|
||||
defaultBoltDb.Prefix = "/traefik"
|
||||
defaultBoltDb.Constraints = types.Constraints{}
|
||||
|
||||
//default Kubernetes
|
||||
var defaultKubernetes kubernetes.Provider
|
||||
defaultKubernetes.Watch = true
|
||||
defaultKubernetes.Endpoint = ""
|
||||
defaultKubernetes.LabelSelector = ""
|
||||
defaultKubernetes.Constraints = types.Constraints{}
|
||||
|
||||
// default Mesos
|
||||
var defaultMesos mesos.Provider
|
||||
defaultMesos.Watch = true
|
||||
defaultMesos.Endpoint = "http://127.0.0.1:5050"
|
||||
defaultMesos.ExposedByDefault = true
|
||||
defaultMesos.Constraints = types.Constraints{}
|
||||
defaultMesos.RefreshSeconds = 30
|
||||
defaultMesos.ZkDetectionTimeout = 30
|
||||
defaultMesos.StateTimeoutSecond = 30
|
||||
|
||||
//default ECS
|
||||
var defaultECS ecs.Provider
|
||||
defaultECS.Watch = true
|
||||
defaultECS.ExposedByDefault = true
|
||||
defaultECS.AutoDiscoverClusters = false
|
||||
defaultECS.Clusters = ecs.Clusters{"default"}
|
||||
defaultECS.RefreshSeconds = 15
|
||||
defaultECS.Constraints = types.Constraints{}
|
||||
|
||||
//default Rancher
|
||||
var defaultRancher rancher.Provider
|
||||
defaultRancher.Watch = true
|
||||
defaultRancher.ExposedByDefault = true
|
||||
defaultRancher.RefreshSeconds = 15
|
||||
|
||||
// default DynamoDB
|
||||
var defaultDynamoDB dynamodb.Provider
|
||||
defaultDynamoDB.Constraints = types.Constraints{}
|
||||
defaultDynamoDB.RefreshSeconds = 15
|
||||
defaultDynamoDB.TableName = "traefik"
|
||||
defaultDynamoDB.Watch = true
|
||||
|
||||
// default AccessLog
|
||||
defaultAccessLog := types.AccessLog{
|
||||
Format: accesslog.CommonFormat,
|
||||
FilePath: "",
|
||||
}
|
||||
|
||||
defaultConfiguration := configuration.GlobalConfiguration{
|
||||
Docker: &defaultDocker,
|
||||
File: &defaultFile,
|
||||
Web: &defaultWeb,
|
||||
Marathon: &defaultMarathon,
|
||||
Consul: &defaultConsul,
|
||||
ConsulCatalog: &defaultConsulCatalog,
|
||||
Etcd: &defaultEtcd,
|
||||
Zookeeper: &defaultZookeeper,
|
||||
Boltdb: &defaultBoltDb,
|
||||
Kubernetes: &defaultKubernetes,
|
||||
Mesos: &defaultMesos,
|
||||
ECS: &defaultECS,
|
||||
Rancher: &defaultRancher,
|
||||
DynamoDB: &defaultDynamoDB,
|
||||
Retry: &configuration.Retry{},
|
||||
HealthCheck: &configuration.HealthCheckConfig{},
|
||||
AccessLog: &defaultAccessLog,
|
||||
}
|
||||
|
||||
return &TraefikConfiguration{
|
||||
GlobalConfiguration: defaultConfiguration,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTraefikConfiguration creates a TraefikConfiguration with default values
|
||||
func NewTraefikConfiguration() *TraefikConfiguration {
|
||||
return &TraefikConfiguration{
|
||||
GlobalConfiguration: configuration.GlobalConfiguration{
|
||||
GraceTimeOut: flaeg.Duration(10 * time.Second),
|
||||
AccessLogsFile: "",
|
||||
TraefikLogsFile: "",
|
||||
LogLevel: "ERROR",
|
||||
EntryPoints: map[string]*configuration.EntryPoint{},
|
||||
Constraints: types.Constraints{},
|
||||
DefaultEntryPoints: []string{},
|
||||
ProvidersThrottleDuration: flaeg.Duration(2 * time.Second),
|
||||
MaxIdleConnsPerHost: 200,
|
||||
IdleTimeout: flaeg.Duration(0),
|
||||
HealthCheck: &configuration.HealthCheckConfig{
|
||||
Interval: flaeg.Duration(configuration.DefaultHealthCheckInterval),
|
||||
},
|
||||
RespondingTimeouts: &configuration.RespondingTimeouts{
|
||||
IdleTimeout: flaeg.Duration(configuration.DefaultIdleTimeout),
|
||||
},
|
||||
ForwardingTimeouts: &configuration.ForwardingTimeouts{
|
||||
DialTimeout: flaeg.Duration(configuration.DefaultDialTimeout),
|
||||
},
|
||||
CheckNewVersion: true,
|
||||
},
|
||||
ConfigFile: "",
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,49 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
fmtlog "log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/cmd"
|
||||
"github.com/containous/traefik/cmd/bug"
|
||||
"github.com/containous/traefik/cmd/healthcheck"
|
||||
"github.com/containous/traefik/cmd/storeconfig"
|
||||
cmdVersion "github.com/containous/traefik/cmd/version"
|
||||
"github.com/containous/traefik/collector"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/provider/ecs"
|
||||
"github.com/containous/traefik/provider/kubernetes"
|
||||
"github.com/containous/traefik/provider/rancher"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/server"
|
||||
"github.com/containous/traefik/server/uuid"
|
||||
traefiktls "github.com/containous/traefik/tls"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/containous/traefik/version"
|
||||
"github.com/coreos/go-systemd/daemon"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/satori/go.uuid"
|
||||
"github.com/ogier/pflag"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vulcand/oxy/roundrobin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
// traefik config inits
|
||||
traefikConfiguration := cmd.NewTraefikConfiguration()
|
||||
traefikPointersConfiguration := cmd.NewTraefikDefaultPointersConfiguration()
|
||||
|
||||
//traefik config inits
|
||||
traefikConfiguration := NewTraefikConfiguration()
|
||||
traefikPointersConfiguration := NewTraefikDefaultPointersConfiguration()
|
||||
//traefik Command init
|
||||
// traefik Command init
|
||||
traefikCmd := &flaeg.Command{
|
||||
Name: "traefik",
|
||||
Description: `traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||
@@ -46,271 +51,146 @@ Complete documentation is available at https://traefik.io`,
|
||||
Config: traefikConfiguration,
|
||||
DefaultPointersConfig: traefikPointersConfiguration,
|
||||
Run: func() error {
|
||||
globalConfiguration := traefikConfiguration.GlobalConfiguration
|
||||
if globalConfiguration.File != nil && len(globalConfiguration.File.Filename) == 0 {
|
||||
// no filename, setting to global config file
|
||||
if len(traefikConfiguration.ConfigFile) != 0 {
|
||||
globalConfiguration.File.Filename = traefikConfiguration.ConfigFile
|
||||
} else {
|
||||
log.Errorln("Error using file configuration backend, no filename defined")
|
||||
}
|
||||
}
|
||||
if len(traefikConfiguration.ConfigFile) != 0 {
|
||||
log.Infof("Using TOML configuration file %s", traefikConfiguration.ConfigFile)
|
||||
}
|
||||
run(&globalConfiguration)
|
||||
runCmd(&traefikConfiguration.GlobalConfiguration, traefikConfiguration.ConfigFile)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
//storeconfig Command init
|
||||
var kv *staert.KvSource
|
||||
var err error
|
||||
// storeconfig Command init
|
||||
storeConfigCmd := storeconfig.NewCmd(traefikConfiguration, traefikPointersConfiguration)
|
||||
|
||||
storeConfigCmd := &flaeg.Command{
|
||||
Name: "storeconfig",
|
||||
Description: `Store the static traefik configuration into a Key-value stores. Traefik will not start.`,
|
||||
Config: traefikConfiguration,
|
||||
DefaultPointersConfig: traefikPointersConfiguration,
|
||||
Run: func() error {
|
||||
if kv == nil {
|
||||
return fmt.Errorf("Error using command storeconfig, no Key-value store defined")
|
||||
}
|
||||
jsonConf, err := json.Marshal(traefikConfiguration.GlobalConfiguration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmtlog.Printf("Storing configuration: %s\n", jsonConf)
|
||||
err = kv.StoreConfig(traefikConfiguration.GlobalConfiguration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if traefikConfiguration.GlobalConfiguration.ACME != nil && len(traefikConfiguration.GlobalConfiguration.ACME.StorageFile) > 0 {
|
||||
// convert ACME json file to KV store
|
||||
localStore := acme.NewLocalStore(traefikConfiguration.GlobalConfiguration.ACME.StorageFile)
|
||||
object, err := localStore.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
meta := cluster.NewMetadata(object)
|
||||
err = meta.Marshall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
source := staert.KvSource{
|
||||
Store: kv,
|
||||
Prefix: traefikConfiguration.GlobalConfiguration.ACME.Storage,
|
||||
}
|
||||
err = source.StoreConfig(meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Metadata: map[string]string{
|
||||
"parseAllSources": "true",
|
||||
},
|
||||
}
|
||||
|
||||
healthCheckCmd := &flaeg.Command{
|
||||
Name: "healthcheck",
|
||||
Description: `Calls traefik /ping to check health (web provider must be enabled)`,
|
||||
Config: traefikConfiguration,
|
||||
DefaultPointersConfig: traefikPointersConfiguration,
|
||||
Run: func() error {
|
||||
if traefikConfiguration.Web == nil {
|
||||
fmt.Println("Please enable the web provider to use healtcheck.")
|
||||
os.Exit(1)
|
||||
}
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
protocol := "http"
|
||||
if len(traefikConfiguration.Web.CertFile) > 0 {
|
||||
protocol = "https"
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
client.Transport = tr
|
||||
}
|
||||
resp, err := client.Head(protocol + "://" + traefikConfiguration.Web.Address + "/ping")
|
||||
if err != nil {
|
||||
fmt.Printf("Error calling healthcheck: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
fmt.Printf("Bad healthcheck status: %s\n", resp.Status)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("OK: %s\n", resp.Request.URL)
|
||||
os.Exit(0)
|
||||
return nil
|
||||
},
|
||||
Metadata: map[string]string{
|
||||
"parseAllSources": "true",
|
||||
},
|
||||
}
|
||||
|
||||
//init flaeg source
|
||||
// init flaeg source
|
||||
f := flaeg.New(traefikCmd, os.Args[1:])
|
||||
//add custom parsers
|
||||
// add custom parsers
|
||||
f.AddParser(reflect.TypeOf(configuration.EntryPoints{}), &configuration.EntryPoints{})
|
||||
f.AddParser(reflect.TypeOf(configuration.DefaultEntryPoints{}), &configuration.DefaultEntryPoints{})
|
||||
f.AddParser(reflect.TypeOf(configuration.RootCAs{}), &configuration.RootCAs{})
|
||||
f.AddParser(reflect.TypeOf(traefiktls.RootCAs{}), &traefiktls.RootCAs{})
|
||||
f.AddParser(reflect.TypeOf(types.Constraints{}), &types.Constraints{})
|
||||
f.AddParser(reflect.TypeOf(kubernetes.Namespaces{}), &kubernetes.Namespaces{})
|
||||
f.AddParser(reflect.TypeOf(ecs.Clusters{}), &ecs.Clusters{})
|
||||
f.AddParser(reflect.TypeOf([]acme.Domain{}), &acme.Domains{})
|
||||
f.AddParser(reflect.TypeOf([]types.Domain{}), &types.Domains{})
|
||||
f.AddParser(reflect.TypeOf(types.Buckets{}), &types.Buckets{})
|
||||
f.AddParser(reflect.TypeOf(types.StatusCodes{}), &types.StatusCodes{})
|
||||
f.AddParser(reflect.TypeOf(types.FieldNames{}), &types.FieldNames{})
|
||||
f.AddParser(reflect.TypeOf(types.FieldHeaderNames{}), &types.FieldHeaderNames{})
|
||||
|
||||
//add commands
|
||||
f.AddCommand(newVersionCmd())
|
||||
f.AddCommand(newBugCmd(traefikConfiguration, traefikPointersConfiguration))
|
||||
// add commands
|
||||
f.AddCommand(cmdVersion.NewCmd())
|
||||
f.AddCommand(bug.NewCmd(traefikConfiguration, traefikPointersConfiguration))
|
||||
f.AddCommand(storeConfigCmd)
|
||||
f.AddCommand(healthCheckCmd)
|
||||
f.AddCommand(healthcheck.NewCmd(traefikConfiguration, traefikPointersConfiguration))
|
||||
|
||||
usedCmd, err := f.GetCommand()
|
||||
if err != nil {
|
||||
fmtlog.Println(err)
|
||||
os.Exit(-1)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if _, err := f.Parse(usedCmd); err != nil {
|
||||
if err == pflag.ErrHelp {
|
||||
os.Exit(0)
|
||||
}
|
||||
fmtlog.Printf("Error parsing command: %s\n", err)
|
||||
os.Exit(-1)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
//staert init
|
||||
// staert init
|
||||
s := staert.NewStaert(traefikCmd)
|
||||
//init toml source
|
||||
// init TOML source
|
||||
toml := staert.NewTomlSource("traefik", []string{traefikConfiguration.ConfigFile, "/etc/traefik/", "$HOME/.traefik/", "."})
|
||||
|
||||
//add sources to staert
|
||||
// add sources to staert
|
||||
s.AddSource(toml)
|
||||
s.AddSource(f)
|
||||
if _, err := s.LoadConfig(); err != nil {
|
||||
fmtlog.Println(fmt.Errorf("Error reading TOML config file %s : %s", toml.ConfigFileUsed(), err))
|
||||
os.Exit(-1)
|
||||
fmtlog.Printf("Error reading TOML config file %s : %s\n", toml.ConfigFileUsed(), err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
traefikConfiguration.ConfigFile = toml.ConfigFileUsed()
|
||||
|
||||
kv, err = CreateKvSource(traefikConfiguration)
|
||||
kv, err := storeconfig.CreateKvSource(traefikConfiguration)
|
||||
if err != nil {
|
||||
fmtlog.Printf("Error creating kv store: %s\n", err)
|
||||
os.Exit(-1)
|
||||
os.Exit(1)
|
||||
}
|
||||
storeConfigCmd.Run = storeconfig.Run(kv, traefikConfiguration)
|
||||
|
||||
// IF a KV Store is enable and no sub-command called in args
|
||||
// if a KV Store is enable and no sub-command called in args
|
||||
if kv != nil && usedCmd == traefikCmd {
|
||||
if traefikConfiguration.Cluster == nil {
|
||||
traefikConfiguration.Cluster = &types.Cluster{Node: uuid.NewV4().String()}
|
||||
traefikConfiguration.Cluster = &types.Cluster{Node: uuid.Get()}
|
||||
}
|
||||
if traefikConfiguration.Cluster.Store == nil {
|
||||
traefikConfiguration.Cluster.Store = &types.Store{Prefix: kv.Prefix, Store: kv.Store}
|
||||
}
|
||||
s.AddSource(kv)
|
||||
if _, err := s.LoadConfig(); err != nil {
|
||||
operation := func() error {
|
||||
_, err := s.LoadConfig()
|
||||
return err
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Load config error: %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
fmtlog.Printf("Error loading configuration: %s\n", err)
|
||||
os.Exit(-1)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.Run(); err != nil {
|
||||
fmtlog.Printf("Error running traefik: %s\n", err)
|
||||
os.Exit(-1)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func run(globalConfiguration *configuration.GlobalConfiguration) {
|
||||
fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags)
|
||||
func runCmd(globalConfiguration *configuration.GlobalConfiguration, configFile string) {
|
||||
configureLogging(globalConfiguration)
|
||||
|
||||
if len(configFile) > 0 {
|
||||
log.Infof("Using TOML configuration file %s", configFile)
|
||||
}
|
||||
|
||||
http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment
|
||||
|
||||
if len(globalConfiguration.EntryPoints) == 0 {
|
||||
globalConfiguration.EntryPoints = map[string]*configuration.EntryPoint{"http": {Address: ":80"}}
|
||||
globalConfiguration.DefaultEntryPoints = []string{"http"}
|
||||
if globalConfiguration.AllowMinWeightZero {
|
||||
roundrobin.SetDefaultWeight(0)
|
||||
}
|
||||
|
||||
if globalConfiguration.Rancher != nil {
|
||||
// Ensure backwards compatibility for now
|
||||
if len(globalConfiguration.Rancher.AccessKey) > 0 ||
|
||||
len(globalConfiguration.Rancher.Endpoint) > 0 ||
|
||||
len(globalConfiguration.Rancher.SecretKey) > 0 {
|
||||
globalConfiguration.SetEffectiveConfiguration(configFile)
|
||||
globalConfiguration.ValidateConfiguration()
|
||||
|
||||
if globalConfiguration.Rancher.API == nil {
|
||||
globalConfiguration.Rancher.API = &rancher.APIConfiguration{
|
||||
AccessKey: globalConfiguration.Rancher.AccessKey,
|
||||
SecretKey: globalConfiguration.Rancher.SecretKey,
|
||||
Endpoint: globalConfiguration.Rancher.Endpoint,
|
||||
}
|
||||
}
|
||||
log.Warn("Deprecated configuration found: rancher.[accesskey|secretkey|endpoint]. " +
|
||||
"Please use rancher.api.[accesskey|secretkey|endpoint] instead.")
|
||||
}
|
||||
|
||||
if globalConfiguration.Rancher.Metadata != nil && len(globalConfiguration.Rancher.Metadata.Prefix) == 0 {
|
||||
globalConfiguration.Rancher.Metadata.Prefix = "latest"
|
||||
}
|
||||
}
|
||||
|
||||
if globalConfiguration.Debug {
|
||||
globalConfiguration.LogLevel = "DEBUG"
|
||||
}
|
||||
|
||||
// logging
|
||||
level, err := logrus.ParseLevel(strings.ToLower(globalConfiguration.LogLevel))
|
||||
if err != nil {
|
||||
log.Error("Error getting level", err)
|
||||
}
|
||||
log.SetLevel(level)
|
||||
if len(globalConfiguration.TraefikLogsFile) > 0 {
|
||||
dir := filepath.Dir(globalConfiguration.TraefikLogsFile)
|
||||
|
||||
err := os.MkdirAll(dir, 0755)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create log path %s: %s", dir, err)
|
||||
}
|
||||
|
||||
err = log.OpenFile(globalConfiguration.TraefikLogsFile)
|
||||
defer func() {
|
||||
if err := log.CloseFile(); err != nil {
|
||||
log.Error("Error closing log", err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
log.Error("Error opening file", err)
|
||||
} else {
|
||||
log.SetFormatter(&logrus.TextFormatter{DisableColors: true, FullTimestamp: true, DisableSorting: true})
|
||||
}
|
||||
} else {
|
||||
log.SetFormatter(&logrus.TextFormatter{FullTimestamp: true, DisableSorting: true})
|
||||
}
|
||||
jsonConf, _ := json.Marshal(globalConfiguration)
|
||||
log.Infof("Traefik version %s built on %s", version.Version, version.BuildDate)
|
||||
|
||||
if globalConfiguration.CheckNewVersion {
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
safe.Go(func() {
|
||||
version.CheckNewVersion()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
version.CheckNewVersion()
|
||||
}
|
||||
}
|
||||
})
|
||||
checkNewVersion()
|
||||
}
|
||||
|
||||
stats(globalConfiguration)
|
||||
|
||||
log.Debugf("Global configuration loaded %s", string(jsonConf))
|
||||
svr := server.NewServer(*globalConfiguration)
|
||||
svr.Start()
|
||||
if acme.IsEnabled() {
|
||||
store := acme.NewLocalStore(acme.Get().Storage)
|
||||
acme.Get().Store = &store
|
||||
}
|
||||
svr := server.NewServer(*globalConfiguration, configuration.NewProviderAggregator(globalConfiguration))
|
||||
if acme.IsEnabled() && acme.Get().OnHostRule {
|
||||
acme.Get().SetConfigListenerChan(make(chan types.Configuration))
|
||||
svr.AddListener(acme.Get().ListenConfiguration)
|
||||
}
|
||||
ctx := cmd.ContextWithSignal(context.Background())
|
||||
svr.StartWithContext(ctx)
|
||||
defer svr.Close()
|
||||
|
||||
sent, err := daemon.SdNotify(false, "READY=1")
|
||||
if !sent && err != nil {
|
||||
log.Error("Fail to notify", err)
|
||||
}
|
||||
|
||||
t, err := daemon.SdWatchdogEnabled(false)
|
||||
if err != nil {
|
||||
log.Error("Problem with watchdog", err)
|
||||
@@ -321,48 +201,116 @@ func run(globalConfiguration *configuration.GlobalConfiguration) {
|
||||
safe.Go(func() {
|
||||
tick := time.Tick(t)
|
||||
for range tick {
|
||||
if ok, _ := daemon.SdNotify(false, "WATCHDOG=1"); !ok {
|
||||
log.Error("Fail to tick watchdog")
|
||||
_, errHealthCheck := healthcheck.Do(*globalConfiguration)
|
||||
if globalConfiguration.Ping == nil || errHealthCheck == nil {
|
||||
if ok, _ := daemon.SdNotify(false, "WATCHDOG=1"); !ok {
|
||||
log.Error("Fail to tick watchdog")
|
||||
}
|
||||
} else {
|
||||
log.Error(errHealthCheck)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
svr.Wait()
|
||||
log.Info("Shutting down")
|
||||
logrus.Exit(0)
|
||||
}
|
||||
|
||||
// CreateKvSource creates KvSource
|
||||
// TLS support is enable for Consul and Etcd backends
|
||||
func CreateKvSource(traefikConfiguration *TraefikConfiguration) (*staert.KvSource, error) {
|
||||
var kv *staert.KvSource
|
||||
var kvStore store.Store
|
||||
var err error
|
||||
func configureLogging(globalConfiguration *configuration.GlobalConfiguration) {
|
||||
// configure default log flags
|
||||
fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags)
|
||||
|
||||
switch {
|
||||
case traefikConfiguration.Consul != nil:
|
||||
kvStore, err = traefikConfiguration.Consul.CreateStore()
|
||||
kv = &staert.KvSource{
|
||||
Store: kvStore,
|
||||
Prefix: traefikConfiguration.Consul.Prefix,
|
||||
}
|
||||
case traefikConfiguration.Etcd != nil:
|
||||
kvStore, err = traefikConfiguration.Etcd.CreateStore()
|
||||
kv = &staert.KvSource{
|
||||
Store: kvStore,
|
||||
Prefix: traefikConfiguration.Etcd.Prefix,
|
||||
}
|
||||
case traefikConfiguration.Zookeeper != nil:
|
||||
kvStore, err = traefikConfiguration.Zookeeper.CreateStore()
|
||||
kv = &staert.KvSource{
|
||||
Store: kvStore,
|
||||
Prefix: traefikConfiguration.Zookeeper.Prefix,
|
||||
}
|
||||
case traefikConfiguration.Boltdb != nil:
|
||||
kvStore, err = traefikConfiguration.Boltdb.CreateStore()
|
||||
kv = &staert.KvSource{
|
||||
Store: kvStore,
|
||||
Prefix: traefikConfiguration.Boltdb.Prefix,
|
||||
// configure log level
|
||||
// an explicitly defined log level always has precedence. if none is
|
||||
// given and debug mode is disabled, the default is ERROR, and DEBUG
|
||||
// otherwise.
|
||||
levelStr := strings.ToLower(globalConfiguration.LogLevel)
|
||||
if levelStr == "" {
|
||||
levelStr = "error"
|
||||
if globalConfiguration.Debug {
|
||||
levelStr = "debug"
|
||||
}
|
||||
}
|
||||
level, err := logrus.ParseLevel(levelStr)
|
||||
if err != nil {
|
||||
log.Error("Error getting level", err)
|
||||
}
|
||||
log.SetLevel(level)
|
||||
|
||||
// configure log output file
|
||||
logFile := globalConfiguration.TraefikLogsFile
|
||||
if len(logFile) > 0 {
|
||||
log.Warn("top-level traefikLogsFile has been deprecated -- please use traefiklog.filepath")
|
||||
}
|
||||
if globalConfiguration.TraefikLog != nil && len(globalConfiguration.TraefikLog.FilePath) > 0 {
|
||||
logFile = globalConfiguration.TraefikLog.FilePath
|
||||
}
|
||||
|
||||
// configure log format
|
||||
var formatter logrus.Formatter
|
||||
if globalConfiguration.TraefikLog != nil && globalConfiguration.TraefikLog.Format == "json" {
|
||||
formatter = &logrus.JSONFormatter{}
|
||||
} else {
|
||||
disableColors := len(logFile) > 0
|
||||
formatter = &logrus.TextFormatter{DisableColors: disableColors, FullTimestamp: true, DisableSorting: true}
|
||||
}
|
||||
log.SetFormatter(formatter)
|
||||
|
||||
if len(logFile) > 0 {
|
||||
dir := filepath.Dir(logFile)
|
||||
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
log.Errorf("Failed to create log path %s: %s", dir, err)
|
||||
}
|
||||
|
||||
err = log.OpenFile(logFile)
|
||||
logrus.RegisterExitHandler(func() {
|
||||
if err := log.CloseFile(); err != nil {
|
||||
log.Error("Error closing log", err)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("Error opening file", err)
|
||||
}
|
||||
}
|
||||
return kv, err
|
||||
}
|
||||
|
||||
func checkNewVersion() {
|
||||
ticker := time.Tick(24 * time.Hour)
|
||||
safe.Go(func() {
|
||||
for time.Sleep(10 * time.Minute); ; <-ticker {
|
||||
version.CheckNewVersion()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func stats(globalConfiguration *configuration.GlobalConfiguration) {
|
||||
if globalConfiguration.SendAnonymousUsage {
|
||||
log.Info(`
|
||||
Stats collection is enabled.
|
||||
Many thanks for contributing to Traefik's improvement by allowing us to receive anonymous information from your configuration.
|
||||
Help us improve Traefik by leaving this feature on :)
|
||||
More details on: https://docs.traefik.io/basics/#collected-data
|
||||
`)
|
||||
collect(globalConfiguration)
|
||||
} else {
|
||||
log.Info(`
|
||||
Stats collection is disabled.
|
||||
Help us improve Traefik by turning this feature on :)
|
||||
More details on: https://docs.traefik.io/basics/#collected-data
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
func collect(globalConfiguration *configuration.GlobalConfiguration) {
|
||||
ticker := time.Tick(24 * time.Hour)
|
||||
safe.Go(func() {
|
||||
for time.Sleep(10 * time.Minute); ; <-ticker {
|
||||
if err := collector.Collect(globalConfiguration); err != nil {
|
||||
log.Debug(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"text/template"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/version"
|
||||
)
|
||||
|
||||
var versionTemplate = `Version: {{.Version}}
|
||||
Codename: {{.Codename}}
|
||||
Go version: {{.GoVersion}}
|
||||
Built: {{.BuildTime}}
|
||||
OS/Arch: {{.Os}}/{{.Arch}}`
|
||||
|
||||
// newVersionCmd builds a new Version command
|
||||
func newVersionCmd() *flaeg.Command {
|
||||
|
||||
//version Command init
|
||||
return &flaeg.Command{
|
||||
Name: "version",
|
||||
Description: `Print version`,
|
||||
Config: struct{}{},
|
||||
DefaultPointersConfig: struct{}{},
|
||||
Run: func() error {
|
||||
if err := getVersionPrint(os.Stdout); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Print("\n")
|
||||
return nil
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getVersionPrint(wr io.Writer) error {
|
||||
tmpl, err := template.New("").Parse(versionTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v := struct {
|
||||
Version string
|
||||
Codename string
|
||||
GoVersion string
|
||||
BuildTime string
|
||||
Os string
|
||||
Arch string
|
||||
}{
|
||||
Version: version.Version,
|
||||
Codename: version.Codename,
|
||||
GoVersion: runtime.Version(),
|
||||
BuildTime: version.BuildDate,
|
||||
Os: runtime.GOOS,
|
||||
Arch: runtime.GOARCH,
|
||||
}
|
||||
|
||||
return tmpl.Execute(wr, v)
|
||||
}
|
||||
62
cmd/version/version.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"text/template"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/version"
|
||||
)
|
||||
|
||||
var versionTemplate = `Version: {{.Version}}
|
||||
Codename: {{.Codename}}
|
||||
Go version: {{.GoVersion}}
|
||||
Built: {{.BuildTime}}
|
||||
OS/Arch: {{.Os}}/{{.Arch}}`
|
||||
|
||||
// NewCmd builds a new Version command
|
||||
func NewCmd() *flaeg.Command {
|
||||
return &flaeg.Command{
|
||||
Name: "version",
|
||||
Description: `Print version`,
|
||||
Config: struct{}{},
|
||||
DefaultPointersConfig: struct{}{},
|
||||
Run: func() error {
|
||||
if err := GetPrint(os.Stdout); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Print("\n")
|
||||
return nil
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetPrint write Printable version
|
||||
func GetPrint(wr io.Writer) error {
|
||||
tmpl, err := template.New("").Parse(versionTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v := struct {
|
||||
Version string
|
||||
Codename string
|
||||
GoVersion string
|
||||
BuildTime string
|
||||
Os string
|
||||
Arch string
|
||||
}{
|
||||
Version: version.Version,
|
||||
Codename: version.Codename,
|
||||
GoVersion: runtime.Version(),
|
||||
BuildTime: version.BuildDate,
|
||||
Os: runtime.GOOS,
|
||||
Arch: runtime.GOARCH,
|
||||
}
|
||||
|
||||
return tmpl.Execute(wr, v)
|
||||
}
|
||||
79
collector/collector.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/anonymize"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/version"
|
||||
"github.com/mitchellh/hashstructure"
|
||||
)
|
||||
|
||||
// collectorURL URL where the stats are send
|
||||
const collectorURL = "https://collect.traefik.io/619df80498b60f985d766ce62f912b7c"
|
||||
|
||||
// Collected data
|
||||
type data struct {
|
||||
Version string
|
||||
Codename string
|
||||
BuildDate string
|
||||
Configuration string
|
||||
Hash string
|
||||
}
|
||||
|
||||
// Collect anonymous data.
|
||||
func Collect(globalConfiguration *configuration.GlobalConfiguration) error {
|
||||
anonConfig, err := anonymize.Do(globalConfiguration, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Anonymous stats sent to %s: %s", collectorURL, anonConfig)
|
||||
|
||||
hashConf, err := hashstructure.Hash(globalConfiguration, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := &data{
|
||||
Version: version.Version,
|
||||
Codename: version.Codename,
|
||||
BuildDate: version.BuildDate,
|
||||
Hash: strconv.FormatUint(hashConf, 10),
|
||||
Configuration: base64.StdEncoding.EncodeToString([]byte(anonConfig)),
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
err = json.NewEncoder(buf).Encode(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = makeHTTPClient().Post(collectorURL, "application/json; charset=utf-8", buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func makeHTTPClient() *http.Client {
|
||||
dialer := &net.Dialer{
|
||||
Timeout: configuration.DefaultDialTimeout,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}
|
||||
|
||||
transport := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: dialer.DialContext,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
return &http.Client{Transport: transport}
|
||||
}
|
||||
@@ -1,18 +1,23 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik-extra-service-fabric"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/api"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/middlewares/tracing"
|
||||
"github.com/containous/traefik/middlewares/tracing/jaeger"
|
||||
"github.com/containous/traefik/middlewares/tracing/zipkin"
|
||||
"github.com/containous/traefik/ping"
|
||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/provider/boltdb"
|
||||
"github.com/containous/traefik/provider/consul"
|
||||
"github.com/containous/traefik/provider/consulcatalog"
|
||||
"github.com/containous/traefik/provider/docker"
|
||||
"github.com/containous/traefik/provider/dynamodb"
|
||||
"github.com/containous/traefik/provider/ecs"
|
||||
@@ -23,12 +28,16 @@ import (
|
||||
"github.com/containous/traefik/provider/marathon"
|
||||
"github.com/containous/traefik/provider/mesos"
|
||||
"github.com/containous/traefik/provider/rancher"
|
||||
"github.com/containous/traefik/provider/web"
|
||||
"github.com/containous/traefik/provider/rest"
|
||||
"github.com/containous/traefik/provider/zk"
|
||||
"github.com/containous/traefik/tls"
|
||||
"github.com/containous/traefik/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultInternalEntryPointName the name of the default internal entry point
|
||||
DefaultInternalEntryPointName = "traefik"
|
||||
|
||||
// DefaultHealthCheckInterval is the default health check interval.
|
||||
DefaultHealthCheckInterval = 30 * time.Second
|
||||
|
||||
@@ -37,47 +46,394 @@ const (
|
||||
|
||||
// DefaultIdleTimeout before closing an idle connection.
|
||||
DefaultIdleTimeout = 180 * time.Second
|
||||
|
||||
// DefaultGraceTimeout controls how long Traefik serves pending requests
|
||||
// prior to shutting down.
|
||||
DefaultGraceTimeout = 10 * time.Second
|
||||
|
||||
// DefaultAcmeCAServer is the default ACME API endpoint
|
||||
DefaultAcmeCAServer = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
)
|
||||
|
||||
// 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 flaeg.Duration `short:"g" description:"Duration to give active requests a chance to finish before Traefik stops"`
|
||||
Debug bool `short:"d" description:"Enable debug mode"`
|
||||
CheckNewVersion bool `description:"Periodically check if a new version has been released"`
|
||||
AccessLogsFile string `description:"(Deprecated) Access logs file"` // Deprecated
|
||||
AccessLog *types.AccessLog `description:"Access log settings"`
|
||||
TraefikLogsFile string `description:"Traefik logs file. Stdout is used when omitted or empty"`
|
||||
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;prod/traefik.crt,prod/traefik.key'"`
|
||||
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 flaeg.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"`
|
||||
IdleTimeout flaeg.Duration `description:"(Deprecated) maximum amount of time an idle (keep-alive) connection will remain idle before closing itself."` // Deprecated
|
||||
InsecureSkipVerify bool `description:"Disable SSL certificate verification"`
|
||||
RootCAs RootCAs `description:"Add cert file for self-signed certicate"`
|
||||
Retry *Retry `description:"Enable retry sending request if network error"`
|
||||
HealthCheck *HealthCheckConfig `description:"Health check parameters"`
|
||||
RespondingTimeouts *RespondingTimeouts `description:"Timeouts for incoming requests to the Traefik instance"`
|
||||
ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers"`
|
||||
Docker *docker.Provider `description:"Enable Docker backend with default settings"`
|
||||
File *file.Provider `description:"Enable File backend with default settings"`
|
||||
Web *web.Provider `description:"Enable Web backend with default settings"`
|
||||
Marathon *marathon.Provider `description:"Enable Marathon backend with default settings"`
|
||||
Consul *consul.Provider `description:"Enable Consul backend with default settings"`
|
||||
ConsulCatalog *consul.CatalogProvider `description:"Enable Consul catalog backend with default settings"`
|
||||
Etcd *etcd.Provider `description:"Enable Etcd backend with default settings"`
|
||||
Zookeeper *zk.Provider `description:"Enable Zookeeper backend with default settings"`
|
||||
Boltdb *boltdb.Provider `description:"Enable Boltdb backend with default settings"`
|
||||
Kubernetes *kubernetes.Provider `description:"Enable Kubernetes backend with default settings"`
|
||||
Mesos *mesos.Provider `description:"Enable Mesos backend with default settings"`
|
||||
Eureka *eureka.Provider `description:"Enable Eureka backend with default settings"`
|
||||
ECS *ecs.Provider `description:"Enable ECS backend with default settings"`
|
||||
Rancher *rancher.Provider `description:"Enable Rancher backend with default settings"`
|
||||
DynamoDB *dynamodb.Provider `description:"Enable DynamoDB backend with default settings"`
|
||||
LifeCycle *LifeCycle `description:"Timeouts influencing the server life cycle" export:"true"`
|
||||
GraceTimeOut flaeg.Duration `short:"g" description:"(Deprecated) Duration to give active requests a chance to finish before Traefik stops" export:"true"` // Deprecated
|
||||
Debug bool `short:"d" description:"Enable debug mode" export:"true"`
|
||||
CheckNewVersion bool `description:"Periodically check if a new version has been released" export:"true"`
|
||||
SendAnonymousUsage bool `description:"send periodically anonymous usage statistics" export:"true"`
|
||||
AccessLogsFile string `description:"(Deprecated) Access logs file" export:"true"` // Deprecated
|
||||
AccessLog *types.AccessLog `description:"Access log settings" export:"true"`
|
||||
TraefikLogsFile string `description:"(Deprecated) Traefik logs file. Stdout is used when omitted or empty" export:"true"` // Deprecated
|
||||
TraefikLog *types.TraefikLog `description:"Traefik log settings" export:"true"`
|
||||
Tracing *tracing.Tracing `description:"OpenTracing configuration" export:"true"`
|
||||
LogLevel string `short:"l" description:"Log level" export:"true"`
|
||||
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key;prod/traefik.crt,prod/traefik.key'" export:"true"`
|
||||
Cluster *types.Cluster `description:"Enable clustering" export:"true"`
|
||||
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags" export:"true"`
|
||||
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL" export:"true"`
|
||||
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint" export:"true"`
|
||||
ProvidersThrottleDuration flaeg.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time." export:"true"`
|
||||
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" export:"true"`
|
||||
IdleTimeout flaeg.Duration `description:"(Deprecated) maximum amount of time an idle (keep-alive) connection will remain idle before closing itself." export:"true"` // Deprecated
|
||||
InsecureSkipVerify bool `description:"Disable SSL certificate verification" export:"true"`
|
||||
RootCAs tls.RootCAs `description:"Add cert file for self-signed certificate"`
|
||||
Retry *Retry `description:"Enable retry sending request if network error" export:"true"`
|
||||
HealthCheck *HealthCheckConfig `description:"Health check parameters" export:"true"`
|
||||
RespondingTimeouts *RespondingTimeouts `description:"Timeouts for incoming requests to the Traefik instance" export:"true"`
|
||||
ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers" export:"true"`
|
||||
AllowMinWeightZero bool `description:"Allow weight to take 0 as minimum real value." export:"true"` // Deprecated
|
||||
Web *WebCompatibility `description:"(Deprecated) Enable Web backend with default settings" export:"true"` // Deprecated
|
||||
Docker *docker.Provider `description:"Enable Docker backend with default settings" export:"true"`
|
||||
File *file.Provider `description:"Enable File backend with default settings" export:"true"`
|
||||
Marathon *marathon.Provider `description:"Enable Marathon backend with default settings" export:"true"`
|
||||
Consul *consul.Provider `description:"Enable Consul backend with default settings" export:"true"`
|
||||
ConsulCatalog *consulcatalog.Provider `description:"Enable Consul catalog backend with default settings" export:"true"`
|
||||
Etcd *etcd.Provider `description:"Enable Etcd backend with default settings" export:"true"`
|
||||
Zookeeper *zk.Provider `description:"Enable Zookeeper backend with default settings" export:"true"`
|
||||
Boltdb *boltdb.Provider `description:"Enable Boltdb backend with default settings" export:"true"`
|
||||
Kubernetes *kubernetes.Provider `description:"Enable Kubernetes backend with default settings" export:"true"`
|
||||
Mesos *mesos.Provider `description:"Enable Mesos backend with default settings" export:"true"`
|
||||
Eureka *eureka.Provider `description:"Enable Eureka backend with default settings" export:"true"`
|
||||
ECS *ecs.Provider `description:"Enable ECS backend with default settings" export:"true"`
|
||||
Rancher *rancher.Provider `description:"Enable Rancher backend with default settings" export:"true"`
|
||||
DynamoDB *dynamodb.Provider `description:"Enable DynamoDB backend with default settings" export:"true"`
|
||||
ServiceFabric *servicefabric.Provider `description:"Enable Service Fabric backend with default settings" export:"true"`
|
||||
Rest *rest.Provider `description:"Enable Rest backend with default settings" export:"true"`
|
||||
API *api.Handler `description:"Enable api/dashboard" export:"true"`
|
||||
Metrics *types.Metrics `description:"Enable a metrics exporter" export:"true"`
|
||||
Ping *ping.Handler `description:"Enable ping" export:"true"`
|
||||
}
|
||||
|
||||
// WebCompatibility is a configuration to handle compatibility with deprecated web provider options
|
||||
type WebCompatibility struct {
|
||||
Address string `description:"(Deprecated) Web administration port" export:"true"`
|
||||
CertFile string `description:"(Deprecated) SSL certificate" export:"true"`
|
||||
KeyFile string `description:"(Deprecated) SSL certificate" export:"true"`
|
||||
ReadOnly bool `description:"(Deprecated) Enable read only API" export:"true"`
|
||||
Statistics *types.Statistics `description:"(Deprecated) Enable more detailed statistics" export:"true"`
|
||||
Metrics *types.Metrics `description:"(Deprecated) Enable a metrics exporter" export:"true"`
|
||||
Path string `description:"(Deprecated) Root path for dashboard and API" export:"true"`
|
||||
Auth *types.Auth `export:"true"`
|
||||
Debug bool `export:"true"`
|
||||
}
|
||||
|
||||
func (gc *GlobalConfiguration) handleWebDeprecation() {
|
||||
if gc.Web != nil {
|
||||
log.Warn("web provider configuration is deprecated, you should use these options : api, rest provider, ping and metrics")
|
||||
|
||||
if gc.API != nil || gc.Metrics != nil || gc.Ping != nil || gc.Rest != nil {
|
||||
log.Warn("web option is ignored if you use it with one of these options : api, rest provider, ping or metrics")
|
||||
return
|
||||
}
|
||||
gc.EntryPoints[DefaultInternalEntryPointName] = &EntryPoint{
|
||||
Address: gc.Web.Address,
|
||||
Auth: gc.Web.Auth,
|
||||
}
|
||||
if gc.Web.CertFile != "" {
|
||||
gc.EntryPoints[DefaultInternalEntryPointName].TLS = &tls.TLS{
|
||||
Certificates: []tls.Certificate{
|
||||
{
|
||||
CertFile: tls.FileOrContent(gc.Web.CertFile),
|
||||
KeyFile: tls.FileOrContent(gc.Web.KeyFile),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if gc.API == nil {
|
||||
gc.API = &api.Handler{
|
||||
EntryPoint: DefaultInternalEntryPointName,
|
||||
Statistics: gc.Web.Statistics,
|
||||
Dashboard: true,
|
||||
}
|
||||
}
|
||||
|
||||
if gc.Ping == nil {
|
||||
gc.Ping = &ping.Handler{
|
||||
EntryPoint: DefaultInternalEntryPointName,
|
||||
}
|
||||
}
|
||||
|
||||
if gc.Metrics == nil {
|
||||
gc.Metrics = gc.Web.Metrics
|
||||
}
|
||||
|
||||
if !gc.Debug {
|
||||
gc.Debug = gc.Web.Debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetEffectiveConfiguration adds missing configuration parameters derived from existing ones.
|
||||
// It also takes care of maintaining backwards compatibility.
|
||||
func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
|
||||
if len(gc.EntryPoints) == 0 {
|
||||
gc.EntryPoints = map[string]*EntryPoint{"http": {
|
||||
Address: ":80",
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
}}
|
||||
gc.DefaultEntryPoints = []string{"http"}
|
||||
}
|
||||
|
||||
gc.handleWebDeprecation()
|
||||
|
||||
if (gc.API != nil && gc.API.EntryPoint == DefaultInternalEntryPointName) ||
|
||||
(gc.Ping != nil && gc.Ping.EntryPoint == DefaultInternalEntryPointName) ||
|
||||
(gc.Metrics != nil && gc.Metrics.Prometheus != nil && gc.Metrics.Prometheus.EntryPoint == DefaultInternalEntryPointName) ||
|
||||
(gc.Rest != nil && gc.Rest.EntryPoint == DefaultInternalEntryPointName) {
|
||||
if _, ok := gc.EntryPoints[DefaultInternalEntryPointName]; !ok {
|
||||
gc.EntryPoints[DefaultInternalEntryPointName] = &EntryPoint{Address: ":8080"}
|
||||
}
|
||||
}
|
||||
|
||||
for entryPointName := range gc.EntryPoints {
|
||||
entryPoint := gc.EntryPoints[entryPointName]
|
||||
// ForwardedHeaders must be remove in the next breaking version
|
||||
if entryPoint.ForwardedHeaders == nil {
|
||||
entryPoint.ForwardedHeaders = &ForwardedHeaders{Insecure: true}
|
||||
}
|
||||
|
||||
if len(entryPoint.WhitelistSourceRange) > 0 {
|
||||
log.Warnf("Deprecated configuration found: %s. Please use %s.", "whiteListSourceRange", "whiteList.sourceRange")
|
||||
|
||||
if entryPoint.WhiteList == nil {
|
||||
entryPoint.WhiteList = &types.WhiteList{
|
||||
SourceRange: entryPoint.WhitelistSourceRange,
|
||||
}
|
||||
entryPoint.WhitelistSourceRange = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure LifeCycle isn't nil to spare nil checks elsewhere.
|
||||
if gc.LifeCycle == nil {
|
||||
gc.LifeCycle = &LifeCycle{}
|
||||
}
|
||||
|
||||
// Prefer legacy grace timeout parameter for backwards compatibility reasons.
|
||||
if gc.GraceTimeOut > 0 {
|
||||
log.Warn("top-level grace period configuration has been deprecated -- please use lifecycle grace period")
|
||||
gc.LifeCycle.GraceTimeOut = gc.GraceTimeOut
|
||||
}
|
||||
|
||||
if gc.Docker != nil {
|
||||
if len(gc.Docker.Filename) != 0 && gc.Docker.TemplateVersion != 2 {
|
||||
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
|
||||
gc.Docker.TemplateVersion = 1
|
||||
} else {
|
||||
gc.Docker.TemplateVersion = 2
|
||||
}
|
||||
}
|
||||
|
||||
if gc.Marathon != nil {
|
||||
if len(gc.Marathon.Filename) != 0 && gc.Marathon.TemplateVersion != 2 {
|
||||
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
|
||||
gc.Marathon.TemplateVersion = 1
|
||||
} else {
|
||||
gc.Marathon.TemplateVersion = 2
|
||||
}
|
||||
}
|
||||
|
||||
if gc.Mesos != nil {
|
||||
if len(gc.Mesos.Filename) != 0 && gc.Mesos.TemplateVersion != 2 {
|
||||
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
|
||||
gc.Mesos.TemplateVersion = 1
|
||||
} else {
|
||||
gc.Mesos.TemplateVersion = 2
|
||||
}
|
||||
}
|
||||
|
||||
if gc.Eureka != nil {
|
||||
if gc.Eureka.Delay != 0 {
|
||||
log.Warn("Delay has been deprecated -- please use RefreshSeconds")
|
||||
gc.Eureka.RefreshSeconds = gc.Eureka.Delay
|
||||
}
|
||||
}
|
||||
|
||||
if gc.ECS != nil {
|
||||
if len(gc.ECS.Filename) != 0 && gc.ECS.TemplateVersion != 2 {
|
||||
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
|
||||
gc.ECS.TemplateVersion = 1
|
||||
} else {
|
||||
gc.ECS.TemplateVersion = 2
|
||||
}
|
||||
}
|
||||
|
||||
if gc.ConsulCatalog != nil {
|
||||
if len(gc.ConsulCatalog.Filename) != 0 && gc.ConsulCatalog.TemplateVersion != 2 {
|
||||
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
|
||||
gc.ConsulCatalog.TemplateVersion = 1
|
||||
} else {
|
||||
gc.ConsulCatalog.TemplateVersion = 2
|
||||
}
|
||||
}
|
||||
|
||||
if gc.Rancher != nil {
|
||||
if len(gc.Rancher.Filename) != 0 && gc.Rancher.TemplateVersion != 2 {
|
||||
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
|
||||
gc.Rancher.TemplateVersion = 1
|
||||
} else {
|
||||
gc.Rancher.TemplateVersion = 2
|
||||
}
|
||||
|
||||
// Ensure backwards compatibility for now
|
||||
if len(gc.Rancher.AccessKey) > 0 ||
|
||||
len(gc.Rancher.Endpoint) > 0 ||
|
||||
len(gc.Rancher.SecretKey) > 0 {
|
||||
|
||||
if gc.Rancher.API == nil {
|
||||
gc.Rancher.API = &rancher.APIConfiguration{
|
||||
AccessKey: gc.Rancher.AccessKey,
|
||||
SecretKey: gc.Rancher.SecretKey,
|
||||
Endpoint: gc.Rancher.Endpoint,
|
||||
}
|
||||
}
|
||||
log.Warn("Deprecated configuration found: rancher.[accesskey|secretkey|endpoint]. " +
|
||||
"Please use rancher.api.[accesskey|secretkey|endpoint] instead.")
|
||||
}
|
||||
|
||||
if gc.Rancher.Metadata != nil && len(gc.Rancher.Metadata.Prefix) == 0 {
|
||||
gc.Rancher.Metadata.Prefix = "latest"
|
||||
}
|
||||
}
|
||||
|
||||
if gc.API != nil {
|
||||
gc.API.Debug = gc.Debug
|
||||
}
|
||||
|
||||
if gc.Web != nil && (gc.Web.Path == "" || !strings.HasSuffix(gc.Web.Path, "/")) {
|
||||
gc.Web.Path += "/"
|
||||
}
|
||||
|
||||
if gc.File != nil {
|
||||
gc.File.TraefikFile = configFile
|
||||
}
|
||||
|
||||
gc.initACMEProvider()
|
||||
gc.initTracing()
|
||||
}
|
||||
|
||||
func (gc *GlobalConfiguration) initTracing() {
|
||||
if gc.Tracing != nil {
|
||||
switch gc.Tracing.Backend {
|
||||
case jaeger.Name:
|
||||
if gc.Tracing.Jaeger == nil {
|
||||
gc.Tracing.Jaeger = &jaeger.Config{
|
||||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
}
|
||||
}
|
||||
if gc.Tracing.Zipkin != nil {
|
||||
log.Warn("Zipkin configuration will be ignored")
|
||||
gc.Tracing.Zipkin = nil
|
||||
}
|
||||
case zipkin.Name:
|
||||
if gc.Tracing.Zipkin == nil {
|
||||
gc.Tracing.Zipkin = &zipkin.Config{
|
||||
HTTPEndpoint: "http://localhost:9411/api/v1/spans",
|
||||
SameSpan: false,
|
||||
ID128Bit: true,
|
||||
Debug: false,
|
||||
}
|
||||
}
|
||||
if gc.Tracing.Jaeger != nil {
|
||||
log.Warn("Jaeger configuration will be ignored")
|
||||
gc.Tracing.Jaeger = nil
|
||||
}
|
||||
default:
|
||||
log.Warnf("Unknown tracer %q", gc.Tracing.Backend)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gc *GlobalConfiguration) initACMEProvider() {
|
||||
if gc.ACME != nil {
|
||||
gc.ACME.CAServer = getSafeACMECAServer(gc.ACME.CAServer)
|
||||
|
||||
if gc.ACME.DNSChallenge != nil && gc.ACME.HTTPChallenge != nil {
|
||||
log.Warn("Unable to use DNS challenge and HTTP challenge at the same time. Fallback to DNS challenge.")
|
||||
gc.ACME.HTTPChallenge = nil
|
||||
}
|
||||
|
||||
// TODO: to remove in the future
|
||||
if len(gc.ACME.StorageFile) > 0 && len(gc.ACME.Storage) == 0 {
|
||||
log.Warn("ACME.StorageFile is deprecated, use ACME.Storage instead")
|
||||
gc.ACME.Storage = gc.ACME.StorageFile
|
||||
}
|
||||
|
||||
if len(gc.ACME.DNSProvider) > 0 {
|
||||
log.Warn("ACME.DNSProvider is deprecated, use ACME.DNSChallenge instead")
|
||||
gc.ACME.DNSChallenge = &acmeprovider.DNSChallenge{Provider: gc.ACME.DNSProvider, DelayBeforeCheck: gc.ACME.DelayDontCheckDNS}
|
||||
}
|
||||
|
||||
if gc.ACME.OnDemand {
|
||||
log.Warn("ACME.OnDemand is deprecated")
|
||||
}
|
||||
|
||||
// TODO: Remove when Provider ACME will replace totally ACME
|
||||
// If provider file, use Provider ACME instead of ACME
|
||||
if gc.Cluster == nil {
|
||||
acmeprovider.Get().Configuration = &acmeprovider.Configuration{
|
||||
OnHostRule: gc.ACME.OnHostRule,
|
||||
OnDemand: gc.ACME.OnDemand,
|
||||
Email: gc.ACME.Email,
|
||||
Storage: gc.ACME.Storage,
|
||||
HTTPChallenge: gc.ACME.HTTPChallenge,
|
||||
DNSChallenge: gc.ACME.DNSChallenge,
|
||||
Domains: gc.ACME.Domains,
|
||||
ACMELogging: gc.ACME.ACMELogging,
|
||||
CAServer: gc.ACME.CAServer,
|
||||
EntryPoint: gc.ACME.EntryPoint,
|
||||
}
|
||||
gc.ACME = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getSafeACMECAServer(caServerSrc string) string {
|
||||
if len(caServerSrc) == 0 {
|
||||
return DefaultAcmeCAServer
|
||||
}
|
||||
|
||||
if strings.HasPrefix(caServerSrc, "https://acme-v01.api.letsencrypt.org") {
|
||||
caServer := strings.Replace(caServerSrc, "v01", "v02", 1)
|
||||
log.Warnf("The CA server %[1]q refers to a v01 endpoint of the ACME API, please change to %[2]q. Fallback to %[2]q.", caServerSrc, caServer)
|
||||
return caServer
|
||||
}
|
||||
|
||||
if strings.HasPrefix(caServerSrc, "https://acme-staging.api.letsencrypt.org") {
|
||||
caServer := strings.Replace(caServerSrc, "https://acme-staging.api.letsencrypt.org", "https://acme-staging-v02.api.letsencrypt.org", 1)
|
||||
log.Warnf("The CA server %[1]q refers to a v01 endpoint of the ACME API, please change to %[2]q. Fallback to %[2]q.", caServerSrc, caServer)
|
||||
return caServer
|
||||
}
|
||||
|
||||
return caServerSrc
|
||||
}
|
||||
|
||||
// ValidateConfiguration validate that configuration is coherent
|
||||
func (gc *GlobalConfiguration) ValidateConfiguration() {
|
||||
if gc.ACME != nil {
|
||||
if _, ok := gc.EntryPoints[gc.ACME.EntryPoint]; !ok {
|
||||
log.Fatalf("Unknown entrypoint %q for ACME configuration", gc.ACME.EntryPoint)
|
||||
} else {
|
||||
if gc.EntryPoints[gc.ACME.EntryPoint].TLS == nil {
|
||||
log.Fatalf("Entrypoint %q has no TLS configuration for ACME configuration", gc.ACME.EntryPoint)
|
||||
}
|
||||
}
|
||||
} else if acmeprovider.IsEnabled() {
|
||||
if _, ok := gc.EntryPoints[acmeprovider.Get().EntryPoint]; !ok {
|
||||
log.Fatalf("Unknown entrypoint %q for provider ACME configuration", acmeprovider.Get().EntryPoint)
|
||||
} else {
|
||||
if gc.EntryPoints[acmeprovider.Get().EntryPoint].TLS == nil {
|
||||
log.Fatalf("Entrypoint %q has no TLS configuration for provider ACME configuration", acmeprovider.Get().EntryPoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultEntryPoints holds default entry points
|
||||
@@ -105,12 +461,12 @@ func (dep *DefaultEntryPoints) Set(value string) error {
|
||||
|
||||
// Get return the EntryPoints map
|
||||
func (dep *DefaultEntryPoints) Get() interface{} {
|
||||
return DefaultEntryPoints(*dep)
|
||||
return *dep
|
||||
}
|
||||
|
||||
// SetValue sets the EntryPoints map with val
|
||||
func (dep *DefaultEntryPoints) SetValue(val interface{}) {
|
||||
*dep = DefaultEntryPoints(val.(DefaultEntryPoints))
|
||||
*dep = val.(DefaultEntryPoints)
|
||||
}
|
||||
|
||||
// Type is type of the struct
|
||||
@@ -118,323 +474,32 @@ func (dep *DefaultEntryPoints) Type() string {
|
||||
return "defaultentrypoints"
|
||||
}
|
||||
|
||||
// RootCAs hold the CA we want to have in root
|
||||
type RootCAs []FileOrContent
|
||||
|
||||
// FileOrContent hold a file path or content
|
||||
type FileOrContent string
|
||||
|
||||
func (f FileOrContent) String() string {
|
||||
return string(f)
|
||||
}
|
||||
|
||||
func (f FileOrContent) Read() ([]byte, error) {
|
||||
var content []byte
|
||||
if _, err := os.Stat(f.String()); err == nil {
|
||||
content, err = ioutil.ReadFile(f.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
content = []byte(f)
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (r *RootCAs) String() string {
|
||||
sliceOfString := make([]string, len([]FileOrContent(*r)))
|
||||
for key, value := range *r {
|
||||
sliceOfString[key] = value.String()
|
||||
}
|
||||
return strings.Join(sliceOfString, ",")
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (r *RootCAs) Set(value string) error {
|
||||
rootCAs := strings.Split(value, ",")
|
||||
if len(rootCAs) == 0 {
|
||||
return fmt.Errorf("bad RootCAs format: %s", value)
|
||||
}
|
||||
for _, rootCA := range rootCAs {
|
||||
*r = append(*r, FileOrContent(rootCA))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get return the EntryPoints map
|
||||
func (r *RootCAs) Get() interface{} {
|
||||
return RootCAs(*r)
|
||||
}
|
||||
|
||||
// SetValue sets the EntryPoints map with val
|
||||
func (r *RootCAs) SetValue(val interface{}) {
|
||||
*r = RootCAs(val.(RootCAs))
|
||||
}
|
||||
|
||||
// Type is type of the struct
|
||||
func (r *RootCAs) Type() string {
|
||||
return "rootcas"
|
||||
}
|
||||
|
||||
// EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...)
|
||||
type EntryPoints map[string]*EntryPoint
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (ep *EntryPoints) String() string {
|
||||
return fmt.Sprintf("%+v", *ep)
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (ep *EntryPoints) Set(value string) error {
|
||||
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*))?\s*(?:WhiteListSourceRange:(?P<WhiteListSourceRange>\S*))?\s*(?:ProxyProtocol:(?P<ProxyProtocol>\S*))?`)
|
||||
match := regex.FindAllStringSubmatch(value, -1)
|
||||
if match == nil {
|
||||
return fmt.Errorf("bad EntryPoints format: %s", value)
|
||||
}
|
||||
matchResult := match[0]
|
||||
result := make(map[string]string)
|
||||
for i, name := range regex.SubexpNames() {
|
||||
if i != 0 {
|
||||
result[name] = matchResult[i]
|
||||
}
|
||||
}
|
||||
var configTLS *TLS
|
||||
if len(result["TLS"]) > 0 {
|
||||
certs := Certificates{}
|
||||
if err := certs.Set(result["TLS"]); err != nil {
|
||||
return err
|
||||
}
|
||||
configTLS = &TLS{
|
||||
Certificates: certs,
|
||||
}
|
||||
} else if len(result["TLSACME"]) > 0 {
|
||||
configTLS = &TLS{
|
||||
Certificates: Certificates{},
|
||||
}
|
||||
}
|
||||
if len(result["CA"]) > 0 {
|
||||
files := strings.Split(result["CA"], ",")
|
||||
configTLS.ClientCAFiles = files
|
||||
}
|
||||
var redirect *Redirect
|
||||
if len(result["RedirectEntryPoint"]) > 0 || len(result["RedirectRegex"]) > 0 || len(result["RedirectReplacement"]) > 0 {
|
||||
redirect = &Redirect{
|
||||
EntryPoint: result["RedirectEntryPoint"],
|
||||
Regex: result["RedirectRegex"],
|
||||
Replacement: result["RedirectReplacement"],
|
||||
}
|
||||
}
|
||||
|
||||
compress := false
|
||||
if len(result["Compress"]) > 0 {
|
||||
compress = strings.EqualFold(result["Compress"], "true") ||
|
||||
strings.EqualFold(result["Compress"], "enable") ||
|
||||
strings.EqualFold(result["Compress"], "on")
|
||||
}
|
||||
|
||||
whiteListSourceRange := []string{}
|
||||
if len(result["WhiteListSourceRange"]) > 0 {
|
||||
whiteListSourceRange = strings.Split(result["WhiteListSourceRange"], ",")
|
||||
}
|
||||
|
||||
proxyprotocol := false
|
||||
if len(result["ProxyProtocol"]) > 0 {
|
||||
proxyprotocol = strings.EqualFold(result["ProxyProtocol"], "true") ||
|
||||
strings.EqualFold(result["ProxyProtocol"], "enable") ||
|
||||
strings.EqualFold(result["ProxyProtocol"], "on")
|
||||
}
|
||||
|
||||
(*ep)[result["Name"]] = &EntryPoint{
|
||||
Address: result["Address"],
|
||||
TLS: configTLS,
|
||||
Redirect: redirect,
|
||||
Compress: compress,
|
||||
WhitelistSourceRange: whiteListSourceRange,
|
||||
ProxyProtocol: proxyprotocol,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get return the EntryPoints map
|
||||
func (ep *EntryPoints) Get() interface{} {
|
||||
return EntryPoints(*ep)
|
||||
}
|
||||
|
||||
// SetValue sets the EntryPoints map with val
|
||||
func (ep *EntryPoints) SetValue(val interface{}) {
|
||||
*ep = EntryPoints(val.(EntryPoints))
|
||||
}
|
||||
|
||||
// Type is type of the struct
|
||||
func (ep *EntryPoints) Type() string {
|
||||
return "entrypoints"
|
||||
}
|
||||
|
||||
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
|
||||
type EntryPoint struct {
|
||||
Network string
|
||||
Address string
|
||||
TLS *TLS
|
||||
Redirect *Redirect
|
||||
Auth *types.Auth
|
||||
WhitelistSourceRange []string
|
||||
Compress bool
|
||||
ProxyProtocol bool
|
||||
}
|
||||
|
||||
// Redirect configures a redirection of an entry point to another, or to an URL
|
||||
type Redirect struct {
|
||||
EntryPoint string
|
||||
Regex string
|
||||
Replacement string
|
||||
}
|
||||
|
||||
// TLS configures TLS for an entry point
|
||||
type TLS struct {
|
||||
MinVersion string
|
||||
CipherSuites []string
|
||||
Certificates Certificates
|
||||
ClientCAFiles []string
|
||||
}
|
||||
|
||||
// MinVersion Map of allowed TLS minimum versions
|
||||
var MinVersion = map[string]uint16{
|
||||
`VersionTLS10`: tls.VersionTLS10,
|
||||
`VersionTLS11`: tls.VersionTLS11,
|
||||
`VersionTLS12`: tls.VersionTLS12,
|
||||
}
|
||||
|
||||
// CipherSuites Map of TLS CipherSuites from crypto/tls
|
||||
// Available CipherSuites defined at https://golang.org/pkg/crypto/tls/#pkg-constants
|
||||
var CipherSuites = map[string]uint16{
|
||||
`TLS_RSA_WITH_RC4_128_SHA`: tls.TLS_RSA_WITH_RC4_128_SHA,
|
||||
`TLS_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
`TLS_RSA_WITH_AES_128_CBC_SHA`: tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
`TLS_RSA_WITH_AES_256_CBC_SHA`: tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
`TLS_RSA_WITH_AES_128_CBC_SHA256`: tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
`TLS_RSA_WITH_AES_128_GCM_SHA256`: tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
`TLS_RSA_WITH_AES_256_GCM_SHA384`: tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
`TLS_ECDHE_ECDSA_WITH_RC4_128_SHA`: tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||
`TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
`TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA`: tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
`TLS_ECDHE_RSA_WITH_RC4_128_SHA`: tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||
`TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
`TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
`TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256`: tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
`TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
`TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
`TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
`TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
`TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305`: tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
`TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305`: tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
}
|
||||
|
||||
// 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 {
|
||||
cert := tls.Certificate{}
|
||||
|
||||
var err error
|
||||
|
||||
certContent, err := v.CertFile.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyContent, err := v.KeyFile.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err = tls.X509KeyPair(certContent, keyContent)
|
||||
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 {
|
||||
if len(*certs) == 0 {
|
||||
return ""
|
||||
}
|
||||
var result []string
|
||||
for _, certificate := range *certs {
|
||||
result = append(result, certificate.CertFile.String()+","+certificate.KeyFile.String())
|
||||
}
|
||||
return strings.Join(result, ";")
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (certs *Certificates) Set(value string) error {
|
||||
certificates := strings.Split(value, ";")
|
||||
for _, certificate := range certificates {
|
||||
files := strings.Split(certificate, ",")
|
||||
if len(files) != 2 {
|
||||
return fmt.Errorf("bad certificates format: %s", value)
|
||||
}
|
||||
*certs = append(*certs, Certificate{
|
||||
CertFile: FileOrContent(files[0]),
|
||||
KeyFile: FileOrContent(files[1]),
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type is type of the struct
|
||||
func (certs *Certificates) Type() string {
|
||||
return "certificates"
|
||||
}
|
||||
|
||||
// Certificate holds a SSL cert/key pair
|
||||
// Certs and Key could be either a file path, or the file content itself
|
||||
type Certificate struct {
|
||||
CertFile FileOrContent
|
||||
KeyFile FileOrContent
|
||||
}
|
||||
|
||||
// Retry contains request retry config
|
||||
type Retry struct {
|
||||
Attempts int `description:"Number of attempts"`
|
||||
Attempts int `description:"Number of attempts" export:"true"`
|
||||
}
|
||||
|
||||
// HealthCheckConfig contains health check configuration parameters.
|
||||
type HealthCheckConfig struct {
|
||||
Interval flaeg.Duration `description:"Default periodicity of enabled health checks"`
|
||||
Interval flaeg.Duration `description:"Default periodicity of enabled health checks" export:"true"`
|
||||
}
|
||||
|
||||
// RespondingTimeouts contains timeout configurations for incoming requests to the Traefik instance.
|
||||
type RespondingTimeouts struct {
|
||||
ReadTimeout flaeg.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set"`
|
||||
WriteTimeout flaeg.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set"`
|
||||
IdleTimeout flaeg.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. Defaults to 180 seconds. If zero, no timeout is set"`
|
||||
ReadTimeout flaeg.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set" export:"true"`
|
||||
WriteTimeout flaeg.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set" export:"true"`
|
||||
IdleTimeout flaeg.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. Defaults to 180 seconds. If zero, no timeout is set" export:"true"`
|
||||
}
|
||||
|
||||
// ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers.
|
||||
type ForwardingTimeouts struct {
|
||||
DialTimeout flaeg.Duration `description:"The amount of time to wait until a connection to a backend server can be established. Defaults to 30 seconds. If zero, no timeout exists"`
|
||||
ResponseHeaderTimeout flaeg.Duration `description:"The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists"`
|
||||
DialTimeout flaeg.Duration `description:"The amount of time to wait until a connection to a backend server can be established. Defaults to 30 seconds. If zero, no timeout exists" export:"true"`
|
||||
ResponseHeaderTimeout flaeg.Duration `description:"The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists" export:"true"`
|
||||
}
|
||||
|
||||
// LifeCycle contains configurations relevant to the lifecycle (such as the
|
||||
// shutdown phase) of Traefik.
|
||||
type LifeCycle struct {
|
||||
RequestAcceptGraceTimeout flaeg.Duration `description:"Duration to keep accepting requests before Traefik initiates the graceful shutdown procedure"`
|
||||
GraceTimeOut flaeg.Duration `description:"Duration to give active requests a chance to finish before Traefik stops"`
|
||||
}
|
||||
|
||||
218
configuration/configuration_test.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/middlewares/tracing"
|
||||
"github.com/containous/traefik/middlewares/tracing/jaeger"
|
||||
"github.com/containous/traefik/middlewares/tracing/zipkin"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/containous/traefik/provider/file"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const defaultConfigFile = "traefik.toml"
|
||||
|
||||
func TestSetEffectiveConfigurationGraceTimeout(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
legacyGraceTimeout time.Duration
|
||||
lifeCycleGraceTimeout time.Duration
|
||||
wantGraceTimeout time.Duration
|
||||
}{
|
||||
{
|
||||
desc: "legacy grace timeout given only",
|
||||
legacyGraceTimeout: 5 * time.Second,
|
||||
wantGraceTimeout: 5 * time.Second,
|
||||
},
|
||||
{
|
||||
desc: "legacy and life cycle grace timeouts given",
|
||||
legacyGraceTimeout: 5 * time.Second,
|
||||
lifeCycleGraceTimeout: 12 * time.Second,
|
||||
wantGraceTimeout: 5 * time.Second,
|
||||
},
|
||||
{
|
||||
desc: "legacy grace timeout omitted",
|
||||
legacyGraceTimeout: 0,
|
||||
lifeCycleGraceTimeout: 12 * time.Second,
|
||||
wantGraceTimeout: 12 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gc := &GlobalConfiguration{
|
||||
GraceTimeOut: flaeg.Duration(test.legacyGraceTimeout),
|
||||
}
|
||||
if test.lifeCycleGraceTimeout > 0 {
|
||||
gc.LifeCycle = &LifeCycle{
|
||||
GraceTimeOut: flaeg.Duration(test.lifeCycleGraceTimeout),
|
||||
}
|
||||
}
|
||||
|
||||
gc.SetEffectiveConfiguration(defaultConfigFile)
|
||||
|
||||
assert.Equal(t, test.wantGraceTimeout, time.Duration(gc.LifeCycle.GraceTimeOut))
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetEffectiveConfigurationFileProviderFilename(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
fileProvider *file.Provider
|
||||
wantFileProviderFilename string
|
||||
wantFileProviderTraefikFile string
|
||||
}{
|
||||
{
|
||||
desc: "no filename for file provider given",
|
||||
fileProvider: &file.Provider{},
|
||||
wantFileProviderFilename: "",
|
||||
wantFileProviderTraefikFile: defaultConfigFile,
|
||||
},
|
||||
{
|
||||
desc: "filename for file provider given",
|
||||
fileProvider: &file.Provider{BaseProvider: provider.BaseProvider{Filename: "other.toml"}},
|
||||
wantFileProviderFilename: "other.toml",
|
||||
wantFileProviderTraefikFile: defaultConfigFile,
|
||||
},
|
||||
{
|
||||
desc: "directory for file provider given",
|
||||
fileProvider: &file.Provider{Directory: "/"},
|
||||
wantFileProviderFilename: "",
|
||||
wantFileProviderTraefikFile: defaultConfigFile,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gc := &GlobalConfiguration{
|
||||
File: test.fileProvider,
|
||||
}
|
||||
|
||||
gc.SetEffectiveConfiguration(defaultConfigFile)
|
||||
|
||||
assert.Equal(t, test.wantFileProviderFilename, gc.File.Filename)
|
||||
assert.Equal(t, test.wantFileProviderTraefikFile, gc.File.TraefikFile)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetEffectiveConfigurationTracing(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
tracing *tracing.Tracing
|
||||
expected *tracing.Tracing
|
||||
}{
|
||||
{
|
||||
desc: "no tracing configuration",
|
||||
tracing: &tracing.Tracing{},
|
||||
expected: &tracing.Tracing{},
|
||||
},
|
||||
{
|
||||
desc: "tracing bad backend name",
|
||||
tracing: &tracing.Tracing{
|
||||
Backend: "powpow",
|
||||
},
|
||||
expected: &tracing.Tracing{
|
||||
Backend: "powpow",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "tracing jaeger backend name",
|
||||
tracing: &tracing.Tracing{
|
||||
Backend: "jaeger",
|
||||
Zipkin: &zipkin.Config{
|
||||
HTTPEndpoint: "http://localhost:9411/api/v1/spans",
|
||||
SameSpan: false,
|
||||
ID128Bit: true,
|
||||
Debug: false,
|
||||
},
|
||||
},
|
||||
expected: &tracing.Tracing{
|
||||
Backend: "jaeger",
|
||||
Jaeger: &jaeger.Config{
|
||||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
},
|
||||
Zipkin: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "tracing zipkin backend name",
|
||||
tracing: &tracing.Tracing{
|
||||
Backend: "zipkin",
|
||||
Jaeger: &jaeger.Config{
|
||||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
},
|
||||
},
|
||||
expected: &tracing.Tracing{
|
||||
Backend: "zipkin",
|
||||
Jaeger: nil,
|
||||
Zipkin: &zipkin.Config{
|
||||
HTTPEndpoint: "http://localhost:9411/api/v1/spans",
|
||||
SameSpan: false,
|
||||
ID128Bit: true,
|
||||
Debug: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "tracing zipkin backend name value override",
|
||||
tracing: &tracing.Tracing{
|
||||
Backend: "zipkin",
|
||||
Jaeger: &jaeger.Config{
|
||||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
},
|
||||
Zipkin: &zipkin.Config{
|
||||
HTTPEndpoint: "http://powpow:9411/api/v1/spans",
|
||||
SameSpan: true,
|
||||
ID128Bit: true,
|
||||
Debug: true,
|
||||
},
|
||||
},
|
||||
expected: &tracing.Tracing{
|
||||
Backend: "zipkin",
|
||||
Jaeger: nil,
|
||||
Zipkin: &zipkin.Config{
|
||||
HTTPEndpoint: "http://powpow:9411/api/v1/spans",
|
||||
SameSpan: true,
|
||||
ID128Bit: true,
|
||||
Debug: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gc := &GlobalConfiguration{
|
||||
Tracing: test.tracing,
|
||||
}
|
||||
|
||||
gc.SetEffectiveConfiguration(defaultConfigFile)
|
||||
|
||||
assert.Equal(t, test.expected, gc.Tracing)
|
||||
})
|
||||
}
|
||||
}
|
||||
266
configuration/entrypoints.go
Normal file
@@ -0,0 +1,266 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/tls"
|
||||
"github.com/containous/traefik/types"
|
||||
)
|
||||
|
||||
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
|
||||
type EntryPoint struct {
|
||||
Address string
|
||||
TLS *tls.TLS `export:"true"`
|
||||
Redirect *types.Redirect `export:"true"`
|
||||
Auth *types.Auth `export:"true"`
|
||||
WhitelistSourceRange []string // Deprecated
|
||||
WhiteList *types.WhiteList `export:"true"`
|
||||
Compress bool `export:"true"`
|
||||
ProxyProtocol *ProxyProtocol `export:"true"`
|
||||
ForwardedHeaders *ForwardedHeaders `export:"true"`
|
||||
}
|
||||
|
||||
// ProxyProtocol contains Proxy-Protocol configuration
|
||||
type ProxyProtocol struct {
|
||||
Insecure bool `export:"true"`
|
||||
TrustedIPs []string
|
||||
}
|
||||
|
||||
// ForwardedHeaders Trust client forwarding headers
|
||||
type ForwardedHeaders struct {
|
||||
Insecure bool `export:"true"`
|
||||
TrustedIPs []string
|
||||
}
|
||||
|
||||
// EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...)
|
||||
type EntryPoints map[string]*EntryPoint
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (ep EntryPoints) String() string {
|
||||
return fmt.Sprintf("%+v", map[string]*EntryPoint(ep))
|
||||
}
|
||||
|
||||
// Get return the EntryPoints map
|
||||
func (ep *EntryPoints) Get() interface{} {
|
||||
return *ep
|
||||
}
|
||||
|
||||
// SetValue sets the EntryPoints map with val
|
||||
func (ep *EntryPoints) SetValue(val interface{}) {
|
||||
*ep = val.(EntryPoints)
|
||||
}
|
||||
|
||||
// Type is type of the struct
|
||||
func (ep *EntryPoints) Type() string {
|
||||
return "entrypoints"
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (ep *EntryPoints) Set(value string) error {
|
||||
result := parseEntryPointsConfiguration(value)
|
||||
|
||||
var whiteListSourceRange []string
|
||||
if len(result["whitelistsourcerange"]) > 0 {
|
||||
whiteListSourceRange = strings.Split(result["whitelistsourcerange"], ",")
|
||||
}
|
||||
|
||||
compress := toBool(result, "compress")
|
||||
|
||||
configTLS, err := makeEntryPointTLS(result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
(*ep)[result["name"]] = &EntryPoint{
|
||||
Address: result["address"],
|
||||
TLS: configTLS,
|
||||
Auth: makeEntryPointAuth(result),
|
||||
Redirect: makeEntryPointRedirect(result),
|
||||
Compress: compress,
|
||||
WhitelistSourceRange: whiteListSourceRange,
|
||||
WhiteList: makeWhiteList(result),
|
||||
ProxyProtocol: makeEntryPointProxyProtocol(result),
|
||||
ForwardedHeaders: makeEntryPointForwardedHeaders(result),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeWhiteList(result map[string]string) *types.WhiteList {
|
||||
var wl *types.WhiteList
|
||||
if rawRange, ok := result["whitelist_sourcerange"]; ok {
|
||||
wl = &types.WhiteList{
|
||||
SourceRange: strings.Split(rawRange, ","),
|
||||
UseXForwardedFor: toBool(result, "whitelist_usexforwardedfor"),
|
||||
}
|
||||
}
|
||||
return wl
|
||||
}
|
||||
|
||||
func makeEntryPointAuth(result map[string]string) *types.Auth {
|
||||
var basic *types.Basic
|
||||
if v, ok := result["auth_basic_users"]; ok {
|
||||
basic = &types.Basic{
|
||||
Users: strings.Split(v, ","),
|
||||
}
|
||||
}
|
||||
|
||||
var digest *types.Digest
|
||||
if v, ok := result["auth_digest_users"]; ok {
|
||||
digest = &types.Digest{
|
||||
Users: strings.Split(v, ","),
|
||||
}
|
||||
}
|
||||
|
||||
var forward *types.Forward
|
||||
if address, ok := result["auth_forward_address"]; ok {
|
||||
var clientTLS *types.ClientTLS
|
||||
|
||||
cert := result["auth_forward_tls_cert"]
|
||||
key := result["auth_forward_tls_key"]
|
||||
insecureSkipVerify := toBool(result, "auth_forward_tls_insecureskipverify")
|
||||
|
||||
if len(cert) > 0 && len(key) > 0 || insecureSkipVerify {
|
||||
clientTLS = &types.ClientTLS{
|
||||
CA: result["auth_forward_tls_ca"],
|
||||
CAOptional: toBool(result, "auth_forward_tls_caoptional"),
|
||||
Cert: cert,
|
||||
Key: key,
|
||||
InsecureSkipVerify: insecureSkipVerify,
|
||||
}
|
||||
}
|
||||
|
||||
forward = &types.Forward{
|
||||
Address: address,
|
||||
TLS: clientTLS,
|
||||
TrustForwardHeader: toBool(result, "auth_forward_trustforwardheader"),
|
||||
}
|
||||
}
|
||||
|
||||
var auth *types.Auth
|
||||
if basic != nil || digest != nil || forward != nil {
|
||||
auth = &types.Auth{
|
||||
Basic: basic,
|
||||
Digest: digest,
|
||||
Forward: forward,
|
||||
HeaderField: result["auth_headerfield"],
|
||||
}
|
||||
}
|
||||
|
||||
return auth
|
||||
}
|
||||
|
||||
func makeEntryPointProxyProtocol(result map[string]string) *ProxyProtocol {
|
||||
var proxyProtocol *ProxyProtocol
|
||||
|
||||
ppTrustedIPs := result["proxyprotocol_trustedips"]
|
||||
if len(result["proxyprotocol_insecure"]) > 0 || len(ppTrustedIPs) > 0 {
|
||||
proxyProtocol = &ProxyProtocol{
|
||||
Insecure: toBool(result, "proxyprotocol_insecure"),
|
||||
}
|
||||
if len(ppTrustedIPs) > 0 {
|
||||
proxyProtocol.TrustedIPs = strings.Split(ppTrustedIPs, ",")
|
||||
}
|
||||
}
|
||||
|
||||
if proxyProtocol != nil && proxyProtocol.Insecure {
|
||||
log.Warn("ProxyProtocol.Insecure:true is dangerous. Please use 'ProxyProtocol.TrustedIPs:IPs' and remove 'ProxyProtocol.Insecure:true'")
|
||||
}
|
||||
|
||||
return proxyProtocol
|
||||
}
|
||||
|
||||
func makeEntryPointForwardedHeaders(result map[string]string) *ForwardedHeaders {
|
||||
// TODO must be changed to false by default in the next breaking version.
|
||||
forwardedHeaders := &ForwardedHeaders{Insecure: true}
|
||||
if _, ok := result["forwardedheaders_insecure"]; ok {
|
||||
forwardedHeaders.Insecure = toBool(result, "forwardedheaders_insecure")
|
||||
}
|
||||
|
||||
fhTrustedIPs := result["forwardedheaders_trustedips"]
|
||||
if len(fhTrustedIPs) > 0 {
|
||||
// TODO must be removed in the next breaking version.
|
||||
forwardedHeaders.Insecure = toBool(result, "forwardedheaders_insecure")
|
||||
forwardedHeaders.TrustedIPs = strings.Split(fhTrustedIPs, ",")
|
||||
}
|
||||
|
||||
return forwardedHeaders
|
||||
}
|
||||
|
||||
func makeEntryPointRedirect(result map[string]string) *types.Redirect {
|
||||
var redirect *types.Redirect
|
||||
|
||||
if len(result["redirect_entrypoint"]) > 0 || len(result["redirect_regex"]) > 0 || len(result["redirect_replacement"]) > 0 {
|
||||
redirect = &types.Redirect{
|
||||
EntryPoint: result["redirect_entrypoint"],
|
||||
Regex: result["redirect_regex"],
|
||||
Replacement: result["redirect_replacement"],
|
||||
Permanent: toBool(result, "redirect_permanent"),
|
||||
}
|
||||
}
|
||||
|
||||
return redirect
|
||||
}
|
||||
|
||||
func makeEntryPointTLS(result map[string]string) (*tls.TLS, error) {
|
||||
var configTLS *tls.TLS
|
||||
|
||||
if len(result["tls"]) > 0 {
|
||||
certs := tls.Certificates{}
|
||||
if err := certs.Set(result["tls"]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configTLS = &tls.TLS{
|
||||
Certificates: certs,
|
||||
}
|
||||
} else if len(result["tls_acme"]) > 0 {
|
||||
configTLS = &tls.TLS{
|
||||
Certificates: tls.Certificates{},
|
||||
}
|
||||
}
|
||||
|
||||
if len(result["ca"]) > 0 {
|
||||
files := strings.Split(result["ca"], ",")
|
||||
optional := toBool(result, "ca_optional")
|
||||
configTLS.ClientCA = tls.ClientCA{
|
||||
Files: files,
|
||||
Optional: optional,
|
||||
}
|
||||
}
|
||||
|
||||
return configTLS, nil
|
||||
}
|
||||
|
||||
func parseEntryPointsConfiguration(raw string) map[string]string {
|
||||
sections := strings.Fields(raw)
|
||||
|
||||
config := make(map[string]string)
|
||||
for _, part := range sections {
|
||||
field := strings.SplitN(part, ":", 2)
|
||||
name := strings.ToLower(strings.Replace(field[0], ".", "_", -1))
|
||||
if len(field) > 1 {
|
||||
config[name] = field[1]
|
||||
} else {
|
||||
if strings.EqualFold(name, "TLS") {
|
||||
config["tls_acme"] = "TLS"
|
||||
} else {
|
||||
config[name] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func toBool(conf map[string]string, key string) bool {
|
||||
if val, ok := conf[key]; ok {
|
||||
return strings.EqualFold(val, "true") ||
|
||||
strings.EqualFold(val, "enable") ||
|
||||
strings.EqualFold(val, "on")
|
||||
}
|
||||
return false
|
||||
}
|
||||
467
configuration/entrypoints_test.go
Normal file
@@ -0,0 +1,467 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/tls"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_parseEntryPointsConfiguration(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
value string
|
||||
expectedResult map[string]string
|
||||
}{
|
||||
{
|
||||
name: "all parameters",
|
||||
value: "Name:foo " +
|
||||
"Address::8000 " +
|
||||
"TLS:goo,gii " +
|
||||
"TLS " +
|
||||
"CA:car " +
|
||||
"CA.Optional:true " +
|
||||
"Redirect.EntryPoint:https " +
|
||||
"Redirect.Regex:http://localhost/(.*) " +
|
||||
"Redirect.Replacement:http://mydomain/$1 " +
|
||||
"Redirect.Permanent:true " +
|
||||
"Compress:true " +
|
||||
"ProxyProtocol.TrustedIPs:192.168.0.1 " +
|
||||
"ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " +
|
||||
"Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " +
|
||||
"Auth.Digest.Users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e " +
|
||||
"Auth.HeaderField:X-WebAuth-User " +
|
||||
"Auth.Forward.Address:https://authserver.com/auth " +
|
||||
"Auth.Forward.TrustForwardHeader:true " +
|
||||
"Auth.Forward.TLS.CA:path/to/local.crt " +
|
||||
"Auth.Forward.TLS.CAOptional:true " +
|
||||
"Auth.Forward.TLS.Cert:path/to/foo.cert " +
|
||||
"Auth.Forward.TLS.Key:path/to/foo.key " +
|
||||
"Auth.Forward.TLS.InsecureSkipVerify:true " +
|
||||
"WhiteListSourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"whiteList.sourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"whiteList.useXForwardedFor:true ",
|
||||
expectedResult: map[string]string{
|
||||
"address": ":8000",
|
||||
"auth_basic_users": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
"auth_digest_users": "test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e",
|
||||
"auth_forward_address": "https://authserver.com/auth",
|
||||
"auth_forward_tls_ca": "path/to/local.crt",
|
||||
"auth_forward_tls_caoptional": "true",
|
||||
"auth_forward_tls_cert": "path/to/foo.cert",
|
||||
"auth_forward_tls_insecureskipverify": "true",
|
||||
"auth_forward_tls_key": "path/to/foo.key",
|
||||
"auth_forward_trustforwardheader": "true",
|
||||
"auth_headerfield": "X-WebAuth-User",
|
||||
"ca": "car",
|
||||
"ca_optional": "true",
|
||||
"compress": "true",
|
||||
"forwardedheaders_trustedips": "10.0.0.3/24,20.0.0.3/24",
|
||||
"name": "foo",
|
||||
"proxyprotocol_trustedips": "192.168.0.1",
|
||||
"redirect_entrypoint": "https",
|
||||
"redirect_permanent": "true",
|
||||
"redirect_regex": "http://localhost/(.*)",
|
||||
"redirect_replacement": "http://mydomain/$1",
|
||||
"tls": "goo,gii",
|
||||
"tls_acme": "TLS",
|
||||
"whitelistsourcerange": "10.42.0.0/16,152.89.1.33/32,afed:be44::/16",
|
||||
"whitelist_sourcerange": "10.42.0.0/16,152.89.1.33/32,afed:be44::/16",
|
||||
"whitelist_usexforwardedfor": "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "compress on",
|
||||
value: "name:foo Compress:on",
|
||||
expectedResult: map[string]string{
|
||||
"name": "foo",
|
||||
"compress": "on",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TLS",
|
||||
value: "Name:foo TLS:goo TLS",
|
||||
expectedResult: map[string]string{
|
||||
"name": "foo",
|
||||
"tls": "goo",
|
||||
"tls_acme": "TLS",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conf := parseEntryPointsConfiguration(test.value)
|
||||
|
||||
assert.Len(t, conf, len(test.expectedResult))
|
||||
assert.Equal(t, test.expectedResult, conf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_toBool(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
value string
|
||||
key string
|
||||
expectedBool bool
|
||||
}{
|
||||
{
|
||||
name: "on",
|
||||
value: "on",
|
||||
key: "foo",
|
||||
expectedBool: true,
|
||||
},
|
||||
{
|
||||
name: "true",
|
||||
value: "true",
|
||||
key: "foo",
|
||||
expectedBool: true,
|
||||
},
|
||||
{
|
||||
name: "enable",
|
||||
value: "enable",
|
||||
key: "foo",
|
||||
expectedBool: true,
|
||||
},
|
||||
{
|
||||
name: "arbitrary string",
|
||||
value: "bar",
|
||||
key: "foo",
|
||||
expectedBool: false,
|
||||
},
|
||||
{
|
||||
name: "no existing entry",
|
||||
value: "bar",
|
||||
key: "fii",
|
||||
expectedBool: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conf := map[string]string{
|
||||
"foo": test.value,
|
||||
}
|
||||
|
||||
result := toBool(conf, test.key)
|
||||
|
||||
assert.Equal(t, test.expectedBool, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEntryPoints_Set(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
expression string
|
||||
expectedEntryPointName string
|
||||
expectedEntryPoint *EntryPoint
|
||||
}{
|
||||
{
|
||||
name: "all parameters camelcase",
|
||||
expression: "Name:foo " +
|
||||
"Address::8000 " +
|
||||
"TLS:goo,gii;foo,fii " +
|
||||
"TLS " +
|
||||
"CA:car " +
|
||||
"CA.Optional:true " +
|
||||
"Redirect.EntryPoint:https " +
|
||||
"Redirect.Regex:http://localhost/(.*) " +
|
||||
"Redirect.Replacement:http://mydomain/$1 " +
|
||||
"Redirect.Permanent:true " +
|
||||
"Compress:true " +
|
||||
"ProxyProtocol.TrustedIPs:192.168.0.1 " +
|
||||
"ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " +
|
||||
"Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " +
|
||||
"Auth.Digest.Users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e " +
|
||||
"Auth.HeaderField:X-WebAuth-User " +
|
||||
"Auth.Forward.Address:https://authserver.com/auth " +
|
||||
"Auth.Forward.TrustForwardHeader:true " +
|
||||
"Auth.Forward.TLS.CA:path/to/local.crt " +
|
||||
"Auth.Forward.TLS.CAOptional:true " +
|
||||
"Auth.Forward.TLS.Cert:path/to/foo.cert " +
|
||||
"Auth.Forward.TLS.Key:path/to/foo.key " +
|
||||
"Auth.Forward.TLS.InsecureSkipVerify:true " +
|
||||
"WhiteListSourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"whiteList.sourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"whiteList.useXForwardedFor:true ",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
Address: ":8000",
|
||||
TLS: &tls.TLS{
|
||||
Certificates: tls.Certificates{
|
||||
{
|
||||
CertFile: tls.FileOrContent("goo"),
|
||||
KeyFile: tls.FileOrContent("gii"),
|
||||
},
|
||||
{
|
||||
CertFile: tls.FileOrContent("foo"),
|
||||
KeyFile: tls.FileOrContent("fii"),
|
||||
},
|
||||
},
|
||||
ClientCA: tls.ClientCA{
|
||||
Files: []string{"car"},
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
Redirect: &types.Redirect{
|
||||
EntryPoint: "https",
|
||||
Regex: "http://localhost/(.*)",
|
||||
Replacement: "http://mydomain/$1",
|
||||
Permanent: true,
|
||||
},
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
Users: types.Users{
|
||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
},
|
||||
},
|
||||
Digest: &types.Digest{
|
||||
Users: types.Users{
|
||||
"test:traefik:a2688e031edb4be6a3797f3882655c05",
|
||||
"test2:traefik:518845800f9e2bfb1f1f740ec24f074e",
|
||||
},
|
||||
},
|
||||
Forward: &types.Forward{
|
||||
Address: "https://authserver.com/auth",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "path/to/local.crt",
|
||||
CAOptional: true,
|
||||
Cert: "path/to/foo.cert",
|
||||
Key: "path/to/foo.key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
},
|
||||
HeaderField: "X-WebAuth-User",
|
||||
},
|
||||
WhitelistSourceRange: []string{
|
||||
"10.42.0.0/16",
|
||||
"152.89.1.33/32",
|
||||
"afed:be44::/16",
|
||||
},
|
||||
WhiteList: &types.WhiteList{
|
||||
SourceRange: []string{
|
||||
"10.42.0.0/16",
|
||||
"152.89.1.33/32",
|
||||
"afed:be44::/16",
|
||||
},
|
||||
UseXForwardedFor: true,
|
||||
},
|
||||
Compress: true,
|
||||
ProxyProtocol: &ProxyProtocol{
|
||||
Insecure: false,
|
||||
TrustedIPs: []string{"192.168.0.1"},
|
||||
},
|
||||
ForwardedHeaders: &ForwardedHeaders{
|
||||
Insecure: false,
|
||||
TrustedIPs: []string{
|
||||
"10.0.0.3/24",
|
||||
"20.0.0.3/24",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all parameters lowercase",
|
||||
expression: "Name:foo " +
|
||||
"address::8000 " +
|
||||
"tls:goo,gii;foo,fii " +
|
||||
"tls " +
|
||||
"ca:car " +
|
||||
"ca.Optional:true " +
|
||||
"redirect.entryPoint:https " +
|
||||
"redirect.regex:http://localhost/(.*) " +
|
||||
"redirect.replacement:http://mydomain/$1 " +
|
||||
"redirect.permanent:true " +
|
||||
"compress:true " +
|
||||
"whiteListSourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"proxyProtocol.TrustedIPs:192.168.0.1 " +
|
||||
"forwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " +
|
||||
"auth.basic.users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " +
|
||||
"auth.digest.users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e " +
|
||||
"auth.headerField:X-WebAuth-User " +
|
||||
"auth.forward.address:https://authserver.com/auth " +
|
||||
"auth.forward.trustForwardHeader:true " +
|
||||
"auth.forward.tls.ca:path/to/local.crt " +
|
||||
"auth.forward.tls.caOptional:true " +
|
||||
"auth.forward.tls.cert:path/to/foo.cert " +
|
||||
"auth.forward.tls.key:path/to/foo.key " +
|
||||
"auth.forward.tls.insecureSkipVerify:true ",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
Address: ":8000",
|
||||
TLS: &tls.TLS{
|
||||
Certificates: tls.Certificates{
|
||||
{
|
||||
CertFile: tls.FileOrContent("goo"),
|
||||
KeyFile: tls.FileOrContent("gii"),
|
||||
},
|
||||
{
|
||||
CertFile: tls.FileOrContent("foo"),
|
||||
KeyFile: tls.FileOrContent("fii"),
|
||||
},
|
||||
},
|
||||
ClientCA: tls.ClientCA{
|
||||
Files: []string{"car"},
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
Redirect: &types.Redirect{
|
||||
EntryPoint: "https",
|
||||
Regex: "http://localhost/(.*)",
|
||||
Replacement: "http://mydomain/$1",
|
||||
Permanent: true,
|
||||
},
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
Users: types.Users{
|
||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
},
|
||||
},
|
||||
Digest: &types.Digest{
|
||||
Users: types.Users{
|
||||
"test:traefik:a2688e031edb4be6a3797f3882655c05",
|
||||
"test2:traefik:518845800f9e2bfb1f1f740ec24f074e",
|
||||
},
|
||||
},
|
||||
Forward: &types.Forward{
|
||||
Address: "https://authserver.com/auth",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "path/to/local.crt",
|
||||
CAOptional: true,
|
||||
Cert: "path/to/foo.cert",
|
||||
Key: "path/to/foo.key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
},
|
||||
HeaderField: "X-WebAuth-User",
|
||||
},
|
||||
WhitelistSourceRange: []string{
|
||||
"10.42.0.0/16",
|
||||
"152.89.1.33/32",
|
||||
"afed:be44::/16",
|
||||
},
|
||||
Compress: true,
|
||||
ProxyProtocol: &ProxyProtocol{
|
||||
Insecure: false,
|
||||
TrustedIPs: []string{"192.168.0.1"},
|
||||
},
|
||||
ForwardedHeaders: &ForwardedHeaders{
|
||||
Insecure: false,
|
||||
TrustedIPs: []string{
|
||||
"10.0.0.3/24",
|
||||
"20.0.0.3/24",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "default",
|
||||
expression: "Name:foo",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ForwardedHeaders insecure true",
|
||||
expression: "Name:foo ForwardedHeaders.Insecure:true",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ForwardedHeaders insecure false",
|
||||
expression: "Name:foo ForwardedHeaders.Insecure:false",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ForwardedHeaders TrustedIPs",
|
||||
expression: "Name:foo ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{
|
||||
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ProxyProtocol insecure true",
|
||||
expression: "Name:foo ProxyProtocol.Insecure:true",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
ProxyProtocol: &ProxyProtocol{Insecure: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ProxyProtocol insecure false",
|
||||
expression: "Name:foo ProxyProtocol.Insecure:false",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
ProxyProtocol: &ProxyProtocol{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ProxyProtocol TrustedIPs",
|
||||
expression: "Name:foo ProxyProtocol.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
ProxyProtocol: &ProxyProtocol{
|
||||
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "compress on",
|
||||
expression: "Name:foo Compress:on",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
Compress: true,
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "compress true",
|
||||
expression: "Name:foo Compress:true",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
Compress: true,
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
eps := EntryPoints{}
|
||||
err := eps.Set(test.expression)
|
||||
require.NoError(t, err)
|
||||
|
||||
ep := eps[test.expectedEntryPointName]
|
||||
assert.EqualValues(t, test.expectedEntryPoint, ep)
|
||||
})
|
||||
}
|
||||
}
|
||||
97
configuration/provider_aggregator.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/provider"
|
||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
)
|
||||
|
||||
type providerAggregator struct {
|
||||
providers []provider.Provider
|
||||
}
|
||||
|
||||
// NewProviderAggregator return an aggregate of all the providers configured in GlobalConfiguration
|
||||
func NewProviderAggregator(gc *GlobalConfiguration) provider.Provider {
|
||||
provider := providerAggregator{}
|
||||
if gc.Docker != nil {
|
||||
provider.providers = append(provider.providers, gc.Docker)
|
||||
}
|
||||
if gc.Marathon != nil {
|
||||
provider.providers = append(provider.providers, gc.Marathon)
|
||||
}
|
||||
if gc.File != nil {
|
||||
provider.providers = append(provider.providers, gc.File)
|
||||
}
|
||||
if gc.Rest != nil {
|
||||
provider.providers = append(provider.providers, gc.Rest)
|
||||
}
|
||||
if gc.Consul != nil {
|
||||
provider.providers = append(provider.providers, gc.Consul)
|
||||
}
|
||||
if gc.ConsulCatalog != nil {
|
||||
provider.providers = append(provider.providers, gc.ConsulCatalog)
|
||||
}
|
||||
if gc.Etcd != nil {
|
||||
provider.providers = append(provider.providers, gc.Etcd)
|
||||
}
|
||||
if gc.Zookeeper != nil {
|
||||
provider.providers = append(provider.providers, gc.Zookeeper)
|
||||
}
|
||||
if gc.Boltdb != nil {
|
||||
provider.providers = append(provider.providers, gc.Boltdb)
|
||||
}
|
||||
if gc.Kubernetes != nil {
|
||||
provider.providers = append(provider.providers, gc.Kubernetes)
|
||||
}
|
||||
if gc.Mesos != nil {
|
||||
provider.providers = append(provider.providers, gc.Mesos)
|
||||
}
|
||||
if gc.Eureka != nil {
|
||||
provider.providers = append(provider.providers, gc.Eureka)
|
||||
}
|
||||
if gc.ECS != nil {
|
||||
provider.providers = append(provider.providers, gc.ECS)
|
||||
}
|
||||
if gc.Rancher != nil {
|
||||
provider.providers = append(provider.providers, gc.Rancher)
|
||||
}
|
||||
if gc.DynamoDB != nil {
|
||||
provider.providers = append(provider.providers, gc.DynamoDB)
|
||||
}
|
||||
if gc.ServiceFabric != nil {
|
||||
provider.providers = append(provider.providers, gc.ServiceFabric)
|
||||
}
|
||||
if acmeprovider.IsEnabled() {
|
||||
provider.providers = append(provider.providers, acmeprovider.Get())
|
||||
acme.ConvertToNewFormat(acmeprovider.Get().Storage)
|
||||
}
|
||||
if len(provider.providers) == 1 {
|
||||
return provider.providers[0]
|
||||
}
|
||||
return provider
|
||||
}
|
||||
|
||||
func (p providerAggregator) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
for _, p := range p.providers {
|
||||
providerType := reflect.TypeOf(p)
|
||||
jsonConf, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
log.Debugf("Unable to marshal provider conf %v with error: %v", providerType, err)
|
||||
}
|
||||
log.Infof("Starting provider %v %s", providerType, jsonConf)
|
||||
currentProvider := p
|
||||
safe.Go(func() {
|
||||
err := currentProvider.Provide(configurationChan, pool, constraints)
|
||||
if err != nil {
|
||||
log.Errorf("Error starting provider %v: %s", providerType, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -42,6 +42,18 @@ set -o nounset
|
||||
|
||||
USAGE="$(basename "$0") <path to acme> <destination cert directory>"
|
||||
|
||||
# Platform variations
|
||||
case "$(uname)" in
|
||||
'Linux')
|
||||
# On Linux, -d should always work. --decode does not work with Alpine's busybox-binary
|
||||
CMD_DECODE_BASE64="base64 -d"
|
||||
;;
|
||||
*)
|
||||
# Max OS-X supports --decode and -D, but --decode may be supported by other platforms as well.
|
||||
CMD_DECODE_BASE64="base64 --decode"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Allow us to exit on a missing jq binary
|
||||
exit_jq() {
|
||||
echo "
|
||||
@@ -92,7 +104,7 @@ fi
|
||||
|
||||
jq=$(command -v jq) || exit_jq
|
||||
|
||||
priv=$(${jq} -e -r '.PrivateKey' "${acmefile}") || bad_acme
|
||||
priv=$(${jq} -e -r '.Account.PrivateKey' "${acmefile}") || bad_acme
|
||||
|
||||
if [ ! -n "${priv}" ]; then
|
||||
echo "
|
||||
@@ -130,9 +142,11 @@ trap 'umask ${oldumask}' EXIT
|
||||
#
|
||||
# and sed:
|
||||
# echo "-----BEGIN RSA PRIVATE KEY-----" > "${pdir}/letsencrypt.key"
|
||||
# echo ${priv} | sed 's/(.{64})/\1\n/g' >> "${pdir}/letsencrypt.key"
|
||||
# echo "-----END RSA PRIVATE KEY-----" > "${pdir}/letsencrypt.key"
|
||||
#
|
||||
# echo ${priv} | sed -E 's/(.{64})/\1\n/g' >> "${pdir}/letsencrypt.key"
|
||||
# sed -i '$ d' "${pdir}/letsencrypt.key"
|
||||
# echo "-----END RSA PRIVATE KEY-----" >> "${pdir}/letsencrypt.key"
|
||||
# openssl rsa -noout -in "${pdir}/letsencrypt.key" -check # To check if the key is valid
|
||||
|
||||
# In the end, openssl was chosen because most users will need this script
|
||||
# *because* of openssl combined with the fact that it will refuse to write the
|
||||
# key if it does not parse out correctly. The other mechanisms were left as
|
||||
@@ -141,11 +155,16 @@ echo -e "-----BEGIN RSA PRIVATE KEY-----\n${priv}\n-----END RSA PRIVATE KEY-----
|
||||
| openssl rsa -inform pem -out "${pdir}/letsencrypt.key"
|
||||
|
||||
# Process the certificates for each of the domains in acme.json
|
||||
for domain in $(jq -r '.DomainsCertificate.Certs[].Certificate.Domain' acme.json); do
|
||||
for domain in $(jq -r '.Certificates[].Domain.Main' ${acmefile}); do
|
||||
# Traefik stores a cert bundle for each domain. Within this cert
|
||||
# bundle there is both proper the certificate and the Let's Encrypt CA
|
||||
echo "Extracting cert bundle for ${domain}"
|
||||
cert=$(jq -e -r --arg domain "$domain" '.DomainsCertificate.Certs[].Certificate |
|
||||
select (.Domain == $domain )| .Certificate' ${acmefile}) || bad_acme
|
||||
echo "${cert}" | base64 --decode > "${cdir}/${domain}.pem"
|
||||
cert=$(jq -e -r --arg domain "$domain" '.Certificates[] |
|
||||
select (.Domain.Main == $domain )| .Certificate' ${acmefile}) || bad_acme
|
||||
echo "${cert}" | ${CMD_DECODE_BASE64} > "${cdir}/${domain}.crt"
|
||||
|
||||
echo "Extracting private key for ${domain}"
|
||||
key=$(jq -e -r --arg domain "$domain" '.Certificates[] |
|
||||
select (.Domain.Main == $domain )| .Key' ${acmefile}) || bad_acme
|
||||
echo "${key}" | ${CMD_DECODE_BASE64} > "${pdir}/${domain}.key"
|
||||
done
|
||||
|
||||
11
docs.Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM alpine
|
||||
|
||||
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/bin
|
||||
|
||||
COPY requirements.txt /mkdocs/
|
||||
WORKDIR /mkdocs
|
||||
|
||||
RUN apk --update upgrade \
|
||||
&& apk --no-cache --no-progress add py-pip \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& pip install --user -r requirements.txt
|
||||
352
docs/basics.md
@@ -2,7 +2,7 @@
|
||||
|
||||
## Concepts
|
||||
|
||||
Let's take our example from the [overview](https://docs.traefik.io/#overview) again:
|
||||
Let's take our example from the [overview](/#overview) again:
|
||||
|
||||
|
||||
> Imagine that you have deployed a bunch of microservices on your infrastructure. You probably used a service registry (like etcd or consul) and/or an orchestrator (swarm, Mesos/Marathon) to manage all these services.
|
||||
@@ -62,7 +62,9 @@ And here is another example with client certificate authentication:
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
clientCAFiles = ["tests/clientca1.crt", "tests/clientca2.crt"]
|
||||
[entryPoints.https.tls.ClientCA]
|
||||
files = ["tests/clientca1.crt", "tests/clientca2.crt"]
|
||||
optional = false
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "tests/traefik.crt"
|
||||
keyFile = "tests/traefik.key"
|
||||
@@ -86,6 +88,7 @@ Following is the list of existing modifier rules:
|
||||
|
||||
- `AddPrefix: /products`: Add path prefix to the existing request path prior to forwarding the request to the backend.
|
||||
- `ReplacePath: /serverless-path`: Replaces the path and adds the old path to the `X-Replaced-Path` header. Useful for mapping to AWS Lambda or Google Cloud Functions.
|
||||
- `ReplacePathRegex: ^/api/v2/(.*) /api/$1`: Replaces the path with a regular expression and adds the old path to the `X-Replaced-Path` header. Separate the regular expression and the replacement by a space.
|
||||
|
||||
#### Matchers
|
||||
|
||||
@@ -167,7 +170,7 @@ Here is an example of frontends definition:
|
||||
|
||||
- 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)
|
||||
- `frontend2` will forward the traffic to the `backend1` if the rule `HostRegexp: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
|
||||
|
||||
#### Combining multiple rules
|
||||
@@ -185,6 +188,7 @@ In TOML file, you can use multiple routes:
|
||||
```
|
||||
|
||||
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
|
||||
@@ -208,7 +212,8 @@ Finally, you can create a rule to bind multiple domains or Path to a frontend, u
|
||||
|
||||
#### Rules Order
|
||||
|
||||
When combining `Modifier` rules with `Matcher` rules, it is important to remember that `Modifier` rules **ALWAYS** apply after the `Matcher` rules.
|
||||
When combining `Modifier` rules with `Matcher` rules, it is important to remember that `Modifier` rules **ALWAYS** apply after the `Matcher` rules.
|
||||
|
||||
The following rules are both `Matchers` and `Modifiers`, so the `Matcher` portion of the rule will apply first, and the `Modifier` will apply later.
|
||||
|
||||
- `PathStrip`
|
||||
@@ -228,31 +233,36 @@ The following rules are both `Matchers` and `Modifiers`, so the `Matcher` portio
|
||||
#### 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`.
|
||||
`PathPrefix:/foo;Host:foo.com` (length == 28) will be matched before `PathPrefixStrip:/foobar` (length == 23) will be matched before `PathPrefix:/foo,/bar` (length == 20).
|
||||
|
||||
You can customize priority by frontend:
|
||||
You can customize priority by frontend. The priority value override the rule length during sorting:
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
priority = 10
|
||||
priority = 20
|
||||
passHostHeader = true
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "PathPrefix:/to"
|
||||
[frontends.frontend2]
|
||||
priority = 5
|
||||
backend = "backend2"
|
||||
passHostHeader = true
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "PathPrefix:/toto"
|
||||
```
|
||||
|
||||
Here, `frontend1` will be matched before `frontend2` (`10 > 5`).
|
||||
Here, `frontend1` will be matched before `frontend2` (`20 > 16`).
|
||||
|
||||
#### Custom headers
|
||||
|
||||
Custom headers can be configured through the frontends, to add headers to either requests or responses that match the frontend's rules. This allows for setting headers such as `X-Script-Name` to be added to the request, or custom headers to be added to the response:
|
||||
Custom headers can be configured through the frontends, to add headers to either requests or responses that match the frontend's rules.
|
||||
This allows for setting headers such as `X-Script-Name` to be added to the request, or custom headers to be added to the response.
|
||||
|
||||
!!! warning
|
||||
If the custom header name is the same as one header name of the request or response, it will be replaced.
|
||||
|
||||
In this example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request and the `X-Custom-Response-Header` header added to the response.
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
@@ -266,11 +276,27 @@ Custom headers can be configured through the frontends, to add headers to either
|
||||
rule = "PathPrefixStrip:/cheese"
|
||||
```
|
||||
|
||||
In this example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request, and the `X-Custom-Response-Header` added to the response.
|
||||
In this second example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request, the `X-Custom-Request-Header` header removed from the request, and the `X-Custom-Response-Header` header removed from the response.
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.headers.customresponseheaders]
|
||||
X-Custom-Response-Header = ""
|
||||
[frontends.frontend1.headers.customrequestheaders]
|
||||
X-Script-Name = "test"
|
||||
X-Custom-Request-Header = ""
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "PathPrefixStrip:/cheese"
|
||||
```
|
||||
|
||||
#### Security headers
|
||||
|
||||
Security related headers (HSTS headers, SSL redirection, Browser XSS filter, etc) can be added and configured per frontend in a similar manner to the custom headers above. This functionality allows for some easy security features to quickly be set. An example of some of the security headers:
|
||||
Security related headers (HSTS headers, SSL redirection, Browser XSS filter, etc) can be added and configured per frontend in a similar manner to the custom headers above.
|
||||
This functionality allows for some easy security features to quickly be set.
|
||||
|
||||
An example of some of the security headers:
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
@@ -290,15 +316,55 @@ Security related headers (HSTS headers, SSL redirection, Browser XSS filter, etc
|
||||
|
||||
In this example, traffic routed through the first frontend will have the `X-Frame-Options` header set to `DENY`, and the second will only allow HTTPS request through, otherwise will return a 301 HTTPS redirect.
|
||||
|
||||
The detailed documentation for those security headers can be found in [unrolled/secure](https://github.com/unrolled/secure#available-options).
|
||||
!!! note
|
||||
The detailed documentation for those security headers can be found in [unrolled/secure](https://github.com/unrolled/secure#available-options).
|
||||
|
||||
### Backends
|
||||
|
||||
A backend is responsible to load-balance the traffic coming from one or more frontends to a set of http servers.
|
||||
|
||||
#### 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).
|
||||
|
||||
!!! note
|
||||
Paths in `url` are ignored. Use `Modifier` to specify paths instead.
|
||||
|
||||
Here is an example of backends and servers definition:
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
# ...
|
||||
[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.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
|
||||
```
|
||||
|
||||
- Two backends are defined: `backend1` and `backend2`
|
||||
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1`.
|
||||
- `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`.
|
||||
|
||||
#### Load-balancing
|
||||
|
||||
Various methods of load-balancing are supported:
|
||||
|
||||
- `wrr`: Weighted Round Robin
|
||||
- `drr`: Dynamic Round Robin: increases weights on servers that perform better than others. It also rolls back to original weights if the servers have changed.
|
||||
- `wrr`: Weighted Round Robin.
|
||||
- `drr`: Dynamic Round Robin: increases weights on servers that perform better than others.
|
||||
It also rolls back to original weights if the servers have changed.
|
||||
|
||||
#### Circuit breakers
|
||||
|
||||
A circuit breaker can also be applied to a backend, preventing high loads on failing servers.
|
||||
Initial state is Standby. CB observes the statistics and does not modify the request.
|
||||
@@ -313,16 +379,33 @@ It can be configured using:
|
||||
|
||||
For example:
|
||||
|
||||
- `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window for a frontend
|
||||
- `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window for a frontend.
|
||||
- `LatencyAtQuantileMS(50.0) > 50`: watch latency at quantile in milliseconds.
|
||||
- `ResponseCodeRatio(500, 600, 0, 600) > 0.5`: ratio of response codes in range [500-600) to [0-600)
|
||||
- `ResponseCodeRatio(500, 600, 0, 600) > 0.5`: ratio of response codes in ranges [500-600) and [0-600).
|
||||
|
||||
To proactively prevent backends from being overwhelmed with high load, a maximum connection limit can
|
||||
also be applied to each backend.
|
||||
Here is an example of backends and servers definition:
|
||||
|
||||
Maximum connections can be configured by specifying an integer value for `maxconn.amount` and
|
||||
`maxconn.extractorfunc` which is a strategy used to determine how to categorize requests in order to
|
||||
evaluate the maximum connections.
|
||||
```toml
|
||||
[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
|
||||
```
|
||||
|
||||
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1` using default `wrr` load-balancing strategy.
|
||||
- a circuit breaker is added on `backend1` using the expression `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window
|
||||
|
||||
#### Maximum connections
|
||||
|
||||
To proactively prevent backends from being overwhelmed with high load, a maximum connection limit can also be applied to each backend.
|
||||
|
||||
Maximum connections can be configured by specifying an integer value for `maxconn.amount` and `maxconn.extractorfunc` which is a strategy used to determine how to categorize requests in order to evaluate the maximum connections.
|
||||
|
||||
For example:
|
||||
```toml
|
||||
@@ -331,17 +414,37 @@ For example:
|
||||
[backends.backend1.maxconn]
|
||||
amount = 10
|
||||
extractorfunc = "request.host"
|
||||
# ...
|
||||
```
|
||||
|
||||
- `backend1` will return `HTTP code 429 Too Many Requests` if there are already 10 requests in progress for the same Host header.
|
||||
- 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.
|
||||
#### Sticky sessions
|
||||
|
||||
Sticky sessions are supported with both load balancers.
|
||||
When sticky sessions are enabled, a cookie is set on the initial request.
|
||||
The default cookie name is an abbreviation of a sha1 (ex: `_1d52e`).
|
||||
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.
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
# Enable sticky session
|
||||
[backends.backend1.loadbalancer.stickiness]
|
||||
|
||||
# Customize the cookie name
|
||||
#
|
||||
# Optional
|
||||
# Default: a sha1 (6 chars)
|
||||
#
|
||||
# cookieName = "my_cookie"
|
||||
```
|
||||
|
||||
The deprecated way:
|
||||
|
||||
For example:
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
@@ -349,12 +452,11 @@ For example:
|
||||
sticky = true
|
||||
```
|
||||
|
||||
A health check can be configured in order to remove a backend from LB rotation
|
||||
as long as it keeps returning HTTP status codes other than 200 OK to HTTP GET
|
||||
requests periodically carried out by Traefik. The check is defined by a path
|
||||
appended to the backend URL and an interval (given in a format understood by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration)) specifying how
|
||||
often the health check should be executed (the default being 30 seconds).
|
||||
Each backend must respond to the health check within 5 seconds.
|
||||
#### Health Check
|
||||
|
||||
A health check can be configured in order to remove a backend from LB rotation as long as it keeps returning HTTP status codes other than `200 OK` to HTTP GET requests periodically carried out by Traefik.
|
||||
The check is defined by a path appended to the backend URL and an interval (given in a format understood by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration)) specifying how often the health check should be executed (the default being 30 seconds).
|
||||
Each backend must respond to the health check within 5 seconds.
|
||||
By default, the port of the backend server is used, however, this may be overridden.
|
||||
|
||||
A recovering backend returning 200 OK responses again is being returned to the
|
||||
@@ -379,49 +481,12 @@ To use a different port for the healthcheck:
|
||||
port = 8080
|
||||
```
|
||||
|
||||
### 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).
|
||||
|
||||
!!! note
|
||||
Paths in `url` are ignored. Use `Modifier` to specify paths instead.
|
||||
|
||||
Here is an example of backends and servers definition:
|
||||
|
||||
```toml
|
||||
[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.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
|
||||
```
|
||||
|
||||
- Two backends are defined: `backend1` and `backend2`
|
||||
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1` using default `wrr` load-balancing strategy.
|
||||
- `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
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
Træfik's configuration has two parts:
|
||||
|
||||
- The [static Træfik configuration](/basics#static-trfk-configuration) which is loaded only at the beginning.
|
||||
- The [dynamic Træfik configuration](/basics#dynamic-trfk-configuration) which can be hot-reloaded (no need to restart the process).
|
||||
- The [static Træfik configuration](/basics#static-trfik-configuration) which is loaded only at the beginning.
|
||||
- The [dynamic Træfik configuration](/basics#dynamic-trfik-configuration) which can be hot-reloaded (no need to restart the process).
|
||||
|
||||
### Static Træfik configuration
|
||||
|
||||
@@ -437,7 +502,9 @@ Each item takes precedence over the item below it:
|
||||
|
||||
It means that arguments override configuration file, and key-value store overrides arguments.
|
||||
|
||||
Note that the provider-enabling argument parameters (e.g., `--docker`) set all default values for the specific provider. It must not be used if a configuration source with less precedence wants to set a non-default provider value.
|
||||
!!! note
|
||||
the provider-enabling argument parameters (e.g., `--docker`) set all default values for the specific provider.
|
||||
It must not be used if a configuration source with less precedence wants to set a non-default provider value.
|
||||
|
||||
#### Configuration file
|
||||
|
||||
@@ -483,6 +550,7 @@ The dynamic configuration concerns :
|
||||
- [Frontends](/basics/#frontends)
|
||||
- [Backends](/basics/#backends)
|
||||
- [Servers](/basics/#servers)
|
||||
- HTTPS Certificates
|
||||
|
||||
Træfik can hot-reload those rules which could be provided by [multiple configuration backends](/configuration/commons).
|
||||
|
||||
@@ -503,21 +571,27 @@ traefik [command] [--flag=flag_argument]
|
||||
List of Træfik available commands with description :
|
||||
|
||||
- `version` : Print version
|
||||
- `storeconfig` : Store the static traefik configuration into a Key-value stores. Please refer to the [Store Træfik configuration](/user-guide/kv-config/#store-trfk-configuration) section to get documentation on it.
|
||||
- `storeconfig` : Store the static Traefik configuration into a Key-value stores. Please refer to the [Store Træfik configuration](/user-guide/kv-config/#store-configuration-in-key-value-store) section to get documentation on it.
|
||||
- `bug`: The easiest way to submit a pre-filled issue.
|
||||
- `healthcheck`: Calls traefik `/ping` to check health.
|
||||
- `healthcheck`: Calls Traefik `/ping` to check health.
|
||||
|
||||
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 beginning of the help section:
|
||||
Each command is described at the beginning of the help section:
|
||||
|
||||
```bash
|
||||
traefik --help
|
||||
|
||||
# or
|
||||
|
||||
docker run traefik[:version] --help
|
||||
# ex: docker run traefik:1.5 --help
|
||||
```
|
||||
|
||||
### Command: bug
|
||||
@@ -528,16 +602,138 @@ Here is the easiest way to submit a pre-filled issue on [Træfik GitHub](https:/
|
||||
traefik bug
|
||||
```
|
||||
|
||||
See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||
Watch [this demo](https://www.youtube.com/watch?v=Lyz62L8m93I).
|
||||
|
||||
### Command: healthcheck
|
||||
|
||||
This command allows to check the health of Traefik. Its exit status is `0` if Traefik is healthy and `1` if it is unhealthy.
|
||||
|
||||
This can be used with Docker [HEALTHCHECK](https://docs.docker.com/engine/reference/builder/#healthcheck) instruction or any other health check orchestration mechanism.
|
||||
|
||||
Note: the `web` provider must be enabled to allow `/ping` calls by the `healthcheck` command.
|
||||
!!! note
|
||||
The [`ping`](/configuration/ping) must be enabled to allow the `healthcheck` command to call `/ping`.
|
||||
|
||||
```bash
|
||||
$ traefik healthcheck
|
||||
traefik healthcheck
|
||||
```
|
||||
```bash
|
||||
OK: http://:8082/ping
|
||||
```
|
||||
|
||||
|
||||
## Collected Data
|
||||
|
||||
**This feature is disabled by default.**
|
||||
|
||||
You can read the public proposal on this topic [here](https://github.com/containous/traefik/issues/2369).
|
||||
|
||||
### Why ?
|
||||
|
||||
In order to help us learn more about how Træfik is being used and improve it, we collect anonymous usage statistics from running instances.
|
||||
Those data help us prioritize our developments and focus on what's more important (for example, which configuration backend is used and which is not used).
|
||||
|
||||
### What ?
|
||||
|
||||
Once a day (the first call begins 10 minutes after the start of Træfik), we collect:
|
||||
|
||||
- the Træfik version
|
||||
- a hash of the configuration
|
||||
- an **anonymous version** of the static configuration:
|
||||
- token, user name, password, URL, IP, domain, email, etc, are removed
|
||||
|
||||
!!! note
|
||||
We do not collect the dynamic configuration (frontends & backends).
|
||||
|
||||
!!! note
|
||||
We do not collect data behind the scenes to run advertising programs or to sell such data to third-party.
|
||||
|
||||
#### Here is an example
|
||||
|
||||
- Source configuration:
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[api]
|
||||
|
||||
[Docker]
|
||||
endpoint = "tcp://10.10.10.10:2375"
|
||||
domain = "foo.bir"
|
||||
exposedByDefault = true
|
||||
swarmMode = true
|
||||
|
||||
[Docker.TLS]
|
||||
ca = "dockerCA"
|
||||
cert = "dockerCert"
|
||||
key = "dockerKey"
|
||||
insecureSkipVerify = true
|
||||
|
||||
[ECS]
|
||||
domain = "foo.bar"
|
||||
exposedByDefault = true
|
||||
clusters = ["foo-bar"]
|
||||
region = "us-west-2"
|
||||
accessKeyID = "AccessKeyID"
|
||||
secretAccessKey = "SecretAccessKey"
|
||||
```
|
||||
|
||||
- Obfuscated and anonymous configuration:
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[api]
|
||||
|
||||
[Docker]
|
||||
endpoint = "xxxx"
|
||||
domain = "xxxx"
|
||||
exposedByDefault = true
|
||||
swarmMode = true
|
||||
|
||||
[Docker.TLS]
|
||||
ca = "xxxx"
|
||||
cert = "xxxx"
|
||||
key = "xxxx"
|
||||
insecureSkipVerify = false
|
||||
|
||||
[ECS]
|
||||
domain = "xxxx"
|
||||
exposedByDefault = true
|
||||
clusters = []
|
||||
region = "us-west-2"
|
||||
accessKeyID = "xxxx"
|
||||
secretAccessKey = "xxxx"
|
||||
```
|
||||
|
||||
### Show me the code !
|
||||
|
||||
If you want to dig into more details, here is the source code of the collecting system: [collector.go](https://github.com/containous/traefik/blob/master/collector/collector.go)
|
||||
|
||||
By default we anonymize all configuration fields, except fields tagged with `export=true`.
|
||||
|
||||
You can check all fields in the [godoc](https://godoc.org/github.com/containous/traefik/configuration#GlobalConfiguration).
|
||||
|
||||
### How to enable this ?
|
||||
|
||||
You can enable the collecting system by:
|
||||
|
||||
- adding this line in the configuration TOML file:
|
||||
|
||||
```toml
|
||||
# Send anonymous usage data
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
sendAnonymousUsage = true
|
||||
```
|
||||
|
||||
- adding this flag in the CLI:
|
||||
|
||||
```bash
|
||||
./traefik --sendAnonymousUsage=true
|
||||
```
|
||||
|
||||
@@ -14,7 +14,7 @@ I used 4 VMs for the tests with the following configuration:
|
||||
## Setup
|
||||
|
||||
1. One VM used to launch the benchmarking tool [wrk](https://github.com/wg/wrk)
|
||||
2. One VM for traefik (v1.0.0-beta.416) / nginx (v1.4.6)
|
||||
2. One VM for Traefik (v1.0.0-beta.416) / nginx (v1.4.6)
|
||||
3. Two VMs for 2 backend servers in go [whoami](https://github.com/emilevauge/whoamI/)
|
||||
|
||||
Each VM has been tuned using the following limits:
|
||||
@@ -118,7 +118,7 @@ server {
|
||||
Here is the `traefik.toml` file used:
|
||||
|
||||
```toml
|
||||
MaxIdleConnsPerHost = 100000
|
||||
maxIdleConnsPerHost = 100000
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
@@ -182,7 +182,8 @@ Requests/sec: 33591.67
|
||||
Transfer/sec: 4.97MB
|
||||
```
|
||||
|
||||
### traefik:
|
||||
### Traefik:
|
||||
|
||||
```shell
|
||||
wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-traefik:8000/bench
|
||||
Running 1m test @ http://IP-traefik:8000/bench
|
||||
@@ -209,5 +210,5 @@ Not bad for young project :) !
|
||||
Some areas of possible improvements:
|
||||
|
||||
- Use [GO_REUSEPORT](https://github.com/kavu/go_reuseport) listener
|
||||
- Run a separate server instance per CPU core with `GOMAXPROCS=1` (it appears during benchmarks that there is a lot more context switches with traefik than with nginx)
|
||||
- Run a separate server instance per CPU core with `GOMAXPROCS=1` (it appears during benchmarks that there is a lot more context switches with Traefik than with nginx)
|
||||
|
||||
|
||||
@@ -1,114 +1,423 @@
|
||||
## ACME (Let's Encrypt) configuration
|
||||
# ACME (Let's Encrypt) Configuration
|
||||
|
||||
See [Let's Encrypt examples](/user-guide/examples/#lets-encrypt-support) and [Docker & Let's Encrypt user guide](/user-guide/docker-and-lets-encrypt) as well.
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
# Sample entrypoint configuration when using ACME
|
||||
# Sample entrypoint configuration when using ACME.
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
```
|
||||
|
||||
# Enable ACME (Let's Encrypt): automatic SSL
|
||||
```toml
|
||||
# Enable ACME (Let's Encrypt): automatic SSL.
|
||||
[acme]
|
||||
|
||||
# Email address used for registration
|
||||
# Email address used for registration.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
email = "test@traefik.io"
|
||||
|
||||
# File used for certificates storage.
|
||||
#
|
||||
# Optional (Deprecated)
|
||||
#
|
||||
#storageFile = "acme.json"
|
||||
|
||||
# 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
|
||||
#
|
||||
storage = "acme.json" # or "traefik/acme/account" if using KV store
|
||||
storage = "acme.json"
|
||||
# or `storage = "traefik/acme/account"` if using KV store.
|
||||
|
||||
# Entrypoint to proxy acme challenge/apply certificates to.
|
||||
# WARNING, must point to an entrypoint on port 443
|
||||
# Entrypoint to proxy acme apply certificates to.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
entryPoint = "https"
|
||||
|
||||
# Use a DNS based acme challenge rather than external HTTPS access, e.g. for a firewalled server
|
||||
# Select the provider that matches the DNS domain that will host the challenge TXT record,
|
||||
# and provide environment variables with access keys to enable setting it:
|
||||
# - cloudflare: CLOUDFLARE_EMAIL, CLOUDFLARE_API_KEY
|
||||
# - digitalocean: DO_AUTH_TOKEN
|
||||
# - dnsimple: DNSIMPLE_EMAIL, DNSIMPLE_OAUTH_TOKEN
|
||||
# - dnsmadeeasy: DNSMADEEASY_API_KEY, DNSMADEEASY_API_SECRET
|
||||
# - exoscale: EXOSCALE_API_KEY, EXOSCALE_API_SECRET
|
||||
# - gandi: GANDI_API_KEY
|
||||
# - linode: LINODE_API_KEY
|
||||
# - manual: none, but run traefik interactively & turn on acmeLogging to see instructions & press Enter
|
||||
# - namecheap: NAMECHEAP_API_USER, NAMECHEAP_API_KEY
|
||||
# - rfc2136: RFC2136_TSIG_KEY, RFC2136_TSIG_SECRET, RFC2136_TSIG_ALGORITHM, RFC2136_NAMESERVER
|
||||
# - route53: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, or configured user/instance IAM profile
|
||||
# - dyn: DYN_CUSTOMER_NAME, DYN_USER_NAME, DYN_PASSWORD
|
||||
# - vultr: VULTR_API_KEY
|
||||
# - ovh: OVH_ENDPOINT, OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY
|
||||
# - pdns: PDNS_API_KEY, PDNS_API_URL
|
||||
# Deprecated, replaced by [acme.dnsChallenge].
|
||||
#
|
||||
# Optional
|
||||
# Optional.
|
||||
#
|
||||
# dnsProvider = "digitalocean"
|
||||
|
||||
# By default, the dnsProvider will verify the TXT DNS challenge record before letting ACME verify
|
||||
# If delayDontCheckDNS is greater than zero, avoid this & instead just wait so many seconds.
|
||||
# Useful if internal networks block external DNS queries
|
||||
# Deprecated, replaced by [acme.dnsChallenge.delayBeforeCheck].
|
||||
#
|
||||
# Optional
|
||||
# Default: 0
|
||||
#
|
||||
# delayDontCheckDNS = 0
|
||||
|
||||
# If true, display debug log messages from the acme client library
|
||||
# If true, display debug log messages from the acme client library.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# acmeLogging = true
|
||||
|
||||
# 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://letsencrypt.org/docs/rate-limits
|
||||
# Deprecated. Enable on demand certificate generation.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# 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.
|
||||
# Enable certificate generation on frontends host rules.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# onHostRule = true
|
||||
|
||||
# CA server to use.
|
||||
# Uncomment the line to use Let's Encrypt's staging server,
|
||||
# leave commented to go to prod.
|
||||
#
|
||||
# Optional
|
||||
# Default: "https://acme-v02.api.letsencrypt.org/directory"
|
||||
#
|
||||
# caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
|
||||
# Domains list.
|
||||
# Only domains defined here can generate wildcard certificates.
|
||||
#
|
||||
# [[acme.domains]]
|
||||
# main = "local1.com"
|
||||
# sans = ["test1.local1.com", "test2.local1.com"]
|
||||
# [[acme.domains]]
|
||||
# main = "local2.com"
|
||||
# [[acme.domains]]
|
||||
# main = "*.local3.com"
|
||||
# sans = ["local3.com", "test1.test1.local3.com"]
|
||||
|
||||
# Use a HTTP-01 ACME challenge.
|
||||
#
|
||||
# Optional (but recommended)
|
||||
#
|
||||
[acme.httpChallenge]
|
||||
|
||||
# EntryPoint to use for the HTTP-01 challenges.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
entryPoint = "http"
|
||||
|
||||
# Use a DNS-01 ACME challenge rather than HTTP-01 challenge.
|
||||
# Note: mandatory for wildcard certificate generation.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# OnHostRule = true
|
||||
# [acme.dnsChallenge]
|
||||
|
||||
# CA server to use
|
||||
# Uncomment the line to run on the staging let's encrypt server
|
||||
# Leave comment to go to prod
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# caServer = "https://acme-staging.api.letsencrypt.org/directory"
|
||||
# DNS provider used.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
# provider = "digitalocean"
|
||||
|
||||
# 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://letsencrypt.org/docs/rate-limits
|
||||
# Each domain & SANs will lead to a certificate request.
|
||||
# [[acme.domains]]
|
||||
# main = "local1.com"
|
||||
# sans = ["test1.local1.com", "test2.local1.com"]
|
||||
# [[acme.domains]]
|
||||
# main = "local2.com"
|
||||
# sans = ["test1.local2.com", "test2.local2.com"]
|
||||
# [[acme.domains]]
|
||||
# main = "local3.com"
|
||||
# [[acme.domains]]
|
||||
# main = "local4.com"
|
||||
# By default, the provider will verify the TXT DNS challenge record before letting ACME verify.
|
||||
# If delayBeforeCheck is greater than zero, this check is delayed for the configured duration in seconds.
|
||||
# Useful if internal networks block external DNS queries.
|
||||
#
|
||||
# Optional
|
||||
# Default: 0
|
||||
#
|
||||
# delayBeforeCheck = 0
|
||||
```
|
||||
|
||||
### `caServer`
|
||||
|
||||
The CA server to use.
|
||||
|
||||
This example shows the usage of Let's Encrypt's staging server:
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
# ...
|
||||
```
|
||||
|
||||
### `dnsChallenge`
|
||||
|
||||
Use the `DNS-01` challenge to generate and renew ACME certificates by provisioning a DNS record.
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
[acme.dnsChallenge]
|
||||
provider = "digitalocean"
|
||||
delayBeforeCheck = 0
|
||||
# ...
|
||||
```
|
||||
|
||||
#### `delayBeforeCheck`
|
||||
|
||||
By default, the `provider` will verify the TXT DNS challenge record before letting ACME verify.
|
||||
If `delayBeforeCheck` is greater than zero, this check is delayed for the configured duration in seconds.
|
||||
|
||||
Useful if internal networks block external DNS queries.
|
||||
|
||||
!!! note
|
||||
A `provider` is mandatory.
|
||||
|
||||
#### `provider`
|
||||
|
||||
Here is a list of supported `provider`s, that can automate the DNS verification, along with the required environment variables and their [wildcard & root domain support](/configuration/acme/#wildcard-domains) for each. Do not hesitate to complete it.
|
||||
|
||||
| Provider Name | Provider Code | Environment Variables | Wildcard & Root Domain Support |
|
||||
|--------------------------------------------------------|----------------|-----------------------------------------------------------------------------------------------------------------------------|--------------------------------|
|
||||
| [Auroradns](https://www.pcextreme.com/aurora/dns) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` | Not tested yet |
|
||||
| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP` | Not tested yet |
|
||||
| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | `BLUECAT_SERVER_URL`, `BLUECAT_USER_NAME`, `BLUECAT_PASSWORD`, `BLUECAT_CONFIG_NAME`, `BLUECAT_DNS_VIEW` | Not tested yet |
|
||||
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CLOUDFLARE_EMAIL`, `CLOUDFLARE_API_KEY` - The `Global API Key` needs to be used, not the `Origin CA Key` | YES |
|
||||
| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | `CLOUDXNS_API_KEY`, `CLOUDXNS_SECRET_KEY` | Not tested yet |
|
||||
| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` | YES |
|
||||
| [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` | Not tested yet |
|
||||
| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` | Not tested yet |
|
||||
| [DNSPod](http://www.dnspod.net/) | `dnspod` | `DNSPOD_API_KEY` | Not tested yet |
|
||||
| [Duck DNS](https://www.duckdns.org/) | `duckdns` | `DUCKDNS_TOKEN` | Not tested yet |
|
||||
| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | Not tested yet |
|
||||
| External Program | `exec` | `EXEC_PATH` | Not tested yet |
|
||||
| [Exoscale](https://www.exoscale.ch) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` | Not tested yet |
|
||||
| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | Not tested yet |
|
||||
| [Gandi](https://www.gandi.net) | `gandi` | `GANDI_API_KEY` | Not tested yet |
|
||||
| [Gandi V5](http://doc.livedns.gandi.net) | `gandiv5` | `GANDIV5_API_KEY` | Not tested yet |
|
||||
| [Glesys](https://glesys.com/) | `glesys` | `GLESYS_API_USER`, `GLESYS_API_KEY`, `GLESYS_DOMAIN` | Not tested yet |
|
||||
| [GoDaddy](https://godaddy.com/domains) | `godaddy` | `GODADDY_API_KEY`, `GODADDY_API_SECRET` | Not tested yet |
|
||||
| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | `GCE_PROJECT`, `GCE_SERVICE_ACCOUNT_FILE` | YES |
|
||||
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | Not tested yet |
|
||||
| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` | Not tested yet |
|
||||
| manual | - | none, but you need to run Træfik interactively, turn on `acmeLogging` to see instructions and press <kbd>Enter</kbd>. | YES |
|
||||
| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` | Not tested yet |
|
||||
| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | Not tested yet |
|
||||
| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` | Not tested yet |
|
||||
| [Open Telekom Cloud](https://cloud.telekom.de/en/) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` | Not tested yet |
|
||||
| [OVH](https://www.ovh.com) | `ovh` | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` | YES |
|
||||
| [PowerDNS](https://www.powerdns.com) | `pdns` | `PDNS_API_KEY`, `PDNS_API_URL` | Not tested yet |
|
||||
| [Rackspace](https://www.rackspace.com/cloud/dns) | `rackspace` | `RACKSPACE_USER`, `RACKSPACE_API_KEY` | Not tested yet |
|
||||
| [RFC2136](https://tools.ietf.org/html/rfc2136) | `rfc2136` | `RFC2136_TSIG_KEY`, `RFC2136_TSIG_SECRET`, `RFC2136_TSIG_ALGORITHM`, `RFC2136_NAMESERVER` | Not tested yet |
|
||||
| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_HOSTED_ZONE_ID` or a configured user/instance IAM profile. | YES |
|
||||
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | Not tested yet |
|
||||
|
||||
### `domains`
|
||||
|
||||
You can provide SANs (alternative domains) to each main domain.
|
||||
All domains must have A/AAAA records pointing to Træfik.
|
||||
Each domain & SAN will lead to a certificate request.
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
[[acme.domains]]
|
||||
main = "local1.com"
|
||||
sans = ["test1.local1.com", "test2.local1.com"]
|
||||
[[acme.domains]]
|
||||
main = "local2.com"
|
||||
[[acme.domains]]
|
||||
main = "*.local3.com"
|
||||
sans = ["local3.com", "test1.test1.local3.com"]
|
||||
# ...
|
||||
```
|
||||
|
||||
!!! warning
|
||||
Take note that Let's Encrypt applies [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||
|
||||
!!! note
|
||||
Wildcard certificates can only be verified through a `DNS-01` challenge.
|
||||
|
||||
#### Wildcard Domains
|
||||
|
||||
[ACME V2](https://community.letsencrypt.org/t/acme-v2-and-wildcard-certificate-support-is-live/55579) allows wildcard certificate support.
|
||||
As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/staging-endpoint-for-acme-v2/49605) wildcard certificates can only be generated through a [`DNS-01` challenge](/configuration/acme/#dnschallenge).
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
[[acme.domains]]
|
||||
main = "*.local1.com"
|
||||
sans = ["local1.com"]
|
||||
# ...
|
||||
```
|
||||
|
||||
It is not possible to request a double wildcard certificate for a domain (for example `*.*.local.com`).
|
||||
Due to ACME limitation it is not possible to define wildcards in SANs (alternative domains). Thus, the wildcard domain has to be defined as a main domain.
|
||||
Most likely the root domain should receive a certificate too, so it needs to be specified as SAN and 2 `DNS-01` challenges are executed.
|
||||
In this case the generated DNS TXT record for both domains is the same.
|
||||
Eventhough this behaviour is [DNS RFC](https://community.letsencrypt.org/t/wildcard-issuance-two-txt-records-for-the-same-name/54528/2) compliant, it can lead to problems as all DNS providers keep DNS records cached for a certain time (TTL) and this TTL can be superior to the challenge timeout making the `DNS-01` challenge fail.
|
||||
The Træfik ACME client library [LEGO](https://github.com/xenolf/lego) supports some but not all DNS providers to work around this issue.
|
||||
The [`provider` table](/configuration/acme/#provider) indicates if they allow generating certificates for a wildcard domain and its root domain.
|
||||
|
||||
### `httpChallenge`
|
||||
|
||||
Use the `HTTP-01` challenge to generate and renew ACME certificates by provisioning a HTTP resource under a well-known URI.
|
||||
|
||||
Redirection is fully compatible with the `HTTP-01` challenge.
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
entryPoint = "https"
|
||||
[acme.httpChallenge]
|
||||
entryPoint = "http"
|
||||
```
|
||||
|
||||
!!! note
|
||||
If the `HTTP-01` challenge is used, `acme.httpChallenge.entryPoint` has to be defined and reachable by Let's Encrypt through port 80.
|
||||
This is a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
|
||||
|
||||
#### `entryPoint`
|
||||
|
||||
Specify the entryPoint to use during the challenges.
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http", "https"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
# ...
|
||||
|
||||
[acme]
|
||||
# ...
|
||||
entryPoint = "https"
|
||||
[acme.httpChallenge]
|
||||
entryPoint = "http"
|
||||
```
|
||||
|
||||
!!! note
|
||||
`acme.httpChallenge.entryPoint` has to be reachable through port 80. It's a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
|
||||
|
||||
### `onDemand` (Deprecated)
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
This option is deprecated.
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
onDemand = true
|
||||
# ...
|
||||
```
|
||||
|
||||
Enable on demand certificate generation.
|
||||
|
||||
This will request certificates from Let's Encrypt during the first TLS handshake for host names that do not yet have certificates.
|
||||
|
||||
!!! warning
|
||||
TLS handshakes are slow when requesting a host name certificate for the first time. This can lead to DoS attacks!
|
||||
|
||||
!!! warning
|
||||
Take note that Let's Encrypt applies [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||
|
||||
### `onHostRule`
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
onHostRule = true
|
||||
# ...
|
||||
```
|
||||
|
||||
Enable certificate generation on frontend `Host` rules (for frontends wired to the `acme.entryPoint`).
|
||||
|
||||
This will request a certificate from Let's Encrypt for each frontend with a Host rule.
|
||||
|
||||
For example, the rule `Host:test1.traefik.io,test2.traefik.io` will request a certificate with main domain `test1.traefik.io` and SAN `test2.traefik.io`.
|
||||
|
||||
!!! warning
|
||||
`onHostRule` option can not be used to generate wildcard certificates.
|
||||
Refer to [wildcard generation](/configuration/acme/#wildcard-domain) for further information.
|
||||
|
||||
### `storage`
|
||||
|
||||
The `storage` option sets the location where your ACME certificates are saved to.
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
storage = "acme.json"
|
||||
# ...
|
||||
```
|
||||
|
||||
The value can refer to two kinds of storage:
|
||||
|
||||
- a JSON file
|
||||
- a KV store entry
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
`storage` replaces `storageFile` which is deprecated.
|
||||
|
||||
!!! note
|
||||
During migration to a KV store use both `storageFile` and `storage` to migrate ACME certificates too. See [`storeconfig` subcommand](/user-guide/kv-config/#store-configuration-in-key-value-store) for further information.
|
||||
|
||||
#### As a File
|
||||
|
||||
ACME certificates can be stored in a JSON file that needs to have file mode `600`.
|
||||
|
||||
In Docker you can either mount the JSON file or the folder containing it:
|
||||
|
||||
```bash
|
||||
docker run -v "/my/host/acme.json:acme.json" traefik
|
||||
```
|
||||
```bash
|
||||
docker run -v "/my/host/acme:/etc/traefik/acme" traefik
|
||||
```
|
||||
|
||||
!!! warning
|
||||
This file cannot be shared across multiple instances of Træfik at the same time. Please use a [KV Store entry](/configuration/acme/#as-a-key-value-store-entry) instead.
|
||||
|
||||
#### As a Key Value Store Entry
|
||||
|
||||
ACME certificates can be stored in a KV Store entry. This kind of storage is **mandatory in cluster mode**.
|
||||
|
||||
```toml
|
||||
storage = "traefik/acme/account"
|
||||
```
|
||||
|
||||
Because KV stores (like Consul) have limited entry size the certificates list is compressed before it is saved as KV store entry.
|
||||
|
||||
!!! note
|
||||
It is possible to store up to approximately 100 ACME certificates in Consul.
|
||||
|
||||
#### ACME v2 Migration
|
||||
|
||||
During migration from ACME v1 to ACME v2, using a storage file, a backup of the original file is created in the same place as the latter (with a `.bak` extension).
|
||||
|
||||
For example: if `acme.storage`'s value is `/etc/traefik/acme/acme.json`, the backup file will be `/etc/traefik/acme/acme.json.bak`.
|
||||
|
||||
!!! note
|
||||
When Træfik is launched in a container, the storage file's parent directory needs to be mounted to be able to access the backup file on the host.
|
||||
Otherwise the backup file will be deleted when the container is stopped. Træfik will only generate it once!
|
||||
|
||||
### `dnsProvider` (Deprecated)
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
This option is deprecated. Please use [dnsChallenge.provider](/configuration/acme/#provider) instead.
|
||||
|
||||
### `delayDontCheckDNS` (Deprecated)
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
This option is deprecated. Please use [dnsChallenge.delayBeforeCheck](/configuration/acme/#dnschallenge) instead.
|
||||
|
||||
## Fallbacks
|
||||
|
||||
If Let's Encrypt is not reachable, these certificates will be used:
|
||||
|
||||
1. ACME certificates already generated before downtime
|
||||
1. Expired ACME certificates
|
||||
1. Provided certificates
|
||||
|
||||
!!! note
|
||||
For new (sub)domains which need Let's Encrypt authentification, the default Træfik certificate will be used until Træfik is restarted.
|
||||
|
||||
328
docs/configuration/api.md
Normal file
@@ -0,0 +1,328 @@
|
||||
# API Definition
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
# API definition
|
||||
[api]
|
||||
# Name of the related entry point
|
||||
#
|
||||
# Optional
|
||||
# Default: "traefik"
|
||||
#
|
||||
entryPoint = "traefik"
|
||||
|
||||
# Enabled Dashboard
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
dashboard = true
|
||||
|
||||
# Enable debug mode.
|
||||
# This will install HTTP handlers to expose Go expvars under /debug/vars and
|
||||
# pprof profiling data under /debug/pprof.
|
||||
# Additionally, the log level will be set to DEBUG.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
debug = true
|
||||
```
|
||||
|
||||
For more customization, see [entry points](/configuration/entrypoints/) documentation and [examples](/user-guide/examples/#ping-health-check).
|
||||
|
||||
## Web UI
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## API
|
||||
|
||||
| Path | Method | Description |
|
||||
|-----------------------------------------------------------------|------------------|-------------------------------------------|
|
||||
| `/` | `GET` | Provides a simple HTML frontend of Træfik |
|
||||
| `/cluster/leader` | `GET` | JSON leader true/false response |
|
||||
| `/health` | `GET` | JSON health metrics |
|
||||
| `/api` | `GET` | Configuration for all providers |
|
||||
| `/api/providers` | `GET` | Providers |
|
||||
| `/api/providers/{provider}` | `GET`, `PUT` | Get or update provider (1) |
|
||||
| `/api/providers/{provider}/backends` | `GET` | List backends |
|
||||
| `/api/providers/{provider}/backends/{backend}` | `GET` | Get backend |
|
||||
| `/api/providers/{provider}/backends/{backend}/servers` | `GET` | List servers in backend |
|
||||
| `/api/providers/{provider}/backends/{backend}/servers/{server}` | `GET` | Get a server in a backend |
|
||||
| `/api/providers/{provider}/frontends` | `GET` | List frontends |
|
||||
| `/api/providers/{provider}/frontends/{frontend}` | `GET` | Get a frontend |
|
||||
| `/api/providers/{provider}/frontends/{frontend}/routes` | `GET` | List routes in a frontend |
|
||||
| `/api/providers/{provider}/frontends/{frontend}/routes/{route}` | `GET` | Get a route in a frontend |
|
||||
|
||||
<1> See [Rest](/configuration/backends/rest/#api) for more information.
|
||||
|
||||
!!! warning
|
||||
For compatibility reason, when you activate the rest provider, you can use `web` or `rest` as `provider` value.
|
||||
But be careful, in the configuration for all providers the key is still `web`.
|
||||
|
||||
### Address / Port
|
||||
|
||||
You can define a custom address/port like this:
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.foo]
|
||||
address = ":8082"
|
||||
|
||||
[entryPoints.bar]
|
||||
address = ":8083"
|
||||
|
||||
[ping]
|
||||
entryPoint = "foo"
|
||||
|
||||
[api]
|
||||
entryPoint = "bar"
|
||||
```
|
||||
|
||||
In the above example, you would access a regular path, administration panel, and health-check as follows:
|
||||
|
||||
* Regular path: `http://hostname:80/path`
|
||||
* Admin Panel: `http://hostname:8083/`
|
||||
* Ping URL: `http://hostname:8082/ping`
|
||||
|
||||
In the above example, it is _very_ important to create a named dedicated entry point, and do **not** include it in `defaultEntryPoints`.
|
||||
Otherwise, you are likely to expose _all_ services via that entry point.
|
||||
|
||||
### Custom Path
|
||||
|
||||
You can define a custom path like this:
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.foo]
|
||||
address = ":8080"
|
||||
|
||||
[entryPoints.bar]
|
||||
address = ":8081"
|
||||
|
||||
# Activate API and Dashboard
|
||||
[api]
|
||||
entryPoint = "bar"
|
||||
dashboard = true
|
||||
|
||||
[file]
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://127.0.0.1:8081"
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
entryPoints = ["foo"]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "PathPrefixStrip:/yourprefix;PathPrefix:/yourprefix"
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
You can define the authentication like this:
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.foo]
|
||||
address=":8080"
|
||||
[entryPoints.foo.auth]
|
||||
[entryPoints.foo.auth.basic]
|
||||
users = [
|
||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
]
|
||||
|
||||
[api]
|
||||
entrypoint="foo"
|
||||
```
|
||||
|
||||
For more information, see [entry points](/configuration/entrypoints/) .
|
||||
|
||||
### Provider call example
|
||||
|
||||
```shell
|
||||
curl -s "http://localhost:8080/api" | jq .
|
||||
```
|
||||
```json
|
||||
{
|
||||
"file": {
|
||||
"frontends": {
|
||||
"frontend2": {
|
||||
"routes": {
|
||||
"test_2": {
|
||||
"rule": "Path:/test"
|
||||
}
|
||||
},
|
||||
"backend": "backend1"
|
||||
},
|
||||
"frontend1": {
|
||||
"routes": {
|
||||
"test_1": {
|
||||
"rule": "Host:test.localhost"
|
||||
}
|
||||
},
|
||||
"backend": "backend2"
|
||||
}
|
||||
},
|
||||
"backends": {
|
||||
"backend2": {
|
||||
"loadBalancer": {
|
||||
"method": "drr"
|
||||
},
|
||||
"servers": {
|
||||
"server2": {
|
||||
"weight": 2,
|
||||
"URL": "http://172.17.0.5:80"
|
||||
},
|
||||
"server1": {
|
||||
"weight": 1,
|
||||
"url": "http://172.17.0.4:80"
|
||||
}
|
||||
}
|
||||
},
|
||||
"backend1": {
|
||||
"loadBalancer": {
|
||||
"method": "wrr"
|
||||
},
|
||||
"circuitBreaker": {
|
||||
"expression": "NetworkErrorRatio() > 0.5"
|
||||
},
|
||||
"servers": {
|
||||
"server2": {
|
||||
"weight": 1,
|
||||
"url": "http://172.17.0.3:80"
|
||||
},
|
||||
"server1": {
|
||||
"weight": 10,
|
||||
"url": "http://172.17.0.2:80"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Cluster Leadership
|
||||
|
||||
```shell
|
||||
curl -s "http://localhost:8080/cluster/leader" | jq .
|
||||
```
|
||||
```shell
|
||||
< HTTP/1.1 200 OK
|
||||
< Content-Type: application/json; charset=UTF-8
|
||||
< Date: xxx
|
||||
< Content-Length: 15
|
||||
```
|
||||
If the given node is not a cluster leader, an HTTP status of `429-Too-Many-Requests` will be returned.
|
||||
```json
|
||||
{
|
||||
// current leadership status of the queried node
|
||||
"leader": true
|
||||
}
|
||||
```
|
||||
|
||||
### Health
|
||||
|
||||
```shell
|
||||
curl -s "http://localhost:8080/health" | jq .
|
||||
```
|
||||
```json
|
||||
{
|
||||
// Træfik PID
|
||||
"pid": 2458,
|
||||
// Træfik server uptime (formated time)
|
||||
"uptime": "39m6.885931127s",
|
||||
// Træfik server uptime in seconds
|
||||
"uptime_sec": 2346.885931127,
|
||||
// current server date
|
||||
"time": "2015-10-07 18:32:24.362238909 +0200 CEST",
|
||||
// current server date in seconds
|
||||
"unixtime": 1444235544,
|
||||
// count HTTP response status code in realtime
|
||||
"status_code_count": {
|
||||
"502": 1
|
||||
},
|
||||
// count HTTP response status code since Træfik started
|
||||
"total_status_code_count": {
|
||||
"200": 7,
|
||||
"404": 21,
|
||||
"502": 13
|
||||
},
|
||||
// count HTTP response
|
||||
"count": 1,
|
||||
// count HTTP response
|
||||
"total_count": 41,
|
||||
// sum of all response time (formated time)
|
||||
"total_response_time": "35.456865605s",
|
||||
// sum of all response time in seconds
|
||||
"total_response_time_sec": 35.456865605,
|
||||
// average response time (formated time)
|
||||
"average_response_time": "864.8016ms",
|
||||
// average response time in seconds
|
||||
"average_response_time_sec": 0.8648016000000001,
|
||||
|
||||
// request statistics [requires --statistics to be set]
|
||||
// ten most recent requests with 4xx and 5xx status codes
|
||||
"recent_errors": [
|
||||
{
|
||||
// status code
|
||||
"status_code": 500,
|
||||
// description of status code
|
||||
"status": "Internal Server Error",
|
||||
// request HTTP method
|
||||
"method": "GET",
|
||||
// request hostname
|
||||
"host": "localhost",
|
||||
// request path
|
||||
"path": "/path",
|
||||
// RFC 3339 formatted date/time
|
||||
"time": "2016-10-21T16:59:15.418495872-07:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Metrics
|
||||
|
||||
You can enable Traefik to export internal metrics to different monitoring systems.
|
||||
|
||||
```toml
|
||||
[api]
|
||||
# ...
|
||||
|
||||
# Enable more detailed statistics.
|
||||
[api.statistics]
|
||||
|
||||
# Number of recent errors logged.
|
||||
#
|
||||
# Default: 10
|
||||
#
|
||||
recentErrors = 10
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
| Path | Method | Description |
|
||||
|------------|---------------|-------------------------|
|
||||
| `/metrics` | `GET` | Export internal metrics |
|
||||
@@ -1,36 +1,59 @@
|
||||
# BoltDB Backend
|
||||
# BoltDB Provider
|
||||
|
||||
Træfik can be configured to use BoltDB as a backend configuration:
|
||||
Træfik can be configured to use BoltDB as a provider.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# BoltDB configuration backend
|
||||
# BoltDB Provider
|
||||
################################################################
|
||||
|
||||
# Enable BoltDB configuration backend
|
||||
# Enable BoltDB Provider.
|
||||
[boltdb]
|
||||
|
||||
# BoltDB file
|
||||
# BoltDB file.
|
||||
#
|
||||
# Required
|
||||
# Default: "127.0.0.1:4001"
|
||||
#
|
||||
endpoint = "/my.db"
|
||||
|
||||
# Enable watch BoltDB changes
|
||||
# Enable watch BoltDB changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Prefix used for KV store.
|
||||
#
|
||||
# Optional
|
||||
# Default: "/traefik"
|
||||
#
|
||||
prefix = "/traefik"
|
||||
|
||||
# Override default configuration template. For advanced users :)
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
filename = "boltdb.tmpl"
|
||||
|
||||
# Use BoltDB user/pass authentication.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# username = foo
|
||||
# password = bar
|
||||
|
||||
# Enable BoltDB TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [boltdb.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/boltdb.crt"
|
||||
# key = "/etc/ssl/boltdb.key"
|
||||
# insecureSkipVerify = true
|
||||
```
|
||||
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
@@ -1,117 +1,61 @@
|
||||
# Consul Backend
|
||||
# Consul Key-Value Provider
|
||||
|
||||
## Consul Key-Value backend
|
||||
|
||||
Træfik can be configured to use Consul as a backend configuration:
|
||||
Træfik can be configured to use Consul as a provider.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Consul KV configuration backend
|
||||
# Consul KV Provider
|
||||
################################################################
|
||||
|
||||
# Enable Consul KV configuration backend
|
||||
# Enable Consul KV Provider.
|
||||
[consul]
|
||||
|
||||
# Consul server endpoint
|
||||
# Consul server endpoint.
|
||||
#
|
||||
# Required
|
||||
# Default: "127.0.0.1:8500"
|
||||
#
|
||||
endpoint = "127.0.0.1:8500"
|
||||
|
||||
# Enable watch Consul changes
|
||||
# Enable watch Consul changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Prefix used for KV store.
|
||||
#
|
||||
# Optional
|
||||
# Default: traefik
|
||||
#
|
||||
prefix = "traefik"
|
||||
|
||||
# Override default configuration template. For advanced users :)
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "consul.tmpl"
|
||||
|
||||
# Enable consul TLS connection
|
||||
# Use Consul user/pass authentication.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [consul.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/consul.crt"
|
||||
# key = "/etc/ssl/consul.key"
|
||||
# insecureskipverify = true
|
||||
# username = foo
|
||||
# password = bar
|
||||
|
||||
# Enable Consul TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [consul.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/consul.crt"
|
||||
# key = "/etc/ssl/consul.key"
|
||||
# insecureSkipVerify = true
|
||||
```
|
||||
|
||||
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure.
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
## Consul catalog backend
|
||||
|
||||
Træfik can be configured to use service discovery catalog of Consul as a backend configuration:
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Consul Catalog configuration backend
|
||||
################################################################
|
||||
|
||||
# Enable Consul Catalog configuration backend
|
||||
[consulCatalog]
|
||||
|
||||
# Consul server endpoint
|
||||
#
|
||||
# Required
|
||||
#
|
||||
endpoint = "127.0.0.1:8500"
|
||||
|
||||
# Default domain used.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
domain = "consul.localhost"
|
||||
|
||||
# Expose Consul catalog services by default in traefik
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
exposedByDefault = false
|
||||
|
||||
# Prefix for Consul catalog tags
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
prefix = "traefik"
|
||||
|
||||
# Default frontEnd Rule for Consul services
|
||||
#
|
||||
# The format is a Go Template with:
|
||||
# - ".ServiceName", ".Domain" and ".Attributes" available
|
||||
# - "getTag(name, tags, defaultValue)", "hasTag(name, tags)" and "getAttribute(name, tags, defaultValue)" functions are available
|
||||
# - "getAttribute(...)" function uses prefixed tag names based on "prefix" value
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
#frontEndRule = "Host:{{.ServiceName}}.{{Domain}}"
|
||||
```
|
||||
|
||||
This backend will create routes matching on hostname based on the service name used in consul.
|
||||
|
||||
Additional settings can be defined using Consul Catalog tags:
|
||||
|
||||
| Tag | Description |
|
||||
|---------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.protocol=https` | Override the default `http` protocol |
|
||||
| `traefik.backend.weight=10` | Assign this weight to the container |
|
||||
| `traefik.backend.circuitbreaker=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend, ex: `NetworkErrorRatio() > 0.` |
|
||||
| `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:{{.ServiceName}}.{{.Domain}}`). |
|
||||
| `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.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.
|
||||
|
||||
183
docs/configuration/backends/consulcatalog.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# Consul Catalog Provider
|
||||
|
||||
Træfik can be configured to use service discovery catalog of Consul as a provider.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Consul Catalog Provider
|
||||
################################################################
|
||||
|
||||
# Enable Consul Catalog Provider.
|
||||
[consulCatalog]
|
||||
|
||||
# Consul server endpoint.
|
||||
#
|
||||
# Required
|
||||
# Default: "127.0.0.1:8500"
|
||||
#
|
||||
endpoint = "127.0.0.1:8500"
|
||||
|
||||
# Expose Consul catalog services by default in Traefik.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
exposedByDefault = false
|
||||
|
||||
# Default domain used.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
domain = "consul.localhost"
|
||||
|
||||
# Prefix for Consul catalog tags.
|
||||
#
|
||||
# Optional
|
||||
# Default: "traefik"
|
||||
#
|
||||
prefix = "traefik"
|
||||
|
||||
# Default frontEnd Rule for Consul services.
|
||||
#
|
||||
# The format is a Go Template with:
|
||||
# - ".ServiceName", ".Domain" and ".Attributes" available
|
||||
# - "getTag(name, tags, defaultValue)", "hasTag(name, tags)" and "getAttribute(name, tags, defaultValue)" functions are available
|
||||
# - "getAttribute(...)" function uses prefixed tag names based on "prefix" value
|
||||
#
|
||||
# Optional
|
||||
# Default: "Host:{{.ServiceName}}.{{.Domain}}"
|
||||
#
|
||||
#frontEndRule = "Host:{{.ServiceName}}.{{.Domain}}"
|
||||
|
||||
# Enable Consul catalog TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [consulCatalog.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/consul.crt"
|
||||
# key = "/etc/ssl/consul.key"
|
||||
# insecureSkipVerify = true
|
||||
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "consulcatalog.tmpl"
|
||||
|
||||
# Override template version
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
# - "1": previous template version (must be used only with older custom templates, see "filename")
|
||||
# - "2": current template version (must be used to force template version when "filename" is used)
|
||||
#
|
||||
# templateVersion = 2
|
||||
```
|
||||
|
||||
This provider will create routes matching on hostname based on the service name used in Consul.
|
||||
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
## Tags
|
||||
|
||||
Additional settings can be defined using Consul Catalog tags.
|
||||
|
||||
!!! note
|
||||
The default prefix is `traefik`.
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `<prefix>.enable=false` | Disable this container in Træfik. |
|
||||
| `<prefix>.protocol=https` | Override the default `http` protocol. |
|
||||
| `<prefix>.weight=10` | Assign this weight to the container. |
|
||||
| `traefik.backend.buffering.maxRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.maxResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.retryExpression=EXPR` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `<prefix>.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend. ex: `NetworkErrorRatio() > 0.` |
|
||||
| `<prefix>.backend.healthcheck.path=/health` | Enable health check for the backend, hitting the container at `path`. |
|
||||
| `<prefix>.backend.healthcheck.port=8080` | Allow to use a different port for the health check. |
|
||||
| `<prefix>.backend.healthcheck.interval=1s` | Define the health check interval. |
|
||||
| `<prefix>.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm. |
|
||||
| `<prefix>.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions. |
|
||||
| `<prefix>.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions. |
|
||||
| `<prefix>.backend.loadbalancer.sticky=true` | Enable backend sticky sessions. (DEPRECATED) |
|
||||
| `<prefix>.backend.maxconn.amount=10` | Set a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
|
||||
| `<prefix>.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.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `<prefix>.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
| `<prefix>.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
|
||||
| `<prefix>.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `<prefix>.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `<prefix>.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `<prefix>.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
|
||||
| `<prefix>.frontend.passTLSCert=true` | Forward TLS Client certificates to the backend. |
|
||||
| `<prefix>.frontend.priority=10` | Override default frontend priority. |
|
||||
| `<prefix>.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `<prefix>.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `<prefix>.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `<prefix>.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `<prefix>.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS). |
|
||||
| `<prefix>.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||
| `<prefix>.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||
| `<prefix>.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||
| `<prefix>.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{{.ServiceName}}.{{.Domain}}`. |
|
||||
| `<prefix>.frontend.whiteList.sourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `<prefix>.frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
|
||||
### Custom Headers
|
||||
|
||||
!!! note
|
||||
The default prefix is `traefik`.
|
||||
|
||||
| Label | Description |
|
||||
|--------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `<prefix>.frontend.headers.customRequestHeaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `<prefix>.frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
|
||||
### Security Headers
|
||||
|
||||
!!! note
|
||||
The default prefix is `traefik`.
|
||||
|
||||
| Label | Description |
|
||||
|-----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `<prefix>.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
|
||||
| `<prefix>.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `<prefix>.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `<prefix>.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `<prefix>.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `<prefix>.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `<prefix>.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `<prefix>.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `<prefix>.frontend.headers.hostsProxyHeaders=EXPR` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
|
||||
| `<prefix>.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
| `<prefix>.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `<prefix>.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `<prefix>.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `<prefix>.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `<prefix>.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
| `<prefix>.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `<prefix>.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
|
||||
| `<prefix>.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
|
||||
| `<prefix>.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
|
||||
|
||||
### Examples
|
||||
|
||||
If you want that Træfik uses Consul tags correctly you need to defined them like that:
|
||||
|
||||
```js
|
||||
traefik.enable=true
|
||||
traefik.tags=api
|
||||
traefik.tags=external
|
||||
```
|
||||
|
||||
If the prefix defined in Træfik configuration is `bla`, tags need to be defined like that:
|
||||
|
||||
```js
|
||||
bla.enable=true
|
||||
bla.tags=api
|
||||
bla.tags=external
|
||||
```
|
||||
@@ -1,15 +1,16 @@
|
||||
# Docker Backend
|
||||
|
||||
Træfik can be configured to use Docker as a backend configuration.
|
||||
# Docker Provider
|
||||
|
||||
Træfik can be configured to use Docker as a provider.
|
||||
|
||||
## Docker
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Docker configuration backend
|
||||
# Docker Provider
|
||||
################################################################
|
||||
|
||||
# Enable Docker configuration backend
|
||||
# Enable Docker Provider.
|
||||
[docker]
|
||||
|
||||
# Docker server endpoint. Can be a tcp or a unix socket endpoint.
|
||||
@@ -25,41 +26,52 @@ endpoint = "unix:///var/run/docker.sock"
|
||||
#
|
||||
domain = "docker.localhost"
|
||||
|
||||
# Enable watch docker changes
|
||||
# Enable watch docker changes.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Override default configuration template. For advanced users :)
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "docker.tmpl"
|
||||
|
||||
# Expose containers by default in traefik
|
||||
# If set to false, containers that don't have `traefik.enable=true` will be ignored
|
||||
# Override template version
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
# - "1": previous template version (must be used only with older custom templates, see "filename")
|
||||
# - "2": current template version (must be used to force template version when "filename" is used)
|
||||
#
|
||||
# templateVersion = 2
|
||||
|
||||
# Expose containers by default in Traefik.
|
||||
# If set to false, containers that don't have `traefik.enable=true` will be ignored.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
exposedbydefault = true
|
||||
exposedByDefault = true
|
||||
|
||||
# Use the IP address from the binded port instead of the inner network one. For specific use-case :)
|
||||
# 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
|
||||
# Use Swarm Mode services as data provider.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
swarmmode = false
|
||||
swarmMode = false
|
||||
|
||||
# Enable docker TLS connection
|
||||
# Enable docker TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
@@ -67,20 +79,24 @@ swarmmode = false
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/docker.crt"
|
||||
# key = "/etc/ssl/docker.key"
|
||||
# insecureskipverify = true
|
||||
# insecureSkipVerify = true
|
||||
```
|
||||
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
|
||||
## Docker Swarm Mode
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Docker Swarmmode configuration backend
|
||||
# Docker Swarm Mode Provider
|
||||
################################################################
|
||||
|
||||
# Enable Docker configuration backend
|
||||
# Enable Docker Provider.
|
||||
[docker]
|
||||
|
||||
# Docker server endpoint. Can be a tcp or a unix socket endpoint.
|
||||
# Docker server endpoint.
|
||||
# Can be a tcp or a unix socket endpoint.
|
||||
#
|
||||
# Required
|
||||
# Default: "unix:///var/run/docker.sock"
|
||||
@@ -95,77 +111,247 @@ endpoint = "tcp://127.0.0.1:2375"
|
||||
#
|
||||
domain = "docker.localhost"
|
||||
|
||||
# Enable watch docker changes
|
||||
# Enable watch docker changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Use Docker Swarm Mode as data provider
|
||||
swarmmode = true
|
||||
# Use Docker Swarm Mode as data provider.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
swarmMode = true
|
||||
|
||||
# Override default configuration template. For advanced users :)
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "docker.tmpl"
|
||||
|
||||
# Expose services by default in traefik
|
||||
# Override template version
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
# - "1": previous template version (must be used only with older custom templates, see "filename")
|
||||
# - "2": current template version (must be used to force template version when "filename" is used)
|
||||
#
|
||||
# templateVersion = 2
|
||||
|
||||
# Expose services by default in Traefik.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
exposedbydefault = false
|
||||
exposedByDefault = false
|
||||
|
||||
# Enable docker TLS connection
|
||||
# Enable docker TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [swarm.tls]
|
||||
# [docker.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/docker.crt"
|
||||
# key = "/etc/ssl/docker.key"
|
||||
# insecureskipverify = true
|
||||
# insecureSkipVerify = true
|
||||
```
|
||||
|
||||
## Labels can be used on containers to override default behaviour
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.backend=foo` | Give the name `foo` to the generated backend for this container. |
|
||||
| `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.loadbalancer.swarm=true` | Use Swarm's inbuilt load balancer (only relevant under Swarm Mode). |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | 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 |
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. |
|
||||
| `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.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
| `traefik.frontend.whitelistSourceRange:RANGE` | List of IP-Ranges which are allowed to access. An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.docker.network` | Set the docker network to use for connections to this container. If a container is linked to several networks, be sure to set the proper network name (you can check with docker inspect <container_id>) otherwise it will randomly pick one (depending on how docker is returning them). For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name. |
|
||||
## Labels: overriding default behavior
|
||||
|
||||
### Services labels can be used for overriding default behaviour
|
||||
### Using Docker with Swarm Mode
|
||||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------|--------------------------------------------------------------------------------------------------|
|
||||
| `traefik.<service-name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. |
|
||||
| `traefik.<service-name>.protocol` | Overrides `traefik.protocol`. |
|
||||
| `traefik.<service-name>.weight` | Assign this service weight. Overrides `traefik.weight`. |
|
||||
| `traefik.<service-name>.frontend.backend=BACKEND` | Assign this service frontend to `BACKEND`. Default is to assign to the service backend. |
|
||||
| `traefik.<service-name>.frontend.entryPoints` | Overrides `traefik.frontend.entrypoints` |
|
||||
| `traefik.<service-name>.frontend.auth.basic` | Sets a Basic Auth for that frontend |
|
||||
| `traefik.<service-name>.frontend.passHostHeader` | Overrides `traefik.frontend.passHostHeader`. |
|
||||
| `traefik.<service-name>.frontend.priority` | Overrides `traefik.frontend.priority`. |
|
||||
| `traefik.<service-name>.frontend.rule` | Overrides `traefik.frontend.rule`. |
|
||||
If you use a compose file with the Swarm mode, labels should be defined in the `deploy` part of your service.
|
||||
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/#labels-1)).
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
whoami:
|
||||
deploy:
|
||||
labels:
|
||||
traefik.docker.network: traefik
|
||||
```
|
||||
|
||||
### Using Docker Compose
|
||||
|
||||
If you are intending to use only Docker Compose commands (e.g. `docker-compose up --scale whoami=2 -d`), labels should be under your service, otherwise they will be ignored.
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
whoami:
|
||||
labels:
|
||||
traefik.docker.network: traefik
|
||||
```
|
||||
|
||||
### On Containers
|
||||
|
||||
Labels can be used on containers to override default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.docker.network` | Set the docker network to use for connections to this container. [1] |
|
||||
| `traefik.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `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 |
|
||||
| `traefik.backend=foo` | Give the name `foo` to the generated backend for this container. |
|
||||
| `traefik.backend.buffering.maxRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.maxResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.retryExpression=EXPR` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.backend.healthcheck.path=/health` | Enable health check for the backend, hitting the container at `path`. |
|
||||
| `traefik.backend.healthcheck.port=8080` | Allow to use a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.interval=1s` | Define the health check interval. |
|
||||
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||
| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) |
|
||||
| `traefik.backend.loadbalancer.swarm=true` | Use Swarm's inbuilt load balancer (only relevant under Swarm Mode). |
|
||||
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend.<br>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.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` [2] |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
|
||||
| `traefik.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
|
||||
| `traefik.frontend.passTLSCert=true` | Forward TLS Client certificates to the backend. |
|
||||
| `traefik.frontend.priority=10` | Override default frontend priority |
|
||||
| `traefik.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
|
||||
[1] `traefik.docker.network`:
|
||||
If a container is linked to several networks, be sure to set the proper network name (you can check with `docker inspect <container_id>`) otherwise it will randomly pick one (depending on how docker is returning them).
|
||||
For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name.
|
||||
Or if your service references external network use it's name instead.
|
||||
|
||||
[2] `traefik.frontend.auth.basic=EXPR`:
|
||||
To create `user:password` pair, it's possible to use this command `echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g`.
|
||||
The result will be `user:$$apr1$$9Cv/OMGj$$ZomWQzuQbL.3TRCS81A1g/`, note additional symbol `$` makes escaping.
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.customRequestHeaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
|
||||
#### Security Headers
|
||||
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
|
||||
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
|
||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
|
||||
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
|
||||
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
|
||||
|
||||
### On containers with Multiple Ports (segment labels)
|
||||
|
||||
Segment labels are used to define routes to a container exposing multiple ports.
|
||||
A segment is a group of labels that apply to a port exposed by a container.
|
||||
You can define as many segments as ports exposed in a container.
|
||||
|
||||
Segment labels override the default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------------|-------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
||||
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
|
||||
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
|
||||
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.status=RANGE` | Same as `traefik.frontend.errors.<name>.status` |
|
||||
| `traefik.<segment_name>.frontend.passHostHeader=true` | Same as `traefik.frontend.passHostHeader` |
|
||||
| `traefik.<segment_name>.frontend.passTLSCert=true` | Same as `traefik.frontend.passTLSCert` |
|
||||
| `traefik.<segment_name>.frontend.priority=10` | Same as `traefik.frontend.priority` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.extractorFunc=EXP` | Same as `traefik.frontend.rateLimit.extractorFunc` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.period=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.period` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.average=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.average` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.burst=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.burst` |
|
||||
| `traefik.<segment_name>.frontend.redirect.entryPoint=https` | Same as `traefik.frontend.redirect.entryPoint` |
|
||||
| `traefik.<segment_name>.frontend.redirect.regex=^http://localhost/(.*)` | Same as `traefik.frontend.redirect.regex` |
|
||||
| `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Same as `traefik.frontend.redirect.replacement` |
|
||||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Same as `traefik.frontend.whiteList.useXForwardedFor` |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------------------|----------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.customRequestHeaders=EXPR ` | Same as `traefik.frontend.headers.customRequestHeaders` |
|
||||
| `traefik.<segment_name>.frontend.headers.customResponseHeaders=EXPR` | Same as `traefik.frontend.headers.customResponseHeaders` |
|
||||
|
||||
#### Security Headers
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------------------|--------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.allowedHosts=EXPR` | Same as `traefik.frontend.headers.allowedHosts` |
|
||||
| `traefik.<segment_name>.frontend.headers.browserXSSFilter=true` | Same as `traefik.frontend.headers.browserXSSFilter` |
|
||||
| `traefik.<segment_name>.frontend.headers.contentSecurityPolicy=VALUE` | Same as `traefik.frontend.headers.contentSecurityPolicy` |
|
||||
| `traefik.<segment_name>.frontend.headers.contentTypeNosniff=true` | Same as `traefik.frontend.headers.contentTypeNosniff` |
|
||||
| `traefik.<segment_name>.frontend.headers.customBrowserXSSValue=VALUE` | Same as `traefik.frontend.headers.customBrowserXSSValue` |
|
||||
| `traefik.<segment_name>.frontend.headers.customFrameOptionsValue=VALUE` | Same as `traefik.frontend.headers.customFrameOptionsValue` |
|
||||
| `traefik.<segment_name>.frontend.headers.forceSTSHeader=false` | Same as `traefik.frontend.headers.forceSTSHeader` |
|
||||
| `traefik.<segment_name>.frontend.headers.frameDeny=false` | Same as `traefik.frontend.headers.frameDeny` |
|
||||
| `traefik.<segment_name>.frontend.headers.hostsProxyHeaders=EXPR` | Same as `traefik.frontend.headers.hostsProxyHeaders` |
|
||||
| `traefik.<segment_name>.frontend.headers.isDevelopment=false` | Same as `traefik.frontend.headers.isDevelopment` |
|
||||
| `traefik.<segment_name>.frontend.headers.publicKey=VALUE` | Same as `traefik.frontend.headers.publicKey` |
|
||||
| `traefik.<segment_name>.frontend.headers.referrerPolicy=VALUE` | Same as `traefik.frontend.headers.referrerPolicy` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLRedirect=true` | Same as `traefik.frontend.headers.SSLRedirect` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLTemporaryRedirect=true` | Same as `traefik.frontend.headers.SSLTemporaryRedirect` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLHost=HOST` | Same as `traefik.frontend.headers.SSLHost` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLProxyHeaders=EXPR` | Same as `traefik.frontend.headers.SSLProxyHeaders=EXPR` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSSeconds=315360000` | Same as `traefik.frontend.headers.STSSeconds=315360000` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSIncludeSubdomains=true` | Same as `traefik.frontend.headers.STSIncludeSubdomains=true` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSPreload=true` | Same as `traefik.frontend.headers.STSPreload=true` |
|
||||
|
||||
!!! note
|
||||
If a label is defined both as a `container label` and a `segment label` (for example `traefik.<segment_name>.port=PORT` and `traefik.port=PORT` ), the `segment label` is used to defined the `<segment_name>` property (`port` in the example).
|
||||
|
||||
It's possible to mix `container labels` and `segment labels`, in this case `container labels` are used as default value for missing `segment labels` but no frontends are going to be created with the `container labels`.
|
||||
|
||||
More details in this [example](/user-guide/docker-and-lets-encrypt/#labels).
|
||||
|
||||
!!! warning
|
||||
when running inside a container, Træfik will need network access through:
|
||||
When running inside a container, Træfik will need network access through:
|
||||
|
||||
`docker network connect <network> <traefik-container>`
|
||||
|
||||
@@ -1,63 +1,70 @@
|
||||
# DynamoDB Backend
|
||||
# DynamoDB Provider
|
||||
|
||||
Træfik can be configured to use Amazon DynamoDB as a backend configuration:
|
||||
Træfik can be configured to use Amazon DynamoDB as a provider.
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# DynamoDB configuration backend
|
||||
# DynamoDB Provider
|
||||
################################################################
|
||||
|
||||
# Enable DynamoDB configuration backend
|
||||
# Enable DynamoDB Provider.
|
||||
[dynamodb]
|
||||
|
||||
# DyanmoDB Table Name
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
TableName = "traefik"
|
||||
|
||||
# Enable watch DynamoDB changes
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
Watch = true
|
||||
|
||||
# Polling interval (in seconds)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
RefreshSeconds = 15
|
||||
|
||||
# Region to use when connecting to AWS
|
||||
# Region to use when connecting to AWS.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
Region = "us-west-1"
|
||||
region = "us-west-1"
|
||||
|
||||
# AccessKeyID to use when connecting to AWS
|
||||
# DyanmoDB Table Name.
|
||||
#
|
||||
# Optional
|
||||
# Default: "traefik"
|
||||
#
|
||||
tableName = "traefik"
|
||||
|
||||
# Enable watch DynamoDB changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Polling interval (in seconds).
|
||||
#
|
||||
# Optional
|
||||
# Default: 15
|
||||
#
|
||||
refreshSeconds = 15
|
||||
|
||||
# Access Key ID to use when connecting to AWS.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
AccessKeyID = "abc"
|
||||
accessKeyID = "abc"
|
||||
|
||||
# SecretAccessKey to use when connecting to AWS
|
||||
# Secret Access Key to use when connecting to AWS.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
SecretAccessKey = "123"
|
||||
secretAccessKey = "123"
|
||||
|
||||
# Endpoint of local dynamodb instance for testing
|
||||
# Endpoint of local dynamodb instance for testing?
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
Endpoint = "http://localhost:8080"
|
||||
endpoint = "http://localhost:8080"
|
||||
```
|
||||
|
||||
## Table Items
|
||||
|
||||
Items in the `dynamodb` table must have three attributes:
|
||||
|
||||
- `id` (string): The id is the primary key.
|
||||
- `name`(string): The name is used as the name of the frontend or backend.
|
||||
- `frontend` or `backend` (map): This attribute's structure matches exactly the structure of a Frontend or Backend type in traefik.
|
||||
- `frontend` or `backend` (map): This attribute's structure matches exactly the structure of a Frontend or Backend type in Traefik.
|
||||
See `types/types.go` for details.
|
||||
The presence or absence of this attribute determines its type.
|
||||
So an item should never have both a `frontend` and a `backend` attribute.
|
||||
|
||||
@@ -1,102 +1,109 @@
|
||||
# ECS Backend
|
||||
# ECS Provider
|
||||
|
||||
Træfik can be configured to use Amazon ECS as a backend configuration:
|
||||
Træfik can be configured to use Amazon ECS as a provider.
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# ECS configuration backend
|
||||
# ECS Provider
|
||||
################################################################
|
||||
|
||||
# Enable ECS configuration backend
|
||||
# Enable ECS Provider.
|
||||
[ecs]
|
||||
|
||||
# ECS Cluster Name
|
||||
# ECS Cluster Name.
|
||||
#
|
||||
# DEPRECATED - Please use Clusters
|
||||
# DEPRECATED - Please use `clusters`.
|
||||
#
|
||||
Cluster = "default"
|
||||
cluster = "default"
|
||||
|
||||
# ECS Clusters Name
|
||||
# ECS Clusters Name.
|
||||
#
|
||||
# Optional
|
||||
# Default: ["default"]
|
||||
#
|
||||
Clusters = ["default"]
|
||||
clusters = ["default"]
|
||||
|
||||
# Enable watch ECS changes
|
||||
# Enable watch ECS changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
Watch = true
|
||||
watch = true
|
||||
|
||||
# Enable auto discover ECS clusters
|
||||
# Default domain used.
|
||||
# Can be overridden by setting the "traefik.domain" label.
|
||||
#
|
||||
# Optional
|
||||
# Default: ""
|
||||
#
|
||||
domain = "ecs.localhost"
|
||||
|
||||
# Enable auto discover ECS clusters.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
AutoDiscoverClusters = false
|
||||
autoDiscoverClusters = false
|
||||
|
||||
# Polling interval (in seconds)
|
||||
# Polling interval (in seconds).
|
||||
#
|
||||
# Optional
|
||||
# Default: 15
|
||||
#
|
||||
RefreshSeconds = 15
|
||||
refreshSeconds = 15
|
||||
|
||||
# Expose ECS services by default in traefik
|
||||
# Expose ECS services by default in Traefik.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
ExposedByDefault = false
|
||||
exposedByDefault = false
|
||||
|
||||
# Region to use when connecting to AWS
|
||||
# Region to use when connecting to AWS.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
Region = "us-east-1"
|
||||
region = "us-east-1"
|
||||
|
||||
# AccessKeyID to use when connecting to AWS
|
||||
# Access Key ID to use when connecting to AWS.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
AccessKeyID = "abc"
|
||||
accessKeyID = "abc"
|
||||
|
||||
# SecretAccessKey to use when connecting to AWS
|
||||
# Secret Access Key to use when connecting to AWS.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
SecretAccessKey = "123"
|
||||
secretAccessKey = "123"
|
||||
|
||||
# Override default configuration template. For advanced users :)
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "ecs.tmpl"
|
||||
|
||||
# Override template version
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
# - "1": previous template version (must be used only with older custom templates, see "filename")
|
||||
# - "2": current template version (must be used to force template version when "filename" is used)
|
||||
#
|
||||
# templateVersion = 2
|
||||
```
|
||||
|
||||
Labels can be used on task containers to override default behaviour:
|
||||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------|------------------------------------------------------------------------------------------|
|
||||
| `traefik.protocol=https` | override the default `http` protocol |
|
||||
| `traefik.weight=10` | assign this weight to the container |
|
||||
| `traefik.enable=false` | disable this container in Træfik |
|
||||
| `traefik.backend.loadbalancer.method=drr` | override the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions |
|
||||
| `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 |
|
||||
| `traefik.frontend.entryPoints=http,https` | assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
|
||||
If `AccessKeyID`/`SecretAccessKey` is not given credentials will be resolved in the following order:
|
||||
If `accessKeyID`/`secretAccessKey` is not given credentials will be resolved in the following order:
|
||||
|
||||
- From environment variables; `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN`.
|
||||
- Shared credentials, determined by `AWS_PROFILE` and `AWS_SHARED_CREDENTIALS_FILE`, defaults to `default` and `~/.aws/credentials`.
|
||||
- EC2 instance role or ECS task role
|
||||
|
||||
## Policy
|
||||
|
||||
Træfik needs the following policy to read ECS information:
|
||||
|
||||
```json
|
||||
@@ -122,3 +129,81 @@ Træfik needs the following policy to read ECS information:
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Labels: overriding default behaviour
|
||||
|
||||
Labels can be used on task containers to override default behaviour:
|
||||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.port=80` | Override the default `port` value. Overrides `NetworkBindings` from Docker Container |
|
||||
| `traefik.protocol=https` | Override the default `http` protocol |
|
||||
| `traefik.weight=10` | Assign this weight to the container |
|
||||
| `traefik.backend=foo` | Give the name `foo` to the generated backend for this container. |
|
||||
| `traefik.backend.buffering.maxRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.maxResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.retryExpression=EXPR` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.backend.healthcheck.path=/health` | Enable health check for the backend, hitting the container at `path`. |
|
||||
| `traefik.backend.healthcheck.port=8080` | Allow to use a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.interval=1s` | Define the health check interval. (Default: 30s) |
|
||||
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||
| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) |
|
||||
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend.<br>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.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
|
||||
| `traefik.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
|
||||
| `traefik.frontend.passTLSCert=true` | Forward TLS Client certificates to the backend. |
|
||||
| `traefik.frontend.priority=10` | Override default frontend priority |
|
||||
| `traefik.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{instance_name}.{domain}`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
|
||||
### Custom Headers
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.customRequestHeaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
|
||||
### Security Headers
|
||||
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
|
||||
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
|
||||
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
|
||||
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
|
||||
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
|
||||
|
||||
@@ -1,55 +1,75 @@
|
||||
# Etcd Backend
|
||||
# Etcd Provider
|
||||
|
||||
Træfik can be configured to use Etcd as a backend configuration:
|
||||
Træfik can be configured to use Etcd as a provider.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Etcd configuration backend
|
||||
# Etcd Provider
|
||||
################################################################
|
||||
|
||||
# Enable Etcd configuration backend
|
||||
# Enable Etcd Provider.
|
||||
[etcd]
|
||||
|
||||
# Etcd server endpoint
|
||||
# Etcd server endpoint.
|
||||
#
|
||||
# Required
|
||||
# Default: "127.0.0.1:2379"
|
||||
#
|
||||
endpoint = "127.0.0.1:2379"
|
||||
|
||||
# Enable watch Etcd changes
|
||||
# Enable watch Etcd changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Prefix used for KV store.
|
||||
#
|
||||
# Optional
|
||||
# Default: "/traefik"
|
||||
#
|
||||
prefix = "/traefik"
|
||||
|
||||
# Override default configuration template. For advanced users :)
|
||||
# Force to use API V3 (otherwise still use API V2)
|
||||
#
|
||||
# Deprecated
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
useAPIV3 = true
|
||||
|
||||
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "etcd.tmpl"
|
||||
|
||||
# Use etcd user/pass authentication
|
||||
# Use etcd user/pass authentication.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# username = foo
|
||||
# password = bar
|
||||
|
||||
# Enable etcd TLS connection
|
||||
# Enable etcd TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [etcd.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/etcd.crt"
|
||||
# key = "/etc/ssl/etcd.key"
|
||||
# insecureskipverify = true
|
||||
# [etcd.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/etcd.crt"
|
||||
# key = "/etc/ssl/etcd.key"
|
||||
# insecureSkipVerify = true
|
||||
```
|
||||
|
||||
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure.
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.
|
||||
|
||||
!!! note
|
||||
The option `useAPIV3` allows using Etcd API V3 only if it's set to true.
|
||||
This option is **deprecated** and API V2 won't be supported in the future.
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
# Eureka Backend
|
||||
# Eureka Provider
|
||||
|
||||
Træfik can be configured to use Eureka as a backend configuration:
|
||||
Træfik can be configured to use Eureka as a provider.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Eureka configuration backend
|
||||
# Eureka Provider
|
||||
################################################################
|
||||
|
||||
# Enable Eureka configuration backend
|
||||
# Enable Eureka Provider.
|
||||
[eureka]
|
||||
|
||||
# Eureka server endpoint.
|
||||
# endpoint := "http://my.eureka.server/eureka"
|
||||
#
|
||||
# Required
|
||||
#
|
||||
endpoint = "http://my.eureka.server/eureka"
|
||||
|
||||
# Override default configuration time between refresh
|
||||
# Override default configuration time between refresh.
|
||||
#
|
||||
# Optional
|
||||
# default 30s
|
||||
delay = "1m"
|
||||
# Default: 30s
|
||||
#
|
||||
refreshSeconds = "1m"
|
||||
|
||||
# Override default configuration template. For advanced users :)
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
|
||||
@@ -1,8 +1,159 @@
|
||||
# File Backends
|
||||
# File Provider
|
||||
|
||||
Like any other reverse proxy, Træfik can be configured with a file. You have three choices:
|
||||
Træfik can be configured with a file.
|
||||
|
||||
## Simple
|
||||
## Reference
|
||||
|
||||
```toml
|
||||
[file]
|
||||
|
||||
# Backends
|
||||
[backends]
|
||||
|
||||
[backends.backend1]
|
||||
|
||||
[backends.backend1.servers]
|
||||
[backends.backend1.servers.server0]
|
||||
url = "http://10.10.10.1:80"
|
||||
weight = 1
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://10.10.10.2:80"
|
||||
weight = 2
|
||||
# ...
|
||||
|
||||
[backends.backend1.circuitBreaker]
|
||||
expression = "NetworkErrorRatio() > 0.5"
|
||||
|
||||
[backends.backend1.loadBalancer]
|
||||
method = "drr"
|
||||
[backends.backend1.loadBalancer.stickiness]
|
||||
cookieName = "foobar"
|
||||
|
||||
[backends.backend1.maxConn]
|
||||
amount = 10
|
||||
extractorfunc = "request.host"
|
||||
|
||||
[backends.backend1.healthCheck]
|
||||
path = "/health"
|
||||
port = 88
|
||||
interval = "30s"
|
||||
|
||||
[backends.backend2]
|
||||
# ...
|
||||
|
||||
# Frontends
|
||||
[frontends]
|
||||
|
||||
[frontends.frontend1]
|
||||
entryPoints = ["http", "https"]
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
passTLSCert = true
|
||||
priority = 42
|
||||
basicAuth = [
|
||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
]
|
||||
|
||||
[frontends.frontend1.whiteList]
|
||||
sourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"]
|
||||
useXForwardedFor = true
|
||||
|
||||
[frontends.frontend1.routes]
|
||||
[frontends.frontend1.routes.route0]
|
||||
rule = "Host:test.localhost"
|
||||
[frontends.frontend1.routes.Route1]
|
||||
rule = "Method:GET"
|
||||
# ...
|
||||
|
||||
[frontends.frontend1.headers]
|
||||
allowedHosts = ["foobar", "foobar"]
|
||||
hostsProxyHeaders = ["foobar", "foobar"]
|
||||
SSLRedirect = true
|
||||
SSLTemporaryRedirect = true
|
||||
SSLHost = "foobar"
|
||||
STSSeconds = 42
|
||||
STSIncludeSubdomains = true
|
||||
STSPreload = true
|
||||
forceSTSHeader = true
|
||||
frameDeny = true
|
||||
customFrameOptionsValue = "foobar"
|
||||
contentTypeNosniff = true
|
||||
browserXSSFilter = true
|
||||
contentSecurityPolicy = "foobar"
|
||||
publicKey = "foobar"
|
||||
referrerPolicy = "foobar"
|
||||
isDevelopment = true
|
||||
[frontends.frontend1.headers.customRequestHeaders]
|
||||
X-Foo-Bar-01 = "foobar"
|
||||
X-Foo-Bar-02 = "foobar"
|
||||
# ...
|
||||
[frontends.frontend1.headers.customResponseHeaders]
|
||||
X-Foo-Bar-03 = "foobar"
|
||||
X-Foo-Bar-04 = "foobar"
|
||||
# ...
|
||||
[frontends.frontend1.headers.SSLProxyHeaders]
|
||||
X-Foo-Bar-05 = "foobar"
|
||||
X-Foo-Bar-06 = "foobar"
|
||||
# ...
|
||||
|
||||
[frontends.frontend1.errors]
|
||||
[frontends.frontend1.errors.errorPage0]
|
||||
status = ["500-599"]
|
||||
backend = "error"
|
||||
query = "/{status}.html"
|
||||
[frontends.frontend1.errors.errorPage1]
|
||||
status = ["404", "403"]
|
||||
backend = "error"
|
||||
query = "/{status}.html"
|
||||
# ...
|
||||
|
||||
[frontends.frontend1.ratelimit]
|
||||
extractorfunc = "client.ip"
|
||||
[frontends.frontend1.ratelimit.rateset.rateset1]
|
||||
period = "10s"
|
||||
average = 100
|
||||
burst = 200
|
||||
[frontends.frontend1.ratelimit.rateset.rateset2]
|
||||
period = "3s"
|
||||
average = 5
|
||||
burst = 10
|
||||
# ...
|
||||
|
||||
[frontends.frontend1.redirect]
|
||||
entryPoint = "https"
|
||||
regex = "^http://localhost/(.*)"
|
||||
replacement = "http://mydomain/$1"
|
||||
permanent = true
|
||||
|
||||
[frontends.frontend2]
|
||||
# ...
|
||||
|
||||
# HTTPS certificates
|
||||
[[tls]]
|
||||
entryPoints = ["https"]
|
||||
[tls.certificate]
|
||||
certFile = "path/to/my.cert"
|
||||
keyFile = "path/to/my.key"
|
||||
|
||||
[[tls]]
|
||||
# ...
|
||||
```
|
||||
|
||||
## Configuration Mode
|
||||
|
||||
You have two choices:
|
||||
|
||||
- [Rules in Træfik configuration file](/configuration/backends/file/#rules-in-trfik-configuration-file)
|
||||
- [Rules in dedicated files](/configuration/backends/file/#rules-in-dedicated-files)
|
||||
|
||||
To enable the file backend, you must either pass the `--file` option to the Træfik binary or put the `[file]` section (with or without inner settings) in the configuration file.
|
||||
|
||||
The configuration file allows managing both backends/frontends and HTTPS certificates (which are not [Let's Encrypt](https://letsencrypt.org) certificates generated through Træfik).
|
||||
|
||||
TOML templating can be used if rules are not defined in the Træfik configuration file.
|
||||
|
||||
### Rules in Træfik Configuration File
|
||||
|
||||
Add your configuration at the end of the global configuration file `traefik.toml`:
|
||||
|
||||
@@ -11,152 +162,142 @@ defaultEntryPoints = ["http", "https"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.redirect]
|
||||
entryPoint = "https"
|
||||
# ...
|
||||
[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 = "integration/fixtures/https/snitest.org.cert"
|
||||
KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||
# ...
|
||||
|
||||
[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.backend2.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
|
||||
|
||||
# restrict access to this frontend to the specified list of IPv4/IPv6 CIDR Nets
|
||||
# an unset or empty list allows all Source-IPs to access
|
||||
# if one of the Net-Specifications are invalid, the whole list is invalid
|
||||
# and allows all Source-IPs to access.
|
||||
whitelistSourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"]
|
||||
|
||||
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"
|
||||
# ...
|
||||
|
||||
# HTTPS certificate
|
||||
[[tls]]
|
||||
# ...
|
||||
|
||||
[[tls]]
|
||||
# ...
|
||||
```
|
||||
|
||||
## Rules in a Separate File
|
||||
!!! note
|
||||
If `tls.entryPoints` is not defined, the certificate is attached to all the `defaultEntryPoints` with a TLS configuration.
|
||||
|
||||
Put your rules in a separate file, for example `rules.toml`:
|
||||
!!! note
|
||||
Adding certificates directly to the entryPoint is still maintained but certificates declared in this way cannot be managed dynamically.
|
||||
It's recommended to use the file provider to declare certificates.
|
||||
|
||||
!!! warning
|
||||
TOML templating cannot be used if rules are defined in the Træfik configuration file.
|
||||
|
||||
### Rules in Dedicated Files
|
||||
|
||||
Træfik allows defining rules in one or more separate files.
|
||||
|
||||
#### One Separate File
|
||||
|
||||
You have to specify the file path in the `file.filename` option.
|
||||
|
||||
```toml
|
||||
# traefik.toml
|
||||
defaultEntryPoints = ["http", "https"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.redirect]
|
||||
entryPoint = "https"
|
||||
# ...
|
||||
[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 = "integration/fixtures/https/snitest.org.cert"
|
||||
KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||
# ...
|
||||
|
||||
[file]
|
||||
filename = "rules.toml"
|
||||
filename = "rules.toml"
|
||||
watch = true
|
||||
```
|
||||
|
||||
The option `file.watch` allows Træfik to watch file changes automatically.
|
||||
|
||||
#### Multiple Separated Files
|
||||
|
||||
You could have multiple `.toml` files in a directory (and recursively in its sub-directories):
|
||||
|
||||
```toml
|
||||
[file]
|
||||
directory = "/path/to/config/"
|
||||
watch = true
|
||||
```
|
||||
|
||||
The option `file.watch` allows Træfik to watch file changes automatically.
|
||||
|
||||
#### Separate Files Content
|
||||
|
||||
If you are defining rules in one or more separate files, you can use two formats.
|
||||
|
||||
##### Simple Format
|
||||
|
||||
Backends, Frontends and TLS certificates are defined one at time, as described in the file `rules.toml`:
|
||||
|
||||
```toml
|
||||
# rules.toml
|
||||
[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.backend2.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"
|
||||
# ...
|
||||
|
||||
# HTTPS certificate
|
||||
[[tls]]
|
||||
# ...
|
||||
|
||||
[[tls]]
|
||||
# ...
|
||||
```
|
||||
|
||||
## Multiple .toml Files
|
||||
##### TOML Templating
|
||||
|
||||
You could have multiple `.toml` files in a directory:
|
||||
!!! warning
|
||||
TOML templating can only be used **if rules are defined in one or more separate files**.
|
||||
Templating will not work in the Træfik configuration file.
|
||||
|
||||
Træfik allows using TOML templating.
|
||||
|
||||
Thus, it's possible to define easily lot of Backends, Frontends and TLS certificates as described in the file `template-rules.toml` :
|
||||
|
||||
```toml
|
||||
[file]
|
||||
directory = "/path/to/config/"
|
||||
```
|
||||
# template-rules.toml
|
||||
[backends]
|
||||
{{ range $i, $e := until 100 }}
|
||||
[backends.backend{{ $e }}]
|
||||
#...
|
||||
{{ end }}
|
||||
|
||||
If you want Træfik to watch file changes automatically, just add:
|
||||
[frontends]
|
||||
{{ range $i, $e := until 100 }}
|
||||
[frontends.frontend{{ $e }}]
|
||||
#...
|
||||
{{ end }}
|
||||
|
||||
```toml
|
||||
[file]
|
||||
watch = true
|
||||
|
||||
# HTTPS certificate
|
||||
{{ range $i, $e := until 100 }}
|
||||
[[tls]]
|
||||
#...
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
@@ -1,30 +1,22 @@
|
||||
# Kubernetes Ingress Backend
|
||||
# Kubernetes Ingress Provider
|
||||
|
||||
Træfik can be configured to use Kubernetes Ingress as a backend configuration:
|
||||
Træfik can be configured to use Kubernetes Ingress as a provider.
|
||||
|
||||
See also [Kubernetes user guide](/user-guide/kubernetes).
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Kubernetes Ingress configuration backend
|
||||
# Kubernetes Ingress Provider
|
||||
################################################################
|
||||
# Enable Kubernetes Ingress configuration backend
|
||||
|
||||
# Enable Kubernetes Ingress Provider.
|
||||
[kubernetes]
|
||||
|
||||
# Kubernetes server endpoint
|
||||
# Kubernetes server endpoint.
|
||||
#
|
||||
# When deployed as a replication controller in Kubernetes, Traefik will use
|
||||
# the environment variables KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT
|
||||
# to construct the endpoint.
|
||||
# Secure token will be found in /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
# and SSL CA cert in /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
#
|
||||
# The endpoint may be given to override the environment variable values.
|
||||
#
|
||||
# When the environment variables are not found, Traefik will try to connect to
|
||||
# the Kubernetes API server with an external-cluster client. In this case, the
|
||||
# endpoint is required. Specifically, it may be set to the URL used by
|
||||
# `kubectl proxy` to connect to a Kubernetes cluster from localhost.
|
||||
#
|
||||
# Optional for in-cluster configuration, required otherwise
|
||||
# Optional for in-cluster configuration, required otherwise.
|
||||
# Default: empty
|
||||
#
|
||||
# endpoint = "http://localhost:8080"
|
||||
@@ -36,8 +28,8 @@ Træfik can be configured to use Kubernetes Ingress as a backend configuration:
|
||||
#
|
||||
# token = "my token"
|
||||
|
||||
# Path to the certificate authority file used for the Kubernetes client
|
||||
# configuration.
|
||||
# Path to the certificate authority file.
|
||||
# Used for the Kubernetes client configuration.
|
||||
#
|
||||
# Optional
|
||||
# Default: empty
|
||||
@@ -51,50 +43,222 @@ Træfik can be configured to use Kubernetes Ingress as a backend configuration:
|
||||
#
|
||||
# namespaces = ["default", "production"]
|
||||
|
||||
# Ingress label selector to identify Ingress objects that should be processed.
|
||||
# See https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors for details.
|
||||
# Ingress label selector to filter Ingress objects that should be processed.
|
||||
#
|
||||
# Optional
|
||||
# Default: empty (process all Ingresses)
|
||||
#
|
||||
# labelselector = "A and not B"
|
||||
|
||||
# Value of `kubernetes.io/ingress.class` annotation that identifies Ingress objects to be processed.
|
||||
# If the parameter is non-empty, only Ingresses containing an annotation with the same value are processed.
|
||||
# Otherwise, Ingresses missing the annotation, having an empty value, or the value `traefik` are processed.
|
||||
#
|
||||
# Note : `ingressClass` option must begin with the "traefik" prefix.
|
||||
#
|
||||
# Optional
|
||||
# Default: empty
|
||||
#
|
||||
# ingressClass = "traefik-internal"
|
||||
|
||||
# Disable PassHost Headers.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# disablePassHostHeaders = true
|
||||
|
||||
# Enable PassTLSCert Headers.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# enablePassTLSCert = true
|
||||
|
||||
# Override default configuration template.
|
||||
#
|
||||
# Optional
|
||||
# Default: <built-in template>
|
||||
#
|
||||
# filename = "kubernetes.tmpl"
|
||||
```
|
||||
|
||||
Annotations can be used on containers to override default behaviour for the whole Ingress resource:
|
||||
### `endpoint`
|
||||
|
||||
- `traefik.frontend.rule.type: PathPrefixStrip`: override the default frontend rule type (Default: `PathPrefix`).
|
||||
- `traefik.frontend.priority: 3`: override the default frontend rule priority (Default: `len(Path)`).
|
||||
The Kubernetes server endpoint as URL.
|
||||
|
||||
Annotations can be used on the Kubernetes service to override default behaviour:
|
||||
When deployed into Kubernetes, Traefik will read the environment variables `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` to construct the endpoint.
|
||||
|
||||
- `traefik.backend.loadbalancer.method=drr`: override the default `wrr` load balancer algorithm
|
||||
- `traefik.backend.loadbalancer.sticky=true`: enable backend sticky sessions
|
||||
The access token will be looked up in `/var/run/secrets/kubernetes.io/serviceaccount/token` and the SSL CA certificate in `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt`.
|
||||
Both are provided mounted automatically when deployed inside Kubernetes.
|
||||
|
||||
You can find here an example [ingress](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-ingress.yaml) and [replication controller](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik.yaml).
|
||||
The endpoint may be specified to override the environment variable values inside a cluster.
|
||||
|
||||
Additionally, an annotation can be used on Kubernetes services to set the [circuit breaker expression](https://docs.traefik.io/basics/#backends) for a backend.
|
||||
When the environment variables are not found, Traefik will try to connect to the Kubernetes API server with an external-cluster client.
|
||||
In this case, the endpoint is required.
|
||||
Specifically, it may be set to the URL used by `kubectl proxy` to connect to a Kubernetes cluster using the granted autentication and authorization of the associated kubeconfig.
|
||||
|
||||
- `traefik.backend.circuitbreaker: <expression>`: set the circuit breaker expression for the backend (Default: nil).
|
||||
### `labelselector`
|
||||
|
||||
As known from nginx when used as Kubernetes Ingress Controller, a List of IP-Ranges which are allowed to access can be configured by using an ingress annotation:
|
||||
By default, Traefik processes all Ingress objects in the configured namespaces.
|
||||
A label selector can be defined to filter on specific Ingress objects only.
|
||||
|
||||
- `ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"`
|
||||
See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details.
|
||||
|
||||
An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access.
|
||||
### TLS communication between Traefik and backend pods
|
||||
|
||||
Traefik automatically requests endpoint information based on the service provided in the ingress spec.
|
||||
Although traefik will connect directly to the endpoints (pods), it still checks the service port to see if TLS communication is required.
|
||||
If the service port defined in the ingress spec is 443, then the backend communication protocol is assumed to be TLS, and will connect via TLS automatically.
|
||||
|
||||
!!! note
|
||||
Please note that by enabling TLS communication between traefik and your pods, you will have to have trusted certificates that have the proper trust chain and IP subject name.
|
||||
If this is not an option, you may need to skip TLS certificate verification.
|
||||
See the [insecureSkipVerify](/configuration/commons/#main-section) setting for more details.
|
||||
|
||||
## Annotations
|
||||
|
||||
### General annotations
|
||||
|
||||
The following general annotations are applicable on the Ingress object:
|
||||
|
||||
| Annotation | Description |
|
||||
|---------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.ingress.kubernetes.io/buffering: <YML>` | (3) See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.ingress.kubernetes.io/error-pages: <YML>` | (1) See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.ingress.kubernetes.io/frontend-entry-points: http,https` | Override the default frontend endpoints. |
|
||||
| `traefik.ingress.kubernetes.io/pass-tls-cert: "true"` | Override the default frontend PassTLSCert value. Default: `false`. |
|
||||
| `traefik.ingress.kubernetes.io/preserve-host: "true"` | Forward client `Host` header to the backend. |
|
||||
| `traefik.ingress.kubernetes.io/priority: "3"` | Override the default frontend rule priority. |
|
||||
| `traefik.ingress.kubernetes.io/rate-limit: <YML>` | (2) See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.ingress.kubernetes.io/redirect-entry-point: https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS). |
|
||||
| `traefik.ingress.kubernetes.io/redirect-permanent: "true"` | Return 301 instead of 302. |
|
||||
| `traefik.ingress.kubernetes.io/redirect-regex: ^http://localhost/(.*)` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-replacement`. |
|
||||
| `traefik.ingress.kubernetes.io/redirect-replacement: http://mydomain/$1` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-regex`. |
|
||||
| `traefik.ingress.kubernetes.io/rewrite-target: /users` | Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header. |
|
||||
| `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Override the default frontend rule type. Default: `PathPrefix`. |
|
||||
| `traefik.ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"` | A comma-separated list of IP ranges permitted for access. all source IPs are permitted if the list is empty or a single range is ill-formatted. Please note, you may have to set `service.spec.externalTrafficPolicy` to the value `Local` to preserve the source IP of the request for filtering. Please see [this link](https://kubernetes.io/docs/tutorials/services/source-ip/) for more information.|
|
||||
| `traefik.ingress.kubernetes.io/app-root: "/index.html"` | Redirects all requests for `/` to the defined path. (4) |
|
||||
|
||||
<1> `traefik.ingress.kubernetes.io/error-pages` example:
|
||||
|
||||
```yaml
|
||||
foo:
|
||||
status:
|
||||
- "404"
|
||||
backend: bar
|
||||
query: /bar
|
||||
fii:
|
||||
status:
|
||||
- "503"
|
||||
- "500"
|
||||
backend: bar
|
||||
query: /bir
|
||||
```
|
||||
|
||||
<2> `traefik.ingress.kubernetes.io/rate-limit` example:
|
||||
|
||||
```yaml
|
||||
extractorfunc: client.ip
|
||||
rateset:
|
||||
bar:
|
||||
period: 3s
|
||||
average: 6
|
||||
burst: 9
|
||||
foo:
|
||||
period: 6s
|
||||
average: 12
|
||||
burst: 18
|
||||
```
|
||||
|
||||
<3> `traefik.ingress.kubernetes.io/buffering` example:
|
||||
|
||||
```yaml
|
||||
maxrequestbodybytes: 10485760
|
||||
memrequestbodybytes: 2097153
|
||||
maxresponsebodybytes: 10485761
|
||||
memresponsebodybytes: 2097152
|
||||
retryexpression: IsNetworkError() && Attempts() <= 2
|
||||
```
|
||||
|
||||
<4> `traefik.ingress.kubernetes.io/app-root`:
|
||||
Non-root paths will not be affected by this annotation and handled normally.
|
||||
This annotation may not be combined with the `ReplacePath` rule type or any other annotation leveraging that rule type.
|
||||
Trying to do so leads to an error and the corresponding Ingress object being ignored.
|
||||
|
||||
!!! note
|
||||
Please note that `traefik.ingress.kubernetes.io/redirect-regex` and `traefik.ingress.kubernetes.io/redirect-replacement` do not have to be set if `traefik.ingress.kubernetes.io/redirect-entry-point` is defined for the redirection (they will not be used in this case).
|
||||
|
||||
The following annotations are applicable on the Service object associated with a particular Ingress object:
|
||||
|
||||
| Annotation | Description |
|
||||
|--------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.backend.loadbalancer.sticky: "true"` | Enable backend sticky sessions (DEPRECATED). |
|
||||
| `traefik.ingress.kubernetes.io/affinity: "true"` | Enable backend sticky sessions. |
|
||||
| `traefik.ingress.kubernetes.io/circuit-breaker-expression: <expression>` | Set the circuit breaker expression for the backend. |
|
||||
| `traefik.ingress.kubernetes.io/load-balancer-method: drr` | Override the default `wrr` load balancer algorithm. |
|
||||
| `traefik.ingress.kubernetes.io/max-conn-amount: 10` | Set a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
|
||||
| `traefik.ingress.kubernetes.io/max-conn-extractor-func: client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.ingress.kubernetes.io/session-cookie-name: <NAME>` | Manually set the cookie name for sticky sessions. |
|
||||
|
||||
!!! note
|
||||
`traefik.ingress.kubernetes.io/` and `ingress.kubernetes.io/` are supported prefixes.
|
||||
|
||||
### Custom Headers Annotations
|
||||
|
||||
| Annotation | Description |
|
||||
| ------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `ingress.kubernetes.io/custom-request-headers: EXPR` | Provides the container with custom request headers that will be appended to each request forwarded to the container. Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `ingress.kubernetes.io/custom-response-headers: EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client. Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
|
||||
### Security Headers Annotations
|
||||
|
||||
The following security annotations are applicable on the Ingress object:
|
||||
|
||||
| Annotation | Description |
|
||||
| ----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `ingress.kubernetes.io/allowed-hosts: EXPR` | Provides a list of allowed hosts that requests will be processed. Format: `Host1,Host2` |
|
||||
| `ingress.kubernetes.io/browser-xss-filter: "true"` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `ingress.kubernetes.io/content-security-policy: VALUE` | Adds CSP Header with the custom value. |
|
||||
| `ingress.kubernetes.io/content-type-nosniff: "true"` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `ingress.kubernetes.io/custom-browser-xss-value: VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `ingress.kubernetes.io/custom-frame-options-value: VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `ingress.kubernetes.io/force-hsts: "false"` | Adds the STS header to non-SSL requests. |
|
||||
| `ingress.kubernetes.io/frame-deny: "false"` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `ingress.kubernetes.io/hsts-max-age: "315360000"` | Sets the max-age of the HSTS header. |
|
||||
| `ingress.kubernetes.io/hsts-include-subdomains: "true"` | Adds the IncludeSubdomains section of the STS header. |
|
||||
| `ingress.kubernetes.io/hsts-preload: "true"` | Adds the preload flag to the HSTS header. |
|
||||
| `ingress.kubernetes.io/is-development: "false"` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
| `ingress.kubernetes.io/proxy-headers: EXPR` | Provides a list of headers that the proxied hostname may be stored. Format: `HEADER1,HEADER2` |
|
||||
| `ingress.kubernetes.io/public-key: VALUE` | Adds pinned HTST public key header. |
|
||||
| `ingress.kubernetes.io/referrer-policy: VALUE` | Adds referrer policy header. |
|
||||
| `ingress.kubernetes.io/ssl-redirect: "true"` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `ingress.kubernetes.io/ssl-temporary-redirect: "true"` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `ingress.kubernetes.io/ssl-host: HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
| `ingress.kubernetes.io/ssl-proxy-headers: EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`). Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
|
||||
### Authentication
|
||||
|
||||
Is possible to add additional authentication annotations in the Ingress rule.
|
||||
The source of the authentication is a secret that contains usernames and passwords inside the the key auth.
|
||||
Additional authentication annotations can be added to the Ingress object.
|
||||
The source of the authentication is a Secret object that contains the credentials.
|
||||
|
||||
- `ingress.kubernetes.io/auth-type`: `basic`
|
||||
- `ingress.kubernetes.io/auth-secret`: contains the usernames and passwords with access to the paths defined in the Ingress Rule.
|
||||
| Annotation | Description |
|
||||
|-----------------------------------------------|-------------------------------------------------------------------------------------------------------------|
|
||||
| `ingress.kubernetes.io/auth-type: basic` | Contains the authentication type. The only permitted type is `basic`. |
|
||||
| `ingress.kubernetes.io/auth-secret: mysecret` | Name of Secret containing the username and password with access to the paths defined in the Ingress object. |
|
||||
|
||||
The secret must be created in the same namespace as the Ingress rule.
|
||||
The secret must be created in the same namespace as the Ingress object.
|
||||
|
||||
Limitations:
|
||||
The following limitations hold:
|
||||
|
||||
- Basic authentication only.
|
||||
- Realm not configurable; only `traefik` default.
|
||||
- Secret must contain only single file.
|
||||
- The realm is not configurable; the only supported (and default) value is `traefik`.
|
||||
- The Secret must contain a single file only.
|
||||
|
||||
### TLS certificates management
|
||||
|
||||
TLS certificates can be managed in Secrets objects.
|
||||
More information are available in the [User Guide](/user-guide/kubernetes/#add-a-tls-certificate-to-the-ingress).
|
||||
|
||||
!!! note
|
||||
Only TLS certificates provided by users can be stored in Kubernetes Secrets.
|
||||
[Let's Encrypt](https://letsencrypt.org) certificates cannot be managed in Kubernets Secrets yet.
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
# Marathon Backend
|
||||
# Marathon Provider
|
||||
|
||||
Træfik can be configured to use Marathon as a backend configuration:
|
||||
Træfik can be configured to use Marathon as a provider.
|
||||
|
||||
See also [Marathon user guide](/user-guide/marathon).
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Mesos/Marathon configuration backend
|
||||
# Mesos/Marathon Provider
|
||||
################################################################
|
||||
|
||||
# Enable Marathon configuration backend
|
||||
# Enable Marathon Provider.
|
||||
[marathon]
|
||||
|
||||
# Marathon server endpoint.
|
||||
# You can also specify multiple endpoint for Marathon:
|
||||
# endpoint := "http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080"
|
||||
# endpoint = "http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080"
|
||||
#
|
||||
# Required
|
||||
# Default: "http://127.0.0.1:8080"
|
||||
#
|
||||
endpoint = "http://127.0.0.1:8080"
|
||||
|
||||
# Enable watch Marathon changes
|
||||
# Enable watch Marathon changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
@@ -31,20 +38,30 @@ watch = true
|
||||
#
|
||||
domain = "marathon.localhost"
|
||||
|
||||
# Override default configuration template. For advanced users :)
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "marathon.tmpl"
|
||||
|
||||
# Expose Marathon apps by default in traefik
|
||||
# Override template version
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
# - "1": previous template version (must be used only with older custom templates, see "filename")
|
||||
# - "2": current template version (must be used to force template version when "filename" is used)
|
||||
#
|
||||
# templateVersion = 2
|
||||
|
||||
# Expose Marathon apps by default in Traefik.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
# exposedByDefault = true
|
||||
# exposedByDefault = false
|
||||
|
||||
# Convert Marathon groups to subdomains
|
||||
# Convert Marathon groups to subdomains.
|
||||
# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain}
|
||||
# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain}
|
||||
#
|
||||
@@ -53,52 +70,64 @@ domain = "marathon.localhost"
|
||||
#
|
||||
# groupsAsSubDomains = true
|
||||
|
||||
# Enable compatibility with marathon-lb labels
|
||||
# Enable compatibility with marathon-lb labels.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# marathonLBCompatibility = true
|
||||
|
||||
# Enable Marathon basic authentication
|
||||
# Enable filtering using Marathon constraints..
|
||||
# If enabled, Traefik will read Marathon constraints, as defined in https://mesosphere.github.io/marathon/docs/constraints.html
|
||||
# Each individual constraint will be treated as a verbatim compounded tag.
|
||||
# i.e. "rack_id:CLUSTER:rack-1", with all constraint groups concatenated together using ":"
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# filterMarathonConstraints = true
|
||||
|
||||
# Enable Marathon basic authentication.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [marathon.basic]
|
||||
# httpBasicAuthUser = "foo"
|
||||
# httpBasicPassword = "bar"
|
||||
# [marathon.basic]
|
||||
# httpBasicAuthUser = "foo"
|
||||
# httpBasicPassword = "bar"
|
||||
|
||||
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [marathon.TLS]
|
||||
# CA = "/etc/ssl/ca.crt"
|
||||
# Cert = "/etc/ssl/marathon.cert"
|
||||
# Key = "/etc/ssl/marathon.key"
|
||||
# InsecureSkipVerify = true
|
||||
# [marathon.TLS]
|
||||
# CA = "/etc/ssl/ca.crt"
|
||||
# Cert = "/etc/ssl/marathon.cert"
|
||||
# Key = "/etc/ssl/marathon.key"
|
||||
# insecureSkipVerify = true
|
||||
|
||||
# DCOSToken for DCOS environment, This will override the Authorization header
|
||||
# DCOSToken for DCOS environment.
|
||||
# This will override the Authorization header.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# dcosToken = "xxxxxx"
|
||||
|
||||
# Override DialerTimeout
|
||||
# Override DialerTimeout.
|
||||
# Amount of time to allow the Marathon provider to wait to open a TCP connection
|
||||
# to a Marathon master.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||
# values (digits). If no units are provided, the value is parsed assuming
|
||||
# seconds.
|
||||
# values (digits).
|
||||
# If no units are provided, the value is parsed assuming seconds.
|
||||
#
|
||||
# Optional
|
||||
# Default: "60s"
|
||||
#
|
||||
# dialerTimeout = "60s"
|
||||
|
||||
# Set the TCP Keep Alive interval for the Marathon HTTP Client.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||
# values (digits). If no units are provided, the value is parsed assuming
|
||||
# seconds.
|
||||
# values (digits).
|
||||
# If no units are provided, the value is parsed assuming seconds.
|
||||
#
|
||||
# Optional
|
||||
# Default: "10s"
|
||||
@@ -113,55 +142,172 @@ domain = "marathon.localhost"
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# forceTaskHostname = false
|
||||
# forceTaskHostname = true
|
||||
|
||||
# Applications may define readiness checks which are probed by Marathon during
|
||||
# deployments periodically and the results exposed via the API. Enabling the
|
||||
# following parameter causes Traefik to filter out tasks whose readiness checks
|
||||
# have not succeeded.
|
||||
# Note that the checks are only valid at deployment times. See the Marathon
|
||||
# guide for details.
|
||||
# deployments periodically and the results exposed via the API.
|
||||
# Enabling the following parameter causes Traefik to filter out tasks
|
||||
# whose readiness checks have not succeeded.
|
||||
# Note that the checks are only valid at deployment times.
|
||||
# See the Marathon guide for details.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# respectReadinessChecks = false
|
||||
# respectReadinessChecks = true
|
||||
```
|
||||
|
||||
Labels can be used on containers to override default behaviour:
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
| Label | Description |
|
||||
|-----------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `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.backend.healthcheck.path=/health` | set the Traefik health check path [default: no health checks] |
|
||||
| `traefik.backend.healthcheck.interval=5s` | sets a custom health check interval in Go-parseable (`time.ParseDuration`) format [default: 30s] |
|
||||
| `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 |
|
||||
| `traefik.weight=10` | assign this weight to the application |
|
||||
| `traefik.enable=false` | disable this application in Træfik |
|
||||
| `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 |
|
||||
| `traefik.frontend.entryPoints=http,https` | assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash`. |
|
||||
## Labels: overriding default behavior
|
||||
|
||||
If several ports need to be exposed from a container, the services labels can be used:
|
||||
Marathon labels may be used to dynamically change the routing and forwarding behavior.
|
||||
|
||||
| Label | Description |
|
||||
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.<service-name>.port=443` | create a service binding with frontend/backend using this port. Overrides `traefik.port`. |
|
||||
| `traefik.<service-name>.portIndex=1` | create a service binding with frontend/backend using this port index. Overrides `traefik.portIndex`. |
|
||||
| `traefik.<service-name>.protocol=https` | assign `https` protocol. Overrides `traefik.protocol`. |
|
||||
| `traefik.<service-name>.weight=10` | assign this service weight. Overrides `traefik.weight`. |
|
||||
| `traefik.<service-name>.frontend.backend=fooBackend` | assign this service frontend to `foobackend`. Default is to assign to the service backend. |
|
||||
| `traefik.<service-name>.frontend.entryPoints=http` | assign this service entrypoints. Overrides `traefik.frontend.entrypoints`. |
|
||||
| `traefik.<service-name>.frontend.auth.basic=test:EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
| `traefik.<service-name>.frontend.passHostHeader=true` | Forward client `Host` header to the backend. Overrides `traefik.frontend.passHostHeader`. |
|
||||
| `traefik.<service-name>.frontend.priority=10` | assign the service frontend priority. Overrides `traefik.frontend.priority`. |
|
||||
| `traefik.<service-name>.frontend.rule=Path:/foo` | assign the service frontend rule. Overrides `traefik.frontend.rule`. |
|
||||
They may be specified on one of two levels: Application or service.
|
||||
|
||||
### Application Level
|
||||
|
||||
The following labels can be defined on Marathon applications. They adjust the behavior for the entire application.
|
||||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
|
||||
| `traefik.portIndex=1` | Register port by index in the application's ports array. Useful when the application exposes multiple ports. |
|
||||
| `traefik.protocol=https` | Override the default `http` protocol |
|
||||
| `traefik.weight=10` | Assign this weight to the container |
|
||||
| `traefik.backend=foo` | Give the name `foo` to the generated backend for this container. |
|
||||
| `traefik.backend.buffering.maxRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.maxResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.retryExpression=EXPR` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.backend.healthcheck.path=/health` | Enable health check for the backend, hitting the container at `path`. |
|
||||
| `traefik.backend.healthcheck.port=8080` | Allow to use a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.interval=1s` | Define the health check interval. (Default: 30s) |
|
||||
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||
| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) |
|
||||
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend.<br>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.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
|
||||
| `traefik.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
|
||||
| `traefik.frontend.passTLSCert=true` | Forward TLS Client certificates to the backend. |
|
||||
| `traefik.frontend.priority=10` | Override default frontend priority |
|
||||
| `traefik.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{sub_domain}.{domain}`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.customRequestHeaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
|
|
||||
|
||||
#### Security Headers
|
||||
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
|
||||
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
|
||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
|
||||
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
|
||||
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
|
||||
|
||||
### Applications with Multiple Ports (segment labels)
|
||||
|
||||
Segment labels are used to define routes to an application exposing multiple ports.
|
||||
A segment is a group of labels that apply to a port exposed by an application.
|
||||
You can define as many segments as ports exposed in an application.
|
||||
|
||||
Segment labels override the default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------------|-------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||
| `traefik.<segment_name>.portIndex=1` | Same as `traefik.portIndex` |
|
||||
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
||||
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
|
||||
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
|
||||
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.status=RANGE` | Same as `traefik.frontend.errors.<name>.status` |
|
||||
| `traefik.<segment_name>.frontend.passHostHeader=true` | Same as `traefik.frontend.passHostHeader` |
|
||||
| `traefik.<segment_name>.frontend.passTLSCert=true` | Same as `traefik.frontend.passTLSCert` |
|
||||
| `traefik.<segment_name>.frontend.priority=10` | Same as `traefik.frontend.priority` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.extractorFunc=EXP` | Same as `traefik.frontend.rateLimit.extractorFunc` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.period=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.period` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.average=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.average` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.burst=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.burst` |
|
||||
| `traefik.<segment_name>.frontend.redirect.entryPoint=https` | Same as `traefik.frontend.redirect.entryPoint` |
|
||||
| `traefik.<segment_name>.frontend.redirect.regex=^http://localhost/(.*)` | Same as `traefik.frontend.redirect.regex` |
|
||||
| `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Same as `traefik.frontend.redirect.replacement` |
|
||||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Same as `traefik.frontend.whiteList.useXForwardedFor` |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------------------|----------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.customRequestHeaders=EXPR ` | Same as `traefik.frontend.headers.customRequestHeaders` |
|
||||
| `traefik.<segment_name>.frontend.headers.customResponseHeaders=EXPR` | Same as `traefik.frontend.headers.customResponseHeaders` |
|
||||
|
||||
#### Security Headers
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------------------|--------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.allowedHosts=EXPR` | Same as `traefik.frontend.headers.allowedHosts` |
|
||||
| `traefik.<segment_name>.frontend.headers.browserXSSFilter=true` | Same as `traefik.frontend.headers.browserXSSFilter` |
|
||||
| `traefik.<segment_name>.frontend.headers.contentSecurityPolicy=VALUE` | Same as `traefik.frontend.headers.contentSecurityPolicy` |
|
||||
| `traefik.<segment_name>.frontend.headers.contentTypeNosniff=true` | Same as `traefik.frontend.headers.contentTypeNosniff` |
|
||||
| `traefik.<segment_name>.frontend.headers.customBrowserXSSValue=VALUE` | Same as `traefik.frontend.headers.customBrowserXSSValue` |
|
||||
| `traefik.<segment_name>.frontend.headers.customFrameOptionsValue=VALUE` | Same as `traefik.frontend.headers.customFrameOptionsValue` |
|
||||
| `traefik.<segment_name>.frontend.headers.forceSTSHeader=false` | Same as `traefik.frontend.headers.forceSTSHeader` |
|
||||
| `traefik.<segment_name>.frontend.headers.frameDeny=false` | Same as `traefik.frontend.headers.frameDeny` |
|
||||
| `traefik.<segment_name>.frontend.headers.hostsProxyHeaders=EXPR` | Same as `traefik.frontend.headers.hostsProxyHeaders` |
|
||||
| `traefik.<segment_name>.frontend.headers.isDevelopment=false` | Same as `traefik.frontend.headers.isDevelopment` |
|
||||
| `traefik.<segment_name>.frontend.headers.publicKey=VALUE` | Same as `traefik.frontend.headers.publicKey` |
|
||||
| `traefik.<segment_name>.frontend.headers.referrerPolicy=VALUE` | Same as `traefik.frontend.headers.referrerPolicy` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLRedirect=true` | Same as `traefik.frontend.headers.SSLRedirect` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLTemporaryRedirect=true` | Same as `traefik.frontend.headers.SSLTemporaryRedirect` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLHost=HOST` | Same as `traefik.frontend.headers.SSLHost` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLProxyHeaders=EXPR` | Same as `traefik.frontend.headers.SSLProxyHeaders=EXPR` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSSeconds=315360000` | Same as `traefik.frontend.headers.STSSeconds=315360000` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSIncludeSubdomains=true` | Same as `traefik.frontend.headers.STSIncludeSubdomains=true` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSPreload=true` | Same as `traefik.frontend.headers.STSPreload=true` |
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# Mesos Generic Backend
|
||||
# Mesos Generic Provider
|
||||
|
||||
Træfik can be configured to use Mesos as a backend configuration:
|
||||
Træfik can be configured to use Mesos as a provider.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Mesos configuration backend
|
||||
# Mesos Provider
|
||||
################################################################
|
||||
|
||||
# Enable Mesos configuration backend
|
||||
# Enable Mesos Provider.
|
||||
[mesos]
|
||||
|
||||
# Mesos server endpoint.
|
||||
@@ -16,12 +16,14 @@ Træfik can be configured to use Mesos as a backend configuration:
|
||||
# endpoint = "zk://192.168.35.20:2181,192.168.35.21:2181,192.168.35.22:2181/mesos"
|
||||
#
|
||||
# Required
|
||||
# Default: "http://127.0.0.1:5050"
|
||||
#
|
||||
endpoint = "http://127.0.0.1:8080"
|
||||
|
||||
# Enable watch Mesos changes
|
||||
# Enable watch Mesos changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
@@ -32,50 +34,148 @@ watch = true
|
||||
#
|
||||
domain = "mesos.localhost"
|
||||
|
||||
# Override default configuration template. For advanced users :)
|
||||
# Expose Mesos apps by default in Traefik.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
# exposedByDefault = false
|
||||
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "mesos.tmpl"
|
||||
|
||||
# Expose Mesos apps by default in traefik
|
||||
# Override template version
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
# - "1": previous template version (must be used only with older custom templates, see "filename")
|
||||
# - "2": current template version (must be used to force template version when "filename" is used)
|
||||
#
|
||||
# ExposedByDefault = true
|
||||
# templateVersion = 2
|
||||
|
||||
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [mesos.TLS]
|
||||
# InsecureSkipVerify = true
|
||||
# insecureSkipVerify = true
|
||||
|
||||
# Zookeeper timeout (in seconds)
|
||||
# Zookeeper timeout (in seconds).
|
||||
#
|
||||
# Optional
|
||||
# Default: 30
|
||||
#
|
||||
# ZkDetectionTimeout = 30
|
||||
# zkDetectionTimeout = 30
|
||||
|
||||
# Polling interval (in seconds)
|
||||
# Polling interval (in seconds).
|
||||
#
|
||||
# Optional
|
||||
# Default: 30
|
||||
#
|
||||
# RefreshSeconds = 30
|
||||
# refreshSeconds = 30
|
||||
|
||||
# IP sources (e.g. host, docker, mesos, rkt)
|
||||
# IP sources (e.g. host, docker, mesos, netinfo).
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# IPSources = "host"
|
||||
# ipSources = "host"
|
||||
|
||||
# HTTP Timeout (in seconds)
|
||||
# HTTP Timeout (in seconds).
|
||||
#
|
||||
# Optional
|
||||
# Default: 30
|
||||
#
|
||||
# StateTimeoutSecond = "30"
|
||||
# stateTimeoutSecond = "30"
|
||||
|
||||
# Convert groups to subdomains.
|
||||
# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain}
|
||||
# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain}
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# groupsAsSubDomains = true
|
||||
|
||||
```
|
||||
|
||||
## Labels: overriding default behavior
|
||||
|
||||
The following labels can be defined on Mesos tasks. They adjust the behavior for the entire application.
|
||||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
|
||||
| `traefik.portIndex=1` | Register port by index in the application's ports array. Useful when the application exposes multiple ports. |
|
||||
| `traefik.protocol=https` | Override the default `http` protocol |
|
||||
| `traefik.weight=10` | Assign this weight to the container |
|
||||
| `traefik.backend=foo` | Give the name `foo` to the generated backend for this container. |
|
||||
| `traefik.backend.buffering.maxRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.maxResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.retryExpression=EXPR` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.backend.healthcheck.path=/health` | Enable health check for the backend, hitting the container at `path`. |
|
||||
| `traefik.backend.healthcheck.port=8080` | Allow to use a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.interval=1s` | Define the health check interval. (Default: 30s) |
|
||||
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend.<br>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.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
|
||||
| `traefik.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
|
||||
| `traefik.frontend.passTLSCert=true` | Forward TLS Client certificates to the backend. |
|
||||
| `traefik.frontend.priority=10` | Override default frontend priority |
|
||||
| `traefik.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{discovery_name}.{domain}`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
|
||||
### Custom Headers
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.customRequestHeaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
|
||||
### Security Headers
|
||||
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
|
||||
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
|
||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
|
||||
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
|
||||
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
# Rancher Backend
|
||||
# Rancher Provider
|
||||
|
||||
Træfik can be configured to use Rancher as a backend configuration:
|
||||
Træfik can be configured to use Rancher as a provider.
|
||||
|
||||
## Global Configuration
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Rancher configuration backend
|
||||
# Rancher Provider
|
||||
################################################################
|
||||
|
||||
# Enable Rancher configuration backend
|
||||
# Enable Rancher Provider.
|
||||
[rancher]
|
||||
|
||||
# Default domain used.
|
||||
@@ -17,46 +19,65 @@ Træfik can be configured to use Rancher as a backend configuration:
|
||||
#
|
||||
domain = "rancher.localhost"
|
||||
|
||||
# Enable watch Rancher changes
|
||||
# Enable watch Rancher changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Polling interval (in seconds)
|
||||
# Polling interval (in seconds).
|
||||
#
|
||||
# Optional
|
||||
# Default: 15
|
||||
#
|
||||
refreshSeconds = 15
|
||||
|
||||
# Expose Rancher services by default in traefik
|
||||
# Expose Rancher services by default in Traefik.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
exposedByDefault = false
|
||||
|
||||
# Filter services with unhealthy states and inactive states
|
||||
# Filter services with unhealthy states and inactive states.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
enableServiceHealthFilter = true
|
||||
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "rancher.tmpl"
|
||||
|
||||
# Override template version
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
# - "1": previous template version (must be used only with older custom templates, see "filename")
|
||||
# - "2": current template version (must be used to force template version when "filename" is used)
|
||||
#
|
||||
# templateVersion = 2
|
||||
```
|
||||
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
## Rancher Metadata Service
|
||||
|
||||
```toml
|
||||
# Enable Rancher metadata service configuration backend instead of the API
|
||||
# configuration backend
|
||||
# Enable Rancher metadata service provider instead of the API
|
||||
# provider.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
[rancher.metadata]
|
||||
|
||||
# Poll the Rancher metadata service for changes every `rancher.RefreshSeconds`
|
||||
# Poll the Rancher metadata service for changes every `rancher.refreshSeconds`.
|
||||
# NOTE: this is less accurate than the default long polling technique which
|
||||
# will provide near instantaneous updates to Traefik
|
||||
#
|
||||
@@ -65,7 +86,7 @@ enableServiceHealthFilter = true
|
||||
#
|
||||
intervalPoll = true
|
||||
|
||||
# Prefix used for accessing the Rancher metadata service
|
||||
# Prefix used for accessing the Rancher metadata service.
|
||||
#
|
||||
# Optional
|
||||
# Default: "/latest"
|
||||
@@ -76,24 +97,24 @@ prefix = "/2016-07-29"
|
||||
## Rancher API
|
||||
|
||||
```toml
|
||||
# Enable Rancher API configuration backend
|
||||
# Enable Rancher API provider.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
[rancher.api]
|
||||
|
||||
# Endpoint to use when connecting to the Rancher API
|
||||
# Endpoint to use when connecting to the Rancher API.
|
||||
#
|
||||
# Required
|
||||
endpoint = "http://rancherserver.example.com/v1"
|
||||
|
||||
# AccessKey to use when connecting to the Rancher API
|
||||
# AccessKey to use when connecting to the Rancher API.
|
||||
#
|
||||
# Required
|
||||
accessKey = "XXXXXXXXXXXXXXXXXXXX"
|
||||
|
||||
# SecretKey to use when connecting to the Rancher API
|
||||
# SecretKey to use when connecting to the Rancher API.
|
||||
#
|
||||
# Required
|
||||
secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
@@ -102,20 +123,157 @@ secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
!!! note
|
||||
If Traefik needs access to the Rancher API, you need to set the `endpoint`, `accesskey` and `secretkey` parameters.
|
||||
|
||||
To enable traefik to fetch information about the Environment it's deployed in only, you need to create an `Environment API Key`.
|
||||
To enable Traefik to fetch information about the Environment it's deployed in only, you need to create an `Environment API Key`.
|
||||
This can be found within the API Key advanced options.
|
||||
|
||||
## Labels
|
||||
Add these labels to traefik docker deployment to autogenerated these values:
|
||||
```
|
||||
io.rancher.container.agent.role: environment
|
||||
io.rancher.container.create_agent: true
|
||||
```
|
||||
|
||||
Labels can be used on task containers to override default behaviour:
|
||||
## Labels: overriding default behavior
|
||||
|
||||
| Label | Description |
|
||||
|----------------------------------------------|------------------------------------------------------------------------------------------|
|
||||
| `traefik.protocol=https` | override the default `http` protocol |
|
||||
| `traefik.weight=10` | assign this weight to the container |
|
||||
| `traefik.enable=false` | disable this container in Træfik |
|
||||
| `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 |
|
||||
| `traefik.frontend.entryPoints=http,https` | assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash`. |
|
||||
### On Containers
|
||||
|
||||
Labels can be used on task containers to override default behavior:
|
||||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `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 |
|
||||
| `traefik.backend=foo` | Give the name `foo` to the generated backend for this container. |
|
||||
| `traefik.backend.buffering.maxRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.maxResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memRequestBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.memResponseBodyBytes=0` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.buffering.retryExpression=EXPR` | See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.backend.healthcheck.path=/health` | Enable health check for the backend, hitting the container at `path`. |
|
||||
| `traefik.backend.healthcheck.port=8080` | Allow to use a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.interval=1s` | Define the health check interval. |
|
||||
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||
| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) |
|
||||
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend.<br>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.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
|
||||
| `traefik.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
|
||||
| `traefik.frontend.passTLSCert=true` | Forward TLS Client certificates to the backend. |
|
||||
| `traefik.frontend.priority=10` | Override default frontend priority |
|
||||
| `traefik.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{service_name}.{stack_name}.{domain}`. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.customRequestHeaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
|
||||
#### Security Headers
|
||||
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
|
||||
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
|
||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
|
||||
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
|
||||
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
|
||||
|
||||
### On containers with Multiple Ports (segment labels)
|
||||
|
||||
Segment labels are used to define routes to a container exposing multiple ports.
|
||||
A segment is a group of labels that apply to a port exposed by a container.
|
||||
You can define as many segments as ports exposed in a container.
|
||||
|
||||
Segment labels override the default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------------|-------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
||||
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
|
||||
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
|
||||
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
|
||||
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |
|
||||
| `traefik.<segment_name>.frontend.errors.<name>.status=RANGE` | Same as `traefik.frontend.errors.<name>.status` |
|
||||
| `traefik.<segment_name>.frontend.passHostHeader=true` | Same as `traefik.frontend.passHostHeader` |
|
||||
| `traefik.<segment_name>.frontend.passTLSCert=true` | Same as `traefik.frontend.passTLSCert` |
|
||||
| `traefik.<segment_name>.frontend.priority=10` | Same as `traefik.frontend.priority` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.extractorFunc=EXP` | Same as `traefik.frontend.rateLimit.extractorFunc` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.period=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.period` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.average=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.average` |
|
||||
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.burst=6` | Same as `traefik.frontend.rateLimit.rateSet.<name>.burst` |
|
||||
| `traefik.<segment_name>.frontend.redirect.entryPoint=https` | Same as `traefik.frontend.redirect.entryPoint` |
|
||||
| `traefik.<segment_name>.frontend.redirect.regex=^http://localhost/(.*)` | Same as `traefik.frontend.redirect.regex` |
|
||||
| `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Same as `traefik.frontend.redirect.replacement` |
|
||||
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` |
|
||||
| `traefik.<segment_name>.frontend.rule=EXP` | Same as `traefik.frontend.rule` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` |
|
||||
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Same as `traefik.frontend.whiteList.useXForwardedFor` |
|
||||
|
||||
#### Custom Headers
|
||||
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------------------|------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.customRequestHeaders=EXPR ` | overrides `traefik.frontend.headers.customRequestHeaders` |
|
||||
| `traefik.<segment_name>.frontend.headers.customResponseHeaders=EXPR` | overrides `traefik.frontend.headers.customResponseHeaders` |
|
||||
|
||||
#### Security Headers
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------------------------|--------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.frontend.headers.allowedHosts=EXPR` | overrides `traefik.frontend.headers.allowedHosts` |
|
||||
| `traefik.<segment_name>.frontend.headers.browserXSSFilter=true` | overrides `traefik.frontend.headers.browserXSSFilter` |
|
||||
| `traefik.<segment_name>.frontend.headers.contentSecurityPolicy=VALUE` | overrides `traefik.frontend.headers.contentSecurityPolicy` |
|
||||
| `traefik.<segment_name>.frontend.headers.contentTypeNosniff=true` | overrides `traefik.frontend.headers.contentTypeNosniff` |
|
||||
| `traefik.<segment_name>.frontend.headers.customBrowserXSSValue=VALUE` | overrides `traefik.frontend.headers.customBrowserXSSValue` |
|
||||
| `traefik.<segment_name>.frontend.headers.customFrameOptionsValue=VALUE` | overrides `traefik.frontend.headers.customFrameOptionsValue` |
|
||||
| `traefik.<segment_name>.frontend.headers.forceSTSHeader=false` | overrides `traefik.frontend.headers.forceSTSHeader` |
|
||||
| `traefik.<segment_name>.frontend.headers.frameDeny=false` | overrides `traefik.frontend.headers.frameDeny` |
|
||||
| `traefik.<segment_name>.frontend.headers.hostsProxyHeaders=EXPR` | overrides `traefik.frontend.headers.hostsProxyHeaders` |
|
||||
| `traefik.<segment_name>.frontend.headers.isDevelopment=false` | overrides `traefik.frontend.headers.isDevelopment` |
|
||||
| `traefik.<segment_name>.frontend.headers.publicKey=VALUE` | overrides `traefik.frontend.headers.publicKey` |
|
||||
| `traefik.<segment_name>.frontend.headers.referrerPolicy=VALUE` | overrides `traefik.frontend.headers.referrerPolicy` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLRedirect=true` | overrides `traefik.frontend.headers.SSLRedirect` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLTemporaryRedirect=true` | overrides `traefik.frontend.headers.SSLTemporaryRedirect` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLHost=HOST` | overrides `traefik.frontend.headers.SSLHost` |
|
||||
| `traefik.<segment_name>.frontend.headers.SSLProxyHeaders=EXPR` | overrides `traefik.frontend.headers.SSLProxyHeaders` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSSeconds=315360000` | overrides `traefik.frontend.headers.STSSeconds` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSIncludeSubdomains=true` | overrides `traefik.frontend.headers.STSIncludeSubdomains` |
|
||||
| `traefik.<segment_name>.frontend.headers.STSPreload=true` | overrides `traefik.frontend.headers.STSPreload` |
|
||||
|
||||
92
docs/configuration/backends/rest.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Rest Provider
|
||||
|
||||
Træfik can be configured:
|
||||
|
||||
- using a RESTful api.
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
# Enable REST Provider.
|
||||
[rest]
|
||||
# Name of the related entry point
|
||||
#
|
||||
# Optional
|
||||
# Default: "traefik"
|
||||
#
|
||||
entryPoint = "traefik"
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
| Path | Method | Description |
|
||||
|------------------------------|--------|-----------------|
|
||||
| `/api/providers/web` | `PUT` | update provider |
|
||||
| `/api/providers/rest` | `PUT` | update provider |
|
||||
|
||||
!!! warning
|
||||
For compatibility reason, when you activate the rest provider, you can use `web` or `rest` as `provider` value.
|
||||
|
||||
|
||||
```shell
|
||||
curl -XPUT @file "http://localhost:8080/api/providers/rest"
|
||||
```
|
||||
|
||||
with `@file`:
|
||||
```json
|
||||
{
|
||||
"frontends": {
|
||||
"frontend2": {
|
||||
"routes": {
|
||||
"test_2": {
|
||||
"rule": "Path:/test"
|
||||
}
|
||||
},
|
||||
"backend": "backend1"
|
||||
},
|
||||
"frontend1": {
|
||||
"routes": {
|
||||
"test_1": {
|
||||
"rule": "Host:test.localhost"
|
||||
}
|
||||
},
|
||||
"backend": "backend2"
|
||||
}
|
||||
},
|
||||
"backends": {
|
||||
"backend2": {
|
||||
"loadBalancer": {
|
||||
"method": "drr"
|
||||
},
|
||||
"servers": {
|
||||
"server2": {
|
||||
"weight": 2,
|
||||
"URL": "http://172.17.0.5:80"
|
||||
},
|
||||
"server1": {
|
||||
"weight": 1,
|
||||
"url": "http://172.17.0.4:80"
|
||||
}
|
||||
}
|
||||
},
|
||||
"backend1": {
|
||||
"loadBalancer": {
|
||||
"method": "wrr"
|
||||
},
|
||||
"circuitBreaker": {
|
||||
"expression": "NetworkErrorRatio() > 0.5"
|
||||
},
|
||||
"servers": {
|
||||
"server2": {
|
||||
"weight": 1,
|
||||
"url": "http://172.17.0.3:80"
|
||||
},
|
||||
"server1": {
|
||||
"weight": 10,
|
||||
"url": "http://172.17.0.2:80"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
156
docs/configuration/backends/servicefabric.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# Azure Service Fabric Provider
|
||||
|
||||
Træfik can be configured to use Azure Service Fabric as a provider.
|
||||
|
||||
See [this repository for an example deployment package and further documentation.](https://aka.ms/traefikonsf)
|
||||
|
||||
## Azure Service Fabric
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Azure Service Fabric Provider
|
||||
################################################################
|
||||
|
||||
# Enable Azure Service Fabric Provider
|
||||
[serviceFabric]
|
||||
|
||||
# Azure Service Fabric Management Endpoint
|
||||
#
|
||||
# Required
|
||||
#
|
||||
clusterManagementUrl = "https://localhost:19080"
|
||||
|
||||
# Azure Service Fabric Management Endpoint API Version
|
||||
#
|
||||
# Required
|
||||
# Default: "3.0"
|
||||
#
|
||||
apiVersion = "3.0"
|
||||
|
||||
# Azure Service Fabric Polling Interval (in seconds)
|
||||
#
|
||||
# Required
|
||||
# Default: 10
|
||||
#
|
||||
refreshSeconds = 10
|
||||
|
||||
# Enable TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [serviceFabric.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/servicefabric.crt"
|
||||
# key = "/etc/ssl/servicefabric.key"
|
||||
# insecureSkipVerify = true
|
||||
```
|
||||
|
||||
## Labels
|
||||
|
||||
The provider uses labels to configure how services are exposed through Træfik.
|
||||
These can be set using Extensions and the Property Manager API
|
||||
|
||||
#### Extensions
|
||||
|
||||
Set labels with extensions through the services `ServiceManifest.xml` file.
|
||||
Here is an example of an extension setting Træfik labels:
|
||||
|
||||
```xml
|
||||
<StatelessServiceType ServiceTypeName="WebServiceType">
|
||||
<Extensions>
|
||||
<Extension Name="Traefik">
|
||||
<Labels xmlns="http://schemas.microsoft.com/2015/03/fabact-no-schema">
|
||||
<Label Key="traefik.frontend.rule.example2">PathPrefixStrip: /a/path/to/strip</Label>
|
||||
<Label Key="traefik.enable">true</Label>
|
||||
<Label Key="traefik.frontend.passHostHeader">true</Label>
|
||||
</Labels>
|
||||
</Extension>
|
||||
</Extensions>
|
||||
</StatelessServiceType>
|
||||
```
|
||||
|
||||
#### Property Manager
|
||||
|
||||
Set Labels with the property manager API to overwrite and add labels, while your service is running.
|
||||
Here is an example of adding a frontend rule using the property manager API.
|
||||
|
||||
```shell
|
||||
curl -X PUT \
|
||||
'http://localhost:19080/Names/GettingStartedApplication2/WebService/$/GetProperty?api-version=6.0&IncludeValues=true' \
|
||||
-d '{
|
||||
"PropertyName": "traefik.frontend.rule.default",
|
||||
"Value": {
|
||||
"Kind": "String",
|
||||
"Data": "PathPrefixStrip: /a/path/to/strip"
|
||||
},
|
||||
"CustomTypeId": "LabelType"
|
||||
}'
|
||||
```
|
||||
|
||||
!!! note
|
||||
This functionality will be released in a future version of the [sfctl](https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-application-lifecycle-sfctl) tool.
|
||||
|
||||
## Available Labels
|
||||
|
||||
Labels, set through extensions or the property manager, can be used on services to override default behavior.
|
||||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.servicefabric.groupname` | Group all services with the same name into a single backend in Træfik |
|
||||
| `traefik.servicefabric.groupweight` | Set the weighting of the current services nodes in the backend group |
|
||||
| `traefik.servicefabric.enablelabeloverrides` | Toggle whether labels can be overridden using the Service Fabric Property Manager API |
|
||||
| `traefik.backend.healthcheck.path=/health` | Enable health check for the backend, hitting the container at `path`. |
|
||||
| `traefik.backend.healthcheck.port=8080` | Allow to use a different port for the health check. |
|
||||
| `traefik.backend.healthcheck.interval=1s` | Define the health check interval. |
|
||||
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||
| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) |
|
||||
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend.<br>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.<br>Must be used in conjunction with the above label to take effect. |
|
||||
| `traefik.backend.weight=10` | Assign this weight to the container |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
|
||||
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
|
||||
| `traefik.frontend.passTLSCert=true` | Forward TLS Client certificates to the backend. |
|
||||
| `traefik.frontend.priority=10` | Override default frontend priority |
|
||||
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
|
||||
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
|
||||
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
|
||||
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
|
||||
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Defaults to SF address. |
|
||||
| `traefik.frontend.whiteList.sourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `traefik.frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
|
||||
### Custom Headers
|
||||
|
||||
| Label | Description |
|
||||
|-------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.customRequestHeaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client.<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
|
||||
### Security Headers
|
||||
|
||||
| Label | Description |
|
||||
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
|
||||
| `traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
|
||||
| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value||HEADER2:value2</code> |
|
||||
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
|
||||
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
|
||||
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
|
||||
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
|
||||
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
|
||||
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
|
||||
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
|
||||
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
|
||||
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
|
||||
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
|
||||
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
|
||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||
@@ -1,37 +1,48 @@
|
||||
# Web Backend
|
||||
# Web Provider
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
The web provider is deprecated, please use the [api](/configuration/api.md), the [ping](/configuration/ping.md), the [metrics](/configuration/metrics) and the [rest](/configuration/backends/rest.md) provider.
|
||||
|
||||
Træfik can be configured:
|
||||
|
||||
- using a RESTful api.
|
||||
- to use a metric system (like Prometheus, DataDog or StatD, ...).
|
||||
- to use a monitoring system (like Prometheus, DataDog or StatD, ...).
|
||||
- to expose a Web Dashboard.
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
# Enable Web Provider.
|
||||
[web]
|
||||
|
||||
# Web administration port
|
||||
# Web administration port.
|
||||
#
|
||||
# Required
|
||||
# Default: ":8080"
|
||||
#
|
||||
address = ":8080"
|
||||
|
||||
# SSL certificate and key used
|
||||
# SSL certificate and key used.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# CertFile = "traefik.crt"
|
||||
# KeyFile = "traefik.key"
|
||||
# certFile = "traefik.crt"
|
||||
# keyFile = "traefik.key"
|
||||
|
||||
# Set REST API to read-only mode
|
||||
# Set REST API to read-only mode.
|
||||
#
|
||||
# Optional
|
||||
# ReadOnly = false
|
||||
# Default: false
|
||||
#
|
||||
readOnly = true
|
||||
|
||||
# Enable more detailed statistics
|
||||
# [web.statistics]
|
||||
# RecentErrors = 10
|
||||
# Set the root path for webui and API
|
||||
#
|
||||
# Deprecated
|
||||
# Optional
|
||||
#
|
||||
# path = "/mypath"
|
||||
#
|
||||
```
|
||||
|
||||
## Web UI
|
||||
@@ -42,72 +53,174 @@ address = ":8080"
|
||||
|
||||
### Authentication
|
||||
|
||||
- Basic Authentication
|
||||
!!! note
|
||||
The `/ping` path of the API is excluded from authentication (since 1.4).
|
||||
|
||||
#### Basic Authentication
|
||||
|
||||
Passwords can be encoded in MD5, SHA1 and BCrypt: you can use `htpasswd` to generate those ones.
|
||||
|
||||
Users can be specified directly in the toml file, or indirectly by referencing an external file;
|
||||
Users can be specified directly in the TOML file, or indirectly by referencing an external file;
|
||||
if both are provided, the two are merged, with external file contents having precedence.
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# To enable basic auth on the webui with 2 user/pass: test:test and test2:test2
|
||||
[web.auth.basic]
|
||||
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||
usersFile = "/path/to/.htpasswd"
|
||||
[web.auth.basic]
|
||||
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||
usersFile = "/path/to/.htpasswd"
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
- Digest Authentication
|
||||
#### Digest Authentication
|
||||
|
||||
You can use `htdigest` to generate those ones.
|
||||
|
||||
Users can be specified directly in the toml file, or indirectly by referencing an external file;
|
||||
Users can be specified directly in the TOML file, or indirectly by referencing an external file;
|
||||
if both are provided, the two are merged, with external file contents having precedence
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# To enable digest auth on the webui with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
|
||||
[web.auth.digest]
|
||||
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||
usersFile = "/path/to/.htdigest"
|
||||
[web.auth.digest]
|
||||
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||
usersFile = "/path/to/.htdigest"
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
|
||||
## Metrics
|
||||
|
||||
You can enable Traefik to export internal metrics to different monitoring systems.
|
||||
You can enable Træfik to export internal metrics to different monitoring systems.
|
||||
|
||||
- Prometheus
|
||||
### Prometheus
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# To enable Traefik to export internal metrics to Prometheus
|
||||
[web.metrics.prometheus]
|
||||
Buckets=[0.1,0.3,1.2,5.0]
|
||||
|
||||
# Buckets for latency metrics
|
||||
#
|
||||
# Optional
|
||||
# Default: [0.1, 0.3, 1.2, 5]
|
||||
buckets=[0.1,0.3,1.2,5.0]
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
- DataDog
|
||||
### DataDog
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# DataDog metrics exporter type
|
||||
[web.metrics.datadog]
|
||||
Address = "localhost:8125"
|
||||
Pushinterval = "10s"
|
||||
|
||||
# DataDog's address.
|
||||
#
|
||||
# Required
|
||||
# Default: "localhost:8125"
|
||||
#
|
||||
address = "localhost:8125"
|
||||
|
||||
# DataDog push interval
|
||||
#
|
||||
# Optional
|
||||
# Default: "10s"
|
||||
#
|
||||
pushinterval = "10s"
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
- StatsD
|
||||
### StatsD
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# StatsD metrics exporter type
|
||||
[web.metrics.statsd]
|
||||
Address = "localhost:8125"
|
||||
Pushinterval = "10s"
|
||||
|
||||
# StatD's address.
|
||||
#
|
||||
# Required
|
||||
# Default: "localhost:8125"
|
||||
#
|
||||
address = "localhost:8125"
|
||||
|
||||
# StatD push interval
|
||||
#
|
||||
# Optional
|
||||
# Default: "10s"
|
||||
#
|
||||
pushinterval = "10s"
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
### InfluxDB
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# InfluxDB metrics exporter type
|
||||
[web.metrics.influxdb]
|
||||
|
||||
# InfluxDB's address.
|
||||
#
|
||||
# Required
|
||||
# Default: "localhost:8089"
|
||||
#
|
||||
address = "localhost:8089"
|
||||
|
||||
# InfluxDB push interval
|
||||
#
|
||||
# Optional
|
||||
# Default: "10s"
|
||||
#
|
||||
pushinterval = "10s"
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
## Statistics
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# Enable more detailed statistics.
|
||||
[web.statistics]
|
||||
|
||||
# Number of recent errors logged.
|
||||
#
|
||||
# Default: 10
|
||||
#
|
||||
recentErrors = 10
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
| Path | Method | Description |
|
||||
|-----------------------------------------------------------------|:-------------:|----------------------------------------------------------------------------------------------------|
|
||||
| `/` | `GET` | Provides a simple HTML frontend of Træfik |
|
||||
| `/ping` | `GET`, `HEAD` | A simple endpoint to check for Træfik process liveness. Return a code `200` with the content: `OK` |
|
||||
| `/health` | `GET` | json health metrics |
|
||||
| `/health` | `GET` | JSON health metrics |
|
||||
| `/api` | `GET` | Configuration for all providers |
|
||||
| `/api/providers` | `GET` | Providers |
|
||||
| `/api/providers/{provider}` | `GET`, `PUT` | Get or update provider |
|
||||
@@ -126,11 +239,11 @@ You can enable Traefik to export internal metrics to different monitoring system
|
||||
#### Ping
|
||||
|
||||
```shell
|
||||
$ curl -sv "http://localhost:8080/ping"
|
||||
curl -sv "http://localhost:8080/ping"
|
||||
```
|
||||
```shell
|
||||
* Trying ::1...
|
||||
* Connected to localhost (::1) port 8080 (#0)
|
||||
* Connected to localhost (::1) port 8080 (\#0)
|
||||
> GET /ping HTTP/1.1
|
||||
> Host: localhost:8080
|
||||
> User-Agent: curl/7.43.0
|
||||
@@ -141,14 +254,14 @@ $ curl -sv "http://localhost:8080/ping"
|
||||
< Content-Length: 2
|
||||
< Content-Type: text/plain; charset=utf-8
|
||||
<
|
||||
* Connection #0 to host localhost left intact
|
||||
* Connection \#0 to host localhost left intact
|
||||
OK
|
||||
```
|
||||
|
||||
#### Health
|
||||
|
||||
```shell
|
||||
$ curl -s "http://localhost:8080/health" | jq .
|
||||
curl -s "http://localhost:8080/health" | jq .
|
||||
```
|
||||
```json
|
||||
{
|
||||
@@ -195,7 +308,7 @@ $ curl -s "http://localhost:8080/health" | jq .
|
||||
"status": "Internal Server Error",
|
||||
// request HTTP method
|
||||
"method": "GET",
|
||||
// request hostname
|
||||
// request host name
|
||||
"host": "localhost",
|
||||
// request path
|
||||
"path": "/path",
|
||||
@@ -209,7 +322,7 @@ $ curl -s "http://localhost:8080/health" | jq .
|
||||
#### Provider configurations
|
||||
|
||||
```shell
|
||||
$ curl -s "http://localhost:8080/api" | jq .
|
||||
curl -s "http://localhost:8080/api" | jq .
|
||||
```
|
||||
```json
|
||||
{
|
||||
@@ -270,3 +383,100 @@ $ curl -s "http://localhost:8080/api" | jq .
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Deprecation compatibility
|
||||
|
||||
#### Address
|
||||
|
||||
As the web provider is deprecated, you can handle the `Address` option like this:
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.foo]
|
||||
address = ":8082"
|
||||
|
||||
[entryPoints.bar]
|
||||
address = ":8083"
|
||||
|
||||
[ping]
|
||||
entryPoint = "foo"
|
||||
|
||||
[api]
|
||||
entryPoint = "bar"
|
||||
```
|
||||
|
||||
In the above example, you would access a regular path, administration panel, and health-check as follows:
|
||||
|
||||
* Regular path: `http://hostname:80/path`
|
||||
* Admin Panel: `http://hostname:8083/`
|
||||
* Ping URL: `http://hostname:8082/ping`
|
||||
|
||||
In the above example, it is _very_ important to create a named dedicated entry point, and do **not** include it in `defaultEntryPoints`.
|
||||
Otherwise, you are likely to expose _all_ services via that entry point.
|
||||
|
||||
#### Path
|
||||
|
||||
As the web provider is deprecated, you can handle the `Path` option like this:
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.foo]
|
||||
address = ":8080"
|
||||
|
||||
[entryPoints.bar]
|
||||
address = ":8081"
|
||||
|
||||
# Activate API and Dashboard
|
||||
[api]
|
||||
entryPoint = "bar"
|
||||
dashboard = true
|
||||
|
||||
[file]
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://127.0.0.1:8081"
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
entryPoints = ["foo"]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "PathPrefixStrip:/yourprefix;PathPrefix:/yourprefix"
|
||||
```
|
||||
|
||||
#### Authentication
|
||||
|
||||
As the web provider is deprecated, you can handle the `auth` option like this:
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.foo]
|
||||
address=":8080"
|
||||
[entryPoints.foo.auth]
|
||||
[entryPoints.foo.auth.basic]
|
||||
users = [
|
||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
]
|
||||
|
||||
[api]
|
||||
entrypoint="foo"
|
||||
```
|
||||
|
||||
For more information, see [entry points](/configuration/entrypoints/) .
|
||||
|
||||
@@ -1,38 +1,61 @@
|
||||
# Zookeeper Backend
|
||||
# Zookeeper Provider
|
||||
|
||||
Træfik can be configured to use Zookeeper as a backend configuration:
|
||||
Træfik can be configured to use Zookeeper as a provider.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Zookeeper configuration backend
|
||||
# Zookeeper Provider
|
||||
################################################################
|
||||
|
||||
# Enable Zookeeperconfiguration backend
|
||||
# Enable Zookeeper Provider.
|
||||
[zookeeper]
|
||||
|
||||
# Zookeeper server endpoint
|
||||
# Zookeeper server endpoint.
|
||||
#
|
||||
# Required
|
||||
# Default: "127.0.0.1:2181"
|
||||
#
|
||||
endpoint = "127.0.0.1:2181"
|
||||
|
||||
# Enable watch Zookeeper changes
|
||||
# Enable watch Zookeeper changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Prefix used for KV store.
|
||||
#
|
||||
# Optional
|
||||
# Default: "traefik"
|
||||
#
|
||||
prefix = "traefik"
|
||||
|
||||
# Override default configuration template. For advanced users :)
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "zookeeper.tmpl"
|
||||
|
||||
# Use Zookeeper user/pass authentication.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# username = foo
|
||||
# password = bar
|
||||
|
||||
# Enable Zookeeper TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [zookeeper.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/zookeeper.crt"
|
||||
# key = "/etc/ssl/zookeeper.key"
|
||||
# insecureSkipVerify = true
|
||||
```
|
||||
|
||||
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure.
|
||||
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
|
||||
|
||||
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.
|
||||
|
||||
@@ -3,66 +3,64 @@
|
||||
## Main Section
|
||||
|
||||
```toml
|
||||
# Duration to give active requests a chance to finish before Traefik stops.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
# If no units are provided, the value is parsed assuming seconds.
|
||||
# Note: in this time frame no new requests are accepted.
|
||||
# DEPRECATED - for general usage instruction see [lifeCycle.graceTimeOut].
|
||||
#
|
||||
# If both the deprecated option and the new one are given, the deprecated one
|
||||
# takes precedence.
|
||||
# A value of zero is equivalent to omitting the parameter, causing
|
||||
# [lifeCycle.graceTimeOut] to be effective. Pass zero to the new option in
|
||||
# order to disable the grace period.
|
||||
#
|
||||
# Optional
|
||||
# Default: "10s"
|
||||
# Default: "0s"
|
||||
#
|
||||
# graceTimeOut = "10s"
|
||||
|
||||
# Enable debug mode
|
||||
# Enable debug mode.
|
||||
# This will install HTTP handlers to expose Go expvars under /debug/vars and
|
||||
# pprof profiling data under /debug/pprof.
|
||||
# The log level will be set to DEBUG unless `logLevel` is specified.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# debug = true
|
||||
|
||||
# Periodically check if a new version has been released
|
||||
# Periodically check if a new version has been released.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
# checkNewVersion = false
|
||||
|
||||
# Backends throttle duration: minimum duration in seconds 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.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||
# values (digits). If no units are provided, the value is parsed assuming
|
||||
# seconds.
|
||||
# Providers throttle duration.
|
||||
#
|
||||
# Optional
|
||||
# Default: "2s"
|
||||
#
|
||||
# ProvidersThrottleDuration = "2s"
|
||||
# providersThrottleDuration = "2s"
|
||||
|
||||
# Controls the maximum idle (keep-alive) connections to keep per-host. If zero, DefaultMaxIdleConnsPerHost
|
||||
# from the Go standard library net/http module is used.
|
||||
# If you encounter 'too many open files' errors, you can either increase this
|
||||
# value or change the `ulimit`.
|
||||
# Controls the maximum idle (keep-alive) connections to keep per-host.
|
||||
#
|
||||
# Optional
|
||||
# Default: 200
|
||||
#
|
||||
# MaxIdleConnsPerHost = 200
|
||||
# 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.
|
||||
# This disables detection of man-in-the-middle attacks so should only be used on secure backend networks.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# InsecureSkipVerify = true
|
||||
# insecureSkipVerify = true
|
||||
|
||||
# Register Certificates in the RootCA. This certificates will be use for backends calls.
|
||||
# Note: You can use file path or cert content directly
|
||||
# Register Certificates in the rootCA.
|
||||
#
|
||||
# Optional
|
||||
# Default: []
|
||||
#
|
||||
# RootCAs = [ "/mycert.cert" ]
|
||||
# rootCAs = [ "/mycert.cert" ]
|
||||
|
||||
# Entrypoints to be used by frontends that do not specify any entrypoint.
|
||||
# Each frontend can specify its own entrypoints.
|
||||
@@ -71,26 +69,46 @@
|
||||
# Default: ["http"]
|
||||
#
|
||||
# defaultEntryPoints = ["http", "https"]
|
||||
|
||||
# Allow the use of 0 as server weight.
|
||||
# - false: a weight 0 means internally a weight of 1.
|
||||
# - true: a weight 0 means internally a weight of 0 (a server with a weight of 0 is removed from the available servers).
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# AllowMinWeightZero = true
|
||||
```
|
||||
|
||||
- `graceTimeOut`: Duration to give active requests a chance to finish before Traefik stops.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
**Note:** in this time frame no new requests are accepted.
|
||||
|
||||
- `providersThrottleDuration`: Providers throttle duration: minimum duration in seconds 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.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
- `maxIdleConnsPerHost`: Controls the maximum idle (keep-alive) connections to keep per-host.
|
||||
If zero, `DefaultMaxIdleConnsPerHost` from the Go standard library net/http module is used.
|
||||
If you encounter 'too many open files' errors, you can either increase this value or change the `ulimit`.
|
||||
|
||||
- `insecureSkipVerify` : 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.
|
||||
|
||||
- `rootCAs`: Register Certificates in the RootCA. This certificates will be use for backends calls.
|
||||
**Note** You can use file path or cert content directly
|
||||
|
||||
- `defaultEntryPoints`: Entrypoints to be used by frontends that do not specify any entrypoint.
|
||||
Each frontend can specify its own entrypoints.
|
||||
|
||||
|
||||
## Constraints
|
||||
|
||||
In a micro-service architecture, with a central service discovery, setting constraints limits Træfik scope to a smaller number of routes.
|
||||
|
||||
Træfik filters services according to service attributes/tags set in your configuration backends.
|
||||
|
||||
Supported backends:
|
||||
|
||||
- Docker
|
||||
- Consul K/V
|
||||
- BoltDB
|
||||
- Zookeeper
|
||||
- Etcd
|
||||
- Consul Catalog
|
||||
- Rancher
|
||||
- Marathon
|
||||
- Kubernetes (using a provider-specific mechanism based on label selectors)
|
||||
Træfik filters services according to service attributes/tags set in your providers.
|
||||
|
||||
Supported filters:
|
||||
|
||||
@@ -118,86 +136,41 @@ constraints = ["tag==us-*"]
|
||||
constraints = ["tag!=us-*", "tag!=asia-*"]
|
||||
```
|
||||
|
||||
### Backend-specific
|
||||
### provider-specific
|
||||
|
||||
Supported Providers:
|
||||
|
||||
- Docker
|
||||
- Consul K/V
|
||||
- BoltDB
|
||||
- Zookeeper
|
||||
- Etcd
|
||||
- Consul Catalog
|
||||
- Rancher
|
||||
- Marathon
|
||||
- Kubernetes (using a provider-specific mechanism based on label selectors)
|
||||
|
||||
```toml
|
||||
# Backend-specific constraint
|
||||
# Provider-specific constraint
|
||||
[consulCatalog]
|
||||
endpoint = "127.0.0.1:8500"
|
||||
# ...
|
||||
constraints = ["tag==api"]
|
||||
|
||||
# Provider-specific constraint
|
||||
[marathon]
|
||||
endpoint = "127.0.0.1:8800"
|
||||
# ...
|
||||
constraints = ["tag==api", "tag!=v*-beta"]
|
||||
```
|
||||
|
||||
|
||||
## Logs Definition
|
||||
|
||||
### Traefik logs
|
||||
|
||||
```toml
|
||||
# Traefik logs file
|
||||
# If not defined, logs to stdout
|
||||
traefikLogsFile = "log/traefik.log"
|
||||
|
||||
# Log level
|
||||
#
|
||||
# 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"
|
||||
```
|
||||
|
||||
### Access Logs
|
||||
|
||||
Access logs are written when `[accessLog]` is defined.
|
||||
By default it will write to stdout and produce logs in the textual Common Log Format (CLF), extended with additional fields.
|
||||
|
||||
To enable access logs using the default settings just add the `[accessLog]` entry.
|
||||
```toml
|
||||
[accessLog]
|
||||
```
|
||||
|
||||
To write the logs into a logfile specify the `filePath`.
|
||||
```toml
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
```
|
||||
|
||||
To write JSON format logs, specify `json` as the format:
|
||||
```toml
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
format = "json"
|
||||
```
|
||||
|
||||
Deprecated way (before 1.4):
|
||||
```toml
|
||||
# Access logs file
|
||||
#
|
||||
# DEPRECATED - see [accessLog] lower down
|
||||
#
|
||||
accessLogsFile = "log/access.log"
|
||||
```
|
||||
|
||||
### Log Rotation
|
||||
|
||||
Traefik will close and reopen its log files, assuming they're configured, on receipt of a USR1 signal.
|
||||
This allows the logs to be rotated and processed by an external program, such as `logrotate`.
|
||||
|
||||
!!! note
|
||||
that this does not work on Windows due to the lack of USR signals.
|
||||
|
||||
|
||||
## Custom Error pages
|
||||
|
||||
Custom error pages can be returned, in lieu of the default, according to frontend-configured ranges of HTTP Status codes.
|
||||
|
||||
In the example below, if a 503 status is returned from the frontend "website", the custom error page at http://2.3.4.5/503.html is returned with the actual status code set in the HTTP header.
|
||||
Note, the `503.html` page itself is not hosted on traefik, but some other infrastructure.
|
||||
|
||||
!!! note
|
||||
The `503.html` page itself is not hosted on Traefik, but some other infrastructure.
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
@@ -227,6 +200,52 @@ Now the `500s.html` error page is returned for the configured code range.
|
||||
The configured status code ranges are inclusive; that is, in the above example, the `500s.html` page will be returned for status codes `500` through, and including, `599`.
|
||||
|
||||
|
||||
## Rate limiting
|
||||
|
||||
Rate limiting can be configured per frontend.
|
||||
Multiple sets of rates can be added to each frontend, but the time periods must be unique.
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
# ...
|
||||
[frontends.frontend1.ratelimit]
|
||||
extractorfunc = "client.ip"
|
||||
[frontends.frontend1.ratelimit.rateset.rateset1]
|
||||
period = "10s"
|
||||
average = 100
|
||||
burst = 200
|
||||
[frontends.frontend1.ratelimit.rateset.rateset2]
|
||||
period = "3s"
|
||||
average = 5
|
||||
burst = 10
|
||||
```
|
||||
|
||||
In the above example, frontend1 is configured to limit requests by the client's ip address.
|
||||
An average of 5 requests every 3 seconds is allowed and an average of 100 requests every 10 seconds.
|
||||
These can "burst" up to 10 and 200 in each period respectively.
|
||||
|
||||
## Buffering
|
||||
|
||||
In some cases request/buffering can be enabled for a specific backend.
|
||||
By enabling this, Træfik will read the entire request into memory (possibly buffering large requests into disk) and will reject requests that are over a specified limit.
|
||||
This may help services deal with large data (multipart/form-data for example) more efficiently and should minimise time spent when sending data to a backend server.
|
||||
|
||||
For more information please check [oxy/buffer](http://godoc.org/github.com/vulcand/oxy/buffer) documentation.
|
||||
|
||||
Example configuration:
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.buffering]
|
||||
maxRequestBodyBytes = 10485760
|
||||
memRequestBodyBytes = 2097152
|
||||
maxResponseBodyBytes = 10485760
|
||||
memResponseBodyBytes = 2097152
|
||||
retryExpression = "IsNetworkError() && Attempts() <= 2"
|
||||
```
|
||||
|
||||
## Retry Configuration
|
||||
|
||||
```toml
|
||||
@@ -248,12 +267,7 @@ The configured status code ranges are inclusive; that is, in the above example,
|
||||
# Enable custom health check options.
|
||||
[healthcheck]
|
||||
|
||||
# Set the default health check interval. Will only be effective if health check
|
||||
# paths are defined. Given provider-specific support, the value may be
|
||||
# overridden on a per-backend basis.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||
# values (digits). If no units are provided, the value is parsed assuming
|
||||
# seconds.
|
||||
# Set the default health check interval.
|
||||
#
|
||||
# Optional
|
||||
# Default: "30s"
|
||||
@@ -261,6 +275,43 @@ The configured status code ranges are inclusive; that is, in the above example,
|
||||
# interval = "30s"
|
||||
```
|
||||
|
||||
- `interval` set the default health check interval.
|
||||
Will only be effective if health check paths are defined.
|
||||
Given provider-specific support, the value may be overridden on a per-backend basis.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
## Life Cycle
|
||||
|
||||
Controls the behavior of Traefik during the shutdown phase.
|
||||
|
||||
```toml
|
||||
[lifeCycle]
|
||||
|
||||
# Duration to keep accepting requests prior to initiating the graceful
|
||||
# termination period (as defined by the `graceTimeOut` option). This
|
||||
# option is meant to give downstream load-balancers sufficient time to
|
||||
# take Traefik out of rotation.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
# If no units are provided, the value is parsed assuming seconds.
|
||||
# The zero duration disables the request accepting grace period, i.e.,
|
||||
# Traefik will immediately proceed to the grace period.
|
||||
#
|
||||
# Optional
|
||||
# Default: 0
|
||||
#
|
||||
# requestAcceptGraceTimeout = "10s"
|
||||
|
||||
# Duration to give active requests a chance to finish before Traefik stops.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
# If no units are provided, the value is parsed assuming seconds.
|
||||
# Note: in this time frame no new requests are accepted.
|
||||
#
|
||||
# Optional
|
||||
# Default: "10s"
|
||||
#
|
||||
# graceTimeOut = "10s"
|
||||
```
|
||||
|
||||
## Timeouts
|
||||
|
||||
@@ -272,20 +323,13 @@ The configured status code ranges are inclusive; that is, in the above example,
|
||||
[respondingTimeouts]
|
||||
|
||||
# readTimeout is the maximum duration for reading the entire request, including the body.
|
||||
# If zero, no timeout exists.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||
# values (digits). If no units are provided, the value is parsed assuming seconds.
|
||||
#
|
||||
# Optional
|
||||
# Default: "0s"
|
||||
#
|
||||
# readTimeout = "5s"
|
||||
|
||||
# writeTimeout is the maximum duration before timing out writes of the response. It covers the time from the end of
|
||||
# the request header read to the end of the response write.
|
||||
# If zero, no timeout exists.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||
# values (digits). If no units are provided, the value is parsed assuming seconds.
|
||||
# writeTimeout is the maximum duration before timing out writes of the response.
|
||||
#
|
||||
# Optional
|
||||
# Default: "0s"
|
||||
@@ -293,9 +337,6 @@ The configured status code ranges are inclusive; that is, in the above example,
|
||||
# writeTimeout = "5s"
|
||||
|
||||
# idleTimeout is the maximum duration an idle (keep-alive) connection will remain idle before closing itself.
|
||||
# If zero, no timeout exists.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||
# values (digits). If no units are provided, the value is parsed assuming seconds.
|
||||
#
|
||||
# Optional
|
||||
# Default: "180s"
|
||||
@@ -303,6 +344,22 @@ The configured status code ranges are inclusive; that is, in the above example,
|
||||
# idleTimeout = "360s"
|
||||
```
|
||||
|
||||
- `readTimeout` is the maximum duration for reading the entire request, including the body.
|
||||
If zero, no timeout exists.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
- `writeTimeout` is the maximum duration before timing out writes of the response.
|
||||
It covers the time from the end of the request header read to the end of the response write.
|
||||
If zero, no timeout exists.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
- `idleTimeout` is the maximum duration an idle (keep-alive) connection will remain idle before closing itself.
|
||||
If zero, no timeout exists.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
### Forwarding Timeouts
|
||||
|
||||
`forwardingTimeouts` are timeouts for requests forwarded to the backend servers.
|
||||
@@ -311,9 +368,6 @@ The configured status code ranges are inclusive; that is, in the above example,
|
||||
[forwardingTimeouts]
|
||||
|
||||
# dialTimeout is the amount of time to wait until a connection to a backend server can be established.
|
||||
# If zero, no timeout exists.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||
# values (digits). If no units are provided, the value is parsed assuming seconds.
|
||||
#
|
||||
# Optional
|
||||
# Default: "30s"
|
||||
@@ -321,9 +375,6 @@ The configured status code ranges are inclusive; that is, in the above example,
|
||||
# dialTimeout = "30s"
|
||||
|
||||
# responseHeaderTimeout is the amount of time to wait for a server's response headers after fully writing the request (including its body, if any).
|
||||
# If zero, no timeout exists.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||
# values (digits). If no units are provided, the value is parsed assuming seconds.
|
||||
#
|
||||
# Optional
|
||||
# Default: "0s"
|
||||
@@ -331,26 +382,37 @@ The configured status code ranges are inclusive; that is, in the above example,
|
||||
# responseHeaderTimeout = "0s"
|
||||
```
|
||||
|
||||
- `dialTimeout` is the amount of time to wait until a connection to a backend server can be established.
|
||||
If zero, no timeout exists.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
- `responseHeaderTimeout` is the amount of time to wait for a server's response headers after fully writing the request (including its body, if any).
|
||||
If zero, no timeout exists.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
|
||||
### Idle Timeout (deprecated)
|
||||
|
||||
Use [respondingTimeouts](/configuration/commons/#responding-timeouts) instead of `IdleTimeout`.
|
||||
Use [respondingTimeouts](/configuration/commons/#responding-timeouts) instead of `idleTimeout`.
|
||||
In the case both settings are configured, the deprecated option will be overwritten.
|
||||
|
||||
`IdleTimeout` is the maximum amount of time an idle (keep-alive) connection will remain idle before closing itself.
|
||||
`idleTimeout` is the maximum amount of time an idle (keep-alive) connection will remain idle before closing itself.
|
||||
This is set to enforce closing of stale client connections.
|
||||
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
```toml
|
||||
# IdleTimeout
|
||||
# idleTimeout
|
||||
#
|
||||
# DEPRECATED - see [respondingTimeouts] section.
|
||||
#
|
||||
# Optional
|
||||
# Default: "180s"
|
||||
#
|
||||
IdleTimeout = "360s"
|
||||
idleTimeout = "360s"
|
||||
```
|
||||
|
||||
|
||||
@@ -359,12 +421,12 @@ IdleTimeout = "360s"
|
||||
!!! warning
|
||||
For advanced users only.
|
||||
|
||||
Supported by all backends except: File backend, Web backend and DynamoDB backend.
|
||||
Supported by all providers except: File Provider, Web Provider and DynamoDB Provider.
|
||||
|
||||
```toml
|
||||
[backend_name]
|
||||
[provider_name]
|
||||
|
||||
# Override default configuration template. For advanced users :)
|
||||
# Override default provider configuration template. For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
# Default: ""
|
||||
|
||||
@@ -1,5 +1,139 @@
|
||||
# Entry Points Definition
|
||||
|
||||
## Reference
|
||||
|
||||
### TOML
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
compress = true
|
||||
|
||||
[entryPoints.http.whitelist]
|
||||
sourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"]
|
||||
useXForwardedFor = true
|
||||
|
||||
[entryPoints.http.tls]
|
||||
minVersion = "VersionTLS12"
|
||||
cipherSuites = [
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_RSA_WITH_AES_256_GCM_SHA384"
|
||||
]
|
||||
[[entryPoints.http.tls.certificates]]
|
||||
certFile = "path/to/my.cert"
|
||||
keyFile = "path/to/my.key"
|
||||
[[entryPoints.http.tls.certificates]]
|
||||
certFile = "path/to/other.cert"
|
||||
keyFile = "path/to/other.key"
|
||||
# ...
|
||||
[entryPoints.http.tls.clientCA]
|
||||
files = ["path/to/ca1.crt", "path/to/ca2.crt"]
|
||||
optional = false
|
||||
|
||||
[entryPoints.http.redirect]
|
||||
entryPoint = "https"
|
||||
regex = "^http://localhost/(.*)"
|
||||
replacement = "http://mydomain/$1"
|
||||
permanent = true
|
||||
|
||||
[entryPoints.http.auth]
|
||||
headerField = "X-WebAuth-User"
|
||||
[entryPoints.http.auth.basic]
|
||||
users = [
|
||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
]
|
||||
usersFile = "/path/to/.htpasswd"
|
||||
[entryPoints.http.auth.digest]
|
||||
users = [
|
||||
"test:traefik:a2688e031edb4be6a3797f3882655c05",
|
||||
"test2:traefik:518845800f9e2bfb1f1f740ec24f074e",
|
||||
]
|
||||
usersFile = "/path/to/.htdigest"
|
||||
[entryPoints.http.auth.forward]
|
||||
address = "https://authserver.com/auth"
|
||||
trustForwardHeader = true
|
||||
[entryPoints.http.auth.forward.tls]
|
||||
ca = [ "path/to/local.crt"]
|
||||
caOptional = true
|
||||
cert = "path/to/foo.cert"
|
||||
key = "path/to/foo.key"
|
||||
insecureSkipVerify = true
|
||||
|
||||
[entryPoints.http.proxyProtocol]
|
||||
insecure = true
|
||||
trustedIPs = ["10.10.10.1", "10.10.10.2"]
|
||||
|
||||
[entryPoints.http.forwardedHeaders]
|
||||
trustedIPs = ["10.10.10.1", "10.10.10.2"]
|
||||
|
||||
[entryPoints.https]
|
||||
# ...
|
||||
```
|
||||
|
||||
### CLI
|
||||
|
||||
For more information about the CLI, see the documentation about [Traefik command](/basics/#traefik).
|
||||
|
||||
```shell
|
||||
--entryPoints='Name:http Address::80'
|
||||
--entryPoints='Name:https Address::443 TLS'
|
||||
```
|
||||
|
||||
!!! note
|
||||
Whitespace is used as option separator and `,` is used as value separator for the list.
|
||||
The names of the options are case-insensitive.
|
||||
|
||||
In compose file the entrypoint syntax is different:
|
||||
|
||||
```yaml
|
||||
traefik:
|
||||
image: traefik
|
||||
command:
|
||||
- --defaultentrypoints=powpow
|
||||
- "--entryPoints=Name:powpow Address::42 Compress:true"
|
||||
```
|
||||
or
|
||||
```yaml
|
||||
traefik:
|
||||
image: traefik
|
||||
command: --defaultentrypoints=powpow --entryPoints='Name:powpow Address::42 Compress:true'
|
||||
```
|
||||
|
||||
#### All available options:
|
||||
|
||||
```ini
|
||||
Name:foo
|
||||
Address::80
|
||||
TLS:/my/path/foo.cert,/my/path/foo.key;/my/path/goo.cert,/my/path/goo.key;/my/path/hoo.cert,/my/path/hoo.key
|
||||
TLS
|
||||
CA:car
|
||||
CA.Optional:true
|
||||
Redirect.EntryPoint:https
|
||||
Redirect.Regex:http://localhost/(.*)
|
||||
Redirect.Replacement:http://mydomain/$1
|
||||
Redirect.Permanent:true
|
||||
Compress:true
|
||||
WhiteList.SourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16
|
||||
WhiteList.UseXForwardedFor:true
|
||||
ProxyProtocol.TrustedIPs:192.168.0.1
|
||||
ProxyProtocol.Insecure:true
|
||||
ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24
|
||||
Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0
|
||||
Auth.Digest.Users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e
|
||||
Auth.HeaderField:X-WebAuth-User
|
||||
Auth.Forward.Address:https://authserver.com/auth
|
||||
Auth.Forward.TrustForwardHeader:true
|
||||
Auth.Forward.TLS.CA:path/to/local.crt
|
||||
Auth.Forward.TLS.CAOptional:true
|
||||
Auth.Forward.TLS.Cert:path/to/foo.cert
|
||||
Auth.Forward.TLS.Key:path/to/foo.key
|
||||
Auth.Forward.TLS.InsecureSkipVerify:true
|
||||
```
|
||||
|
||||
## Basic
|
||||
|
||||
```toml
|
||||
# Entrypoints definition
|
||||
#
|
||||
@@ -27,13 +161,16 @@ To redirect an http entrypoint to an https entrypoint (with SNI support).
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||
KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||
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"
|
||||
certFile = "integration/fixtures/https/snitest.org.cert"
|
||||
keyFile = "integration/fixtures/https/snitest.org.key"
|
||||
```
|
||||
|
||||
!!! note
|
||||
Please note that `regex` and `replacement` do not have to be set in the `redirect` structure if an entrypoint is defined for the redirection (they will not be used in this case).
|
||||
|
||||
## Rewriting URL
|
||||
|
||||
To redirect an entrypoint rewriting the URL.
|
||||
@@ -47,13 +184,47 @@ To redirect an entrypoint rewriting the URL.
|
||||
replacement = "http://mydomain/$1"
|
||||
```
|
||||
|
||||
!!! note
|
||||
Please note that `regex` and `replacement` do not have to be set in the `redirect` structure if an `entrypoint` is defined for the redirection (they will not be used in this case).
|
||||
|
||||
Care should be taken when defining replacement expand variables: `$1x` is equivalent to `${1x}`, not `${1}x` (see [Regexp.Expand](https://golang.org/pkg/regexp/#Regexp.Expand)), so use `${1}` syntax.
|
||||
|
||||
Regular expressions and replacements can be tested using online tools such as [Go Playground](https://play.golang.org/p/mWU9p-wk2ru) or the [Regex101](https://regex101.com/r/58sIgx/2).
|
||||
|
||||
## TLS
|
||||
|
||||
### Static Certificates
|
||||
|
||||
Define an entrypoint with SNI support.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[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"
|
||||
```
|
||||
|
||||
!!! note
|
||||
If an empty TLS configuration is done, default self-signed certificates are generated.
|
||||
|
||||
|
||||
### Dynamic Certificates
|
||||
|
||||
If you need to add or remove TLS certificates while Traefik is started, Dynamic TLS certificates are supported using the [file provider](/configuration/backends/file).
|
||||
|
||||
|
||||
## TLS Mutual Authentication
|
||||
|
||||
Only accept clients that present a certificate signed by a specified Certificate Authority (CA).
|
||||
TLS Mutual Authentication can be `optional` or not.
|
||||
If it's `optional`, Træfik will authorize connection with certificates not signed by a specified Certificate Authority (CA).
|
||||
Otherwise, Træfik will 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.
|
||||
By default, `ClientCAFiles` is not optional, 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
|
||||
@@ -63,23 +234,28 @@ In the example below both `snitest.com` and `snitest.org` will require client ce
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
ClientCAFiles = ["tests/clientca1.crt", "tests/clientca2.crt"]
|
||||
[entryPoints.https.tls.ClientCA]
|
||||
files = ["tests/clientca1.crt", "tests/clientca2.crt"]
|
||||
optional = false
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||
KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||
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"
|
||||
certFile = "integration/fixtures/https/snitest.org.cert"
|
||||
keyFile = "integration/fixtures/https/snitest.org.key"
|
||||
```
|
||||
|
||||
!!! note
|
||||
The deprecated argument `ClientCAFiles` allows adding Client CA files which are mandatory.
|
||||
If this parameter exists, the new ones are not checked.
|
||||
|
||||
## Authentication
|
||||
|
||||
### Basic Authentication
|
||||
|
||||
Passwords can be encoded in MD5, SHA1 and BCrypt: you can use `htpasswd` to generate those ones.
|
||||
Passwords can be encoded in MD5, SHA1 and BCrypt: you can use `htpasswd` to generate them.
|
||||
|
||||
Users can be specified directly in the toml file, or indirectly by referencing an external file;
|
||||
Users can be specified directly in the TOML file, or indirectly by referencing an external file;
|
||||
if both are provided, the two are merged, with external file contents having precedence.
|
||||
|
||||
```toml
|
||||
@@ -94,9 +270,9 @@ Users can be specified directly in the toml file, or indirectly by referencing a
|
||||
|
||||
### Digest Authentication
|
||||
|
||||
You can use `htdigest` to generate those ones.
|
||||
You can use `htdigest` to generate them.
|
||||
|
||||
Users can be specified directly in the toml file, or indirectly by referencing an external file;
|
||||
Users can be specified directly in the TOML file, or indirectly by referencing an external file;
|
||||
if both are provided, the two are merged, with external file contents having precedence
|
||||
|
||||
```toml
|
||||
@@ -104,28 +280,63 @@ Users can be specified directly in the toml file, or indirectly by referencing a
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.auth.basic]
|
||||
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||
[entryPoints.http.auth.digest]
|
||||
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||
usersFile = "/path/to/.htdigest"
|
||||
```
|
||||
|
||||
### Forward Authentication
|
||||
|
||||
This configuration will first forward the request to `http://authserver.com/auth`.
|
||||
|
||||
If the response code is 2XX, access is granted and the original request is performed.
|
||||
Otherwise, the response from the authentication server is returned.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
# ...
|
||||
# To enable forward auth on an entrypoint
|
||||
[entryPoints.http.auth.forward]
|
||||
address = "https://authserver.com/auth"
|
||||
|
||||
# Trust existing X-Forwarded-* headers.
|
||||
# Useful with another reverse proxy in front of Traefik.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
trustForwardHeader = true
|
||||
|
||||
# Enable forward auth TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
[entryPoints.http.auth.forward.tls]
|
||||
cert = "authserver.crt"
|
||||
key = "authserver.key"
|
||||
```
|
||||
|
||||
## Specify Minimum TLS Version
|
||||
|
||||
To specify an https entrypoint with a minimum TLS version, and specifying an array of cipher suites (from crypto/tls).
|
||||
To specify an https entry point with a minimum TLS version, and specifying an array of cipher suites (from [crypto/tls](https://godoc.org/crypto/tls#pkg-constants)).
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
MinVersion = "VersionTLS12"
|
||||
CipherSuites = ["TLS_RSA_WITH_AES_256_GCM_SHA384"]
|
||||
minVersion = "VersionTLS12"
|
||||
cipherSuites = [
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"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"
|
||||
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"
|
||||
certFile = "integration/fixtures/https/snitest.org.cert"
|
||||
keyFile = "integration/fixtures/https/snitest.org.key"
|
||||
```
|
||||
|
||||
## Compression
|
||||
@@ -139,24 +350,72 @@ To enable compression support using gzip format.
|
||||
compress = true
|
||||
```
|
||||
|
||||
## Whitelisting
|
||||
Responses are compressed when:
|
||||
|
||||
To enable IP whitelisting at the entrypoint level.
|
||||
* The response body is larger than `512` bytes
|
||||
* And the `Accept-Encoding` request header contains `gzip`
|
||||
* And the response is not already compressed, i.e. the `Content-Encoding` response header is not already set.
|
||||
|
||||
## White Listing
|
||||
|
||||
To enable IP white listing at the entry point level.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
whiteListSourceRange = ["127.0.0.1/32"]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.http.whiteList]
|
||||
sourceRange = ["127.0.0.1/32", "192.168.1.7"]
|
||||
# useXForwardedFor = true
|
||||
```
|
||||
|
||||
## ProxyProtocol Support
|
||||
## ProxyProtocol
|
||||
|
||||
To enable [ProxyProtocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) support.
|
||||
Only IPs in `trustedIPs` will lead to remote client address replacement: you should declare your load-balancer IP or CIDR range here (in testing environment, you can trust everyone using `insecure = true`).
|
||||
|
||||
!!! danger
|
||||
When queuing Træfik behind another load-balancer, be sure to carefully configure Proxy Protocol on both sides.
|
||||
Otherwise, it could introduce a security risk in your system by forging requests.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
proxyprotocol = true
|
||||
address = ":80"
|
||||
|
||||
# Enable ProxyProtocol
|
||||
[entryPoints.http.proxyProtocol]
|
||||
# List of trusted IPs
|
||||
#
|
||||
# Required
|
||||
# Default: []
|
||||
#
|
||||
trustedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||
|
||||
# Insecure mode FOR TESTING ENVIRONNEMENT ONLY
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# insecure = true
|
||||
```
|
||||
|
||||
## Forwarded Header
|
||||
|
||||
Only IPs in `trustedIPs` will be authorized to trust the client forwarded headers (`X-Forwarded-*`).
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
# Enable Forwarded Headers
|
||||
[entryPoints.http.forwardedHeaders]
|
||||
# List of trusted IPs
|
||||
#
|
||||
# Required
|
||||
# Default: []
|
||||
#
|
||||
trustedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||
```
|
||||
|
||||
252
docs/configuration/logs.md
Normal file
@@ -0,0 +1,252 @@
|
||||
# Logs Definition
|
||||
|
||||
## Reference
|
||||
|
||||
### TOML
|
||||
|
||||
```toml
|
||||
logLevel = "INFO"
|
||||
|
||||
[traefikLog]
|
||||
filePath = "/path/to/traefik.log"
|
||||
format = "json"
|
||||
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
format = "json"
|
||||
|
||||
[accessLog.filters]
|
||||
statusCodes = ["200", "300-302"]
|
||||
retryAttempts = true
|
||||
|
||||
[accessLog.fields]
|
||||
defaultMode = "keep"
|
||||
[accessLog.fields.names]
|
||||
"ClientUsername" = "drop"
|
||||
# ...
|
||||
|
||||
[accessLog.fields.headers]
|
||||
defaultMode = "keep"
|
||||
[accessLog.fields.headers.names]
|
||||
"User-Agent" = "redact"
|
||||
"Authorization" = "drop"
|
||||
"Content-Type" = "keep"
|
||||
# ...
|
||||
```
|
||||
|
||||
### CLI
|
||||
|
||||
For more information about the CLI, see the documentation about [Traefik command](/basics/#traefik).
|
||||
|
||||
```shell
|
||||
--logLevel="DEBUG"
|
||||
--traefikLog.filePath="/path/to/traefik.log"
|
||||
--traefikLog.format="json"
|
||||
--accessLog.filePath="/path/to/access.log"
|
||||
--accessLog.format="json"
|
||||
--accessLog.filters.statusCodes="200,300-302"
|
||||
--accessLog.filters.retryAttempts="true"
|
||||
--accessLog.fields.defaultMode="keep"
|
||||
--accessLog.fields.names="Username=drop Hostname=drop"
|
||||
--accessLog.fields.headers.defaultMode="keep"
|
||||
--accessLog.fields.headers.names="User-Agent=redact Authorization=drop Content-Type=keep"
|
||||
```
|
||||
|
||||
|
||||
## Traefik Logs
|
||||
|
||||
By default the Traefik log is written to stdout in text format.
|
||||
|
||||
To write the logs into a log file specify the `filePath`:
|
||||
```toml
|
||||
[traefikLog]
|
||||
filePath = "/path/to/traefik.log"
|
||||
```
|
||||
|
||||
To write JSON format logs, specify `json` as the format:
|
||||
```toml
|
||||
[traefikLog]
|
||||
filePath = "/path/to/traefik.log"
|
||||
format = "json"
|
||||
```
|
||||
|
||||
|
||||
Deprecated way (before 1.4):
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
`traefikLogsFile` is deprecated, use [traefikLog](/configuration/logs/#traefik-logs) instead.
|
||||
|
||||
```toml
|
||||
# Traefik logs file
|
||||
# If not defined, logs to stdout
|
||||
#
|
||||
# DEPRECATED - see [traefikLog] lower down
|
||||
# In case both traefikLogsFile and traefikLog.filePath are specified, the latter will take precedence.
|
||||
# Optional
|
||||
#
|
||||
traefikLogsFile = "log/traefik.log"
|
||||
```
|
||||
|
||||
To customize the log level:
|
||||
```toml
|
||||
# Log level
|
||||
#
|
||||
# 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"
|
||||
```
|
||||
|
||||
|
||||
## Access Logs
|
||||
|
||||
Access logs are written when `[accessLog]` is defined.
|
||||
By default it will write to stdout and produce logs in the textual Common Log Format (CLF), extended with additional fields.
|
||||
|
||||
To enable access logs using the default settings just add the `[accessLog]` entry:
|
||||
```toml
|
||||
[accessLog]
|
||||
```
|
||||
|
||||
To write the logs into a log file specify the `filePath`:
|
||||
```toml
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
```
|
||||
|
||||
To write JSON format logs, specify `json` as the format:
|
||||
```toml
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
format = "json"
|
||||
```
|
||||
|
||||
To filter logs you can specify a set of filters which are logically "OR-connected". Thus, specifying multiple filters will keep more access logs than specifying only one:
|
||||
```toml
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
format = "json"
|
||||
|
||||
[accessLog.filters]
|
||||
|
||||
# statusCodes keep access logs with status codes in the specified range
|
||||
#
|
||||
# Optional
|
||||
# Default: []
|
||||
#
|
||||
statusCodes = ["200", "300-302"]
|
||||
|
||||
# retryAttempts keep access logs when at least one retry happened
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
retryAttempts = true
|
||||
```
|
||||
|
||||
To customize logs format:
|
||||
```toml
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
format = "json"
|
||||
|
||||
[accessLog.filters]
|
||||
|
||||
# statusCodes keep only access logs with status codes in the specified range
|
||||
#
|
||||
# Optional
|
||||
# Default: []
|
||||
#
|
||||
statusCodes = ["200", "300-302"]
|
||||
|
||||
[accessLog.fields]
|
||||
|
||||
# defaultMode
|
||||
#
|
||||
# Optional
|
||||
# Default: "keep"
|
||||
#
|
||||
# Accepted values "keep", "drop"
|
||||
#
|
||||
defaultMode = "keep"
|
||||
|
||||
# Fields map which is used to override fields defaultMode
|
||||
[accessLog.fields.names]
|
||||
"ClientUsername" = "drop"
|
||||
# ...
|
||||
|
||||
[accessLog.fields.headers]
|
||||
# defaultMode
|
||||
#
|
||||
# Optional
|
||||
# Default: "keep"
|
||||
#
|
||||
# Accepted values "keep", "drop", "redact"
|
||||
#
|
||||
defaultMode = "keep"
|
||||
# Fields map which is used to override headers defaultMode
|
||||
[accessLog.fields.headers.names]
|
||||
"User-Agent" = "redact"
|
||||
"Authorization" = "drop"
|
||||
"Content-Type" = "keep"
|
||||
# ...
|
||||
```
|
||||
|
||||
#### List of all available fields
|
||||
|
||||
```ini
|
||||
StartUTC
|
||||
StartLocal
|
||||
Duration
|
||||
FrontendName
|
||||
BackendName
|
||||
BackendURL
|
||||
BackendAddr
|
||||
ClientAddr
|
||||
ClientHost
|
||||
ClientPort
|
||||
ClientUsername
|
||||
RequestAddr
|
||||
RequestHost
|
||||
RequestPort
|
||||
RequestMethod
|
||||
RequestPath
|
||||
RequestProtocol
|
||||
RequestLine
|
||||
RequestContentSize
|
||||
OriginDuration
|
||||
OriginContentSize
|
||||
OriginStatus
|
||||
OriginStatusLine
|
||||
DownstreamStatus
|
||||
DownstreamStatusLine
|
||||
DownstreamContentSize
|
||||
RequestCount
|
||||
GzipRatio
|
||||
Overhead
|
||||
RetryAttempts
|
||||
```
|
||||
|
||||
Deprecated way (before 1.4):
|
||||
|
||||
!!! danger "DEPRECATED"
|
||||
`accessLogsFile` is deprecated, use [accessLog](/configuration/logs/#access-logs) instead.
|
||||
|
||||
```toml
|
||||
# Access logs file
|
||||
#
|
||||
# DEPRECATED - see [accessLog]
|
||||
#
|
||||
accessLogsFile = "log/access.log"
|
||||
```
|
||||
|
||||
## Log Rotation
|
||||
|
||||
Traefik will close and reopen its log files, assuming they're configured, on receipt of a USR1 signal.
|
||||
This allows the logs to be rotated and processed by an external program, such as `logrotate`.
|
||||
|
||||
!!! note
|
||||
This does not work on Windows due to the lack of USR signals.
|
||||
126
docs/configuration/metrics.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Metrics Definition
|
||||
|
||||
## Prometheus
|
||||
|
||||
```toml
|
||||
# Metrics definition
|
||||
[metrics]
|
||||
#...
|
||||
|
||||
# To enable Traefik to export internal metrics to Prometheus
|
||||
[metrics.prometheus]
|
||||
|
||||
# Name of the related entry point
|
||||
#
|
||||
# Optional
|
||||
# Default: "traefik"
|
||||
#
|
||||
entryPoint = "traefik"
|
||||
|
||||
# Buckets for latency metrics
|
||||
#
|
||||
# Optional
|
||||
# Default: [0.1, 0.3, 1.2, 5]
|
||||
#
|
||||
buckets = [0.1,0.3,1.2,5.0]
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
## DataDog
|
||||
|
||||
```toml
|
||||
# Metrics definition
|
||||
[metrics]
|
||||
#...
|
||||
|
||||
# DataDog metrics exporter type
|
||||
[metrics.datadog]
|
||||
|
||||
# DataDog's address.
|
||||
#
|
||||
# Required
|
||||
# Default: "localhost:8125"
|
||||
#
|
||||
address = "localhost:8125"
|
||||
|
||||
# DataDog push interval
|
||||
#
|
||||
# Optional
|
||||
# Default: "10s"
|
||||
#
|
||||
pushInterval = "10s"
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
## StatsD
|
||||
|
||||
```toml
|
||||
# Metrics definition
|
||||
[metrics]
|
||||
#...
|
||||
|
||||
# StatsD metrics exporter type
|
||||
[metrics.statsd]
|
||||
|
||||
# StatD's address.
|
||||
#
|
||||
# Required
|
||||
# Default: "localhost:8125"
|
||||
#
|
||||
address = "localhost:8125"
|
||||
|
||||
# StatD push interval
|
||||
#
|
||||
# Optional
|
||||
# Default: "10s"
|
||||
#
|
||||
pushInterval = "10s"
|
||||
|
||||
# ...
|
||||
```
|
||||
### InfluxDB
|
||||
|
||||
```toml
|
||||
[metrics]
|
||||
# ...
|
||||
|
||||
# InfluxDB metrics exporter type
|
||||
[metrics.influxdb]
|
||||
|
||||
# InfluxDB's address.
|
||||
#
|
||||
# Required
|
||||
# Default: "localhost:8089"
|
||||
#
|
||||
address = "localhost:8089"
|
||||
|
||||
# InfluxDB push interval
|
||||
#
|
||||
# Optional
|
||||
# Default: "10s"
|
||||
#
|
||||
pushinterval = "10s"
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
## Statistics
|
||||
|
||||
```toml
|
||||
# Metrics definition
|
||||
[metrics]
|
||||
# ...
|
||||
|
||||
# Enable more detailed statistics.
|
||||
[metrics.statistics]
|
||||
|
||||
# Number of recent errors logged.
|
||||
#
|
||||
# Default: 10
|
||||
#
|
||||
recentErrors = 10
|
||||
|
||||
# ...
|
||||
```
|
||||
91
docs/configuration/ping.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Ping Definition
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
# Ping definition
|
||||
[ping]
|
||||
# Name of the related entry point
|
||||
#
|
||||
# Optional
|
||||
# Default: "traefik"
|
||||
#
|
||||
entryPoint = "traefik"
|
||||
```
|
||||
|
||||
| Path | Method | Description |
|
||||
|---------|---------------|----------------------------------------------------------------------------------------------------|
|
||||
| `/ping` | `GET`, `HEAD` | A simple endpoint to check for Træfik process liveness. Return a code `200` with the content: `OK` |
|
||||
|
||||
|
||||
!!! warning
|
||||
Even if you have authentication configured on entry point, the `/ping` path of the api is excluded from authentication.
|
||||
|
||||
## Examples
|
||||
|
||||
The `/ping` health-check URL is enabled with the command-line `--ping` or config file option `[ping]`.
|
||||
Thus, if you have a regular path for `/foo` and an entrypoint on `:80`, you would access them as follows:
|
||||
|
||||
* Regular path: `http://hostname:80/foo`
|
||||
* Admin panel: `http://hostname:8080/`
|
||||
* Ping URL: `http://hostname:8080/ping`
|
||||
|
||||
However, for security reasons, you may want to be able to expose the `/ping` health-check URL to outside health-checkers, e.g. an Internet service or cloud load-balancer, _without_ exposing your administration panel's port.
|
||||
In many environments, the security staff may not _allow_ you to expose it.
|
||||
|
||||
You have two options:
|
||||
|
||||
* Enable `/ping` on a regular entry point
|
||||
* Enable `/ping` on a dedicated port
|
||||
|
||||
### Ping health check on a regular entry point
|
||||
|
||||
To proxy `/ping` from a regular entry point to the administration one without exposing the panel, do the following:
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
[ping]
|
||||
entryPoint = "http"
|
||||
|
||||
```
|
||||
|
||||
The above link `ping` on the `http` entry point and then expose it on port `80`
|
||||
|
||||
### Enable ping health check on dedicated port
|
||||
|
||||
If you do not want to or cannot expose the health-check on a regular entry point - e.g. your security rules do not allow it, or you have a conflicting path - then you can enable health-check on its own entry point.
|
||||
Use the following configuration:
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.ping]
|
||||
address = ":8082"
|
||||
|
||||
[ping]
|
||||
entryPoint = "ping"
|
||||
```
|
||||
|
||||
The above is similar to the previous example, but instead of enabling `/ping` on the _default_ entry point, we enable it on a _dedicated_ entry point.
|
||||
|
||||
In the above example, you would access a regular path and health-check as follows:
|
||||
|
||||
* Regular path: `http://hostname:80/foo`
|
||||
* Ping URL: `http://hostname:8082/ping`
|
||||
|
||||
Note the dedicated port `:8082` for `/ping`.
|
||||
|
||||
In the above example, it is _very_ important to create a named dedicated entry point, and do **not** include it in `defaultEntryPoints`.
|
||||
Otherwise, you are likely to expose _all_ services via this entry point.
|
||||
|
||||
### Using ping for external Load-balancer rotation health check
|
||||
|
||||
If you are running traefik behind a external Load-balancer, and want to configure rotation health check on the Load-balancer to take a traefik instance out of rotation gracefully, you can configure [lifecycle.requestAcceptGraceTimeout](/configuration/commons.md#life-cycle) and the ping endpoint will return `503` response on traefik server termination, so that the Load-balancer can take the terminating traefik instance out of rotation, before it stops responding.
|
||||
100
docs/configuration/tracing.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Tracing
|
||||
|
||||
Tracing system allows developers to visualize call flows in there infrastructures.
|
||||
|
||||
We use [OpenTracing](http://opentracing.io). It is an open standard designed for distributed tracing.
|
||||
|
||||
Træfik supports two backends: Jaeger and Zipkin.
|
||||
|
||||
## Jaeger
|
||||
|
||||
```toml
|
||||
# Tracing definition
|
||||
[tracing]
|
||||
# Backend name used to send tracing data
|
||||
#
|
||||
# Default: "jaeger"
|
||||
#
|
||||
backend = "jaeger"
|
||||
|
||||
# Service name used in Jaeger backend
|
||||
#
|
||||
# Default: "traefik"
|
||||
#
|
||||
serviceName = "traefik"
|
||||
|
||||
[tracing.jaeger]
|
||||
# Sampling Server URL is the address of jaeger-agent's HTTP sampling server
|
||||
#
|
||||
# Default: "http://localhost:5778/sampling"
|
||||
#
|
||||
samplingServerURL = "http://localhost:5778/sampling"
|
||||
|
||||
# Sampling Type specifies the type of the sampler: const, probabilistic, rateLimiting
|
||||
#
|
||||
# Default: "const"
|
||||
#
|
||||
samplingType = "const"
|
||||
|
||||
# Sampling Param is a value passed to the sampler.
|
||||
# Valid values for Param field are:
|
||||
# - for "const" sampler, 0 or 1 for always false/true respectively
|
||||
# - for "probabilistic" sampler, a probability between 0 and 1
|
||||
# - for "rateLimiting" sampler, the number of spans per second
|
||||
#
|
||||
# Default: 1.0
|
||||
#
|
||||
samplingParam = 1.0
|
||||
|
||||
# Local Agent Host Port instructs reporter to send spans to jaeger-agent at this address
|
||||
#
|
||||
# Default: "127.0.0.1:6831"
|
||||
#
|
||||
localAgentHostPort = "127.0.0.1:6831"
|
||||
```
|
||||
|
||||
!!! warning
|
||||
Træfik is only able to send data over compact thrift protocol to the [Jaeger agent](https://www.jaegertracing.io/docs/deployment/#agent).
|
||||
|
||||
## Zipkin
|
||||
|
||||
```toml
|
||||
# Tracing definition
|
||||
[tracing]
|
||||
# Backend name used to send tracing data
|
||||
#
|
||||
# Default: "jaeger"
|
||||
#
|
||||
backend = "zipkin"
|
||||
|
||||
# Service name used in Zipkin backend
|
||||
#
|
||||
# Default: "traefik"
|
||||
#
|
||||
serviceName = "traefik"
|
||||
|
||||
[tracing.zipkin]
|
||||
# Zipking HTTP endpoint used to send data
|
||||
#
|
||||
# Default: "http://localhost:9411/api/v1/spans"
|
||||
#
|
||||
httpEndpoint = "http://localhost:9411/api/v1/spans"
|
||||
|
||||
# Enable Zipkin debug
|
||||
#
|
||||
# Default: false
|
||||
#
|
||||
debug = false
|
||||
|
||||
# Use ZipKin SameSpan RPC style traces
|
||||
#
|
||||
# Default: false
|
||||
#
|
||||
sameSpan = false
|
||||
|
||||
# Use ZipKin 128 bit root span IDs
|
||||
#
|
||||
# Default: true
|
||||
#
|
||||
id128Bit = true
|
||||
```
|
||||
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 10 KiB |
4
docs/img/grpc.svg
Normal file
|
After Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 208 KiB |
|
Before Width: | Height: | Size: 255 KiB After Width: | Height: | Size: 274 KiB |
292
docs/index.md
@@ -4,71 +4,173 @@
|
||||
|
||||
[](https://semaphoreci.com/containous/traefik)
|
||||
[](https://docs.traefik.io)
|
||||
[](https://goreportcard.com/report/containous/traefik)
|
||||
[](https://goreportcard.com/report/github.com/containous/traefik)
|
||||
[](https://github.com/containous/traefik/blob/master/LICENSE.md)
|
||||
[](https://traefik.herokuapp.com)
|
||||
[](https://twitter.com/intent/follow?screen_name=traefikproxy)
|
||||
|
||||
|
||||
Træfik (pronounced like [traffic](https://speak-ipa.bearbin.net/speak.cgi?speak=%CB%88tr%C3%A6f%C9%AAk)) 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 mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), and a lot more) to manage its configuration automatically and dynamically.
|
||||
Træfik is a modern HTTP reverse proxy and load balancer that makes deploying microservices easy.
|
||||
Træfik integrates with your existing infrastructure components ([Docker](https://www.docker.com/), [Swarm mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), ...) and configures itself automatically and dynamically.
|
||||
Pointing Træfik at your orchestrator should be the _only_ configuration step you need.
|
||||
|
||||
## Overview
|
||||
|
||||
Imagine that you have deployed a bunch of microservices on your infrastructure. You probably used a service registry (like etcd or consul) and/or an orchestrator (swarm, Mesos/Marathon) to manage all these services.
|
||||
If you want your users to access some of your microservices from the Internet, you will have to use a reverse proxy and configure it using virtual hosts or prefix paths:
|
||||
Imagine that you have deployed a bunch of microservices with the help of an orchestrator (like Swarm or Kubernetes) or a service registry (like etcd or consul).
|
||||
Now you want users to access these microservices, and you need a reverse proxy.
|
||||
|
||||
- domain `api.domain.com` will point the microservice `api` in your private network
|
||||
- path `domain.com/web` will point the microservice `web` in your private network
|
||||
- domain `backoffice.domain.com` will point the microservices `backoffice` in your private network, load-balancing between your multiple instances
|
||||
Traditional reverse-proxies require that you configure _each_ route that will connect paths and subdomains to _each_ microservice.
|
||||
In an environment where you add, remove, kill, upgrade, or scale your services _many_ times a day, the task of keeping the routes up to date becomes tedious.
|
||||
|
||||
But a microservices architecture is dynamic... Services are added, removed, killed or upgraded often, eventually several times a day.
|
||||
**This is when Træfik can help you!**
|
||||
|
||||
Traditional reverse-proxies are not natively dynamic. You can't change their configuration and hot-reload easily.
|
||||
Træfik listens to your service registry/orchestrator API and instantly generates the routes so your microservices are connected to the outside world -- without further intervention from your part.
|
||||
|
||||
Here enters Træfik.
|
||||
**Run Træfik and let it do the work for you!**
|
||||
_(But if you'd rather configure some of your routes manually, Træfik supports that too!)_
|
||||
|
||||

|
||||
|
||||
Træfik can listen to your service registry/orchestrator API, and knows each time a microservice is added, removed, killed or upgraded, and can generate its configuration automatically.
|
||||
Routes to your services will be created instantly.
|
||||
|
||||
Run it and forget it!
|
||||
|
||||
## Features
|
||||
|
||||
- [It's fast](https://docs.traefik.io/benchmarks)
|
||||
- No dependency hell, single binary made with go
|
||||
- [Tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) official docker image
|
||||
- Rest API
|
||||
- Hot-reloading of configuration. No need to restart the process
|
||||
- Continuously updates its configuration (No restarts!)
|
||||
- Supports multiple load balancing algorithms
|
||||
- Provides HTTPS to your microservices by leveraging [Let's Encrypt](https://letsencrypt.org) (wildcard certificates support)
|
||||
- Circuit breakers, retry
|
||||
- Round Robin, rebalancer load-balancers
|
||||
- Metrics (Rest, Prometheus, Datadog, Statd)
|
||||
- Clean AngularJS Web UI
|
||||
- High Availability with cluster mode (beta)
|
||||
- See the magic through its clean web UI
|
||||
- Websocket, HTTP/2, GRPC ready
|
||||
- Access Logs (JSON, CLF)
|
||||
- [Let's Encrypt](https://letsencrypt.org) support (Automatic HTTPS with renewal)
|
||||
- High Availability with cluster mode
|
||||
- Provides metrics (Rest, Prometheus, Datadog, Statsd, InfluxDB)
|
||||
- Keeps access logs (JSON, CLF)
|
||||
- [Fast](/benchmarks) ... which is nice
|
||||
- Exposes a Rest API
|
||||
- Packaged as a single binary file (made with :heart: with go) and available as a [tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image
|
||||
|
||||
|
||||
## Supported backends
|
||||
## Supported Providers
|
||||
|
||||
- [Docker](https://www.docker.com/) / [Swarm mode](https://docs.docker.com/engine/swarm/)
|
||||
- [Kubernetes](https://kubernetes.io)
|
||||
- [Mesos](https://github.com/apache/mesos) / [Marathon](https://mesosphere.github.io/marathon/)
|
||||
- [Rancher](https://rancher.com) (API, Metadata)
|
||||
- [Consul](https://www.consul.io/) / [Etcd](https://coreos.com/etcd/) / [Zookeeper](https://zookeeper.apache.org) / [BoltDB](https://github.com/boltdb/bolt)
|
||||
- [Eureka](https://github.com/Netflix/eureka)
|
||||
- [Amazon ECS](https://aws.amazon.com/ecs)
|
||||
- [Amazon DynamoDB](https://aws.amazon.com/dynamodb)
|
||||
- File
|
||||
- Rest API
|
||||
- [Docker](/configuration/backends/docker/) / [Swarm mode](/configuration/backends/docker/#docker-swarm-mode)
|
||||
- [Kubernetes](/configuration/backends/kubernetes/)
|
||||
- [Mesos](/configuration/backends/mesos/) / [Marathon](/configuration/backends/marathon/)
|
||||
- [Rancher](/configuration/backends/rancher/) (API, Metadata)
|
||||
- [Azure Service Fabric](/configuration/backends/servicefabric/)
|
||||
- [Consul Catalog](/configuration/backends/consulcatalog/)
|
||||
- [Consul](/configuration/backends/consul/) / [Etcd](/configuration/backends/etcd/) / [Zookeeper](/configuration/backends/zookeeper/) / [BoltDB](/configuration/backends/boltdb/)
|
||||
- [Eureka](/configuration/backends/eureka/)
|
||||
- [Amazon ECS](/configuration/backends/ecs/)
|
||||
- [Amazon DynamoDB](/configuration/backends/dynamodb/)
|
||||
- [File](/configuration/backends/file/)
|
||||
- [Rest](/configuration/backends/rest/)
|
||||
|
||||
## The Træfik Quickstart (Using Docker)
|
||||
|
||||
## Quickstart
|
||||
In this quickstart, we'll use [Docker compose](https://docs.docker.com/compose) to create our demo infrastructure.
|
||||
|
||||
You can have a quick look at Træfik in this [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers.
|
||||
To save some time, you can clone [Træfik's repository](https://github.com/containous/traefik) and use the quickstart files located in the [examples/quickstart](https://github.com/containous/traefik/tree/master/examples/quickstart/) directory.
|
||||
|
||||
### 1 — Launch Træfik — Tell It to Listen to Docker
|
||||
|
||||
Create a `docker-compose.yml` file where you will define a `reverse-proxy` service that uses the official Træfik image:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
reverse-proxy:
|
||||
image: traefik # The official Traefik docker image
|
||||
command: --api --docker # Enables the web UI and tells Træfik to listen to docker
|
||||
ports:
|
||||
- "80:80" # The HTTP port
|
||||
- "8080:8080" # The Web UI (enabled by --api)
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the Docker events
|
||||
```
|
||||
|
||||
**That's it. Now you can launch Træfik!**
|
||||
|
||||
Start your `reverse-proxy` with the following command:
|
||||
|
||||
```shell
|
||||
docker-compose up -d reverse-proxy
|
||||
```
|
||||
|
||||
You can open a browser and go to [http://localhost:8080](http://localhost:8080) to see Træfik's dashboard (we'll go back there once we have launched a service in step 2).
|
||||
|
||||
### 2 — Launch a Service — Træfik Detects It and Creates a Route for You
|
||||
|
||||
Now that we have a Træfik instance up and running, we will deploy new services.
|
||||
|
||||
Edit your `docker-compose.yml` file and add the following at the end of your file.
|
||||
|
||||
```yaml
|
||||
# ...
|
||||
whoami:
|
||||
image: emilevauge/whoami # A container that exposes an API to show its IP address
|
||||
labels:
|
||||
- "traefik.frontend.rule=Host:whoami.docker.localhost"
|
||||
```
|
||||
|
||||
The above defines `whoami`: a simple web service that outputs information about the machine it is deployed on (its IP address, host, and so on).
|
||||
|
||||
Start the `whoami` service with the following command:
|
||||
|
||||
```shell
|
||||
docker-compose up -d whoami
|
||||
```
|
||||
|
||||
Go back to your browser ([http://localhost:8080](http://localhost:8080)) and see that Træfik has automatically detected the new container and updated its own configuration.
|
||||
|
||||
When Traefik detects new services, it creates the corresponding routes so you can call them ... _let's see!_ (Here, we're using curl)
|
||||
|
||||
```shell
|
||||
curl -H Host:whoami.docker.localhost http://127.0.0.1
|
||||
```
|
||||
|
||||
_Shows the following output:_
|
||||
```yaml
|
||||
Hostname: 8656c8ddca6c
|
||||
IP: 172.27.0.3
|
||||
#...
|
||||
```
|
||||
|
||||
### 3 — Launch More Instances — Traefik Load Balances Them
|
||||
|
||||
Run more instances of your `whoami` service with the following command:
|
||||
|
||||
```shell
|
||||
docker-compose up -d --scale whoami=2
|
||||
```
|
||||
|
||||
Go back to your browser ([http://localhost:8080](http://localhost:8080)) and see that Træfik has automatically detected the new instance of the container.
|
||||
|
||||
Finally, see that Træfik load-balances between the two instances of your services by running twice the following command:
|
||||
|
||||
```shell
|
||||
curl -H Host:whoami.docker.localhost http://127.0.0.1
|
||||
```
|
||||
|
||||
The output will show alternatively one of the followings:
|
||||
|
||||
```yaml
|
||||
Hostname: 8656c8ddca6c
|
||||
IP: 172.27.0.3
|
||||
#...
|
||||
```
|
||||
|
||||
```yaml
|
||||
Hostname: 8458f154e1f1
|
||||
IP: 172.27.0.4
|
||||
# ...
|
||||
```
|
||||
|
||||
### 4 — Enjoy Træfik's Magic
|
||||
|
||||
Now that you have a basic understanding of how Træfik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](/) and let Træfik work for you!
|
||||
Whatever your infrastructure is, there is probably [an available Træfik provider](/#supported-providers) that will do the job.
|
||||
|
||||
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Træfik's let's encrypt integration](/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](/user-guide/docker-and-lets-encrypt/).
|
||||
|
||||
## Resources
|
||||
|
||||
Here is a talk given by [Emile Vauge](https://github.com/emilevauge) at [GopherCon 2017](https://gophercon.com).
|
||||
You will learn Træfik basics in less than 10 minutes.
|
||||
@@ -80,9 +182,9 @@ You will learn fundamental Træfik features and see some demos with Kubernetes.
|
||||
|
||||
[](https://www.youtube.com/watch?v=aFtpIShV60I)
|
||||
|
||||
## Get it
|
||||
## Downloads
|
||||
|
||||
### Binary
|
||||
### The Official Binary File
|
||||
|
||||
You can grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
||||
|
||||
@@ -90,114 +192,10 @@ You can grab the latest binary from the [releases](https://github.com/containous
|
||||
./traefik -c traefik.toml
|
||||
```
|
||||
|
||||
### Docker
|
||||
### The Official Docker Image
|
||||
|
||||
Using the tiny Docker image:
|
||||
|
||||
```shell
|
||||
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik
|
||||
```
|
||||
|
||||
## Test it
|
||||
|
||||
You can test Træfik easily using [Docker compose](https://docs.docker.com/compose), with this `docker-compose.yml` file in a folder named `traefik`:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
proxy:
|
||||
image: traefik
|
||||
command: --web --docker --docker.domain=docker.localhost --logLevel=DEBUG
|
||||
networks:
|
||||
- webgateway
|
||||
ports:
|
||||
- "80:80"
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /dev/null:/traefik.toml
|
||||
|
||||
networks:
|
||||
webgateway:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
Start it from within the `traefik` folder:
|
||||
|
||||
```shell
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
In a browser you may open [http://localhost:8080](http://localhost:8080) to access Træfik's dashboard and observe the following magic.
|
||||
|
||||
Now, create a folder named `test` and create a `docker-compose.yml` in it with this content:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
whoami:
|
||||
image: emilevauge/whoami
|
||||
networks:
|
||||
- web
|
||||
labels:
|
||||
- "traefik.backend=whoami"
|
||||
- "traefik.frontend.rule=Host:whoami.docker.localhost"
|
||||
|
||||
networks:
|
||||
web:
|
||||
external:
|
||||
name: traefik_webgateway
|
||||
```
|
||||
|
||||
Then, start and scale it in the `test` folder:
|
||||
|
||||
```shell
|
||||
docker-compose up -d
|
||||
docker-compose scale whoami=2
|
||||
```
|
||||
|
||||
Finally, test load-balancing between the two services `test_whoami_1` and `test_whoami_2`:
|
||||
|
||||
```shell
|
||||
curl -H Host:whoami.docker.localhost http://127.0.0.1
|
||||
```
|
||||
|
||||
```yaml
|
||||
Hostname: ef194d07634a
|
||||
IP: 127.0.0.1
|
||||
IP: ::1
|
||||
IP: 172.17.0.4
|
||||
IP: fe80::42:acff:fe11:4
|
||||
GET / HTTP/1.1
|
||||
Host: 172.17.0.4:80
|
||||
User-Agent: curl/7.35.0
|
||||
Accept: */*
|
||||
Accept-Encoding: gzip
|
||||
X-Forwarded-For: 172.17.0.1
|
||||
X-Forwarded-Host: 172.17.0.4:80
|
||||
X-Forwarded-Proto: http
|
||||
X-Forwarded-Server: dbb60406010d
|
||||
```
|
||||
|
||||
```shell
|
||||
curl -H Host:whoami.docker.localhost http://127.0.0.1
|
||||
```
|
||||
|
||||
```yaml
|
||||
Hostname: 6c3c5df0c79a
|
||||
IP: 127.0.0.1
|
||||
IP: ::1
|
||||
IP: 172.17.0.3
|
||||
IP: fe80::42:acff:fe11:3
|
||||
GET / HTTP/1.1
|
||||
Host: 172.17.0.3:80
|
||||
User-Agent: curl/7.35.0
|
||||
Accept: */*
|
||||
Accept-Encoding: gzip
|
||||
X-Forwarded-For: 172.17.0.1
|
||||
X-Forwarded-Host: 172.17.0.3:80
|
||||
X-Forwarded-Proto: http
|
||||
X-Forwarded-Server: dbb60406010d
|
||||
```
|
||||
|
||||
4
docs/theme/partials/footer.html
vendored
@@ -20,7 +20,7 @@
|
||||
IN THE SOFTWARE.
|
||||
-->
|
||||
|
||||
{% import "partials/language.html" as lang %}
|
||||
{% import "partials/language.html" as lang with context %}
|
||||
|
||||
<!-- Application footer -->
|
||||
<footer class="md-footer">
|
||||
@@ -97,7 +97,7 @@
|
||||
|
||||
<!-- Social links -->
|
||||
{% block social %}
|
||||
{% include "partials/social.html" %}
|
||||
{% include "partials/social.html" %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
5
docs/theme/styles/extra.css
vendored
@@ -9,7 +9,12 @@
|
||||
.md-typeset__table td code {
|
||||
word-break: unset;
|
||||
}
|
||||
|
||||
.md-typeset__table tr :nth-child(1) {
|
||||
word-wrap: break-word;
|
||||
max-width: 30em;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
294
docs/user-guide/cluster-docker-consul.md
Normal file
@@ -0,0 +1,294 @@
|
||||
# Clustering / High Availability on Docker Swarm with Consul
|
||||
|
||||
This guide explains how to use Træfik in high availability mode in a Docker Swarm and with Let's Encrypt.
|
||||
|
||||
Why do we need Træfik in cluster mode? Running multiple instances should work out of the box?
|
||||
|
||||
If you want to use Let's Encrypt with Træfik, sharing configuration or TLS certificates between many Træfik instances, you need Træfik cluster/HA.
|
||||
|
||||
Ok, could we mount a shared volume used by all my instances? Yes, you can, but it will not work.
|
||||
When you use Let's Encrypt, you need to store certificates, but not only.
|
||||
When Træfik generates a new certificate, it configures a challenge and once Let's Encrypt will verify the ownership of the domain, it will ping back the challenge.
|
||||
If the challenge is not knowing by other Træfik instances, the validation will fail.
|
||||
|
||||
For more information about challenge: [Automatic Certificate Management Environment (ACME)](https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#http-challenge)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You will need a working Docker Swarm cluster.
|
||||
|
||||
## Træfik configuration
|
||||
|
||||
In this guide, we will not use a TOML configuration file, but only command line flag.
|
||||
With that, we can use the base image without mounting configuration file or building custom image.
|
||||
|
||||
What Træfik should do:
|
||||
|
||||
- Listen to 80 and 443
|
||||
- Redirect HTTP traffic to HTTPS
|
||||
- Generate SSL certificate when a domain is added
|
||||
- Listen to Docker Swarm event
|
||||
|
||||
### EntryPoints configuration
|
||||
|
||||
TL;DR:
|
||||
|
||||
```shell
|
||||
$ traefik \
|
||||
--entrypoints='Name:http Address::80 Redirect.EntryPoint:https' \
|
||||
--entrypoints='Name:https Address::443 TLS' \
|
||||
--defaultentrypoints=http,https
|
||||
```
|
||||
|
||||
To listen to different ports, we need to create an entry point for each.
|
||||
|
||||
The CLI syntax is `--entrypoints='Name:a_name Address:an_ip_or_empty:a_port options'`.
|
||||
If you want to redirect traffic from one entry point to another, it's the option `Redirect.EntryPoint:entrypoint_name`.
|
||||
|
||||
By default, we don't want to configure all our services to listen on http and https, we add a default entry point configuration: `--defaultentrypoints=http,https`.
|
||||
|
||||
### Let's Encrypt configuration
|
||||
|
||||
TL;DR:
|
||||
|
||||
```shell
|
||||
$ traefik \
|
||||
--acme \
|
||||
--acme.storage=/etc/traefik/acme/acme.json \
|
||||
--acme.entryPoint=https \
|
||||
--acme.httpChallenge.entryPoint=http \
|
||||
--acme.email=contact@mydomain.ca
|
||||
```
|
||||
|
||||
Let's Encrypt needs 4 parameters: an TLS entry point to listen to, a non-TLS entry point to allow HTTP challenges, a storage for certificates, and an email for the registration.
|
||||
|
||||
To enable Let's Encrypt support, you need to add `--acme` flag.
|
||||
|
||||
Now, Træfik needs to know where to store the certificates, we can choose between a key in a Key-Value store, or a file path: `--acme.storage=my/key` or `--acme.storage=/path/to/acme.json`.
|
||||
|
||||
The `acme.httpChallenge.entryPoint` flag enables the `HTTP-01` challenge and specifies the entryPoint to use during the challenges.
|
||||
|
||||
For your email and the entry point, it's `--acme.entryPoint` and `--acme.email` flags.
|
||||
|
||||
### Docker configuration
|
||||
|
||||
TL;DR:
|
||||
|
||||
```shell
|
||||
$ traefik \
|
||||
--docker \
|
||||
--docker.swarmMode \
|
||||
--docker.domain=mydomain.ca \
|
||||
--docker.watch
|
||||
```
|
||||
|
||||
To enable docker and swarm-mode support, you need to add `--docker` and `--docker.swarmMode` flags.
|
||||
To watch docker events, add `--docker.watch`.
|
||||
|
||||
### Full docker-compose file
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:1.5
|
||||
command:
|
||||
- "--api"
|
||||
- "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
|
||||
- "--entrypoints=Name:https Address::443 TLS"
|
||||
- "--defaultentrypoints=http,https"
|
||||
- "--acme"
|
||||
- "--acme.storage=/etc/traefik/acme/acme.json"
|
||||
- "--acme.entryPoint=https"
|
||||
- "--acme.httpChallenge.entryPoint=http"
|
||||
- "--acme.onHostRule=true"
|
||||
- "--acme.onDemand=false"
|
||||
- "--acme.email=contact@mydomain.ca"
|
||||
- "--docker"
|
||||
- "--docker.swarmMode"
|
||||
- "--docker.domain=mydomain.ca"
|
||||
- "--docker.watch"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
- webgateway
|
||||
- traefik
|
||||
ports:
|
||||
- target: 80
|
||||
published: 80
|
||||
mode: host
|
||||
- target: 443
|
||||
published: 443
|
||||
mode: host
|
||||
- target: 8080
|
||||
published: 8080
|
||||
mode: host
|
||||
deploy:
|
||||
mode: global
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager
|
||||
update_config:
|
||||
parallelism: 1
|
||||
delay: 10s
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
networks:
|
||||
webgateway:
|
||||
driver: overlay
|
||||
external: true
|
||||
traefik:
|
||||
driver: overlay
|
||||
```
|
||||
|
||||
## Migrate configuration to Consul
|
||||
|
||||
We created a special Træfik command to help configuring your Key Value store from a Træfik TOML configuration file and/or CLI flags.
|
||||
|
||||
## Deploy a Træfik cluster
|
||||
|
||||
The best way we found is to have an initializer service.
|
||||
This service will push the config to Consul via the `storeconfig` sub-command.
|
||||
|
||||
This service will retry until finishing without error because Consul may not be ready when the service tries to push the configuration.
|
||||
|
||||
The initializer in a docker-compose file will be:
|
||||
|
||||
```yaml
|
||||
traefik_init:
|
||||
image: traefik:1.5
|
||||
command:
|
||||
- "storeconfig"
|
||||
- "--api"
|
||||
[...]
|
||||
- "--consul"
|
||||
- "--consul.endpoint=consul:8500"
|
||||
- "--consul.prefix=traefik"
|
||||
networks:
|
||||
- traefik
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
depends_on:
|
||||
- consul
|
||||
```
|
||||
|
||||
And now, the Træfik part will only have the Consul configuration.
|
||||
|
||||
```yaml
|
||||
traefik:
|
||||
image: traefik:1.5
|
||||
depends_on:
|
||||
- traefik_init
|
||||
- consul
|
||||
command:
|
||||
- "--consul"
|
||||
- "--consul.endpoint=consul:8500"
|
||||
- "--consul.prefix=traefik"
|
||||
[...]
|
||||
```
|
||||
|
||||
!!! note
|
||||
For Træfik <1.5.0 add `acme.storage=traefik/acme/account` because Træfik is not reading it from Consul.
|
||||
|
||||
If you have some update to do, update the initializer service and re-deploy it.
|
||||
The new configuration will be stored in Consul, and you need to restart the Træfik node: `docker service update --force traefik_traefik`.
|
||||
|
||||
## Full docker-compose file
|
||||
|
||||
```yaml
|
||||
version: "3.4"
|
||||
services:
|
||||
traefik_init:
|
||||
image: traefik:1.5
|
||||
command:
|
||||
- "storeconfig"
|
||||
- "--api"
|
||||
- "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
|
||||
- "--entrypoints=Name:https Address::443 TLS"
|
||||
- "--defaultentrypoints=http,https"
|
||||
- "--acme"
|
||||
- "--acme.storage=traefik/acme/account"
|
||||
- "--acme.entryPoint=https"
|
||||
- "--acme.httpChallenge.entryPoint=http"
|
||||
- "--acme.onHostRule=true"
|
||||
- "--acme.onDemand=false"
|
||||
- "--acme.email=foobar@example.com"
|
||||
- "--docker"
|
||||
- "--docker.swarmMode"
|
||||
- "--docker.domain=example.com"
|
||||
- "--docker.watch"
|
||||
- "--consul"
|
||||
- "--consul.endpoint=consul:8500"
|
||||
- "--consul.prefix=traefik"
|
||||
networks:
|
||||
- traefik
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
depends_on:
|
||||
- consul
|
||||
traefik:
|
||||
image: traefik:1.5
|
||||
depends_on:
|
||||
- traefik_init
|
||||
- consul
|
||||
command:
|
||||
- "--consul"
|
||||
- "--consul.endpoint=consul:8500"
|
||||
- "--consul.prefix=traefik"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
- webgateway
|
||||
- traefik
|
||||
ports:
|
||||
- target: 80
|
||||
published: 80
|
||||
mode: host
|
||||
- target: 443
|
||||
published: 443
|
||||
mode: host
|
||||
- target: 8080
|
||||
published: 8080
|
||||
mode: host
|
||||
deploy:
|
||||
mode: global
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager
|
||||
update_config:
|
||||
parallelism: 1
|
||||
delay: 10s
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
consul:
|
||||
image: consul
|
||||
command: agent -server -bootstrap-expect=1
|
||||
volumes:
|
||||
- consul-data:/consul/data
|
||||
environment:
|
||||
- CONSUL_LOCAL_CONFIG={"datacenter":"us_east2","server":true}
|
||||
- CONSUL_BIND_INTERFACE=eth0
|
||||
- CONSUL_CLIENT_INTERFACE=eth0
|
||||
deploy:
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
networks:
|
||||
- traefik
|
||||
|
||||
networks:
|
||||
webgateway:
|
||||
driver: overlay
|
||||
external: true
|
||||
traefik:
|
||||
driver: overlay
|
||||
|
||||
volumes:
|
||||
consul-data:
|
||||
driver: [not local]
|
||||
```
|
||||
@@ -1,21 +1,33 @@
|
||||
# Clustering / High Availability (beta)
|
||||
|
||||
This guide explains how to use Træfik in high availability mode.
|
||||
|
||||
In order to deploy and configure multiple Træfik 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.
|
||||
_(Currently, we recommend [Consul](https://consul.io) .)_
|
||||
|
||||
## File configuration to KV store migration
|
||||
|
||||
We created a special Træfik command to help configuring your Key Value store from a Træfik 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æfik cluster
|
||||
|
||||
Once your Træfik configuration is uploaded on your KV store, you can start each Træfik instance.
|
||||
|
||||
A Træfik cluster is based on a manager/worker model.
|
||||
|
||||
When starting, Træfik will elect a manager.
|
||||
If this instance fails, another manager will be automatically elected.
|
||||
|
||||
## Træfik cluster and Let's Encrypt
|
||||
|
||||
**In cluster mode, ACME certificates have to be stored in [a KV Store entry](/configuration/acme/#storage-kv-entry).**
|
||||
|
||||
Thanks to the Træfik cluster mode algorithm (based on [the Raft Consensus Algorithm](https://raft.github.io/)), only one instance will contact Let's encrypt to solve the challenges.
|
||||
|
||||
The others instances will get ACME certificate from the KV Store entry.
|
||||