mirror of
https://github.com/containous/traefik.git
synced 2025-09-22 13:44:25 +03:00
Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4ad0ab5433 | ||
|
2045b250fd | ||
|
eb7a6d925b | ||
|
2d1a973ee5 | ||
|
322f7b2ad4 | ||
|
41aa2672cd | ||
|
f3090a452a | ||
|
52790d3c37 | ||
|
3677252e17 | ||
|
0399d0c4d6 | ||
|
3e3b7238e0 | ||
|
532b5865de | ||
|
6fc110a71a | ||
|
a1fe29347a | ||
|
3a42e457cf | ||
|
9df0a6208b | ||
|
3214904cc7 | ||
|
ec775a016a | ||
|
a2ca235fee | ||
|
fc52d1cfba | ||
|
fdf2a68a11 | ||
|
e63db782c1 | ||
|
a6c6127e33 |
@@ -30,7 +30,9 @@ before_deploy:
|
||||
make release-packages;
|
||||
fi;
|
||||
curl -sfL https://raw.githubusercontent.com/containous/structor/master/godownloader.sh | bash -s -- -b "${GOPATH}/bin" ${STRUCTOR_VERSION}
|
||||
curl -sSfL https://raw.githubusercontent.com/traefik/mixtus/master/godownloader.sh | sh -s -- -b "${GOPATH}/bin" ${MIXTUS_VERSION}
|
||||
structor -o containous -r traefik --dockerfile-url="https://raw.githubusercontent.com/containous/traefik/v1.7/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" --force-edit-url --exp-branch=master --debug;
|
||||
mixtus --dst-doc-path="./traefik" --dst-owner=traefik --dst-repo-name=doc --git-user-email="30906710+traefiker@users.noreply.github.com" --git-user-name=traefiker --src-doc-path="./site" --src-owner=containous --src-repo-name=traefik;
|
||||
fi
|
||||
|
||||
deploy:
|
||||
|
44
CHANGELOG.md
44
CHANGELOG.md
@@ -1,3 +1,47 @@
|
||||
## [v2.2.11](https://github.com/containous/traefik/tree/v2.2.11) (2020-09-07)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v2.2.10...v2.2.11)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[middleware]** fix: header middleware response writer. ([#7252](https://github.com/containous/traefik/pull/7252) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Documentation:**
|
||||
- **[healthcheck]** Clarified hostname documentation for load balancer healthcheck ([#7254](https://github.com/containous/traefik/pull/7254) by [AndrewSav](https://github.com/AndrewSav))
|
||||
|
||||
## [v2.2.10](https://github.com/containous/traefik/tree/v2.2.10) (2020-09-04)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v2.2.7...v2.2.10)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Update go-acme/lego to v4.0.1 ([#7238](https://github.com/containous/traefik/pull/7238) by [ldez](https://github.com/ldez))
|
||||
- **[middleware]** Add missing IPStrategy struct tag for YAML ([#7233](https://github.com/containous/traefik/pull/7233) by [kevinpollet](https://github.com/kevinpollet))
|
||||
- **[middleware]** Headers response modifier is directly applied by headers middleware ([#7230](https://github.com/containous/traefik/pull/7230) by [juliens](https://github.com/juliens))
|
||||
- **[webui]** chore(webui): upgrade nodejs to Node current LTS ([#7125](https://github.com/containous/traefik/pull/7125) by [Slashgear](https://github.com/Slashgear))
|
||||
|
||||
**Documentation:**
|
||||
- **[docker]** doc: fix dead link. ([#7172](https://github.com/containous/traefik/pull/7172) by [ldez](https://github.com/ldez))
|
||||
- **[k8s]** kubernetes-crd: fix whitespace in configuration examples ([#7134](https://github.com/containous/traefik/pull/7134) by [NT-florianernst](https://github.com/NT-florianernst))
|
||||
- **[k8s]** doc: replace underscore by hyphen for k8s metadata names. ([#7131](https://github.com/containous/traefik/pull/7131) by [ldez](https://github.com/ldez))
|
||||
- **[logs]** doc: added tz section to access log ([#7178](https://github.com/containous/traefik/pull/7178) by [notsureifkevin](https://github.com/notsureifkevin))
|
||||
- **[tls]** doc: Minor language improvement in TLS documentation ([#7206](https://github.com/containous/traefik/pull/7206) by [sharmarajdaksh](https://github.com/sharmarajdaksh))
|
||||
- doc: fix typo in migration guide ([#7181](https://github.com/containous/traefik/pull/7181) by [ScuttleSE](https://github.com/ScuttleSE))
|
||||
- doc: specify HostSNI rule removal only for HTTP routers ([#7237](https://github.com/containous/traefik/pull/7237) by [rtribotte](https://github.com/rtribotte))
|
||||
- Reorder migrations for v2 minor upgrades ([#7214](https://github.com/containous/traefik/pull/7214) by [peschmae](https://github.com/peschmae))
|
||||
- Harmonize docs ([#7124](https://github.com/containous/traefik/pull/7124) by [matthieuh](https://github.com/matthieuh))
|
||||
|
||||
## [v2.2.9](https://github.com/containous/traefik/tree/v2.2.9) (2020-09-04)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v2.2.8...v2.2.9)
|
||||
|
||||
Release canceled due to a bad tag.
|
||||
|
||||
## [v2.2.8](https://github.com/containous/traefik/tree/v2.2.8) (2020-07-28)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v2.2.7...v2.2.8)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[webui]** fix: clean X-Forwarded-Prefix header for the dashboard. ([#7109](https://github.com/containous/traefik/pull/7109) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Documentation:**
|
||||
- **[docker]** spelling(docs/content/routing/providers/docker.md) ([#7101](https://github.com/containous/traefik/pull/7101) by [szczot3k](https://github.com/szczot3k))
|
||||
- **[k8s]** doc: add name of used key for kubernetes client auth ([#7068](https://github.com/containous/traefik/pull/7068) by [smueller18](https://github.com/smueller18))
|
||||
|
||||
## [v2.2.7](https://github.com/containous/traefik/tree/v2.2.7) (2020-07-20)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v2.2.6...v2.2.7)
|
||||
|
||||
|
BIN
docs/content/assets/img/traefik.logo.horizontal.png
Normal file
BIN
docs/content/assets/img/traefik.logo.horizontal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
70
docs/content/assets/styles/content.css
Normal file
70
docs/content/assets/styles/content.css
Normal file
@@ -0,0 +1,70 @@
|
||||
.md-container {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.md-content h1 {
|
||||
color: var(--dark) !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.md-content a {
|
||||
color: var(--blue) !important;
|
||||
}
|
||||
|
||||
.md-content a:hover {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.md-typeset p code,
|
||||
.md-typeset .codehilite,
|
||||
.md-typeset .highlight {
|
||||
background-color: var(--light-blue) !important;
|
||||
}
|
||||
|
||||
.md-typeset table:not([class]) th {
|
||||
background: var(--dark) !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
/* Front page image size */
|
||||
img[src$='#small'] {
|
||||
width: 150px;
|
||||
}
|
||||
img[src$='#medium'] {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
/* Center table and objects */
|
||||
.center,
|
||||
img,
|
||||
.md-typeset__table {
|
||||
display: block !important;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.md-typeset table:not([class]) tr td:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
.md-typeset table:not([class]) th:not([align]),
|
||||
.md-typeset table:not([class]) td:not([align]) {
|
||||
text-align: center;
|
||||
}
|
||||
article p:not([class]),
|
||||
article ul:not([class]),
|
||||
article ol:not([class]) {
|
||||
padding-left: 0.8em !important;
|
||||
}
|
||||
|
||||
/* Fix for Chrome */
|
||||
.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;
|
||||
}
|
@@ -1,63 +0,0 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Noto+Sans|Noto+Serif');
|
||||
|
||||
.md-logo img {
|
||||
background-color: white;
|
||||
border-radius: 50%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
/* Fix for Chrome */
|
||||
.md-typeset__table td code {
|
||||
word-break: unset;
|
||||
}
|
||||
|
||||
.md-typeset__table tr :nth-child(1) {
|
||||
word-wrap: break-word;
|
||||
max-width: 30em;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans', sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: bold !important;
|
||||
color: rgba(0,0,0,.9) !important;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.md-typeset h5 {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
text-align: center;
|
||||
font-size: 0.8em;
|
||||
font-style: italic;
|
||||
color: #8D909F;
|
||||
}
|
||||
|
||||
p.subtitle {
|
||||
color: rgba(0,0,0,.54);
|
||||
padding-top: 0;
|
||||
margin-top: -2em;
|
||||
font-weight: bold;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
.markdown-body .task-list-item {
|
||||
list-style-type: none !important;
|
||||
}
|
||||
|
||||
.markdown-body .task-list-item input[type="checkbox"] {
|
||||
margin: 0 4px 0.25em -20px;
|
||||
vertical-align: middle;
|
||||
}
|
10
docs/content/assets/styles/footer.css
Normal file
10
docs/content/assets/styles/footer.css
Normal file
@@ -0,0 +1,10 @@
|
||||
.md-footer-meta {
|
||||
background-color: var(--dark);
|
||||
}
|
||||
|
||||
.md-footer-privacy-policy {
|
||||
margin: 0 .6rem;
|
||||
padding: .4rem 0;
|
||||
color: hsla(0,0%,100%,.3);
|
||||
font-size: .64rem;
|
||||
}
|
462
docs/content/assets/styles/header.css
Normal file
462
docs/content/assets/styles/header.css
Normal file
@@ -0,0 +1,462 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Rubik:300i,400,400i,500,500i,700&display=swap');
|
||||
|
||||
.wrapper-1200 {
|
||||
width: 100%;
|
||||
max-width: 61rem;
|
||||
margin: 0 auto;
|
||||
padding: 0 .6rem;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.wrapper-1200 {
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-type-1 {
|
||||
outline: none;
|
||||
border: none;
|
||||
background-color: #1e54d5;
|
||||
line-height: 1em;
|
||||
border-radius: 8px;
|
||||
padding: 12px 15px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-size: 1.25rem;
|
||||
background-image: linear-gradient(to top, rgba(0, 0, 0, 0.28) 1%, #1e54d5 99%);
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
color: white;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.button--secondary {
|
||||
outline: none;
|
||||
border: 2px solid #1e54d5 !important;
|
||||
background: transparent;
|
||||
line-height: 1em;
|
||||
border-radius: 8px;
|
||||
padding: 9px 13px;
|
||||
letter-spacing: 0;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
color: #1e54d5;
|
||||
transition: all 0.2s;
|
||||
display: inline-block;
|
||||
}
|
||||
.button--secondary:hover {
|
||||
color: white !important;
|
||||
background: #1e54d5;
|
||||
}
|
||||
.button--secondary:focus {
|
||||
color: white !important;
|
||||
background: #1e54d5;
|
||||
}
|
||||
|
||||
.site-header-and-placeholder-wrapper {
|
||||
position: relative;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transition: height 0.1s;
|
||||
z-index: 100;
|
||||
background: white;
|
||||
box-shadow: 0 0 7px 0 #00000021;
|
||||
border-bottom: 1px solid #e2e2e2;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 10px;
|
||||
font-family: 'Rubik', -apple-system, 'BlinkMacSystemFont', 'Segoe UI',
|
||||
'Helvetica Neue', sans-serif;
|
||||
color: #06102a;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.site-header.scrolled {
|
||||
box-shadow: 0 0 5px 0 #00000028;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
height: 52px;
|
||||
}
|
||||
.site-header.scrolled .site-header__title a {
|
||||
font-size: 2.2em;
|
||||
}
|
||||
|
||||
.header-placeholder {
|
||||
background: none;
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
position: absolute;
|
||||
}
|
||||
.header-placeholder.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.site-header .wrapper-1200 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.site-header .wrapper-1200 .left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.site-header__logo {
|
||||
max-width: 145px;
|
||||
}
|
||||
|
||||
.site-header__title a {
|
||||
color: #06102a;
|
||||
font-size: 2.2em;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.site-header__nav .menu-item-wrapper {
|
||||
display: inline-block;
|
||||
padding-left: 30px;
|
||||
}
|
||||
.site-header__nav .menu-item {
|
||||
color: #06102a;
|
||||
transition: all 0.05s;
|
||||
font-size: 1.45em;
|
||||
line-height: 1em;
|
||||
font-weight: 500;
|
||||
}
|
||||
.site-header__nav .menu-item:hover {
|
||||
color: #8a959e;
|
||||
}
|
||||
.site-header__nav .menu-item--with-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.site-header__nav .menu-item--with-icon .title {
|
||||
margin-right: 3px;
|
||||
}
|
||||
.site-header__nav .menu-item--with-icon .icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: all 0.1s;
|
||||
}
|
||||
.site-header__nav .menu-item--with-icon .icon svg {
|
||||
stroke-width: 2.5 !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.site-header__nav .menu-item-wrapper--dropdown {
|
||||
position: relative;
|
||||
}
|
||||
.site-header__nav .menu-item-wrapper--dropdown:hover .nav-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
.site-header__nav .nav-dropdown-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-dropdown-menu {
|
||||
position: absolute;
|
||||
z-index: 500;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.nav-dropdown-menu-wrapper {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 12px 40px 0 rgba(1, 10, 32, 0.24);
|
||||
background: white;
|
||||
margin: 8px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Products, Solutions dropdown menu */
|
||||
.nav-dropdown-menu--products,
|
||||
.nav-dropdown-menu--solutions {
|
||||
width: 500px;
|
||||
}
|
||||
.nav-dropdown-menu--products .nav-dropdown-menu-wrapper,
|
||||
.nav-dropdown-menu--solutions .nav-dropdown-menu-wrapper {
|
||||
padding: 20px;
|
||||
}
|
||||
.nav-dropdown-menu--products .dm-header,
|
||||
.nav-dropdown-menu--solutions .dm-header {
|
||||
font-size: 1.1em;
|
||||
font-weight: 500;
|
||||
font-stretch: normal;
|
||||
font-style: normal;
|
||||
line-height: normal;
|
||||
letter-spacing: 3.67px;
|
||||
color: #505769;
|
||||
margin-bottom: 20px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.nav-dropdown-menu--products .dm-item,
|
||||
.nav-dropdown-menu--solutions .dm-item {
|
||||
border: none;
|
||||
margin: 0 0 20px;
|
||||
color: #06102a;
|
||||
transition: all 0.1s;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.nav-dropdown-menu--products .dm-item:last-child,
|
||||
.nav-dropdown-menu--solutions .dm-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.nav-dropdown-menu--products .dm-item .dmi-image,
|
||||
.nav-dropdown-menu--solutions .dm-item .dmi-image {
|
||||
width: 118px;
|
||||
height: 92px;
|
||||
position: absolute;
|
||||
background: #f4f4f4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
z-index: 0;
|
||||
}
|
||||
.nav-dropdown-menu--products .dm-item .dmi-image img,
|
||||
.nav-dropdown-menu--solutions .dm-item .dmi-image img {
|
||||
width: 100%;
|
||||
}
|
||||
.nav-dropdown-menu--products .dm-item .dmi-details,
|
||||
.nav-dropdown-menu--solutions .dm-item .dmi-details {
|
||||
padding: 8px 0 10px 135px;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
display: block;
|
||||
color: #06102a;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.nav-dropdown-menu--products .dm-item .dmi-details:hover,
|
||||
.nav-dropdown-menu--solutions .dm-item .dmi-details:hover {
|
||||
color: #1e54d5;
|
||||
}
|
||||
.nav-dropdown-menu--products .dm-item .dmi-title,
|
||||
.nav-dropdown-menu--solutions .dm-item .dmi-title {
|
||||
font-size: 1.6em;
|
||||
font-weight: 500;
|
||||
margin: 0 0 2px;
|
||||
}
|
||||
.nav-dropdown-menu--products .dm-item .dmi-description,
|
||||
.nav-dropdown-menu--solutions .dm-item .dmi-description {
|
||||
font-size: 1.4em;
|
||||
opacity: 0.7;
|
||||
line-height: 1.6em;
|
||||
}
|
||||
.nav-dropdown-menu--products .dm-item--traefikee .dmi-image img,
|
||||
.nav-dropdown-menu--solutions .dm-item--traefikee .dmi-image img {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.nav-dropdown-menu--solutions .dm-item .dmi-image {
|
||||
width: 65px;
|
||||
padding: 10px;
|
||||
background: white;
|
||||
height: auto;
|
||||
}
|
||||
.nav-dropdown-menu--solutions .dm-item .dmi-details {
|
||||
padding: 5px 0 0 80px;
|
||||
}
|
||||
.nav-dropdown-menu--solutions .dm-item:last-child {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Dropdown menu: Learn */
|
||||
.nav-dropdown-menu--learn {
|
||||
width: 250px;
|
||||
}
|
||||
.nav-dropdown-menu--company {
|
||||
width: 500px;
|
||||
}
|
||||
.nav-dropdown-menu--company .nav-dropdown-menu-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 50% 50%;
|
||||
}
|
||||
.nav-dropdown-menu--learn .dm-left,
|
||||
.nav-dropdown-menu--company .dm-left {
|
||||
padding: 25px;
|
||||
}
|
||||
.nav-dropdown-menu--learn .dm-header,
|
||||
.nav-dropdown-menu--company .dm-header {
|
||||
font-size: 1.1em;
|
||||
font-weight: 500;
|
||||
font-stretch: normal;
|
||||
font-style: normal;
|
||||
line-height: normal;
|
||||
letter-spacing: 3.67px;
|
||||
color: #505769;
|
||||
margin-bottom: 20px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.nav-dropdown-menu--learn .dm-item,
|
||||
.nav-dropdown-menu--company .dm-item {
|
||||
display: block;
|
||||
font-size: 1.6em;
|
||||
font-weight: 500;
|
||||
color: #06102a;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.nav-dropdown-menu--learn .dm-item:last-child,
|
||||
.nav-dropdown-menu--company .dm-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.nav-dropdown-menu--learn .dm-item:hover,
|
||||
.nav-dropdown-menu--company .dm-item:hover {
|
||||
color: #1e54d5;
|
||||
}
|
||||
|
||||
.dm-preview {
|
||||
background: #edeff4;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dm-preview__feature-image {
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dm-preview__feature-image img {
|
||||
width: 100%;
|
||||
height: 145px;
|
||||
background: #ffffff no-repeat 50%;
|
||||
object-fit: cover;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.dm-preview__content {
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dm-preview__tag {
|
||||
display: block;
|
||||
font-size: 1.2em;
|
||||
color: #db7d11;
|
||||
letter-spacing: 2.5px;
|
||||
font-weight: 500;
|
||||
margin: 0 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.dm-preview__title {
|
||||
font-size: 1.6em;
|
||||
font-weight: 500;
|
||||
line-height: 1.6em;
|
||||
margin: 0;
|
||||
color: #06102a;
|
||||
display: block;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.dm-preview .arrow-link {
|
||||
justify-content: flex-start;
|
||||
font-size: 1.4em;
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* Dropdown menu: Company */
|
||||
.nav-dropdown-menu--company {
|
||||
width: 450px;
|
||||
}
|
||||
|
||||
.nav-dropdown-menu--company .dm-right {
|
||||
background: #06102a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
}
|
||||
.nav-dropdown-menu--company .dm-right p {
|
||||
font-size: 1.6em;
|
||||
font-weight: 500;
|
||||
margin: 0 0 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.nav-dropdown-menu--company .dm-right a {
|
||||
text-transform: uppercase;
|
||||
line-height: 1.5em;
|
||||
padding: 9px 12px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
/* Demo */
|
||||
.site-header__demo-button .button--secondary {
|
||||
font-size: 1.4em;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
/* Drawer */
|
||||
.site-header .drawer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 1060px) {
|
||||
.site-header__nav .menu-item-wrapper {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 980px) {
|
||||
.site-header__nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.site-header .drawer {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.site-header .right .site-header__demo-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html [data-md-color-primary=indigo] .md-nav--primary .md-nav__title--site {
|
||||
background-color: #06102a;
|
||||
}
|
||||
|
||||
html .md-nav--primary .md-nav__title {
|
||||
padding: 64px .8rem .2rem;
|
||||
}
|
||||
|
||||
.md-search__inner {
|
||||
top: 64px;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.md-header .md-search {
|
||||
margin-left: 12.1rem;
|
||||
}
|
||||
|
||||
.site-header__main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
101
docs/content/assets/styles/menu.css
Normal file
101
docs/content/assets/styles/menu.css
Normal file
@@ -0,0 +1,101 @@
|
||||
.md-nav__link {
|
||||
margin-left: -0.4rem;
|
||||
padding: 0 0.4rem;
|
||||
line-height: 32px;
|
||||
color: var(--dark) !important;
|
||||
}
|
||||
|
||||
.md-nav__link::after {
|
||||
font-size: 16px;
|
||||
vertical-align: -.25em;
|
||||
}
|
||||
|
||||
.md-nav__toggle:checked + .md-nav__link,
|
||||
.md-nav__link--active,
|
||||
.md-nav__link:hover {
|
||||
border-radius: 8px;
|
||||
background-color: var(--light-blue) !important;
|
||||
color: var(--dark) !important;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.md-nav__link--active {
|
||||
color: var(--blue) !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.md-sidebar--primary {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.md-sidebar--secondary .md-nav__title {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 0.4rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.md-sidebar--secondary .md-sidebar__scrollwrap {
|
||||
border-radius: 8px;
|
||||
background-color: var(--light-blue) !important;
|
||||
}
|
||||
|
||||
.md-sidebar--secondary .md-nav__title {
|
||||
padding: 0.8rem 0.4rem 0.8rem;
|
||||
}
|
||||
|
||||
.md-sidebar--secondary .md-nav__list {
|
||||
padding: 0 0.4rem 0.8rem 1.2rem;
|
||||
}
|
||||
|
||||
.md-sidebar--secondary .md-sidebar__scrollwrap .md-nav__link {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.md-sidebar--secondary
|
||||
.md-sidebar__scrollwrap
|
||||
.md-nav__link[data-md-state='blur'],
|
||||
.md-sidebar--secondary .md-sidebar__scrollwrap .md-nav__link:hover {
|
||||
color: var(--blue) !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.md-sidebar--secondary .md-nav__item {
|
||||
padding: 0 0 0 0.4rem;
|
||||
}
|
||||
|
||||
.md-sidebar--secondary .md-nav__link {
|
||||
margin-top: 0.225em;
|
||||
padding: 0.1rem 0.2rem;
|
||||
}
|
||||
|
||||
.md-sidebar--secondary li {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.md-sidebar--secondary .repo_url {
|
||||
padding: 10px 0 14px 0;
|
||||
}
|
||||
|
||||
|
||||
.md-search__inner {
|
||||
width: inherit;
|
||||
float: inherit;
|
||||
}
|
||||
|
||||
.md-search__input {
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
background-color: inherit;
|
||||
border: 1px solid rgba(0,0,0,.07);
|
||||
}
|
||||
|
||||
.md-search__input::placeholder {
|
||||
color: rgba(0,0,0,.07);
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 60em) {
|
||||
[data-md-toggle=search]:checked~.md-header .md-search__inner {
|
||||
margin-top: 100px;
|
||||
}
|
||||
}
|
11
docs/content/assets/styles/product-switcher.css
Normal file
11
docs/content/assets/styles/product-switcher.css
Normal file
@@ -0,0 +1,11 @@
|
||||
.product-switcher {
|
||||
font-size: 10px;
|
||||
font-family: 'Rubik', -apple-system, 'BlinkMacSystemFont', 'Segoe UI',
|
||||
'Helvetica Neue', sans-serif;
|
||||
color: #06102a;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.product-switcher img {
|
||||
margin-right: 10px;
|
||||
}
|
10
docs/content/assets/styles/root.css
Normal file
10
docs/content/assets/styles/root.css
Normal file
@@ -0,0 +1,10 @@
|
||||
:root {
|
||||
--dark: #06102a;
|
||||
--blue: #04B5D1;
|
||||
--light-blue: #E4F7FA;
|
||||
|
||||
--input-bg-color: white;
|
||||
--input-color: black;
|
||||
--input-placeholder-color: #bbb;
|
||||
--input-border-color: #dcdcdc;
|
||||
}
|
@@ -308,9 +308,10 @@ For complete details, refer to your provider's _Additional configuration_ link.
|
||||
| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/dyn) |
|
||||
| [Dynu](https://www.dynu.com) | `dynu` | `DYNU_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/dynu) |
|
||||
| [EasyDNS](https://easydns.com/) | `easydns` | `EASYDNS_TOKEN`, `EASYDNS_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/easydns) |
|
||||
| [EdgeDNS](https://www.akamai.com/) | `edgedns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/edgedns) |
|
||||
| External Program | `exec` | `EXEC_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/exec) |
|
||||
| [Exoscale](https://www.exoscale.com) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/exoscale) |
|
||||
| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/fastdns) |
|
||||
| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/edgedns) |
|
||||
| [Gandi](https://www.gandi.net) | `gandi` | `GANDI_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/gandi) |
|
||||
| [Gandi v5](http://doc.livedns.gandi.net) | `gandiv5` | `GANDIV5_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/gandiv5) |
|
||||
| [Glesys](https://glesys.com/) | `glesys` | `GLESYS_API_USER`, `GLESYS_API_KEY`, `GLESYS_DOMAIN` | [Additional configuration](https://go-acme.github.io/lego/dns/glesys) |
|
||||
@@ -319,12 +320,12 @@ For complete details, refer to your provider's _Additional configuration_ link.
|
||||
| [Hetzner](https://hetzner.com) | `hetzner` | `HETZNER_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/hetzner) |
|
||||
| [hosting.de](https://www.hosting.de) | `hostingde` | `HOSTINGDE_API_KEY`, `HOSTINGDE_ZONE_NAME` | [Additional configuration](https://go-acme.github.io/lego/dns/hostingde) |
|
||||
| HTTP request | `httpreq` | `HTTPREQ_ENDPOINT`, `HTTPREQ_MODE`, `HTTPREQ_USERNAME`, `HTTPREQ_PASSWORD` [^1] | [Additional configuration](https://go-acme.github.io/lego/dns/httpreq) |
|
||||
| [HyperOne](https://www.hyperone.com) | `hyperone` | `HYPERONE_PASSPORT_LOCATION`, `HYPERONE_LOCATION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/hyperone) |
|
||||
| [IIJ](https://www.iij.ad.jp/) | `iij` | `IIJ_API_ACCESS_KEY`, `IIJ_API_SECRET_KEY`, `IIJ_DO_SERVICE_CODE` | [Additional configuration](https://go-acme.github.io/lego/dns/iij) |
|
||||
| [INWX](https://www.inwx.de/en) | `inwx` | `INWX_USERNAME`, `INWX_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/inwx) |
|
||||
| [Joker.com](https://joker.com) | `joker` | `JOKER_API_KEY` or `JOKER_USERNAME`, `JOKER_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/joker) |
|
||||
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | [Additional configuration](https://go-acme.github.io/lego/dns/lightsail) |
|
||||
| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/linode) |
|
||||
| [Linode v4](https://www.linode.com) | `linodev4` | `LINODE_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/linodev4) |
|
||||
| [Linode v4](https://www.linode.com) | `linode` | `LINODE_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/linode) |
|
||||
| [Liquid Web](https://www.liquidweb.com/) | `liquidweb` | `LIQUID_WEB_PASSWORD`, `LIQUID_WEB_USERNAME`, `LIQUID_WEB_ZONE` | [Additional configuration](https://go-acme.github.io/lego/dns/liquidweb) |
|
||||
| [LuaDNS](https://luadns.com) | `luadns` | `LUADNS_API_USERNAME`, `LUADNS_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/luadns) |
|
||||
| manual | `manual` | none, but you need to run Traefik interactively [^4], turn on debug log to see instructions and press <kbd>Enter</kbd>. | |
|
||||
@@ -336,7 +337,7 @@ For complete details, refer to your provider's _Additional configuration_ link.
|
||||
| [Netcup](https://www.netcup.eu/) | `netcup` | `NETCUP_CUSTOMER_NUMBER`, `NETCUP_API_KEY`, `NETCUP_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/netcup) |
|
||||
| [Netlify](https://www.netlify.com) | `netlify` | `NETLIFY_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/netlify) |
|
||||
| [NIFCloud](https://cloud.nifty.com/service/dns.htm) | `nifcloud` | `NIFCLOUD_ACCESS_KEY_ID`, `NIFCLOUD_SECRET_ACCESS_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/nifcloud) |
|
||||
| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ns1) |
|
||||
| [NS1](https://ns1.com/) | `ns1` | `NS1_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ns1) |
|
||||
| [Open Telekom Cloud](https://cloud.telekom.de) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/otc) |
|
||||
| [OVH](https://www.ovh.com) | `ovh` | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ovh) |
|
||||
| [Openstack Designate](https://docs.openstack.org/designate) | `designate` | `OS_AUTH_URL`, `OS_USERNAME`, `OS_PASSWORD`, `OS_TENANT_NAME`, `OS_REGION_NAME` | [Additional configuration](https://go-acme.github.io/lego/dns/designate) |
|
||||
@@ -362,7 +363,7 @@ For complete details, refer to your provider's _Additional configuration_ link.
|
||||
| [Zonomi](https://zonomi.com) | `zonomi` | `ZONOMI_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/zonomi) |
|
||||
|
||||
[^1]: more information about the HTTP message format can be found [here](https://go-acme.github.io/lego/dns/httpreq/)
|
||||
[^2]: [providing_credentials_to_your_application](https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application)
|
||||
[^2]: [providing_credentials_to_your_application](https://cloud.google.com/docs/authentication/production)
|
||||
[^3]: [google/default.go](https://github.com/golang/oauth2/blob/36a7019397c4c86cf59eeab3bc0d188bac444277/google/default.go#L61-L76)
|
||||
[^4]: `docker stack` remark: there is no way to support terminal attached to container when deploying with `docker stack`, so you might need to run container with `docker run -it` to generate certificates using `manual` provider.
|
||||
[^5]: The `Global API Key` needs to be used, not the `Origin CA Key`.
|
||||
@@ -484,6 +485,37 @@ docker run -v "/my/host/acme:/etc/traefik/acme" traefik
|
||||
!!! warning
|
||||
For concurrency reasons, this file cannot be shared across multiple instances of Traefik.
|
||||
|
||||
### `preferredChain`
|
||||
|
||||
_Optional, Default=""_
|
||||
|
||||
Preferred chain to use.
|
||||
|
||||
If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.
|
||||
If no match, the default offered chain will be used.
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[certificatesResolvers.myresolver.acme]
|
||||
# ...
|
||||
preferredChain = "ISRG Root X1"
|
||||
# ...
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
certificatesResolvers:
|
||||
myresolver:
|
||||
acme:
|
||||
# ...
|
||||
preferredChain: 'ISRG Root X1'
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
# ...
|
||||
--certificatesresolvers.myresolver.acme.preferredChain="ISRG Root X1"
|
||||
# ...
|
||||
```
|
||||
|
||||
## Fallback
|
||||
|
||||
If Let's Encrypt is not reachable, the following certificates will apply:
|
||||
|
@@ -22,6 +22,16 @@
|
||||
#
|
||||
# caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
|
||||
# Preferred chain to use.
|
||||
#
|
||||
# If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.
|
||||
# If no match, the default offered chain will be used.
|
||||
#
|
||||
# Optional
|
||||
# Default: ""
|
||||
#
|
||||
# preferredChain = "ISRG Root X1"
|
||||
|
||||
# KeyType to use.
|
||||
#
|
||||
# Optional
|
||||
|
@@ -21,6 +21,16 @@
|
||||
#
|
||||
--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
|
||||
# Preferred chain to use.
|
||||
#
|
||||
# If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.
|
||||
# If no match, the default offered chain will be used.
|
||||
#
|
||||
# Optional
|
||||
# Default: ""
|
||||
#
|
||||
--certificatesresolvers.myresolver.acme.preferredchain="ISRG Root X1"
|
||||
|
||||
# KeyType to use.
|
||||
#
|
||||
# Optional
|
||||
|
@@ -24,6 +24,16 @@ certificatesResolvers:
|
||||
#
|
||||
# caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
|
||||
# Preferred chain to use.
|
||||
#
|
||||
# If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.
|
||||
# If no match, the default offered chain will be used.
|
||||
#
|
||||
# Optional
|
||||
# Default: ""
|
||||
#
|
||||
# preferredChain: 'ISRG Root X1'
|
||||
|
||||
# KeyType to use.
|
||||
#
|
||||
# Optional
|
||||
|
@@ -183,9 +183,9 @@ spec:
|
||||
|
||||
### Maximum TLS Version
|
||||
|
||||
We discourages the use of this setting to disable TLS1.3.
|
||||
We discourage the use of this setting to disable TLS1.3.
|
||||
|
||||
The right approach is to update the clients to support TLS1.3.
|
||||
The recommended approach is to update the clients to support TLS1.3.
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
# Dynamic configuration
|
||||
@@ -316,7 +316,7 @@ spec:
|
||||
|
||||
### Strict SNI Checking
|
||||
|
||||
With strict SNI checking, Traefik won't allow connections from clients connections
|
||||
With strict SNI checking enabled, Traefik won't allow connections from clients
|
||||
that do not specify a server_name extension or don't match any certificate configured on the tlsOption.
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
@@ -428,6 +428,7 @@ metadata:
|
||||
|
||||
spec:
|
||||
clientAuth:
|
||||
# the CA certificate is extracted from key `tls.ca` of the given secrets.
|
||||
secretNames:
|
||||
- secretCA
|
||||
clientAuthType: RequireAndVerifyClientCert
|
||||
|
@@ -453,7 +453,7 @@ To apply a redirection:
|
||||
- name: whoami
|
||||
port: 80
|
||||
middlewares:
|
||||
- name: https_redirect
|
||||
- name: https-redirect
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
@@ -476,7 +476,7 @@ To apply a redirection:
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: https_redirect
|
||||
name: https-redirect
|
||||
spec:
|
||||
redirectScheme:
|
||||
scheme: https
|
||||
@@ -528,7 +528,7 @@ To apply a redirection:
|
||||
tls: {}
|
||||
|
||||
middlewares:
|
||||
https_redirect:
|
||||
https-redirect:
|
||||
redirectScheme:
|
||||
scheme: https
|
||||
permanent: true
|
||||
|
@@ -1,17 +1,5 @@
|
||||
# Migration: Steps needed between the versions
|
||||
|
||||
## v2.2.2 to v2.2.5
|
||||
|
||||
### InsecureSNI removal
|
||||
|
||||
In `v2.2.2` we introduced a new flag (`insecureSNI`) which was available as a global option to disable domain fronting.
|
||||
Since `v2.2.5` this global option has been removed, and you should not use it anymore.
|
||||
|
||||
### HostSNI rule matcher removal
|
||||
|
||||
In `v2.2.2` we introduced a new rule matcher (`HostSNI`) which was allowing to match the Server Name Indication at the router level.
|
||||
Since `v2.2.5` this rule has been removed, and you should not use it anymore.
|
||||
|
||||
## v2.0 to v2.1
|
||||
|
||||
### Kubernetes CRD
|
||||
@@ -314,3 +302,15 @@ providers:
|
||||
--entryPoints.websecure.address=:443
|
||||
--providers.kubernetesIngress=true
|
||||
```
|
||||
|
||||
## v2.2.2 to v2.2.5
|
||||
|
||||
### InsecureSNI removal
|
||||
|
||||
In `v2.2.2` we introduced a new flag (`insecureSNI`) which was available as a global option to disable domain fronting.
|
||||
Since `v2.2.5` this global option has been removed, and you should not use it anymore.
|
||||
|
||||
### HostSNI rule matcher removal
|
||||
|
||||
In `v2.2.2` we introduced a new rule matcher (`HostSNI`) for HTTP routers which was allowing to match the Server Name Indication at the router level.
|
||||
Since `v2.2.5` this rule has been removed for HTTP routers, and you should not use it anymore.
|
||||
|
@@ -217,3 +217,32 @@ This allows the logs to be rotated and processed by an external program, such as
|
||||
|
||||
!!! warning
|
||||
This does not work on Windows due to the lack of USR signals.
|
||||
|
||||
## Time Zones
|
||||
|
||||
Traefik will timestamp each log line in UTC time by default.
|
||||
|
||||
It is possible to configure the Traefik to timestamp in a specific timezone by ensuring the following configuration has been made in your environment:
|
||||
|
||||
1. Provide time zone data to `/etc/localtime` or `/usr/share/zoneinfo` (based on your distribution) or set the environment variable TZ to the desired timezone
|
||||
2. Specify the field `StartLocal` by dropping the field named `StartUTC` (available on the default Common Log Format (CLF) as well as JSON)
|
||||
|
||||
Example utilizing Docker Compose:
|
||||
|
||||
```yaml
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v2.2
|
||||
environment:
|
||||
- TZ=US/Alaska
|
||||
command:
|
||||
- --accesslog
|
||||
- --accesslog.fields.names.StartUTC=drop
|
||||
- --providers.docker
|
||||
ports:
|
||||
- 80:80
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
```
|
||||
|
@@ -199,7 +199,7 @@ Therefore you **must** specify the port to use for communication by using the la
|
||||
Docker Swarm Mode follows the same rules as Docker [API Access](#docker-api-access).
|
||||
|
||||
As the Swarm API is only exposed on the [manager nodes](https://docs.docker.com/engine/swarm/how-swarm-mode-works/nodes/#manager-nodes), you should schedule Traefik on the Swarm manager nodes by default,
|
||||
by deploying Traefik with a [constraint](https://success.docker.com/article/using-contraints-and-labels-to-control-the-placement-of-containers) on the node's "role":
|
||||
by deploying Traefik with a constraint on the node's "role":
|
||||
|
||||
```shell tab="With Docker CLI"
|
||||
docker service create \
|
||||
|
@@ -222,7 +222,7 @@ http:
|
||||
inFlightReq:
|
||||
amount: 42
|
||||
sourceCriterion:
|
||||
ipstrategy:
|
||||
ipStrategy:
|
||||
depth: 42
|
||||
excludedIPs:
|
||||
- foobar
|
||||
@@ -259,7 +259,7 @@ http:
|
||||
period: 42
|
||||
burst: 42
|
||||
sourceCriterion:
|
||||
ipstrategy:
|
||||
ipStrategy:
|
||||
depth: 42
|
||||
excludedIPs:
|
||||
- foobar
|
||||
|
@@ -81,6 +81,9 @@ HTTP challenge EntryPoint
|
||||
`--certificatesresolvers.<name>.acme.keytype`:
|
||||
KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. (Default: ```RSA4096```)
|
||||
|
||||
`--certificatesresolvers.<name>.acme.preferredchain`:
|
||||
Preferred chain to use.
|
||||
|
||||
`--certificatesresolvers.<name>.acme.storage`:
|
||||
Storage to use. (Default: ```acme.json```)
|
||||
|
||||
|
@@ -81,6 +81,9 @@ HTTP challenge EntryPoint
|
||||
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_KEYTYPE`:
|
||||
KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. (Default: ```RSA4096```)
|
||||
|
||||
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_PREFERREDCHAIN`:
|
||||
Preferred chain to use.
|
||||
|
||||
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_STORAGE`:
|
||||
Storage to use. (Default: ```acme.json```)
|
||||
|
||||
|
@@ -314,6 +314,7 @@
|
||||
[certificatesResolvers.CertificateResolver0.acme]
|
||||
email = "foobar"
|
||||
caServer = "foobar"
|
||||
preferredChain = "foobar"
|
||||
storage = "foobar"
|
||||
keyType = "foobar"
|
||||
[certificatesResolvers.CertificateResolver0.acme.dnsChallenge]
|
||||
@@ -328,6 +329,7 @@
|
||||
[certificatesResolvers.CertificateResolver1.acme]
|
||||
email = "foobar"
|
||||
caServer = "foobar"
|
||||
preferredChain = "foobar"
|
||||
storage = "foobar"
|
||||
keyType = "foobar"
|
||||
[certificatesResolvers.CertificateResolver1.acme.dnsChallenge]
|
||||
|
@@ -328,6 +328,7 @@ certificatesResolvers:
|
||||
acme:
|
||||
email: foobar
|
||||
caServer: foobar
|
||||
preferredChain: foobar
|
||||
storage: foobar
|
||||
keyType: foobar
|
||||
dnsChallenge:
|
||||
@@ -344,6 +345,7 @@ certificatesResolvers:
|
||||
acme:
|
||||
email: foobar
|
||||
caServer: foobar
|
||||
preferredChain: foobar
|
||||
storage: foobar
|
||||
keyType: foobar
|
||||
dnsChallenge:
|
||||
|
@@ -535,7 +535,7 @@ You can declare UDP Routers and/or Services using labels.
|
||||
my-container:
|
||||
# ...
|
||||
labels:
|
||||
- "traefik.udp.routers.my-router.entrypoint=udp"
|
||||
- "traefik.udp.routers.my-router.entrypoints=udp"
|
||||
- "traefik.udp.services.my-service.loadbalancer.server.port=4123"
|
||||
```
|
||||
|
||||
|
@@ -108,16 +108,16 @@ The Kubernetes Ingress Controller, The Custom Resource Way.
|
||||
name: myingressroute
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
|
||||
routes:
|
||||
- match: Host(`foo`) && PathPrefix(`/bar`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: whoami
|
||||
port: 80
|
||||
routes:
|
||||
- match: Host(`foo`) && PathPrefix(`/bar`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: whoami
|
||||
port: 80
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
@@ -126,15 +126,15 @@ The Kubernetes Ingress Controller, The Custom Resource Way.
|
||||
name: ingressroute.tcp
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- tcpep
|
||||
routes:
|
||||
- match: HostSNI(`bar`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: whoamitcp
|
||||
port: 8080
|
||||
spec:
|
||||
entryPoints:
|
||||
- tcpep
|
||||
routes:
|
||||
- match: HostSNI(`bar`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: whoamitcp
|
||||
port: 8080
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
@@ -143,14 +143,14 @@ The Kubernetes Ingress Controller, The Custom Resource Way.
|
||||
name: ingressroute.udp
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- fooudp
|
||||
routes:
|
||||
- kind: Rule
|
||||
services:
|
||||
- name: whoamiudp
|
||||
port: 8080
|
||||
spec:
|
||||
entryPoints:
|
||||
- fooudp
|
||||
routes:
|
||||
- kind: Rule
|
||||
services:
|
||||
- name: whoamiudp
|
||||
port: 8080
|
||||
```
|
||||
|
||||
```yaml tab="Whoami"
|
||||
|
@@ -317,7 +317,7 @@ Below are the available options for the health check mechanism:
|
||||
|
||||
- `path` is appended to the server URL to set the health check endpoint.
|
||||
- `scheme`, if defined, will replace the server URL `scheme` for the health check endpoint
|
||||
- `hostname`, if defined, will replace the server URL `hostname` for the health check endpoint.
|
||||
- `hostname`, if defined, will apply `Host` header `hostname` to the the health check request.
|
||||
- `port`, if defined, will replace the server URL `port` for the health check endpoint.
|
||||
- `interval` defines the frequency of the health check calls.
|
||||
- `timeout` defines the maximum duration Traefik will wait for a health check request before considering the server failed (unhealthy).
|
||||
|
@@ -29,7 +29,12 @@ theme:
|
||||
copyright: "Copyright © 2016-2020 Containous"
|
||||
|
||||
extra_css:
|
||||
- assets/styles/extra.css # Our custom styles
|
||||
- assets/styles/root.css
|
||||
- assets/styles/header.css
|
||||
- assets/styles/footer.css
|
||||
- assets/styles/menu.css
|
||||
- assets/styles/content.css
|
||||
- assets/styles/product-switcher.css
|
||||
- assets/styles/atom-one-light.css # HightlightJS's CSS theme
|
||||
|
||||
extra_javascript:
|
||||
|
@@ -21,7 +21,7 @@ find "${PATH_TO_SITE}" -type f -not -path "/app/site/theme/*" \
|
||||
--check_external_hash \
|
||||
--alt_ignore="/traefik.logo.png/" \
|
||||
--http_status_ignore="0,500,501,503" \
|
||||
--url_ignore="/https://groups.google.com/a/traefik.io/forum/#!forum/security/,/localhost:/,/127.0.0.1:/,/fonts.gstatic.com/,/.minikube/,/github.com\/containous\/traefik\/*edit*/,/github.com\/containous\/traefik\/$/,/docs.traefik.io/,/github\.com\/golang\/oauth2\/blob\/36a7019397c4c86cf59eeab3bc0d188bac444277\/.+/,/www.akamai.com/" \
|
||||
--url_ignore="/https://groups.google.com/a/traefik.io/forum/#!forum/security/,/localhost:/,/127.0.0.1:/,/fonts.gstatic.com/,/.minikube/,/github.com\/containous\/traefik\/*edit*/,/github.com\/containous\/traefik\/$/,/docs.traefik.io/,/github\.com\/golang\/oauth2\/blob\/36a7019397c4c86cf59eeab3bc0d188bac444277\/.+/,/www.akamai.com/,/pilot.traefik.io\/profile/,/containo.us/,/docs.mae.sh/,/www.mkdocs.org/,/squidfunk.github.io/,/ietf.org/" \
|
||||
'{}' 1>/dev/null
|
||||
## HTML-proofer options at https://github.com/gjtorikian/html-proofer#configuration
|
||||
|
||||
|
44
docs/theme/main.html
vendored
44
docs/theme/main.html
vendored
@@ -10,6 +10,46 @@
|
||||
<!-- End Google Tag Manager -->
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
{% include "partials/containous-header.html" %}
|
||||
{% include "partials/header.html" %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block site_nav %}
|
||||
{% if nav %}
|
||||
{% include "partials/product-switcher.html" %}
|
||||
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
|
||||
{% include "partials/search.html" %}
|
||||
{% if "search" in config["plugins"] %}
|
||||
<label class="md-icon md-icon--search md-header-nav__button" for="__search"></label>
|
||||
{% endif %}
|
||||
<div class="md-sidebar__scrollwrap">
|
||||
<div class="md-sidebar__inner">
|
||||
{% include "partials/nav.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if page.toc %}
|
||||
<div class="md-sidebar md-sidebar--secondary" data-md-component="toc">
|
||||
{% if config.repo_url %}
|
||||
<div class="md-flex__cell md-flex__cell--shrink repo_url">
|
||||
<div class="md-header-nav__source">
|
||||
{% include "partials/source.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="md-sidebar__scrollwrap">
|
||||
<div class="md-sidebar__inner">
|
||||
{% include "partials/toc.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block footer %}
|
||||
|
||||
{% import "partials/language.html" as lang with context %}
|
||||
@@ -36,6 +76,10 @@
|
||||
Material for MkDocs</a>
|
||||
</div>
|
||||
|
||||
<div class="md-footer-privacy-policy">
|
||||
<a href="https://containo.us/legal/privacy-and-cookie-policy/" title="Privacy Policy">Privacy Policy</a>
|
||||
</div>
|
||||
|
||||
<!-- Social links -->
|
||||
{% block social %}
|
||||
{% include "partials/social.html" %}
|
||||
|
238
docs/theme/partials/containous-header.html
vendored
Normal file
238
docs/theme/partials/containous-header.html
vendored
Normal file
File diff suppressed because one or more lines are too long
49
docs/theme/partials/product-switcher.html
vendored
Normal file
49
docs/theme/partials/product-switcher.html
vendored
Normal file
File diff suppressed because one or more lines are too long
14
go.mod
14
go.mod
@@ -16,7 +16,7 @@ require (
|
||||
github.com/abbot/go-http-auth v0.0.0-00010101000000-000000000000
|
||||
github.com/abronan/valkeyrie v0.0.0-20200127174252-ef4277a138cd
|
||||
github.com/c0va23/go-proxyprotocol v0.9.1
|
||||
github.com/cenkalti/backoff/v4 v4.0.0
|
||||
github.com/cenkalti/backoff/v4 v4.0.2
|
||||
github.com/containerd/containerd v1.3.2 // indirect
|
||||
github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||
@@ -36,7 +36,7 @@ require (
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2
|
||||
github.com/go-acme/lego/v3 v3.8.0
|
||||
github.com/go-acme/lego/v4 v4.0.1
|
||||
github.com/go-check/check v0.0.0-00010101000000-000000000000
|
||||
github.com/go-kit/kit v0.9.0
|
||||
github.com/gogo/protobuf v1.3.0 // indirect
|
||||
@@ -55,7 +55,7 @@ require (
|
||||
github.com/libkermit/docker-check v0.0.0-20171122104347-1113af38e591
|
||||
github.com/magiconair/properties v1.8.1 // indirect
|
||||
github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f
|
||||
github.com/miekg/dns v1.1.27
|
||||
github.com/miekg/dns v1.1.31
|
||||
github.com/mitchellh/copystructure v1.0.0
|
||||
github.com/mitchellh/hashstructure v1.0.0
|
||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
|
||||
@@ -72,7 +72,7 @@ require (
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4
|
||||
github.com/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154
|
||||
github.com/tinylib/msgp v1.0.2 // indirect
|
||||
github.com/uber/jaeger-client-go v2.22.1+incompatible
|
||||
@@ -84,13 +84,13 @@ require (
|
||||
github.com/vulcand/predicate v1.1.0
|
||||
go.elastic.co/apm v1.7.0
|
||||
go.elastic.co/apm/module/apmot v1.7.0
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
|
||||
google.golang.org/grpc v1.27.1
|
||||
gopkg.in/DataDog/dd-trace-go.v1 v1.19.0
|
||||
gopkg.in/fsnotify.v1 v1.4.7
|
||||
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
k8s.io/api v0.17.3
|
||||
k8s.io/apimachinery v0.17.3
|
||||
k8s.io/client-go v0.17.3
|
||||
|
138
go.sum
138
go.sum
@@ -101,12 +101,12 @@ github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrd
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
github.com/abronan/valkeyrie v0.0.0-20200127174252-ef4277a138cd h1:UlQRt3CZdeD+WfDamDtdDDOu84CYbGIh9/B28TgzCZk=
|
||||
github.com/abronan/valkeyrie v0.0.0-20200127174252-ef4277a138cd/go.mod h1:2RUNONRAQ8bS1QcVJF3dYO/faiEro6NAAIQ6CqBkpD0=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.8 h1:6rJvj+NXjjauunLeS7uGy891F1cuAwsWKa9iGzTjz1s=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.8/go.mod h1:aVvklgKsPENRkl29bNwrHISa1F+YLGTHArMxZMBqWM8=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.18 h1:KyEv96ncdgOIJRTKMcWlIqM0umf8X3LQP6oOyg0hNsM=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.18/go.mod h1:L+HB2uBoDgi3+r1pJEJcbGwyyHhd2QXaGsKLbDwtm8Q=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.112 h1:E273ePcLllLIBGg5BHr3T0Fp1BJTvUyh5Y57ziSy81w=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.112/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.458 h1:UdFGeD4Eg6gZFQ7tLWdguNLpBTevJwBa97S0YunGy1k=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.458/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
|
||||
github.com/apache/thrift v0.12.0 h1:pODnxUFNcjP9UTLZGTdeh+j16A8lJbRvD3rOtrk/7bs=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
@@ -130,8 +130,8 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/c0va23/go-proxyprotocol v0.9.1 h1:5BCkp0fDJOhzzH1lhjUgHhmZz9VvRMMif1U2D31hb34=
|
||||
github.com/c0va23/go-proxyprotocol v0.9.1/go.mod h1:TNjUV+llvk8TvWJxlPYAeAYZgSzT/iicNr3nWBWX320=
|
||||
github.com/cenkalti/backoff/v4 v4.0.0 h1:6VeaLF9aI+MAUQ95106HwWzYZgJJpZ4stumjj6RFYAU=
|
||||
github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
|
||||
github.com/cenkalti/backoff/v4 v4.0.2 h1:JIufpQLbh4DkbQoii76ItQIUFzevQSqOLZca4eamEDs=
|
||||
github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -141,8 +141,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/cloudflare-go v0.10.2 h1:VBodKICVPnwmDxstcW3biKcDSpFIfS/RELUXsZSBYK4=
|
||||
github.com/cloudflare/cloudflare-go v0.10.2/go.mod h1:qhVI5MKwBGhdNU89ZRz2plgYutcJ5PCekLxXn56w6SY=
|
||||
github.com/cloudflare/cloudflare-go v0.13.2 h1:bhMGoNhAg21DuqJjU9jQepRRft6vYfo6pejT3NN4V6A=
|
||||
github.com/cloudflare/cloudflare-go v0.13.2/go.mod h1:27kfc1apuifUmJhp069y0+hwlKDg4bd8LWlu7oKeZvM=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY=
|
||||
@@ -183,8 +183,8 @@ github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pq
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpu/goacmedns v0.0.2 h1:hYAgjnPu7HogTgb8trqQouR/RrBgXq1TPBgmxbK9eRA=
|
||||
github.com/cpu/goacmedns v0.0.2/go.mod h1:4MipLkI+qScwqtVxcNO6okBhbgRrr7/tKXUSgSL0teQ=
|
||||
github.com/cpu/goacmedns v0.0.3 h1:QOeMpIEsIdm1LSASSswjaTf8CXmzcrgy5OeCfHjppA4=
|
||||
github.com/cpu/goacmedns v0.0.3/go.mod h1:4MipLkI+qScwqtVxcNO6okBhbgRrr7/tKXUSgSL0teQ=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -194,10 +194,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2 h1:G9/PqfhOrt8JXnw0DGTfVoOkKHDhOlEZqhE/cu+NvQM=
|
||||
github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/dnsimple/dnsimple-go v0.60.0 h1:N+q+ML1CZGf+5r4udu9Opy7WJNtOaFT9aM86Af9gLhk=
|
||||
github.com/dnsimple/dnsimple-go v0.60.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
|
||||
github.com/dnsimple/dnsimple-go v0.63.0 h1:0doY8VW/ckRIMTmOw4E1vwqo+bhtjDzvh1pU2ZteFGA=
|
||||
github.com/dnsimple/dnsimple-go v0.63.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
|
||||
github.com/docker/cli v0.0.0-20200221155518-740919cc7fc0 h1:hlGHcYGaaHs/yffSubcUKlp8TyV1v7qhcZZ5nGNQ2Fw=
|
||||
github.com/docker/cli v0.0.0-20200221155518-740919cc7fc0/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||
@@ -245,8 +243,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
||||
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/exoscale/egoscale v0.18.1 h1:1FNZVk8jHUx0AvWhOZxLEDNlacTU0chMXUUNkm9EZaI=
|
||||
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
|
||||
github.com/exoscale/egoscale v0.23.0 h1:hoUDzrO8yNoobNdnrRvlRFjfg3Ng0vQTrv6bXRJu6z0=
|
||||
github.com/exoscale/egoscale v0.23.0/go.mod h1:hRo78jkjkCDKpivQdRBEpNYF5+cVpCJCPDg2/r45KaY=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
@@ -261,8 +259,8 @@ github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2/go.mod h1:GLy
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-acme/lego/v3 v3.8.0 h1:9OOEn54eZvEPRRdM7xiC5f7EBW0MlEeChr+kzlIhdN8=
|
||||
github.com/go-acme/lego/v3 v3.8.0/go.mod h1:kYiHYgSRzb1l2NQPWvWvkVG5etNCusGFsZc2MTak3m0=
|
||||
github.com/go-acme/lego/v4 v4.0.1 h1:vPwbTYfw5+fOaON9rWCN43iNrPw5cdJBhNMnA8oxBTM=
|
||||
github.com/go-acme/lego/v4 v4.0.1/go.mod h1:pIFm5tWkXSgiAEfJ/XQCQIvX1cEvHFwbgLZyx8OVSUE=
|
||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
@@ -291,6 +289,8 @@ github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dp
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 h1:JVrqSeQfdhYRFk24TvhTZWU0q8lfCojxZQFi3Ou7+uY=
|
||||
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
@@ -370,8 +370,11 @@ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsC
|
||||
github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI=
|
||||
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gophercloud/gophercloud v0.3.0 h1:6sjpKIpVwRIIwmcEGp+WwNovNsem+c+2vm6oxshRpL8=
|
||||
github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gophercloud/gophercloud v0.6.1-0.20191122030953-d8ac278c1c9d/go.mod h1:ozGNgr9KYOVATV5jsgHl/ceCDXGuguqOZAzoQ/2vcNM=
|
||||
github.com/gophercloud/gophercloud v0.7.0 h1:vhmQQEM2SbnGCg2/3EzQnQZ3V7+UCGy9s8exQCprNYg=
|
||||
github.com/gophercloud/gophercloud v0.7.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss=
|
||||
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c h1:iawx2ojEQA7c+GmkaVO5sN+k8YONibXyDO8RlsC+1bs=
|
||||
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c/go.mod h1:ehWUbLQJPqS0Ep+CxeD559hsm9pthPXadJNKwZkp43w=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
@@ -417,8 +420,8 @@ github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:
|
||||
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.7 h1:8/CAEZt/+F7kR7GevNHulKkUjLht3CPmn7egmhieNKo=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.7/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
|
||||
@@ -503,10 +506,10 @@ github.com/libkermit/docker v0.0.0-20171122101128-e6674d32b807 h1:/7J1WDQd6Xn1Pr
|
||||
github.com/libkermit/docker v0.0.0-20171122101128-e6674d32b807/go.mod h1:std11u6pTaNwryy0Hy1dTQNdHKka1jNpflEieKtv5VE=
|
||||
github.com/libkermit/docker-check v0.0.0-20171122104347-1113af38e591 h1:+zkZyvOyHZZUnITx0oJxAG/2+YLOjmy8YMMa1aWyrs4=
|
||||
github.com/libkermit/docker-check v0.0.0-20171122104347-1113af38e591/go.mod h1:EBQ0jeOrBpOTkquwjmJl4W6z5xqlf5oA2LZfTqRNcO0=
|
||||
github.com/linode/linodego v0.10.0 h1:AMdb82HVgY8o3mjBXJcUv9B+fnJjfDMn2rNRGbX+jvM=
|
||||
github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA=
|
||||
github.com/liquidweb/liquidweb-go v1.6.0 h1:vIj1I/Wf97fUnyirD+bi6Y63c0GiXk9nKI1+sFFl3G0=
|
||||
github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ=
|
||||
github.com/linode/linodego v0.21.0 h1:XykohqzVIV6hvjBn03cj7FGxYARFSrlfJodQrtHynqk=
|
||||
github.com/linode/linodego v0.21.0/go.mod h1:UTpq1JUZD0CZsJ8rt+0CRkqbzrp1MbGakVPt2DXY5Mk=
|
||||
github.com/liquidweb/liquidweb-go v1.6.1 h1:O51RbJo3ZEWFkZFfP32zIF6MCoZzwuuybuXsvZvVEEI=
|
||||
github.com/liquidweb/liquidweb-go v1.6.1/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ=
|
||||
github.com/looplab/fsm v0.1.0 h1:Qte7Zdn/5hBNbXzP7yxVU4OIFHWXBovyTT2LaBTyC20=
|
||||
github.com/looplab/fsm v0.1.0/go.mod h1:m2VaOfDHxqXBBMgc26m6yUOwkFn8H2AlJDE+jd/uafI=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
@@ -524,13 +527,13 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
|
||||
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo=
|
||||
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
@@ -547,8 +550,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA=
|
||||
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
|
||||
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -569,13 +572,15 @@ github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/nrdcg/auroradns v1.0.1 h1:m/kBq83Xvy3cU261MOknd8BdnOk12q4lAWM+kOdsC2Y=
|
||||
github.com/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI=
|
||||
github.com/nrdcg/desec v0.5.0 h1:foL7hqivYOMlv0qDhHXJtuuEXkqf0wW9EQMqyrt228g=
|
||||
github.com/nrdcg/desec v0.5.0/go.mod h1:2ejvMazkav1VdDbv2HeQO7w+Ta1CGHqzQr27ZBYTuEQ=
|
||||
github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U=
|
||||
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
|
||||
github.com/nrdcg/goinwx v0.7.0 h1:j6JlOp0nNwtvaP09TvKqc9pktjH81nOad0+Gx9S1t9U=
|
||||
github.com/nrdcg/goinwx v0.7.0/go.mod h1:4tKJOCi/1lTxuw9/yB2Ez0aojwtUCSkckjc22eALpqE=
|
||||
github.com/nrdcg/goinwx v0.8.1 h1:20EQ/JaGFnSKwiDH2JzjIpicffl3cPk6imJBDqVBVtU=
|
||||
github.com/nrdcg/goinwx v0.8.1/go.mod h1:tILVc10gieBp/5PMvbcYeXM6pVQ+c9jxDZnpaR1UW7c=
|
||||
github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg=
|
||||
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
@@ -612,10 +617,10 @@ github.com/openzipkin/zipkin-go v0.2.1 h1:noL5/5Uf1HpVl3wNsfkZhIKbSWCVi5jgqkONNx
|
||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI=
|
||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/oracle/oci-go-sdk v7.0.0+incompatible h1:oj5ESjXwwkFRdhZSnPlShvLWYdt/IZ65RQxveYM3maA=
|
||||
github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
|
||||
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014 h1:37VE5TYj2m/FLA9SNr4z0+A0JefvTmR60Zwf8XSEV7c=
|
||||
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
|
||||
github.com/oracle/oci-go-sdk v24.2.0+incompatible h1:T+OS7BSWy5vVKfngy6Ln5lzIO09nqVxNxHJY2Waivs8=
|
||||
github.com/oracle/oci-go-sdk v24.2.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
|
||||
github.com/ovh/go-ovh v1.1.0 h1:bHXZmw8nTgZin4Nv7JuaLs0KG5x54EQR7migYTd1zrk=
|
||||
github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
@@ -677,8 +682,8 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sacloud/libsacloud v1.26.1 h1:td3Kd7lvpSAxxHEVpnaZ9goHmmhi0D/RfP0Rqqf/kek=
|
||||
github.com/sacloud/libsacloud v1.26.1/go.mod h1:79ZwATmHLIFZIMd7sxA3LwzVy/B77uj3LDoToVTxDoQ=
|
||||
github.com/sacloud/libsacloud v1.36.2 h1:aosI7clbQ9IU0Hj+3rpk3SKJop5nLPpLThnWCivPqjI=
|
||||
github.com/sacloud/libsacloud v1.36.2/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec h1:6ncX5ko6B9LntYM0YBRXkiSaZMmLYeZ/NWcmeB43mMY=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis=
|
||||
@@ -717,17 +722,17 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 h1:XGopsea1Dw7ecQ8JscCNQXDGYAKDiWjDeXnpN/+BY9g=
|
||||
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7 h1:CpHxIaZzVy26GqJn8ptRyto8fuoYOd1v0fXm9bG3wQ8=
|
||||
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY=
|
||||
github.com/tinylib/msgp v1.0.2 h1:DfdQrzQa7Yh2es9SuLkixqxuXS2SxsdYn0KbdrOGWD8=
|
||||
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/transip/gotransip/v6 v6.0.2 h1:rOCMY607PYF+YvMHHtJt7eZRd0mx/uhyz6dsXWPmn+4=
|
||||
github.com/transip/gotransip/v6 v6.0.2/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
|
||||
github.com/transip/gotransip/v6 v6.2.0 h1:0Z+qVsyeiQdWfcAUeJyF0IEKAPvhJwwpwPi2WGtBIiE=
|
||||
github.com/transip/gotransip/v6 v6.2.0/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/uber-go/atomic v1.3.2 h1:Azu9lPBWRNKzYXSIwRfgRuDuS0YKsK4NFhiQv98gkxo=
|
||||
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
|
||||
@@ -740,15 +745,15 @@ github.com/unrolled/render v1.0.2/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0ob
|
||||
github.com/unrolled/secure v1.0.7 h1:BcQHp3iKZyZCKj5gRqwQG+5urnGBF00wGgoPPwtheVQ=
|
||||
github.com/unrolled/secure v1.0.7/go.mod h1:uGc1OcRF8gCVBA+ANksKmvM85Hka6SZtQIbrKc3sHS4=
|
||||
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/vdemeester/shakers v0.1.0 h1:K+n9sSyUCg2ywmZkv+3c7vsYZfivcfKhMh8kRxCrONM=
|
||||
github.com/vdemeester/shakers v0.1.0/go.mod h1:IZ1HHynUOQt32iQ3rvAeVddXLd19h/6LWiKsh9RZtAQ=
|
||||
github.com/vulcand/oxy v1.1.0 h1:DbBijGo1+6cFqR9jarkMxasdj0lgWwrrFtue6ijek4Q=
|
||||
github.com/vulcand/oxy v1.1.0/go.mod h1:ADiMYHi8gkGl2987yQIzDRoXZilANF4WtKaQ92OppKY=
|
||||
github.com/vulcand/predicate v1.1.0 h1:Gq/uWopa4rx/tnZu2opOSBqHK63Yqlou/SzrbwdJiNg=
|
||||
github.com/vulcand/predicate v1.1.0/go.mod h1:mlccC5IRBoc2cIFmCB8ZM62I3VDb6p2GXESMHa3CnZg=
|
||||
github.com/vultr/govultr v0.4.2 h1:9i8xKZ+xp6vwZ9raqHoBLzhB4wCnMj7nOQTj5YIRLWY=
|
||||
github.com/vultr/govultr v0.4.2/go.mod h1:TUuUizMOFc7z+PNMssb6iGjKjQfpw5arIaOLfocVudQ=
|
||||
github.com/vultr/govultr v0.5.0 h1:iQzYhzbokmpDARbvIkvTkoyS7WMH82zVTKAL1PZ4JOA=
|
||||
github.com/vultr/govultr v0.5.0/go.mod h1:wZZXZbYbqyY1n3AldoeYNZK4Wnmmoq6dNFkvd5TV3ss=
|
||||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
|
||||
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||
@@ -805,10 +810,11 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49N
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6 h1:TjszyFsQsyZNHwdVdZ5m7bjmreu0znc2kRYsEml9/Ww=
|
||||
golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -845,7 +851,6 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -863,13 +868,13 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3 h1:6KET3Sqa7fkVfD63QnAM81ZeYg5n4HwApOJkufONnHA=
|
||||
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -878,6 +883,8 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
@@ -924,6 +931,7 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPT
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e h1:9vRrk9YW2BTzLP0VCB9ZDjU4cPqkg+IDWL7XgxA1yxQ=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -934,6 +942,8 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -943,10 +953,10 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0 h1:xQwXv67TxFo9nC1GJFyab5eq/5B590r6RlnL/G8Sz7w=
|
||||
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -977,6 +987,7 @@ golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361 h1:RIIXAeV6GvDBuADKumTODatUqANFZ+5BPMnzsy4hulY=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
@@ -1064,6 +1075,8 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
@@ -1074,6 +1087,8 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.51.1 h1:GyboHr4UqMiLUybYjd22ZjQIKEJEpgtLXtuGbR21Oho=
|
||||
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
|
||||
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
|
||||
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
|
||||
@@ -1084,23 +1099,28 @@ gopkg.in/jcmturner/gokrb5.v7 v7.2.3 h1:hHMV/yKPwMnJhPuPx7pH2Uw/3Qyf+thJYlisUc440
|
||||
gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
|
||||
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
|
||||
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc h1:GAcf+t0o8gdJAdSFYdE9wChu4bIyguMVqz0RHiFL5VY=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.4.2 h1:H6VnvLez0GjxXsXat6MUFmKuiMFuDaMBdGF9qtkmODo=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.4.2/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk=
|
||||
gopkg.in/redis.v5 v5.2.9 h1:MNZYOLPomQzZMfpN3ZtD1uyJ2IDonTTlxYiV/pEApiw=
|
||||
gopkg.in/redis.v5 v5.2.9/go.mod h1:6gtv0/+A4iM08kdRfocWYB3bLX2tebpNtfKlFT6H4mY=
|
||||
gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc=
|
||||
gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
33
integration/fixtures/headers/secure_multiple.toml
Normal file
33
integration/fixtures/headers/secure_multiple.toml
Normal file
@@ -0,0 +1,33 @@
|
||||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.web]
|
||||
address = ":8000"
|
||||
|
||||
[providers.file]
|
||||
filename = "{{ .SelfFilename }}"
|
||||
|
||||
## dynamic configuration ##
|
||||
|
||||
[http.routers]
|
||||
[http.routers.router1]
|
||||
rule = "Host(`test.localhost`)"
|
||||
middlewares = ["foo", "bar"]
|
||||
service = "service1"
|
||||
|
||||
|
||||
[http.middlewares]
|
||||
[http.middlewares.foo.headers]
|
||||
frameDeny = true
|
||||
[http.middlewares.bar.headers]
|
||||
contentTypeNosniff = true
|
||||
|
||||
[http.services]
|
||||
[http.services.service1.loadBalancer]
|
||||
[[http.services.service1.loadBalancer.servers]]
|
||||
url = "http://127.0.0.1:9000"
|
@@ -162,3 +162,44 @@ func (s *HeadersSuite) TestSecureHeadersResponses(c *check.C) {
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HeadersSuite) TestMultipleSecureHeadersResponses(c *check.C) {
|
||||
file := s.adaptFile(c, "fixtures/headers/secure_multiple.toml", struct{}{})
|
||||
defer os.Remove(file)
|
||||
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||
defer display(c)
|
||||
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
backend := startTestServer("9000", http.StatusOK, "")
|
||||
defer backend.Close()
|
||||
|
||||
err = try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
testCase := []struct {
|
||||
desc string
|
||||
expected http.Header
|
||||
reqHost string
|
||||
}{
|
||||
{
|
||||
desc: "Feature-Policy Set",
|
||||
expected: http.Header{
|
||||
"X-Frame-Options": {"DENY"},
|
||||
"X-Content-Type-Options": {"nosniff"},
|
||||
},
|
||||
reqHost: "test.localhost",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCase {
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = test.reqHost
|
||||
|
||||
err = try.Request(req, 500*time.Millisecond, try.HasHeaderStruct(test.expected))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/log"
|
||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||
@@ -23,11 +24,29 @@ func (g DashboardHandler) Append(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/", http.StatusFound)
|
||||
HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
http.Redirect(resp, req, safePrefix(req)+"/dashboard/", http.StatusFound)
|
||||
})
|
||||
|
||||
router.Methods(http.MethodGet).
|
||||
PathPrefix("/dashboard/").
|
||||
Handler(http.StripPrefix("/dashboard/", http.FileServer(g.Assets)))
|
||||
}
|
||||
|
||||
func safePrefix(req *http.Request) string {
|
||||
prefix := req.Header.Get("X-Forwarded-Prefix")
|
||||
if prefix == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
parse, err := url.Parse(prefix)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if parse.Host != "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return parse.Path
|
||||
}
|
||||
|
54
pkg/api/dashboard_test.go
Normal file
54
pkg/api/dashboard_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_safePrefix(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
value string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "host",
|
||||
value: "https://example.com",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
desc: "host with path",
|
||||
value: "https://example.com/foo/bar?test",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
desc: "path",
|
||||
value: "/foo/bar",
|
||||
expected: "/foo/bar",
|
||||
},
|
||||
{
|
||||
desc: "path without leading slash",
|
||||
value: "foo/bar",
|
||||
expected: "foo/bar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
req.Header.Set("X-Forwarded-Prefix", test.value)
|
||||
|
||||
prefix := safePrefix(req)
|
||||
|
||||
assert.Equal(t, test.expected, prefix)
|
||||
})
|
||||
}
|
||||
}
|
@@ -300,7 +300,7 @@ type PassTLSClientCert struct {
|
||||
// If none are set, the default is to use the request's remote address field.
|
||||
// All fields are mutually exclusive.
|
||||
type SourceCriterion struct {
|
||||
IPStrategy *IPStrategy `json:"ipStrategy" toml:"ipStrategy, omitempty"`
|
||||
IPStrategy *IPStrategy `json:"ipStrategy,omitempty" toml:"ipStrategy,omitempty" yaml:"ipStrategy,omitempty"`
|
||||
RequestHeaderName string `json:"requestHeaderName,omitempty" toml:"requestHeaderName,omitempty" yaml:"requestHeaderName,omitempty"`
|
||||
RequestHost bool `json:"requestHost,omitempty" toml:"requestHost,omitempty" yaml:"requestHost,omitempty"`
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ import (
|
||||
"github.com/containous/traefik/v2/pkg/tracing/zipkin"
|
||||
"github.com/containous/traefik/v2/pkg/types"
|
||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||
legolog "github.com/go-acme/lego/v3/log"
|
||||
legolog "github.com/go-acme/lego/v4/log"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@@ -33,7 +33,7 @@ const (
|
||||
)
|
||||
|
||||
type serviceBuilder interface {
|
||||
BuildHTTP(ctx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error)
|
||||
BuildHTTP(ctx context.Context, serviceName string) (http.Handler, error)
|
||||
}
|
||||
|
||||
// customErrors is a middleware that provides the custom error pages..
|
||||
@@ -54,7 +54,7 @@ func New(ctx context.Context, next http.Handler, config dynamic.ErrorPage, servi
|
||||
return nil, err
|
||||
}
|
||||
|
||||
backend, err := serviceBuilder.BuildHTTP(ctx, config.Service, nil)
|
||||
backend, err := serviceBuilder.BuildHTTP(ctx, config.Service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -150,7 +150,7 @@ type mockServiceBuilder struct {
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
func (m *mockServiceBuilder) BuildHTTP(_ context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error) {
|
||||
func (m *mockServiceBuilder) BuildHTTP(ctx context.Context, serviceName string) (http.Handler, error) {
|
||||
return m.handler, nil
|
||||
}
|
||||
|
||||
|
170
pkg/middlewares/headers/header.go
Normal file
170
pkg/middlewares/headers/header.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package headers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/v2/pkg/log"
|
||||
)
|
||||
|
||||
// Header is a middleware that helps setup a few basic security features.
|
||||
// A single headerOptions struct can be provided to configure which features should be enabled,
|
||||
// and the ability to override a few of the default values.
|
||||
type Header struct {
|
||||
next http.Handler
|
||||
hasCustomHeaders bool
|
||||
hasCorsHeaders bool
|
||||
headers *dynamic.Headers
|
||||
}
|
||||
|
||||
// NewHeader constructs a new header instance from supplied frontend header struct.
|
||||
func NewHeader(next http.Handler, cfg dynamic.Headers) *Header {
|
||||
hasCustomHeaders := cfg.HasCustomHeadersDefined()
|
||||
hasCorsHeaders := cfg.HasCorsHeadersDefined()
|
||||
|
||||
ctx := log.With(context.Background(), log.Str(log.MiddlewareType, typeName))
|
||||
handleDeprecation(ctx, &cfg)
|
||||
|
||||
return &Header{
|
||||
next: next,
|
||||
headers: &cfg,
|
||||
hasCustomHeaders: hasCustomHeaders,
|
||||
hasCorsHeaders: hasCorsHeaders,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Header) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
// Handle Cors headers and preflight if configured.
|
||||
if isPreflight := s.processCorsHeaders(rw, req); isPreflight {
|
||||
return
|
||||
}
|
||||
|
||||
if s.hasCustomHeaders {
|
||||
s.modifyCustomRequestHeaders(req)
|
||||
}
|
||||
|
||||
// If there is a next, call it.
|
||||
if s.next != nil {
|
||||
s.next.ServeHTTP(newResponseModifier(rw, req, s.PostRequestModifyResponseHeaders), req)
|
||||
}
|
||||
}
|
||||
|
||||
// modifyCustomRequestHeaders sets or deletes custom request headers.
|
||||
func (s *Header) modifyCustomRequestHeaders(req *http.Request) {
|
||||
// Loop through Custom request headers
|
||||
for header, value := range s.headers.CustomRequestHeaders {
|
||||
switch {
|
||||
case value == "":
|
||||
req.Header.Del(header)
|
||||
|
||||
case strings.EqualFold(header, "Host"):
|
||||
req.Host = value
|
||||
|
||||
default:
|
||||
req.Header.Set(header, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PostRequestModifyResponseHeaders set or delete response headers.
|
||||
// This method is called AFTER the response is generated from the backend
|
||||
// and can merge/override headers from the backend response.
|
||||
func (s *Header) PostRequestModifyResponseHeaders(res *http.Response) error {
|
||||
// Loop through Custom response headers
|
||||
for header, value := range s.headers.CustomResponseHeaders {
|
||||
if value == "" {
|
||||
res.Header.Del(header)
|
||||
} else {
|
||||
res.Header.Set(header, value)
|
||||
}
|
||||
}
|
||||
|
||||
if res != nil && res.Request != nil {
|
||||
originHeader := res.Request.Header.Get("Origin")
|
||||
allowed, match := s.isOriginAllowed(originHeader)
|
||||
|
||||
if allowed {
|
||||
res.Header.Set("Access-Control-Allow-Origin", match)
|
||||
}
|
||||
}
|
||||
|
||||
if s.headers.AccessControlAllowCredentials {
|
||||
res.Header.Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
|
||||
if len(s.headers.AccessControlExposeHeaders) > 0 {
|
||||
exposeHeaders := strings.Join(s.headers.AccessControlExposeHeaders, ",")
|
||||
res.Header.Set("Access-Control-Expose-Headers", exposeHeaders)
|
||||
}
|
||||
|
||||
if !s.headers.AddVaryHeader {
|
||||
return nil
|
||||
}
|
||||
|
||||
varyHeader := res.Header.Get("Vary")
|
||||
if varyHeader == "Origin" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if varyHeader != "" {
|
||||
varyHeader += ","
|
||||
}
|
||||
varyHeader += "Origin"
|
||||
|
||||
res.Header.Set("Vary", varyHeader)
|
||||
return nil
|
||||
}
|
||||
|
||||
// processCorsHeaders processes the incoming request,
|
||||
// and returns if it is a preflight request.
|
||||
// If not a preflight, it handles the preRequestModifyCorsResponseHeaders.
|
||||
func (s *Header) processCorsHeaders(rw http.ResponseWriter, req *http.Request) bool {
|
||||
if !s.hasCorsHeaders {
|
||||
return false
|
||||
}
|
||||
|
||||
reqAcMethod := req.Header.Get("Access-Control-Request-Method")
|
||||
originHeader := req.Header.Get("Origin")
|
||||
|
||||
if reqAcMethod != "" && originHeader != "" && req.Method == http.MethodOptions {
|
||||
// If the request is an OPTIONS request with an Access-Control-Request-Method header,
|
||||
// and Origin headers, then it is a CORS preflight request,
|
||||
// and we need to build a custom response: https://www.w3.org/TR/cors/#preflight-request
|
||||
if s.headers.AccessControlAllowCredentials {
|
||||
rw.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
|
||||
allowHeaders := strings.Join(s.headers.AccessControlAllowHeaders, ",")
|
||||
if allowHeaders != "" {
|
||||
rw.Header().Set("Access-Control-Allow-Headers", allowHeaders)
|
||||
}
|
||||
|
||||
allowMethods := strings.Join(s.headers.AccessControlAllowMethods, ",")
|
||||
if allowMethods != "" {
|
||||
rw.Header().Set("Access-Control-Allow-Methods", allowMethods)
|
||||
}
|
||||
|
||||
allowed, match := s.isOriginAllowed(originHeader)
|
||||
if allowed {
|
||||
rw.Header().Set("Access-Control-Allow-Origin", match)
|
||||
}
|
||||
|
||||
rw.Header().Set("Access-Control-Max-Age", strconv.Itoa(int(s.headers.AccessControlMaxAge)))
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Header) isOriginAllowed(origin string) (bool, string) {
|
||||
for _, item := range s.headers.AccessControlAllowOriginList {
|
||||
if item == "*" || item == origin {
|
||||
return true, item
|
||||
}
|
||||
}
|
||||
|
||||
return false, ""
|
||||
}
|
492
pkg/middlewares/headers/header_test.go
Normal file
492
pkg/middlewares/headers/header_test.go
Normal file
@@ -0,0 +1,492 @@
|
||||
package headers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewHeader_customRequestHeader(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
cfg dynamic.Headers
|
||||
expected http.Header
|
||||
}{
|
||||
{
|
||||
desc: "adds a header",
|
||||
cfg: dynamic.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
"X-Custom-Request-Header": "test_request",
|
||||
},
|
||||
},
|
||||
expected: http.Header{"Foo": []string{"bar"}, "X-Custom-Request-Header": []string{"test_request"}},
|
||||
},
|
||||
{
|
||||
desc: "delete a header",
|
||||
cfg: dynamic.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
"X-Custom-Request-Header": "",
|
||||
"Foo": "",
|
||||
},
|
||||
},
|
||||
expected: http.Header{},
|
||||
},
|
||||
{
|
||||
desc: "override a header",
|
||||
cfg: dynamic.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
"Foo": "test",
|
||||
},
|
||||
},
|
||||
expected: http.Header{"Foo": []string{"test"}},
|
||||
},
|
||||
}
|
||||
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mid := NewHeader(emptyHandler, test.cfg)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/foo", nil)
|
||||
req.Header.Set("Foo", "bar")
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
mid.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rw.Code)
|
||||
assert.Equal(t, test.expected, req.Header)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewHeader_customRequestHeader_Host(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
customHeaders map[string]string
|
||||
expectedHost string
|
||||
expectedURLHost string
|
||||
}{
|
||||
{
|
||||
desc: "standard Host header",
|
||||
customHeaders: map[string]string{},
|
||||
expectedHost: "example.org",
|
||||
expectedURLHost: "example.org",
|
||||
},
|
||||
{
|
||||
desc: "custom Host header",
|
||||
customHeaders: map[string]string{
|
||||
"Host": "example.com",
|
||||
},
|
||||
expectedHost: "example.com",
|
||||
expectedURLHost: "example.org",
|
||||
},
|
||||
}
|
||||
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
mid := NewHeader(emptyHandler, dynamic.Headers{CustomRequestHeaders: test.customHeaders})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://example.org/foo", nil)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
mid.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rw.Code)
|
||||
assert.Equal(t, test.expectedHost, req.Host)
|
||||
assert.Equal(t, test.expectedURLHost, req.URL.Host)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewHeader_CORSPreflights(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
cfg dynamic.Headers
|
||||
requestHeaders http.Header
|
||||
expected http.Header
|
||||
}{
|
||||
{
|
||||
desc: "Test Simple Preflight",
|
||||
cfg: dynamic.Headers{
|
||||
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
|
||||
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
|
||||
AccessControlMaxAge: 600,
|
||||
},
|
||||
requestHeaders: map[string][]string{
|
||||
"Access-Control-Request-Headers": {"origin"},
|
||||
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Wildcard origin Preflight",
|
||||
cfg: dynamic.Headers{
|
||||
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
AccessControlMaxAge: 600,
|
||||
},
|
||||
requestHeaders: map[string][]string{
|
||||
"Access-Control-Request-Headers": {"origin"},
|
||||
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Allow Credentials Preflight",
|
||||
cfg: dynamic.Headers{
|
||||
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
AccessControlAllowCredentials: true,
|
||||
AccessControlMaxAge: 600,
|
||||
},
|
||||
requestHeaders: map[string][]string{
|
||||
"Access-Control-Request-Headers": {"origin"},
|
||||
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
"Access-Control-Allow-Credentials": {"true"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Allow Headers Preflight",
|
||||
cfg: dynamic.Headers{
|
||||
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
AccessControlAllowHeaders: []string{"origin", "X-Forwarded-For"},
|
||||
AccessControlMaxAge: 600,
|
||||
},
|
||||
requestHeaders: map[string][]string{
|
||||
"Access-Control-Request-Headers": {"origin"},
|
||||
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
"Access-Control-Allow-Headers": {"origin,X-Forwarded-For"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "No Request Headers Preflight",
|
||||
cfg: dynamic.Headers{
|
||||
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
AccessControlAllowHeaders: []string{"origin", "X-Forwarded-For"},
|
||||
AccessControlMaxAge: 600,
|
||||
},
|
||||
requestHeaders: map[string][]string{
|
||||
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
"Access-Control-Allow-Headers": {"origin,X-Forwarded-For"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
mid := NewHeader(emptyHandler, test.cfg)
|
||||
|
||||
req := httptest.NewRequest(http.MethodOptions, "/foo", nil)
|
||||
req.Header = test.requestHeaders
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
mid.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, test.expected, rw.Result().Header)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewHeader_CORSResponses(t *testing.T) {
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
next http.Handler
|
||||
cfg dynamic.Headers
|
||||
requestHeaders http.Header
|
||||
expected http.Header
|
||||
}{
|
||||
{
|
||||
desc: "Test Simple Request",
|
||||
next: emptyHandler,
|
||||
cfg: dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
|
||||
},
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Wildcard origin Request",
|
||||
next: emptyHandler,
|
||||
cfg: dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
},
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Empty origin Request",
|
||||
next: emptyHandler,
|
||||
cfg: dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
|
||||
},
|
||||
requestHeaders: map[string][]string{},
|
||||
expected: map[string][]string{},
|
||||
},
|
||||
{
|
||||
desc: "Not Defined origin Request",
|
||||
next: emptyHandler,
|
||||
requestHeaders: map[string][]string{},
|
||||
expected: map[string][]string{},
|
||||
},
|
||||
{
|
||||
desc: "Allow Credentials Request",
|
||||
next: emptyHandler,
|
||||
cfg: dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
AccessControlAllowCredentials: true,
|
||||
},
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Allow-Credentials": {"true"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Expose Headers Request",
|
||||
next: emptyHandler,
|
||||
cfg: dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
AccessControlExposeHeaders: []string{"origin", "X-Forwarded-For"},
|
||||
},
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Expose-Headers": {"origin,X-Forwarded-For"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Test Simple Request with Vary Headers",
|
||||
next: emptyHandler,
|
||||
cfg: dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
|
||||
AddVaryHeader: true,
|
||||
},
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
|
||||
"Vary": {"Origin"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Test Simple Request with Vary Headers and non-empty response",
|
||||
next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// nonEmptyHandler
|
||||
w.Header().Set("Vary", "Testing")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}),
|
||||
cfg: dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
|
||||
AddVaryHeader: true,
|
||||
},
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
|
||||
"Vary": {"Testing,Origin"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Test Simple Request with Vary Headers and existing vary:origin response",
|
||||
next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// existingOriginHandler
|
||||
w.Header().Set("Vary", "Origin")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}),
|
||||
cfg: dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
|
||||
AddVaryHeader: true,
|
||||
},
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
|
||||
"Vary": {"Origin"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Test Simple Request with non-empty response: set ACAO",
|
||||
next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// existingAccessControlAllowOriginHandlerSet
|
||||
w.Header().Set("Access-Control-Allow-Origin", "http://foo.bar.org")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}),
|
||||
cfg: dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
},
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Test Simple Request with non-empty response: add ACAO",
|
||||
next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// existingAccessControlAllowOriginHandlerAdd
|
||||
w.Header().Add("Access-Control-Allow-Origin", "http://foo.bar.org")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}),
|
||||
cfg: dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
},
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Test Simple CustomRequestHeaders Not Hijacked by CORS",
|
||||
next: emptyHandler,
|
||||
cfg: dynamic.Headers{
|
||||
CustomRequestHeaders: map[string]string{"foo": "bar"},
|
||||
},
|
||||
requestHeaders: map[string][]string{
|
||||
"Access-Control-Request-Headers": {"origin"},
|
||||
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
mid := NewHeader(test.next, test.cfg)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/foo", nil)
|
||||
req.Header = test.requestHeaders
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
mid.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, test.expected, rw.Result().Header)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewHeader_customResponseHeaders(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
config map[string]string
|
||||
expected http.Header
|
||||
}{
|
||||
{
|
||||
desc: "Test Simple Response",
|
||||
config: map[string]string{
|
||||
"Testing": "foo",
|
||||
"Testing2": "bar",
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Foo": {"bar"},
|
||||
"Testing": {"foo"},
|
||||
"Testing2": {"bar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "empty Custom Header",
|
||||
config: map[string]string{
|
||||
"Testing": "foo",
|
||||
"Testing2": "",
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Foo": {"bar"},
|
||||
"Testing": {"foo"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Deleting Custom Header",
|
||||
config: map[string]string{
|
||||
"Testing": "foo",
|
||||
"Foo": "",
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Testing": {"foo"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Foo", "bar")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
mid := NewHeader(emptyHandler, dynamic.Headers{CustomResponseHeaders: test.config})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/foo", nil)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
mid.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, test.expected, rw.Result().Header)
|
||||
})
|
||||
}
|
||||
}
|
@@ -5,15 +5,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/v2/pkg/log"
|
||||
"github.com/containous/traefik/v2/pkg/middlewares"
|
||||
"github.com/containous/traefik/v2/pkg/tracing"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/unrolled/secure"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -55,7 +52,7 @@ func New(ctx context.Context, next http.Handler, cfg dynamic.Headers, name strin
|
||||
|
||||
if hasSecureHeaders {
|
||||
logger.Debugf("Setting up secureHeaders from %v", cfg)
|
||||
handler = newSecure(next, cfg)
|
||||
handler = newSecure(next, cfg, name)
|
||||
nextHandler = handler
|
||||
}
|
||||
|
||||
@@ -77,203 +74,3 @@ func (h *headers) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
func (h *headers) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
h.handler.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
type secureHeader struct {
|
||||
next http.Handler
|
||||
secure *secure.Secure
|
||||
}
|
||||
|
||||
// newSecure constructs a new secure instance with supplied options.
|
||||
func newSecure(next http.Handler, cfg dynamic.Headers) *secureHeader {
|
||||
opt := secure.Options{
|
||||
BrowserXssFilter: cfg.BrowserXSSFilter,
|
||||
ContentTypeNosniff: cfg.ContentTypeNosniff,
|
||||
ForceSTSHeader: cfg.ForceSTSHeader,
|
||||
FrameDeny: cfg.FrameDeny,
|
||||
IsDevelopment: cfg.IsDevelopment,
|
||||
SSLRedirect: cfg.SSLRedirect,
|
||||
SSLForceHost: cfg.SSLForceHost,
|
||||
SSLTemporaryRedirect: cfg.SSLTemporaryRedirect,
|
||||
STSIncludeSubdomains: cfg.STSIncludeSubdomains,
|
||||
STSPreload: cfg.STSPreload,
|
||||
ContentSecurityPolicy: cfg.ContentSecurityPolicy,
|
||||
CustomBrowserXssValue: cfg.CustomBrowserXSSValue,
|
||||
CustomFrameOptionsValue: cfg.CustomFrameOptionsValue,
|
||||
PublicKey: cfg.PublicKey,
|
||||
ReferrerPolicy: cfg.ReferrerPolicy,
|
||||
SSLHost: cfg.SSLHost,
|
||||
AllowedHosts: cfg.AllowedHosts,
|
||||
HostsProxyHeaders: cfg.HostsProxyHeaders,
|
||||
SSLProxyHeaders: cfg.SSLProxyHeaders,
|
||||
STSSeconds: cfg.STSSeconds,
|
||||
FeaturePolicy: cfg.FeaturePolicy,
|
||||
}
|
||||
|
||||
return &secureHeader{
|
||||
next: next,
|
||||
secure: secure.New(opt),
|
||||
}
|
||||
}
|
||||
|
||||
func (s secureHeader) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
s.secure.HandlerFuncWithNextForRequestOnly(rw, req, s.next.ServeHTTP)
|
||||
}
|
||||
|
||||
// Header is a middleware that helps setup a few basic security features.
|
||||
// A single headerOptions struct can be provided to configure which features should be enabled,
|
||||
// and the ability to override a few of the default values.
|
||||
type Header struct {
|
||||
next http.Handler
|
||||
hasCustomHeaders bool
|
||||
hasCorsHeaders bool
|
||||
headers *dynamic.Headers
|
||||
}
|
||||
|
||||
// NewHeader constructs a new header instance from supplied frontend header struct.
|
||||
func NewHeader(next http.Handler, cfg dynamic.Headers) *Header {
|
||||
hasCustomHeaders := cfg.HasCustomHeadersDefined()
|
||||
hasCorsHeaders := cfg.HasCorsHeadersDefined()
|
||||
|
||||
ctx := log.With(context.Background(), log.Str(log.MiddlewareType, typeName))
|
||||
handleDeprecation(ctx, &cfg)
|
||||
|
||||
return &Header{
|
||||
next: next,
|
||||
headers: &cfg,
|
||||
hasCustomHeaders: hasCustomHeaders,
|
||||
hasCorsHeaders: hasCorsHeaders,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Header) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
// Handle Cors headers and preflight if configured.
|
||||
if isPreflight := s.processCorsHeaders(rw, req); isPreflight {
|
||||
return
|
||||
}
|
||||
|
||||
if s.hasCustomHeaders {
|
||||
s.modifyCustomRequestHeaders(req)
|
||||
}
|
||||
|
||||
// If there is a next, call it.
|
||||
if s.next != nil {
|
||||
s.next.ServeHTTP(rw, req)
|
||||
}
|
||||
}
|
||||
|
||||
// modifyCustomRequestHeaders sets or deletes custom request headers.
|
||||
func (s *Header) modifyCustomRequestHeaders(req *http.Request) {
|
||||
// Loop through Custom request headers
|
||||
for header, value := range s.headers.CustomRequestHeaders {
|
||||
switch {
|
||||
case value == "":
|
||||
req.Header.Del(header)
|
||||
|
||||
case strings.EqualFold(header, "Host"):
|
||||
req.Host = value
|
||||
|
||||
default:
|
||||
req.Header.Set(header, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PostRequestModifyResponseHeaders set or delete response headers.
|
||||
// This method is called AFTER the response is generated from the backend
|
||||
// and can merge/override headers from the backend response.
|
||||
func (s *Header) PostRequestModifyResponseHeaders(res *http.Response) error {
|
||||
// Loop through Custom response headers
|
||||
for header, value := range s.headers.CustomResponseHeaders {
|
||||
if value == "" {
|
||||
res.Header.Del(header)
|
||||
} else {
|
||||
res.Header.Set(header, value)
|
||||
}
|
||||
}
|
||||
|
||||
if res != nil && res.Request != nil {
|
||||
originHeader := res.Request.Header.Get("Origin")
|
||||
allowed, match := s.isOriginAllowed(originHeader)
|
||||
|
||||
if allowed {
|
||||
res.Header.Set("Access-Control-Allow-Origin", match)
|
||||
}
|
||||
}
|
||||
|
||||
if s.headers.AccessControlAllowCredentials {
|
||||
res.Header.Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
|
||||
if len(s.headers.AccessControlExposeHeaders) > 0 {
|
||||
exposeHeaders := strings.Join(s.headers.AccessControlExposeHeaders, ",")
|
||||
res.Header.Set("Access-Control-Expose-Headers", exposeHeaders)
|
||||
}
|
||||
|
||||
if !s.headers.AddVaryHeader {
|
||||
return nil
|
||||
}
|
||||
|
||||
varyHeader := res.Header.Get("Vary")
|
||||
if varyHeader == "Origin" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if varyHeader != "" {
|
||||
varyHeader += ","
|
||||
}
|
||||
varyHeader += "Origin"
|
||||
|
||||
res.Header.Set("Vary", varyHeader)
|
||||
return nil
|
||||
}
|
||||
|
||||
// processCorsHeaders processes the incoming request,
|
||||
// and returns if it is a preflight request.
|
||||
// If not a preflight, it handles the preRequestModifyCorsResponseHeaders.
|
||||
func (s *Header) processCorsHeaders(rw http.ResponseWriter, req *http.Request) bool {
|
||||
if !s.hasCorsHeaders {
|
||||
return false
|
||||
}
|
||||
|
||||
reqAcMethod := req.Header.Get("Access-Control-Request-Method")
|
||||
originHeader := req.Header.Get("Origin")
|
||||
|
||||
if reqAcMethod != "" && originHeader != "" && req.Method == http.MethodOptions {
|
||||
// If the request is an OPTIONS request with an Access-Control-Request-Method header,
|
||||
// and Origin headers, then it is a CORS preflight request,
|
||||
// and we need to build a custom response: https://www.w3.org/TR/cors/#preflight-request
|
||||
if s.headers.AccessControlAllowCredentials {
|
||||
rw.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
|
||||
allowHeaders := strings.Join(s.headers.AccessControlAllowHeaders, ",")
|
||||
if allowHeaders != "" {
|
||||
rw.Header().Set("Access-Control-Allow-Headers", allowHeaders)
|
||||
}
|
||||
|
||||
allowMethods := strings.Join(s.headers.AccessControlAllowMethods, ",")
|
||||
if allowMethods != "" {
|
||||
rw.Header().Set("Access-Control-Allow-Methods", allowMethods)
|
||||
}
|
||||
|
||||
allowed, match := s.isOriginAllowed(originHeader)
|
||||
if allowed {
|
||||
rw.Header().Set("Access-Control-Allow-Origin", match)
|
||||
}
|
||||
|
||||
rw.Header().Set("Access-Control-Max-Age", strconv.Itoa(int(s.headers.AccessControlMaxAge)))
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Header) isOriginAllowed(origin string) (bool, string) {
|
||||
for _, item := range s.headers.AccessControlAllowOriginList {
|
||||
if item == "*" || item == origin {
|
||||
return true, item
|
||||
}
|
||||
}
|
||||
|
||||
return false, ""
|
||||
}
|
||||
|
@@ -9,104 +9,21 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/v2/pkg/testhelpers"
|
||||
"github.com/containous/traefik/v2/pkg/tracing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCustomRequestHeader(t *testing.T) {
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
func TestNew_withoutOptions(t *testing.T) {
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
|
||||
|
||||
header := NewHeader(emptyHandler, dynamic.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
"X-Custom-Request-Header": "test_request",
|
||||
},
|
||||
})
|
||||
mid, err := New(context.Background(), next, dynamic.Headers{}, "testing")
|
||||
require.Errorf(t, err, "headers configuration not valid")
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
|
||||
|
||||
header.ServeHTTP(res, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.Code)
|
||||
assert.Equal(t, "test_request", req.Header.Get("X-Custom-Request-Header"))
|
||||
assert.Nil(t, mid)
|
||||
}
|
||||
|
||||
func TestCustomRequestHeader_Host(t *testing.T) {
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
customHeaders map[string]string
|
||||
expectedHost string
|
||||
expectedURLHost string
|
||||
}{
|
||||
{
|
||||
desc: "standard Host header",
|
||||
customHeaders: map[string]string{},
|
||||
expectedHost: "example.org",
|
||||
expectedURLHost: "example.org",
|
||||
},
|
||||
{
|
||||
desc: "custom Host header",
|
||||
customHeaders: map[string]string{
|
||||
"Host": "example.com",
|
||||
},
|
||||
expectedHost: "example.com",
|
||||
expectedURLHost: "example.org",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
header := NewHeader(emptyHandler, dynamic.Headers{
|
||||
CustomRequestHeaders: test.customHeaders,
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(http.MethodGet, "http://example.org/foo", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
header.ServeHTTP(res, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.Code)
|
||||
assert.Equal(t, test.expectedHost, req.Host)
|
||||
assert.Equal(t, test.expectedURLHost, req.URL.Host)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomRequestHeaderEmptyValue(t *testing.T) {
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
header := NewHeader(emptyHandler, dynamic.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
"X-Custom-Request-Header": "test_request",
|
||||
},
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
|
||||
|
||||
header.ServeHTTP(res, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.Code)
|
||||
assert.Equal(t, "test_request", req.Header.Get("X-Custom-Request-Header"))
|
||||
|
||||
header = NewHeader(emptyHandler, dynamic.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
"X-Custom-Request-Header": "",
|
||||
},
|
||||
})
|
||||
|
||||
header.ServeHTTP(res, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.Code)
|
||||
assert.Equal(t, "", req.Header.Get("X-Custom-Request-Header"))
|
||||
}
|
||||
|
||||
func TestSecureHeader(t *testing.T) {
|
||||
func TestNew_allowedHosts(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
fromHost string
|
||||
@@ -129,10 +46,13 @@ func TestSecureHeader(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
header, err := New(context.Background(), emptyHandler, dynamic.Headers{
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
|
||||
|
||||
cfg := dynamic.Headers{
|
||||
AllowedHosts: []string{"foo.com", "bar.com"},
|
||||
}, "foo")
|
||||
}
|
||||
|
||||
mid, err := New(context.Background(), emptyHandler, cfg, "foo")
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, test := range testCases {
|
||||
@@ -140,479 +60,54 @@ func TestSecureHeader(t *testing.T) {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/foo", nil)
|
||||
req.Host = test.fromHost
|
||||
header.ServeHTTP(res, req)
|
||||
assert.Equal(t, test.expected, res.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLForceHost(t *testing.T) {
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
_, _ = rw.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
host string
|
||||
secureMiddleware *secureHeader
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
desc: "http should return a 301",
|
||||
host: "http://powpow.example.com",
|
||||
secureMiddleware: newSecure(next, dynamic.Headers{
|
||||
SSLRedirect: true,
|
||||
SSLForceHost: true,
|
||||
SSLHost: "powpow.example.com",
|
||||
}),
|
||||
expected: http.StatusMovedPermanently,
|
||||
},
|
||||
{
|
||||
desc: "http sub domain should return a 301",
|
||||
host: "http://www.powpow.example.com",
|
||||
secureMiddleware: newSecure(next, dynamic.Headers{
|
||||
SSLRedirect: true,
|
||||
SSLForceHost: true,
|
||||
SSLHost: "powpow.example.com",
|
||||
}),
|
||||
expected: http.StatusMovedPermanently,
|
||||
},
|
||||
{
|
||||
desc: "https should return a 200",
|
||||
host: "https://powpow.example.com",
|
||||
secureMiddleware: newSecure(next, dynamic.Headers{
|
||||
SSLRedirect: true,
|
||||
SSLForceHost: true,
|
||||
SSLHost: "powpow.example.com",
|
||||
}),
|
||||
expected: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "https sub domain should return a 301",
|
||||
host: "https://www.powpow.example.com",
|
||||
secureMiddleware: newSecure(next, dynamic.Headers{
|
||||
SSLRedirect: true,
|
||||
SSLForceHost: true,
|
||||
SSLHost: "powpow.example.com",
|
||||
}),
|
||||
expected: http.StatusMovedPermanently,
|
||||
},
|
||||
{
|
||||
desc: "http without force host and sub domain should return a 301",
|
||||
host: "http://www.powpow.example.com",
|
||||
secureMiddleware: newSecure(next, dynamic.Headers{
|
||||
SSLRedirect: true,
|
||||
SSLForceHost: false,
|
||||
SSLHost: "powpow.example.com",
|
||||
}),
|
||||
expected: http.StatusMovedPermanently,
|
||||
},
|
||||
{
|
||||
desc: "https without force host and sub domain should return a 301",
|
||||
host: "https://www.powpow.example.com",
|
||||
secureMiddleware: newSecure(next, dynamic.Headers{
|
||||
SSLRedirect: true,
|
||||
SSLForceHost: false,
|
||||
SSLHost: "powpow.example.com",
|
||||
}),
|
||||
expected: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, test.host, nil)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
test.secureMiddleware.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, test.expected, rw.Result().StatusCode)
|
||||
mid.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, test.expected, rw.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCORSPreflights(t *testing.T) {
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
func TestNew_customHeaders(t *testing.T) {
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
header *Header
|
||||
requestHeaders http.Header
|
||||
expected http.Header
|
||||
}{
|
||||
{
|
||||
desc: "Test Simple Preflight",
|
||||
header: NewHeader(emptyHandler, dynamic.Headers{
|
||||
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
|
||||
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
|
||||
AccessControlMaxAge: 600,
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Access-Control-Request-Headers": {"origin"},
|
||||
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Wildcard origin Preflight",
|
||||
header: NewHeader(emptyHandler, dynamic.Headers{
|
||||
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
AccessControlMaxAge: 600,
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Access-Control-Request-Headers": {"origin"},
|
||||
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Allow Credentials Preflight",
|
||||
header: NewHeader(emptyHandler, dynamic.Headers{
|
||||
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
AccessControlAllowCredentials: true,
|
||||
AccessControlMaxAge: 600,
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Access-Control-Request-Headers": {"origin"},
|
||||
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
"Access-Control-Allow-Credentials": {"true"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Allow Headers Preflight",
|
||||
header: NewHeader(emptyHandler, dynamic.Headers{
|
||||
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
AccessControlAllowHeaders: []string{"origin", "X-Forwarded-For"},
|
||||
AccessControlMaxAge: 600,
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Access-Control-Request-Headers": {"origin"},
|
||||
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
"Access-Control-Allow-Headers": {"origin,X-Forwarded-For"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "No Request Headers Preflight",
|
||||
header: NewHeader(emptyHandler, dynamic.Headers{
|
||||
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
AccessControlAllowHeaders: []string{"origin", "X-Forwarded-For"},
|
||||
AccessControlMaxAge: 600,
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
"Access-Control-Allow-Headers": {"origin,X-Forwarded-For"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
req := testhelpers.MustNewRequest(http.MethodOptions, "/foo", nil)
|
||||
req.Header = test.requestHeaders
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
test.header.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, test.expected, rw.Result().Header)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyHeaderObject(t *testing.T) {
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
_, err := New(context.Background(), next, dynamic.Headers{}, "testing")
|
||||
require.Errorf(t, err, "headers configuration not valid")
|
||||
}
|
||||
|
||||
func TestCustomHeaderHandler(t *testing.T) {
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
header, _ := New(context.Background(), next, dynamic.Headers{
|
||||
cfg := dynamic.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
"X-Custom-Request-Header": "test_request",
|
||||
},
|
||||
}, "testing")
|
||||
CustomResponseHeaders: map[string]string{
|
||||
"X-Custom-Response-Header": "test_response",
|
||||
},
|
||||
}
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
|
||||
mid, err := New(context.Background(), next, cfg, "testing")
|
||||
require.NoError(t, err)
|
||||
|
||||
header.ServeHTTP(res, req)
|
||||
req := httptest.NewRequest(http.MethodGet, "/foo", nil)
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.Code)
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
mid.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rw.Code)
|
||||
assert.Equal(t, "test_request", req.Header.Get("X-Custom-Request-Header"))
|
||||
assert.Equal(t, "test_response", rw.Header().Get("X-Custom-Response-Header"))
|
||||
}
|
||||
|
||||
func TestGetTracingInformation(t *testing.T) {
|
||||
func Test_headers_getTracingInformation(t *testing.T) {
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
header := &headers{
|
||||
mid := &headers{
|
||||
handler: next,
|
||||
name: "testing",
|
||||
}
|
||||
|
||||
name, trace := header.GetTracingInformation()
|
||||
name, trace := mid.GetTracingInformation()
|
||||
|
||||
assert.Equal(t, "testing", name)
|
||||
assert.Equal(t, tracing.SpanKindNoneEnum, trace)
|
||||
}
|
||||
|
||||
func TestCORSResponses(t *testing.T) {
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
nonEmptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Vary", "Testing") })
|
||||
existingOriginHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Vary", "Origin") })
|
||||
existingAccessControlAllowOriginHandlerSet := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "http://foo.bar.org")
|
||||
})
|
||||
existingAccessControlAllowOriginHandlerAdd := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Access-Control-Allow-Origin", "http://foo.bar.org")
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
header *Header
|
||||
requestHeaders http.Header
|
||||
expected http.Header
|
||||
}{
|
||||
{
|
||||
desc: "Test Simple Request",
|
||||
header: NewHeader(emptyHandler, dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Wildcard origin Request",
|
||||
header: NewHeader(emptyHandler, dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Empty origin Request",
|
||||
header: NewHeader(emptyHandler, dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
|
||||
}),
|
||||
requestHeaders: map[string][]string{},
|
||||
expected: map[string][]string{},
|
||||
},
|
||||
{
|
||||
desc: "Not Defined origin Request",
|
||||
header: NewHeader(emptyHandler, dynamic.Headers{}),
|
||||
requestHeaders: map[string][]string{},
|
||||
expected: map[string][]string{},
|
||||
},
|
||||
{
|
||||
desc: "Allow Credentials Request",
|
||||
header: NewHeader(emptyHandler, dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
AccessControlAllowCredentials: true,
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Allow-Credentials": {"true"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Expose Headers Request",
|
||||
header: NewHeader(emptyHandler, dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
AccessControlExposeHeaders: []string{"origin", "X-Forwarded-For"},
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Expose-Headers": {"origin,X-Forwarded-For"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Test Simple Request with Vary Headers",
|
||||
header: NewHeader(emptyHandler, dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
|
||||
AddVaryHeader: true,
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
|
||||
"Vary": {"Origin"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Test Simple Request with Vary Headers and non-empty response",
|
||||
header: NewHeader(nonEmptyHandler, dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
|
||||
AddVaryHeader: true,
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
|
||||
"Vary": {"Testing,Origin"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Test Simple Request with Vary Headers and existing vary:origin response",
|
||||
header: NewHeader(existingOriginHandler, dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"https://foo.bar.org"},
|
||||
AddVaryHeader: true,
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
|
||||
"Vary": {"Origin"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Test Simple Request with non-empty response: set ACAO",
|
||||
header: NewHeader(existingAccessControlAllowOriginHandlerSet, dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Test Simple Request with non-empty response: add ACAO",
|
||||
header: NewHeader(existingAccessControlAllowOriginHandlerAdd, dynamic.Headers{
|
||||
AccessControlAllowOriginList: []string{"*"},
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Test Simple CustomRequestHeaders Not Hijacked by CORS",
|
||||
header: NewHeader(emptyHandler, dynamic.Headers{
|
||||
CustomRequestHeaders: map[string]string{"foo": "bar"},
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Access-Control-Request-Headers": {"origin"},
|
||||
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
|
||||
req.Header = test.requestHeaders
|
||||
rw := httptest.NewRecorder()
|
||||
test.header.ServeHTTP(rw, req)
|
||||
res := rw.Result()
|
||||
res.Request = req
|
||||
err := test.header.PostRequestModifyResponseHeaders(res)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expected, rw.Result().Header)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomResponseHeaders(t *testing.T) {
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
header *Header
|
||||
expected http.Header
|
||||
}{
|
||||
{
|
||||
desc: "Test Simple Response",
|
||||
header: NewHeader(emptyHandler, dynamic.Headers{
|
||||
CustomResponseHeaders: map[string]string{
|
||||
"Testing": "foo",
|
||||
"Testing2": "bar",
|
||||
},
|
||||
}),
|
||||
expected: map[string][]string{
|
||||
"Testing": {"foo"},
|
||||
"Testing2": {"bar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Deleting Custom Header",
|
||||
header: NewHeader(emptyHandler, dynamic.Headers{
|
||||
CustomResponseHeaders: map[string]string{
|
||||
"Testing": "foo",
|
||||
"Testing2": "",
|
||||
},
|
||||
}),
|
||||
expected: map[string][]string{
|
||||
"Testing": {"foo"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
|
||||
rw := httptest.NewRecorder()
|
||||
test.header.ServeHTTP(rw, req)
|
||||
err := test.header.PostRequestModifyResponseHeaders(rw.Result())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expected, rw.Result().Header)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
94
pkg/middlewares/headers/responsewriter.go
Normal file
94
pkg/middlewares/headers/responsewriter.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package headers
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/log"
|
||||
)
|
||||
|
||||
type responseModifier struct {
|
||||
r *http.Request
|
||||
w http.ResponseWriter
|
||||
|
||||
headersSent bool // whether headers have already been sent
|
||||
code int // status code, must default to 200
|
||||
|
||||
modifier func(*http.Response) error // can be nil
|
||||
modified bool // whether modifier has already been called for the current request
|
||||
modifierErr error // returned by modifier call
|
||||
}
|
||||
|
||||
// modifier can be nil.
|
||||
func newResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(*http.Response) error) *responseModifier {
|
||||
return &responseModifier{
|
||||
r: r,
|
||||
w: w,
|
||||
modifier: modifier,
|
||||
code: http.StatusOK,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *responseModifier) WriteHeader(code int) {
|
||||
if w.headersSent {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
w.code = code
|
||||
w.headersSent = true
|
||||
}()
|
||||
|
||||
if w.modifier == nil || w.modified {
|
||||
w.w.WriteHeader(code)
|
||||
return
|
||||
}
|
||||
|
||||
resp := http.Response{
|
||||
Header: w.w.Header(),
|
||||
Request: w.r,
|
||||
}
|
||||
|
||||
if err := w.modifier(&resp); err != nil {
|
||||
w.modifierErr = err
|
||||
// we are propagating when we are called in Write, but we're logging anyway,
|
||||
// because we could be called from another place which does not take care of
|
||||
// checking w.modifierErr.
|
||||
log.WithoutContext().Errorf("Error when applying response modifier: %v", err)
|
||||
w.w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.modified = true
|
||||
w.w.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (w *responseModifier) Header() http.Header {
|
||||
return w.w.Header()
|
||||
}
|
||||
|
||||
func (w *responseModifier) Write(b []byte) (int, error) {
|
||||
w.WriteHeader(w.code)
|
||||
if w.modifierErr != nil {
|
||||
return 0, w.modifierErr
|
||||
}
|
||||
|
||||
return w.w.Write(b)
|
||||
}
|
||||
|
||||
// Hijack hijacks the connection.
|
||||
func (w *responseModifier) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if h, ok := w.w.(http.Hijacker); ok {
|
||||
return h.Hijack()
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("not a hijacker: %T", w.w)
|
||||
}
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
func (w *responseModifier) Flush() {
|
||||
if flusher, ok := w.w.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
54
pkg/middlewares/headers/secure.go
Normal file
54
pkg/middlewares/headers/secure.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package headers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/unrolled/secure"
|
||||
)
|
||||
|
||||
type secureHeader struct {
|
||||
next http.Handler
|
||||
secure *secure.Secure
|
||||
cfg dynamic.Headers
|
||||
}
|
||||
|
||||
// newSecure constructs a new secure instance with supplied options.
|
||||
func newSecure(next http.Handler, cfg dynamic.Headers, contextKey string) *secureHeader {
|
||||
opt := secure.Options{
|
||||
BrowserXssFilter: cfg.BrowserXSSFilter,
|
||||
ContentTypeNosniff: cfg.ContentTypeNosniff,
|
||||
ForceSTSHeader: cfg.ForceSTSHeader,
|
||||
FrameDeny: cfg.FrameDeny,
|
||||
IsDevelopment: cfg.IsDevelopment,
|
||||
SSLRedirect: cfg.SSLRedirect,
|
||||
SSLForceHost: cfg.SSLForceHost,
|
||||
SSLTemporaryRedirect: cfg.SSLTemporaryRedirect,
|
||||
STSIncludeSubdomains: cfg.STSIncludeSubdomains,
|
||||
STSPreload: cfg.STSPreload,
|
||||
ContentSecurityPolicy: cfg.ContentSecurityPolicy,
|
||||
CustomBrowserXssValue: cfg.CustomBrowserXSSValue,
|
||||
CustomFrameOptionsValue: cfg.CustomFrameOptionsValue,
|
||||
PublicKey: cfg.PublicKey,
|
||||
ReferrerPolicy: cfg.ReferrerPolicy,
|
||||
SSLHost: cfg.SSLHost,
|
||||
AllowedHosts: cfg.AllowedHosts,
|
||||
HostsProxyHeaders: cfg.HostsProxyHeaders,
|
||||
SSLProxyHeaders: cfg.SSLProxyHeaders,
|
||||
STSSeconds: cfg.STSSeconds,
|
||||
FeaturePolicy: cfg.FeaturePolicy,
|
||||
SecureContextKey: contextKey,
|
||||
}
|
||||
|
||||
return &secureHeader{
|
||||
next: next,
|
||||
secure: secure.New(opt),
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (s secureHeader) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
s.secure.HandlerFuncWithNextForRequestOnly(rw, req, func(writer http.ResponseWriter, request *http.Request) {
|
||||
s.next.ServeHTTP(newResponseModifier(writer, request, s.secure.ModifyResponseHeaders), request)
|
||||
})
|
||||
}
|
191
pkg/middlewares/headers/secure_test.go
Normal file
191
pkg/middlewares/headers/secure_test.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package headers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Middleware tests based on https://github.com/unrolled/secure
|
||||
|
||||
func Test_newSecure_sslForceHost(t *testing.T) {
|
||||
type expected struct {
|
||||
statusCode int
|
||||
location string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
host string
|
||||
cfg dynamic.Headers
|
||||
expected
|
||||
}{
|
||||
{
|
||||
desc: "http should return a 301",
|
||||
host: "http://powpow.example.com",
|
||||
cfg: dynamic.Headers{
|
||||
SSLRedirect: true,
|
||||
SSLForceHost: true,
|
||||
SSLHost: "powpow.example.com",
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusMovedPermanently,
|
||||
location: "https://powpow.example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "http sub domain should return a 301",
|
||||
host: "http://www.powpow.example.com",
|
||||
cfg: dynamic.Headers{
|
||||
SSLRedirect: true,
|
||||
SSLForceHost: true,
|
||||
SSLHost: "powpow.example.com",
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusMovedPermanently,
|
||||
location: "https://powpow.example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "https should return a 200",
|
||||
host: "https://powpow.example.com",
|
||||
cfg: dynamic.Headers{
|
||||
SSLRedirect: true,
|
||||
SSLForceHost: true,
|
||||
SSLHost: "powpow.example.com",
|
||||
},
|
||||
expected: expected{statusCode: http.StatusOK},
|
||||
},
|
||||
{
|
||||
desc: "https sub domain should return a 301",
|
||||
host: "https://www.powpow.example.com",
|
||||
cfg: dynamic.Headers{
|
||||
SSLRedirect: true,
|
||||
SSLForceHost: true,
|
||||
SSLHost: "powpow.example.com",
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusMovedPermanently,
|
||||
location: "https://powpow.example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "http without force host and sub domain should return a 301",
|
||||
host: "http://www.powpow.example.com",
|
||||
cfg: dynamic.Headers{
|
||||
SSLRedirect: true,
|
||||
SSLForceHost: false,
|
||||
SSLHost: "powpow.example.com",
|
||||
},
|
||||
expected: expected{
|
||||
statusCode: http.StatusMovedPermanently,
|
||||
location: "https://powpow.example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "https without force host and sub domain should return a 301",
|
||||
host: "https://www.powpow.example.com",
|
||||
cfg: dynamic.Headers{
|
||||
SSLRedirect: true,
|
||||
SSLForceHost: false,
|
||||
SSLHost: "powpow.example.com",
|
||||
},
|
||||
expected: expected{statusCode: http.StatusOK},
|
||||
},
|
||||
}
|
||||
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
_, _ = rw.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
mid := newSecure(next, test.cfg, "mymiddleware")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, test.host, nil)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
mid.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, test.expected.statusCode, rw.Result().StatusCode)
|
||||
assert.Equal(t, test.expected.location, rw.Header().Get("Location"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_newSecure_modifyResponse(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
cfg dynamic.Headers
|
||||
expected http.Header
|
||||
}{
|
||||
{
|
||||
desc: "FeaturePolicy",
|
||||
cfg: dynamic.Headers{
|
||||
FeaturePolicy: "vibrate 'none';",
|
||||
},
|
||||
expected: http.Header{"Feature-Policy": []string{"vibrate 'none';"}},
|
||||
},
|
||||
{
|
||||
desc: "STSSeconds",
|
||||
cfg: dynamic.Headers{
|
||||
STSSeconds: 1,
|
||||
ForceSTSHeader: true,
|
||||
},
|
||||
expected: http.Header{"Strict-Transport-Security": []string{"max-age=1"}},
|
||||
},
|
||||
{
|
||||
desc: "STSSeconds and STSPreload",
|
||||
cfg: dynamic.Headers{
|
||||
STSSeconds: 1,
|
||||
ForceSTSHeader: true,
|
||||
STSPreload: true,
|
||||
},
|
||||
expected: http.Header{"Strict-Transport-Security": []string{"max-age=1; preload"}},
|
||||
},
|
||||
{
|
||||
desc: "CustomFrameOptionsValue",
|
||||
cfg: dynamic.Headers{
|
||||
CustomFrameOptionsValue: "foo",
|
||||
},
|
||||
expected: http.Header{"X-Frame-Options": []string{"foo"}},
|
||||
},
|
||||
{
|
||||
desc: "FrameDeny",
|
||||
cfg: dynamic.Headers{
|
||||
FrameDeny: true,
|
||||
},
|
||||
expected: http.Header{"X-Frame-Options": []string{"DENY"}},
|
||||
},
|
||||
{
|
||||
desc: "ContentTypeNosniff",
|
||||
cfg: dynamic.Headers{
|
||||
ContentTypeNosniff: true,
|
||||
},
|
||||
expected: http.Header{"X-Content-Type-Options": []string{"nosniff"}},
|
||||
},
|
||||
}
|
||||
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
secure := newSecure(emptyHandler, test.cfg, "mymiddleware")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/foo", nil)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
secure.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, test.expected, rw.Result().Header)
|
||||
})
|
||||
}
|
||||
}
|
@@ -8,8 +8,8 @@ import (
|
||||
"crypto/x509"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/log"
|
||||
"github.com/go-acme/lego/v3/certcrypto"
|
||||
"github.com/go-acme/lego/v3/registration"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
)
|
||||
|
||||
// Account is used to store lets encrypt registration info.
|
||||
|
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/containous/traefik/v2/pkg/log"
|
||||
"github.com/containous/traefik/v2/pkg/safe"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/challenge/http01"
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/challenge/http01"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
|
@@ -5,8 +5,8 @@ import (
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/log"
|
||||
"github.com/containous/traefik/v2/pkg/types"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/challenge/tlsalpn01"
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
||||
)
|
||||
|
||||
var _ challenge.Provider = (*challengeTLSALPN)(nil)
|
||||
|
@@ -19,12 +19,12 @@ import (
|
||||
traefiktls "github.com/containous/traefik/v2/pkg/tls"
|
||||
"github.com/containous/traefik/v2/pkg/types"
|
||||
"github.com/containous/traefik/v2/pkg/version"
|
||||
"github.com/go-acme/lego/v3/certificate"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/challenge/dns01"
|
||||
"github.com/go-acme/lego/v3/lego"
|
||||
"github.com/go-acme/lego/v3/providers/dns"
|
||||
"github.com/go-acme/lego/v3/registration"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/providers/dns"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
)
|
||||
|
||||
// oscpMustStaple enables OSCP stapling as from https://github.com/go-acme/lego/issues/270.
|
||||
@@ -32,13 +32,14 @@ var oscpMustStaple = false
|
||||
|
||||
// Configuration holds ACME configuration provided by users.
|
||||
type Configuration struct {
|
||||
Email string `description:"Email address used for registration." json:"email,omitempty" toml:"email,omitempty" yaml:"email,omitempty"`
|
||||
CAServer string `description:"CA server to use." json:"caServer,omitempty" toml:"caServer,omitempty" yaml:"caServer,omitempty"`
|
||||
Storage string `description:"Storage to use." json:"storage,omitempty" toml:"storage,omitempty" yaml:"storage,omitempty"`
|
||||
KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'." json:"keyType,omitempty" toml:"keyType,omitempty" yaml:"keyType,omitempty"`
|
||||
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge." json:"dnsChallenge,omitempty" toml:"dnsChallenge,omitempty" yaml:"dnsChallenge,omitempty" label:"allowEmpty"`
|
||||
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge." json:"httpChallenge,omitempty" toml:"httpChallenge,omitempty" yaml:"httpChallenge,omitempty" label:"allowEmpty"`
|
||||
TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge." json:"tlsChallenge,omitempty" toml:"tlsChallenge,omitempty" yaml:"tlsChallenge,omitempty" label:"allowEmpty"`
|
||||
Email string `description:"Email address used for registration." json:"email,omitempty" toml:"email,omitempty" yaml:"email,omitempty"`
|
||||
CAServer string `description:"CA server to use." json:"caServer,omitempty" toml:"caServer,omitempty" yaml:"caServer,omitempty"`
|
||||
PreferredChain string `description:"Preferred chain to use." json:"preferredChain,omitempty" toml:"preferredChain,omitempty" yaml:"preferredChain,omitempty"`
|
||||
Storage string `description:"Storage to use." json:"storage,omitempty" toml:"storage,omitempty" yaml:"storage,omitempty"`
|
||||
KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'." json:"keyType,omitempty" toml:"keyType,omitempty" yaml:"keyType,omitempty"`
|
||||
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge." json:"dnsChallenge,omitempty" toml:"dnsChallenge,omitempty" yaml:"dnsChallenge,omitempty" label:"allowEmpty"`
|
||||
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge." json:"httpChallenge,omitempty" toml:"httpChallenge,omitempty" yaml:"httpChallenge,omitempty" label:"allowEmpty"`
|
||||
TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge." json:"tlsChallenge,omitempty" toml:"tlsChallenge,omitempty" yaml:"tlsChallenge,omitempty" label:"allowEmpty"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
@@ -262,14 +263,18 @@ func (p *Provider) getClient() (*lego.Client, error) {
|
||||
|
||||
err = client.Challenge.SetDNS01Provider(provider,
|
||||
dns01.CondOption(len(p.DNSChallenge.Resolvers) > 0, dns01.AddRecursiveNameservers(p.DNSChallenge.Resolvers)),
|
||||
dns01.CondOption(p.DNSChallenge.DisablePropagationCheck || p.DNSChallenge.DelayBeforeCheck > 0,
|
||||
dns01.AddPreCheck(func(_, _ string) (bool, error) {
|
||||
if p.DNSChallenge.DelayBeforeCheck > 0 {
|
||||
log.Debugf("Delaying %d rather than validating DNS propagation now.", p.DNSChallenge.DelayBeforeCheck)
|
||||
time.Sleep(time.Duration(p.DNSChallenge.DelayBeforeCheck))
|
||||
}
|
||||
dns01.WrapPreCheck(func(domain, fqdn, value string, check dns01.PreCheckFunc) (bool, error) {
|
||||
if p.DNSChallenge.DisablePropagationCheck {
|
||||
return true, nil
|
||||
})),
|
||||
}
|
||||
|
||||
if p.DNSChallenge.DelayBeforeCheck > 0 {
|
||||
logger.Debugf("Delaying %d rather than validating DNS propagation now.", p.DNSChallenge.DelayBeforeCheck)
|
||||
time.Sleep(time.Duration(p.DNSChallenge.DelayBeforeCheck))
|
||||
}
|
||||
|
||||
return check(fqdn, value)
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -626,7 +631,7 @@ func (p *Provider) renewCertificates(ctx context.Context) {
|
||||
Domain: cert.Domain.Main,
|
||||
PrivateKey: cert.Key,
|
||||
Certificate: cert.Certificate.Certificate,
|
||||
}, true, oscpMustStaple)
|
||||
}, true, oscpMustStaple, p.PreferredChain)
|
||||
if err != nil {
|
||||
logger.Errorf("Error renewing certificate from LE: %v, %v", cert.Domain, err)
|
||||
continue
|
||||
|
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/safe"
|
||||
"github.com/containous/traefik/v2/pkg/types"
|
||||
"github.com/go-acme/lego/v3/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@@ -1,53 +0,0 @@
|
||||
package responsemodifiers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/v2/pkg/middlewares/headers"
|
||||
"github.com/unrolled/secure"
|
||||
)
|
||||
|
||||
func buildHeaders(hdrs *dynamic.Headers) func(*http.Response) error {
|
||||
opt := secure.Options{
|
||||
BrowserXssFilter: hdrs.BrowserXSSFilter,
|
||||
ContentTypeNosniff: hdrs.ContentTypeNosniff,
|
||||
ForceSTSHeader: hdrs.ForceSTSHeader,
|
||||
FrameDeny: hdrs.FrameDeny,
|
||||
IsDevelopment: hdrs.IsDevelopment,
|
||||
SSLRedirect: hdrs.SSLRedirect,
|
||||
SSLForceHost: hdrs.SSLForceHost,
|
||||
SSLTemporaryRedirect: hdrs.SSLTemporaryRedirect,
|
||||
STSIncludeSubdomains: hdrs.STSIncludeSubdomains,
|
||||
STSPreload: hdrs.STSPreload,
|
||||
ContentSecurityPolicy: hdrs.ContentSecurityPolicy,
|
||||
CustomBrowserXssValue: hdrs.CustomBrowserXSSValue,
|
||||
CustomFrameOptionsValue: hdrs.CustomFrameOptionsValue,
|
||||
PublicKey: hdrs.PublicKey,
|
||||
ReferrerPolicy: hdrs.ReferrerPolicy,
|
||||
SSLHost: hdrs.SSLHost,
|
||||
AllowedHosts: hdrs.AllowedHosts,
|
||||
HostsProxyHeaders: hdrs.HostsProxyHeaders,
|
||||
SSLProxyHeaders: hdrs.SSLProxyHeaders,
|
||||
STSSeconds: hdrs.STSSeconds,
|
||||
FeaturePolicy: hdrs.FeaturePolicy,
|
||||
}
|
||||
|
||||
return func(resp *http.Response) error {
|
||||
if hdrs.HasCustomHeadersDefined() || hdrs.HasCorsHeadersDefined() {
|
||||
err := headers.NewHeader(nil, *hdrs).PostRequestModifyResponseHeaders(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if hdrs.HasSecureHeadersDefined() {
|
||||
err := secure.New(opt).ModifyResponseHeaders(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
package responsemodifiers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/log"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// getLogger creates a logger configured with the middleware fields.
|
||||
func getLogger(ctx context.Context, middleware, middlewareType string) logrus.FieldLogger {
|
||||
return log.FromContext(ctx).WithField(log.MiddlewareName, middleware).WithField(log.MiddlewareType, middlewareType)
|
||||
}
|
@@ -1,68 +0,0 @@
|
||||
package responsemodifiers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/runtime"
|
||||
"github.com/containous/traefik/v2/pkg/server/provider"
|
||||
)
|
||||
|
||||
// NewBuilder creates a builder.
|
||||
func NewBuilder(configs map[string]*runtime.MiddlewareInfo) *Builder {
|
||||
return &Builder{configs: configs}
|
||||
}
|
||||
|
||||
// Builder holds builder configuration.
|
||||
type Builder struct {
|
||||
configs map[string]*runtime.MiddlewareInfo
|
||||
}
|
||||
|
||||
// Build Builds the response modifier.
|
||||
// It returns nil if there is no modifier to apply.
|
||||
func (f *Builder) Build(ctx context.Context, names []string) func(*http.Response) error {
|
||||
var modifiers []func(*http.Response) error
|
||||
|
||||
for _, middleName := range names {
|
||||
conf, ok := f.configs[middleName]
|
||||
if !ok {
|
||||
getLogger(ctx, middleName, "undefined").Debug("Middleware name not found in config (ResponseModifier)")
|
||||
continue
|
||||
}
|
||||
if conf == nil || conf.Middleware == nil {
|
||||
getLogger(ctx, middleName, "undefined").Error("Invalid Middleware configuration (ResponseModifier)")
|
||||
continue
|
||||
}
|
||||
|
||||
if conf.Headers != nil {
|
||||
getLogger(ctx, middleName, "Headers").Debug("Creating Middleware (ResponseModifier)")
|
||||
|
||||
modifiers = append(modifiers, buildHeaders(conf.Headers))
|
||||
} else if conf.Chain != nil {
|
||||
chainCtx := provider.AddInContext(ctx, middleName)
|
||||
getLogger(chainCtx, middleName, "Chain").Debug("Creating Middleware (ResponseModifier)")
|
||||
var qualifiedNames []string
|
||||
for _, name := range conf.Chain.Middlewares {
|
||||
qualifiedNames = append(qualifiedNames, provider.GetQualifiedName(chainCtx, name))
|
||||
}
|
||||
|
||||
if rm := f.Build(ctx, qualifiedNames); rm != nil {
|
||||
modifiers = append(modifiers, rm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(modifiers) > 0 {
|
||||
return func(resp *http.Response) error {
|
||||
for i := len(modifiers); i > 0; i-- {
|
||||
err := modifiers[i-1](resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@@ -1,214 +0,0 @@
|
||||
package responsemodifiers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/v2/pkg/config/runtime"
|
||||
"github.com/containous/traefik/v2/pkg/middlewares/headers"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func stubResponse(_ map[string]*dynamic.Middleware) *http.Response {
|
||||
return &http.Response{Header: make(http.Header)}
|
||||
}
|
||||
|
||||
func TestBuilderBuild(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
middlewares []string
|
||||
// buildResponse is needed because secure use a private context key
|
||||
buildResponse func(map[string]*dynamic.Middleware) *http.Response
|
||||
conf map[string]*dynamic.Middleware
|
||||
assertResponse func(*testing.T, *http.Response)
|
||||
}{
|
||||
{
|
||||
desc: "no configuration",
|
||||
middlewares: []string{"foo", "bar"},
|
||||
buildResponse: stubResponse,
|
||||
conf: map[string]*dynamic.Middleware{},
|
||||
assertResponse: func(t *testing.T, resp *http.Response) {},
|
||||
},
|
||||
{
|
||||
desc: "one modifier",
|
||||
middlewares: []string{"foo", "bar"},
|
||||
buildResponse: stubResponse,
|
||||
conf: map[string]*dynamic.Middleware{
|
||||
"foo": {
|
||||
Headers: &dynamic.Headers{
|
||||
CustomResponseHeaders: map[string]string{"X-Foo": "foo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
assertResponse: func(t *testing.T, resp *http.Response) {
|
||||
t.Helper()
|
||||
|
||||
assert.Equal(t, "foo", resp.Header.Get("X-Foo"))
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "secure: one modifier",
|
||||
middlewares: []string{"foo", "bar"},
|
||||
buildResponse: func(middlewares map[string]*dynamic.Middleware) *http.Response {
|
||||
ctx := context.Background()
|
||||
|
||||
var request *http.Request
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
request = req
|
||||
})
|
||||
|
||||
headerM := *middlewares["foo"].Headers
|
||||
handler, err := headers.New(ctx, next, headerM, "secure")
|
||||
require.NoError(t, err)
|
||||
|
||||
handler.ServeHTTP(httptest.NewRecorder(),
|
||||
httptest.NewRequest(http.MethodGet, "http://foo.com", nil))
|
||||
|
||||
return &http.Response{Header: make(http.Header), Request: request}
|
||||
},
|
||||
conf: map[string]*dynamic.Middleware{
|
||||
"foo": {
|
||||
Headers: &dynamic.Headers{
|
||||
ReferrerPolicy: "no-referrer",
|
||||
},
|
||||
},
|
||||
"bar": {
|
||||
Headers: &dynamic.Headers{
|
||||
CustomResponseHeaders: map[string]string{"X-Bar": "bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
assertResponse: func(t *testing.T, resp *http.Response) {
|
||||
t.Helper()
|
||||
|
||||
assert.Equal(t, "no-referrer", resp.Header.Get("Referrer-Policy"))
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "two modifiers",
|
||||
middlewares: []string{"foo", "bar"},
|
||||
buildResponse: stubResponse,
|
||||
conf: map[string]*dynamic.Middleware{
|
||||
"foo": {
|
||||
Headers: &dynamic.Headers{
|
||||
CustomResponseHeaders: map[string]string{"X-Foo": "foo"},
|
||||
},
|
||||
},
|
||||
"bar": {
|
||||
Headers: &dynamic.Headers{
|
||||
CustomResponseHeaders: map[string]string{"X-Bar": "bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
assertResponse: func(t *testing.T, resp *http.Response) {
|
||||
t.Helper()
|
||||
|
||||
assert.Equal(t, "foo", resp.Header.Get("X-Foo"))
|
||||
assert.Equal(t, "bar", resp.Header.Get("X-Bar"))
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "modifier order",
|
||||
middlewares: []string{"foo", "bar"},
|
||||
buildResponse: stubResponse,
|
||||
conf: map[string]*dynamic.Middleware{
|
||||
"foo": {
|
||||
Headers: &dynamic.Headers{
|
||||
CustomResponseHeaders: map[string]string{"X-Foo": "foo"},
|
||||
},
|
||||
},
|
||||
"bar": {
|
||||
Headers: &dynamic.Headers{
|
||||
CustomResponseHeaders: map[string]string{"X-Foo": "bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
assertResponse: func(t *testing.T, resp *http.Response) {
|
||||
t.Helper()
|
||||
|
||||
assert.Equal(t, "foo", resp.Header.Get("X-Foo"))
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "chain",
|
||||
middlewares: []string{"chain"},
|
||||
buildResponse: stubResponse,
|
||||
conf: map[string]*dynamic.Middleware{
|
||||
"foo": {
|
||||
Headers: &dynamic.Headers{
|
||||
CustomResponseHeaders: map[string]string{"X-Foo": "foo"},
|
||||
},
|
||||
},
|
||||
"bar": {
|
||||
Headers: &dynamic.Headers{
|
||||
CustomResponseHeaders: map[string]string{"X-Foo": "bar"},
|
||||
},
|
||||
},
|
||||
"chain": {
|
||||
Chain: &dynamic.Chain{
|
||||
Middlewares: []string{"foo", "bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
assertResponse: func(t *testing.T, resp *http.Response) {
|
||||
t.Helper()
|
||||
|
||||
assert.Equal(t, "foo", resp.Header.Get("X-Foo"))
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "nil middleware",
|
||||
middlewares: []string{"foo"},
|
||||
buildResponse: stubResponse,
|
||||
conf: map[string]*dynamic.Middleware{
|
||||
"foo": nil,
|
||||
},
|
||||
assertResponse: func(t *testing.T, resp *http.Response) {},
|
||||
},
|
||||
|
||||
{
|
||||
desc: "chain without headers",
|
||||
middlewares: []string{"chain"},
|
||||
buildResponse: stubResponse,
|
||||
conf: map[string]*dynamic.Middleware{
|
||||
"foo": {IPWhiteList: &dynamic.IPWhiteList{}},
|
||||
"chain": {
|
||||
Chain: &dynamic.Chain{
|
||||
Middlewares: []string{"foo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
assertResponse: func(t *testing.T, resp *http.Response) {},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rtConf := runtime.NewConfig(dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: test.conf,
|
||||
},
|
||||
})
|
||||
builder := NewBuilder(rtConf.Middlewares)
|
||||
|
||||
rm := builder.Build(context.Background(), test.middlewares)
|
||||
if rm == nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp := test.buildResponse(test.conf)
|
||||
|
||||
err := rm(resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
test.assertResponse(t, resp)
|
||||
})
|
||||
}
|
||||
}
|
@@ -44,7 +44,7 @@ type Builder struct {
|
||||
}
|
||||
|
||||
type serviceBuilder interface {
|
||||
BuildHTTP(ctx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error)
|
||||
BuildHTTP(ctx context.Context, serviceName string) (http.Handler, error)
|
||||
}
|
||||
|
||||
// NewBuilder creates a new Builder.
|
||||
|
@@ -24,12 +24,8 @@ type middlewareBuilder interface {
|
||||
BuildChain(ctx context.Context, names []string) *alice.Chain
|
||||
}
|
||||
|
||||
type responseModifierBuilder interface {
|
||||
Build(ctx context.Context, names []string) func(*http.Response) error
|
||||
}
|
||||
|
||||
type serviceManager interface {
|
||||
BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error)
|
||||
BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error)
|
||||
LaunchHealthCheck()
|
||||
}
|
||||
|
||||
@@ -39,22 +35,15 @@ type Manager struct {
|
||||
serviceManager serviceManager
|
||||
middlewaresBuilder middlewareBuilder
|
||||
chainBuilder *middleware.ChainBuilder
|
||||
modifierBuilder responseModifierBuilder
|
||||
conf *runtime.Configuration
|
||||
}
|
||||
|
||||
// NewManager Creates a new Manager.
|
||||
func NewManager(conf *runtime.Configuration,
|
||||
serviceManager serviceManager,
|
||||
middlewaresBuilder middlewareBuilder,
|
||||
modifierBuilder responseModifierBuilder,
|
||||
chainBuilder *middleware.ChainBuilder,
|
||||
) *Manager {
|
||||
func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder) *Manager {
|
||||
return &Manager{
|
||||
routerHandlers: make(map[string]http.Handler),
|
||||
serviceManager: serviceManager,
|
||||
middlewaresBuilder: middlewaresBuilder,
|
||||
modifierBuilder: modifierBuilder,
|
||||
chainBuilder: chainBuilder,
|
||||
conf: conf,
|
||||
}
|
||||
@@ -176,13 +165,12 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
|
||||
qualifiedNames = append(qualifiedNames, provider.GetQualifiedName(ctx, name))
|
||||
}
|
||||
router.Middlewares = qualifiedNames
|
||||
rm := m.modifierBuilder.Build(ctx, qualifiedNames)
|
||||
|
||||
if router.Service == "" {
|
||||
return nil, errors.New("the service is missing on the router")
|
||||
}
|
||||
|
||||
sHandler, err := m.serviceManager.BuildHTTP(ctx, router.Service, rm)
|
||||
sHandler, err := m.serviceManager.BuildHTTP(ctx, router.Service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/containous/traefik/v2/pkg/config/static"
|
||||
"github.com/containous/traefik/v2/pkg/middlewares/accesslog"
|
||||
"github.com/containous/traefik/v2/pkg/middlewares/requestdecorator"
|
||||
"github.com/containous/traefik/v2/pkg/responsemodifiers"
|
||||
"github.com/containous/traefik/v2/pkg/server/middleware"
|
||||
"github.com/containous/traefik/v2/pkg/server/service"
|
||||
"github.com/containous/traefik/v2/pkg/testhelpers"
|
||||
@@ -290,10 +289,9 @@ func TestRouterManager_Get(t *testing.T) {
|
||||
|
||||
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
|
||||
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
|
||||
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder)
|
||||
|
||||
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
|
||||
|
||||
@@ -395,10 +393,9 @@ func TestAccessLog(t *testing.T) {
|
||||
|
||||
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
|
||||
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
|
||||
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder)
|
||||
|
||||
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
|
||||
|
||||
@@ -683,10 +680,9 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||
|
||||
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
|
||||
responseModifierFactory := responsemodifiers.NewBuilder(map[string]*runtime.MiddlewareInfo{})
|
||||
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder)
|
||||
|
||||
_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
|
||||
|
||||
@@ -765,10 +761,9 @@ func TestProviderOnMiddlewares(t *testing.T) {
|
||||
|
||||
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
|
||||
responseModifierFactory := responsemodifiers.NewBuilder(map[string]*runtime.MiddlewareInfo{})
|
||||
chainBuilder := middleware.NewChainBuilder(staticCfg, nil, nil)
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder)
|
||||
|
||||
_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
|
||||
|
||||
@@ -826,10 +821,9 @@ func BenchmarkRouterServe(b *testing.B) {
|
||||
|
||||
serviceManager := service.NewManager(rtConf.Services, &staticTransport{res}, nil, nil)
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
|
||||
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
|
||||
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder)
|
||||
|
||||
handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false)
|
||||
|
||||
@@ -871,7 +865,7 @@ func BenchmarkService(b *testing.B) {
|
||||
w := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil)
|
||||
|
||||
handler, _ := serviceManager.BuildHTTP(context.Background(), "foo-service", nil)
|
||||
handler, _ := serviceManager.BuildHTTP(context.Background(), "foo-service")
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
handler.ServeHTTP(w, req)
|
||||
|
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/containous/traefik/v2/pkg/config/runtime"
|
||||
"github.com/containous/traefik/v2/pkg/config/static"
|
||||
"github.com/containous/traefik/v2/pkg/log"
|
||||
"github.com/containous/traefik/v2/pkg/responsemodifiers"
|
||||
"github.com/containous/traefik/v2/pkg/server/middleware"
|
||||
"github.com/containous/traefik/v2/pkg/server/router"
|
||||
routertcp "github.com/containous/traefik/v2/pkg/server/router/tcp"
|
||||
@@ -67,9 +66,8 @@ func (f *RouterFactory) CreateRouters(conf dynamic.Configuration) (map[string]*t
|
||||
serviceManager := f.managerFactory.Build(rtConf)
|
||||
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
|
||||
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
|
||||
|
||||
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, f.chainBuilder)
|
||||
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder)
|
||||
|
||||
handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false)
|
||||
handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true)
|
||||
|
@@ -8,11 +8,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/runtime"
|
||||
"github.com/containous/traefik/v2/pkg/log"
|
||||
)
|
||||
|
||||
type serviceManager interface {
|
||||
BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error)
|
||||
BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error)
|
||||
LaunchHealthCheck()
|
||||
}
|
||||
|
||||
@@ -43,87 +42,18 @@ func NewInternalHandlers(api func(configuration *runtime.Configuration) http.Han
|
||||
}
|
||||
}
|
||||
|
||||
type responseModifier struct {
|
||||
r *http.Request
|
||||
w http.ResponseWriter
|
||||
|
||||
headersSent bool // whether headers have already been sent
|
||||
code int // status code, must default to 200
|
||||
|
||||
modifier func(*http.Response) error // can be nil
|
||||
modified bool // whether modifier has already been called for the current request
|
||||
modifierErr error // returned by modifier call
|
||||
}
|
||||
|
||||
// modifier can be nil.
|
||||
func newResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(*http.Response) error) *responseModifier {
|
||||
return &responseModifier{
|
||||
r: r,
|
||||
w: w,
|
||||
modifier: modifier,
|
||||
code: http.StatusOK,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *responseModifier) WriteHeader(code int) {
|
||||
if w.headersSent {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
w.code = code
|
||||
w.headersSent = true
|
||||
}()
|
||||
|
||||
if w.modifier == nil || w.modified {
|
||||
w.w.WriteHeader(code)
|
||||
return
|
||||
}
|
||||
|
||||
resp := http.Response{
|
||||
Header: w.w.Header(),
|
||||
Request: w.r,
|
||||
}
|
||||
|
||||
if err := w.modifier(&resp); err != nil {
|
||||
w.modifierErr = err
|
||||
// we are propagating when we are called in Write, but we're logging anyway,
|
||||
// because we could be called from another place which does not take care of
|
||||
// checking w.modifierErr.
|
||||
log.Errorf("Error when applying response modifier: %v", err)
|
||||
w.w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.modified = true
|
||||
w.w.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (w *responseModifier) Header() http.Header {
|
||||
return w.w.Header()
|
||||
}
|
||||
|
||||
func (w *responseModifier) Write(b []byte) (int, error) {
|
||||
w.WriteHeader(w.code)
|
||||
if w.modifierErr != nil {
|
||||
return 0, w.modifierErr
|
||||
}
|
||||
|
||||
return w.w.Write(b)
|
||||
}
|
||||
|
||||
// BuildHTTP builds an HTTP handler.
|
||||
func (m *InternalHandlers) BuildHTTP(rootCtx context.Context, serviceName string, respModifier func(*http.Response) error) (http.Handler, error) {
|
||||
func (m *InternalHandlers) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) {
|
||||
if !strings.HasSuffix(serviceName, "@internal") {
|
||||
return m.serviceManager.BuildHTTP(rootCtx, serviceName, respModifier)
|
||||
return m.serviceManager.BuildHTTP(rootCtx, serviceName)
|
||||
}
|
||||
|
||||
internalHandler, err := m.get(serviceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
internalHandler.ServeHTTP(newResponseModifier(w, r, respModifier), r)
|
||||
}), nil
|
||||
|
||||
return internalHandler, nil
|
||||
}
|
||||
|
||||
func (m *InternalHandlers) get(serviceName string) (http.Handler, error) {
|
||||
|
@@ -21,7 +21,7 @@ const StatusClientClosedRequest = 499
|
||||
// StatusClientClosedRequestText non-standard HTTP status for client disconnection.
|
||||
const StatusClientClosedRequestText = "Client Closed Request"
|
||||
|
||||
func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwarding, defaultRoundTripper http.RoundTripper, bufferPool httputil.BufferPool, responseModifier func(*http.Response) error) (http.Handler, error) {
|
||||
func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwarding, defaultRoundTripper http.RoundTripper, bufferPool httputil.BufferPool) (http.Handler, error) {
|
||||
var flushInterval types.Duration
|
||||
if responseForwarding != nil {
|
||||
err := flushInterval.Set(responseForwarding.FlushInterval)
|
||||
@@ -76,10 +76,9 @@ func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwar
|
||||
delete(outReq.Header, "Sec-Websocket-Protocol")
|
||||
delete(outReq.Header, "Sec-Websocket-Version")
|
||||
},
|
||||
Transport: defaultRoundTripper,
|
||||
FlushInterval: time.Duration(flushInterval),
|
||||
ModifyResponse: responseModifier,
|
||||
BufferPool: bufferPool,
|
||||
Transport: defaultRoundTripper,
|
||||
FlushInterval: time.Duration(flushInterval),
|
||||
BufferPool: bufferPool,
|
||||
ErrorHandler: func(w http.ResponseWriter, request *http.Request, err error) {
|
||||
statusCode := http.StatusInternalServerError
|
||||
|
||||
|
@@ -28,7 +28,7 @@ func BenchmarkProxy(b *testing.B) {
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil)
|
||||
|
||||
pool := newBufferPool()
|
||||
handler, _ := buildProxy(Bool(false), nil, &staticTransport{res}, pool, nil)
|
||||
handler, _ := buildProxy(Bool(false), nil, &staticTransport{res}, pool)
|
||||
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
@@ -20,7 +20,7 @@ import (
|
||||
func Bool(v bool) *bool { return &v }
|
||||
|
||||
func TestWebSocketTCPClose(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
@@ -59,7 +59,7 @@ func TestWebSocketTCPClose(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWebSocketPingPong(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -125,7 +125,7 @@ func TestWebSocketPingPong(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWebSocketEcho(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
@@ -191,7 +191,7 @@ func TestWebSocketPassHost(t *testing.T) {
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
f, err := buildProxy(Bool(test.passHost), nil, http.DefaultTransport, nil, nil)
|
||||
f, err := buildProxy(Bool(test.passHost), nil, http.DefaultTransport, nil)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -250,7 +250,7 @@ func TestWebSocketPassHost(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWebSocketServerWithoutCheckOrigin(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
upgrader := gorillawebsocket.Upgrader{CheckOrigin: func(r *http.Request) bool {
|
||||
@@ -291,7 +291,7 @@ func TestWebSocketServerWithoutCheckOrigin(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWebSocketRequestWithOrigin(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
upgrader := gorillawebsocket.Upgrader{}
|
||||
@@ -337,7 +337,7 @@ func TestWebSocketRequestWithOrigin(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWebSocketRequestWithQueryParams(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
upgrader := gorillawebsocket.Upgrader{}
|
||||
@@ -377,7 +377,7 @@ func TestWebSocketRequestWithQueryParams(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWebSocketRequestWithHeadersInResponseWriter(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
@@ -409,7 +409,7 @@ func TestWebSocketRequestWithHeadersInResponseWriter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWebSocketRequestWithEncodedChar(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
upgrader := gorillawebsocket.Upgrader{}
|
||||
@@ -449,7 +449,7 @@ func TestWebSocketRequestWithEncodedChar(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWebSocketUpgradeFailed(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
@@ -499,7 +499,7 @@ func TestWebSocketUpgradeFailed(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestForwardsWebsocketTraffic(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
@@ -555,7 +555,7 @@ func TestWebSocketTransferTLSConfig(t *testing.T) {
|
||||
srv := createTLSWebsocketServer()
|
||||
defer srv.Close()
|
||||
|
||||
forwarderWithoutTLSConfig, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
|
||||
forwarderWithoutTLSConfig, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
proxyWithoutTLSConfig := createProxyWithForwarder(t, forwarderWithoutTLSConfig, srv.URL)
|
||||
@@ -574,7 +574,7 @@ func TestWebSocketTransferTLSConfig(t *testing.T) {
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
forwarderWithTLSConfig, err := buildProxy(Bool(true), nil, transport, nil, nil)
|
||||
forwarderWithTLSConfig, err := buildProxy(Bool(true), nil, transport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
proxyWithTLSConfig := createProxyWithForwarder(t, forwarderWithTLSConfig, srv.URL)
|
||||
@@ -593,7 +593,7 @@ func TestWebSocketTransferTLSConfig(t *testing.T) {
|
||||
|
||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
|
||||
forwarderWithTLSConfigFromDefaultTransport, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil)
|
||||
forwarderWithTLSConfigFromDefaultTransport, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
proxyWithTLSConfigFromDefaultTransport := createProxyWithForwarder(t, forwarderWithTLSConfigFromDefaultTransport, srv.URL)
|
||||
|
@@ -62,7 +62,7 @@ type Manager struct {
|
||||
}
|
||||
|
||||
// BuildHTTP Creates a http.Handler for a service configuration.
|
||||
func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error) {
|
||||
func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) {
|
||||
ctx := log.With(rootCtx, log.Str(log.ServiceName, serviceName))
|
||||
|
||||
serviceName = provider.GetQualifiedName(ctx, serviceName)
|
||||
@@ -91,21 +91,21 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, respons
|
||||
switch {
|
||||
case conf.LoadBalancer != nil:
|
||||
var err error
|
||||
lb, err = m.getLoadBalancerServiceHandler(ctx, serviceName, conf.LoadBalancer, responseModifier)
|
||||
lb, err = m.getLoadBalancerServiceHandler(ctx, serviceName, conf.LoadBalancer)
|
||||
if err != nil {
|
||||
conf.AddError(err, true)
|
||||
return nil, err
|
||||
}
|
||||
case conf.Weighted != nil:
|
||||
var err error
|
||||
lb, err = m.getWRRServiceHandler(ctx, serviceName, conf.Weighted, responseModifier)
|
||||
lb, err = m.getWRRServiceHandler(ctx, serviceName, conf.Weighted)
|
||||
if err != nil {
|
||||
conf.AddError(err, true)
|
||||
return nil, err
|
||||
}
|
||||
case conf.Mirroring != nil:
|
||||
var err error
|
||||
lb, err = m.getMirrorServiceHandler(ctx, conf.Mirroring, responseModifier)
|
||||
lb, err = m.getMirrorServiceHandler(ctx, conf.Mirroring)
|
||||
if err != nil {
|
||||
conf.AddError(err, true)
|
||||
return nil, err
|
||||
@@ -119,8 +119,8 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, respons
|
||||
return lb, nil
|
||||
}
|
||||
|
||||
func (m *Manager) getMirrorServiceHandler(ctx context.Context, config *dynamic.Mirroring, responseModifier func(*http.Response) error) (http.Handler, error) {
|
||||
serviceHandler, err := m.BuildHTTP(ctx, config.Service, responseModifier)
|
||||
func (m *Manager) getMirrorServiceHandler(ctx context.Context, config *dynamic.Mirroring) (http.Handler, error) {
|
||||
serviceHandler, err := m.BuildHTTP(ctx, config.Service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -131,7 +131,7 @@ func (m *Manager) getMirrorServiceHandler(ctx context.Context, config *dynamic.M
|
||||
}
|
||||
handler := mirror.New(serviceHandler, m.routinePool, maxBodySize)
|
||||
for _, mirrorConfig := range config.Mirrors {
|
||||
mirrorHandler, err := m.BuildHTTP(ctx, mirrorConfig.Name, responseModifier)
|
||||
mirrorHandler, err := m.BuildHTTP(ctx, mirrorConfig.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -144,7 +144,7 @@ func (m *Manager) getMirrorServiceHandler(ctx context.Context, config *dynamic.M
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string, config *dynamic.WeightedRoundRobin, responseModifier func(*http.Response) error) (http.Handler, error) {
|
||||
func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string, config *dynamic.WeightedRoundRobin) (http.Handler, error) {
|
||||
// TODO Handle accesslog and metrics with multiple service name
|
||||
if config.Sticky != nil && config.Sticky.Cookie != nil {
|
||||
config.Sticky.Cookie.Name = cookie.GetName(config.Sticky.Cookie.Name, serviceName)
|
||||
@@ -152,7 +152,7 @@ func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string,
|
||||
|
||||
balancer := wrr.New(config.Sticky)
|
||||
for _, service := range config.Services {
|
||||
serviceHandler, err := m.BuildHTTP(ctx, service.Name, responseModifier)
|
||||
serviceHandler, err := m.BuildHTTP(ctx, service.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -162,18 +162,13 @@ func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string,
|
||||
return balancer, nil
|
||||
}
|
||||
|
||||
func (m *Manager) getLoadBalancerServiceHandler(
|
||||
ctx context.Context,
|
||||
serviceName string,
|
||||
service *dynamic.ServersLoadBalancer,
|
||||
responseModifier func(*http.Response) error,
|
||||
) (http.Handler, error) {
|
||||
func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName string, service *dynamic.ServersLoadBalancer) (http.Handler, error) {
|
||||
if service.PassHostHeader == nil {
|
||||
defaultPassHostHeader := true
|
||||
service.PassHostHeader = &defaultPassHostHeader
|
||||
}
|
||||
|
||||
fwd, err := buildProxy(service.PassHostHeader, service.ResponseForwarding, m.defaultRoundTripper, m.bufferPool, responseModifier)
|
||||
fwd, err := buildProxy(service.PassHostHeader, service.ResponseForwarding, m.defaultRoundTripper, m.bufferPool)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -259,7 +259,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
handler, err := sm.getLoadBalancerServiceHandler(context.Background(), test.serviceName, test.service, test.responseModifier)
|
||||
handler, err := sm.getLoadBalancerServiceHandler(context.Background(), test.serviceName, test.service)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, handler)
|
||||
@@ -339,7 +339,7 @@ func TestManager_Build(t *testing.T) {
|
||||
ctx = provider.AddInContext(ctx, "foobar@"+test.providerName)
|
||||
}
|
||||
|
||||
_, err := manager.BuildHTTP(ctx, test.serviceName, nil)
|
||||
_, err := manager.BuildHTTP(ctx, test.serviceName)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
@@ -357,7 +357,7 @@ func TestMultipleTypeOnBuildHTTP(t *testing.T) {
|
||||
|
||||
manager := NewManager(services, http.DefaultTransport, nil, nil)
|
||||
|
||||
_, err := manager.BuildHTTP(context.Background(), "test@file", nil)
|
||||
_, err := manager.BuildHTTP(context.Background(), "test@file")
|
||||
assert.Error(t, err, "cannot create service: multi-types service not supported, consider declaring two different pieces of service instead")
|
||||
}
|
||||
|
||||
|
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/containous/traefik/v2/pkg/log"
|
||||
"github.com/containous/traefik/v2/pkg/tls/generate"
|
||||
"github.com/containous/traefik/v2/pkg/types"
|
||||
"github.com/go-acme/lego/v3/challenge/tlsalpn01"
|
||||
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
FROM node:12.11
|
||||
FROM node:12.18
|
||||
|
||||
ENV WEBUI_DIR /src/webui
|
||||
RUN mkdir -p $WEBUI_DIR
|
||||
|
Reference in New Issue
Block a user