forked from Ivasoft/traefik
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23ef8f72d1 | ||
|
|
3445abe7ac | ||
|
|
35a40c8727 | ||
|
|
7f62667569 | ||
|
|
fd4ba585ee | ||
|
|
e73dd31619 | ||
|
|
ef9b79f85c | ||
|
|
32d88a977d | ||
|
|
547c380961 | ||
|
|
4fc077a5d2 | ||
|
|
b386964abc | ||
|
|
319e3065f0 | ||
|
|
a48a8a97a1 | ||
|
|
8be434aaad | ||
|
|
d9fc775084 | ||
|
|
e12630ef06 | ||
|
|
bc5e621683 | ||
|
|
c2c4dc9b58 | ||
|
|
8d4620dc53 | ||
|
|
2a2f7f783f | ||
|
|
985f8778e9 | ||
|
|
2f47bb0df6 | ||
|
|
7e0f0d9d11 | ||
|
|
e1f5866989 | ||
|
|
3c1ed0d9b2 | ||
|
|
10ab39c33b | ||
|
|
3072354ca5 | ||
|
|
14499cd6e5 | ||
|
|
5d3dc3348e | ||
|
|
6d8512bda0 | ||
|
|
cd68cbd3ea | ||
|
|
55845c95bb | ||
|
|
a01cbb42c7 | ||
|
|
b5da5760a2 | ||
|
|
c190b160e9 | ||
|
|
5dab09c42b | ||
|
|
03b08d67f0 |
82
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
82
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -1,82 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
<!-- PLEASE FOLLOW THE ISSUE TEMPLATE TO HELP TRIAGE AND SUPPORT! -->
|
||||
|
||||
### Do you want to request a *feature* or report a *bug*?
|
||||
|
||||
<!--
|
||||
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
|
||||
The issue tracker is for reporting bugs and feature requests only.
|
||||
For end-user related support questions, please refer to one of the following:
|
||||
|
||||
- the Traefik community forum: https://community.containo.us/
|
||||
|
||||
-->
|
||||
|
||||
Bug
|
||||
|
||||
<!--
|
||||
|
||||
The configurations between 1.X and 2.X are NOT compatible.
|
||||
Please have a look here https://doc.traefik.io/traefik/getting-started/configuration-overview/.
|
||||
|
||||
-->
|
||||
|
||||
### What did you do?
|
||||
|
||||
<!--
|
||||
|
||||
HOW TO WRITE A GOOD BUG REPORT?
|
||||
|
||||
- Respect the issue template as much as possible.
|
||||
- The title should be short and descriptive.
|
||||
- Explain the conditions which led you to report this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||
|
||||
-->
|
||||
|
||||
### What did you expect to see?
|
||||
|
||||
|
||||
|
||||
### What did you see instead?
|
||||
|
||||
|
||||
|
||||
### Output of `traefik version`: (_What version of Traefik are you using?_)
|
||||
|
||||
<!--
|
||||
`latest` is not considered as a valid version.
|
||||
|
||||
For the Traefik Docker image:
|
||||
docker run [IMAGE] version
|
||||
ex: docker run traefik version
|
||||
|
||||
-->
|
||||
|
||||
```
|
||||
(paste your output here)
|
||||
```
|
||||
|
||||
### What is your environment & configuration (arguments, toml, provider, platform, ...)?
|
||||
|
||||
```toml
|
||||
# (paste your configuration here)
|
||||
```
|
||||
|
||||
<!--
|
||||
Add more configuration information here.
|
||||
-->
|
||||
|
||||
|
||||
### If applicable, please paste the log output in DEBUG level (`--log.level=DEBUG` switch)
|
||||
|
||||
```
|
||||
(paste your output here)
|
||||
```
|
||||
35
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
35
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
@@ -1,35 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
<!-- PLEASE FOLLOW THE ISSUE TEMPLATE TO HELP TRIAGE AND SUPPORT! -->
|
||||
|
||||
### Do you want to request a *feature* or report a *bug*?
|
||||
|
||||
<!--
|
||||
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
|
||||
The issue tracker is for reporting bugs and feature requests only.
|
||||
For end-user related support questions, please refer to one of the following:
|
||||
|
||||
- the Traefik community forum: https://community.containo.us/
|
||||
|
||||
-->
|
||||
|
||||
Feature
|
||||
|
||||
### What did you expect to see?
|
||||
|
||||
<!--
|
||||
|
||||
HOW TO WRITE A GOOD ISSUE?
|
||||
|
||||
- Respect the issue template as much as possible.
|
||||
- The title should be short and descriptive.
|
||||
- Explain the conditions which led you to report this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||
|
||||
-->
|
||||
80
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
80
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
name: Bug Report (Traefik)
|
||||
description: Create a report to help us improve.
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Welcome!
|
||||
description: |
|
||||
The issue tracker is for reporting bugs and feature requests only. For end-user related support questions, please refer to one of the following:
|
||||
- the Traefik community forum: https://community.containo.us/
|
||||
|
||||
The configurations between 1.X and 2.X are NOT compatible. Please have a look [here](https://doc.traefik.io/traefik/getting-started/configuration-overview/).
|
||||
|
||||
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
options:
|
||||
- label: Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any.
|
||||
required: true
|
||||
- label: Yes, I've searched similar issues on the [Traefik community forum](https://community.containo.us) and didn't find any.
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What did you do?
|
||||
description: |
|
||||
How to write a good bug report?
|
||||
|
||||
- Respect the issue template as much as possible.
|
||||
- The title should be short and descriptive.
|
||||
- Explain the conditions which led you to report this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown)
|
||||
placeholder: What did you do?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What did you see instead?
|
||||
placeholder: What did you see instead?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What version of Traefik are you using?
|
||||
description: |
|
||||
`latest` is not considered as a valid version.
|
||||
|
||||
Output of `traefik version`.
|
||||
|
||||
For the Traefik Docker image (`docker run [IMAGE] version`), example:
|
||||
```console
|
||||
$ docker run traefik version
|
||||
```
|
||||
placeholder: Paste your output here.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What is your environment & configuration?
|
||||
description: arguments, toml, provider, platform, ...
|
||||
placeholder: Add information here.
|
||||
value: |
|
||||
```yaml
|
||||
# (paste your configuration here)
|
||||
```
|
||||
|
||||
Add more configuration information here.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, please paste the log output in DEBUG level
|
||||
description: "`--log.level=DEBUG` switch."
|
||||
placeholder: Paste your output here.
|
||||
validations:
|
||||
required: false
|
||||
33
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
33
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Feature Request (Traefik)
|
||||
description: Suggest an idea for this project.
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Welcome!
|
||||
description: |
|
||||
The issue tracker is for reporting bugs and feature requests only. For end-user related support questions, please refer to one of the following:
|
||||
- the Traefik community forum: https://community.containo.us/
|
||||
|
||||
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
options:
|
||||
- label: Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any.
|
||||
required: true
|
||||
- label: Yes, I've searched similar issues on the [Traefik community forum](https://community.containo.us) and didn't find any.
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What did you expect to see?
|
||||
description: |
|
||||
How to write a good issue?
|
||||
|
||||
- Respect the issue template as much as possible.
|
||||
- The title should be short and descriptive.
|
||||
- Explain the conditions which led you to report this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown)
|
||||
placeholder: What did you expect to see?
|
||||
validations:
|
||||
required: true
|
||||
82
.github/workflows/build.yaml
vendored
Normal file
82
.github/workflows/build.yaml
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
name: Build Binaries
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.16
|
||||
CGO_ENABLED: 0
|
||||
PRE_TARGET: ""
|
||||
|
||||
jobs:
|
||||
|
||||
build-webui:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build webui
|
||||
run: |
|
||||
make generate-webui
|
||||
tar czvf webui.tar.gz ./static/
|
||||
|
||||
- name: Artifact webui
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: webui.tar.gz
|
||||
path: webui.tar.gz
|
||||
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-20.04, macos-latest, windows-latest ]
|
||||
needs:
|
||||
- build-webui
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ github.workspace }}/go/src/github.com/traefik/traefik
|
||||
|
||||
steps:
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: go/src/github.com/traefik/traefik
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
~/.cache/go-build
|
||||
~/Library/Caches/go-build
|
||||
'%LocalAppData%\go-build'
|
||||
key: ${{ runner.os }}-build-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-build-go-
|
||||
|
||||
- name: Installing dependencies
|
||||
run: go install github.com/containous/go-bindata/go-bindata@v1.0.0
|
||||
|
||||
- name: Artifact webui
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: webui.tar.gz
|
||||
path: ${{ github.workspace }}/go/src/github.com/traefik/traefik
|
||||
|
||||
- name: Untar webui
|
||||
run: tar xvf webui.tar.gz
|
||||
|
||||
- name: Build
|
||||
run: make binary
|
||||
5
.github/workflows/check_doc.yml
vendored
5
.github/workflows/check_doc.yml
vendored
@@ -2,15 +2,16 @@ name: Check Documentation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
|
||||
docs:
|
||||
name: Check, verify and build documentation
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
|
||||
10
.github/workflows/documentation.yml
vendored
10
.github/workflows/documentation.yml
vendored
@@ -6,18 +6,18 @@ on:
|
||||
- master
|
||||
- v*
|
||||
|
||||
env:
|
||||
STRUCTOR_VERSION: v1.11.2
|
||||
MIXTUS_VERSION: v0.4.1
|
||||
|
||||
jobs:
|
||||
|
||||
docs:
|
||||
name: Doc Process
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.repository == 'traefik/traefik'
|
||||
env:
|
||||
STRUCTOR_VERSION: v1.11.2
|
||||
MIXTUS_VERSION: v0.4.1
|
||||
|
||||
steps:
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
|
||||
46
.github/workflows/test-unit.yaml
vendored
Normal file
46
.github/workflows/test-unit.yaml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Test Unit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.16
|
||||
PRE_TARGET: ""
|
||||
|
||||
jobs:
|
||||
|
||||
test-unit:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ github.workspace }}/go/src/github.com/traefik/traefik
|
||||
|
||||
steps:
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: go/src/github.com/traefik/traefik
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
~/.cache/go-build
|
||||
key: ${{ runner.os }}-test-unit-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-test-unit-go-
|
||||
|
||||
- name: Installing dependencies
|
||||
run: go install github.com/containous/go-bindata/go-bindata@v1.0.0
|
||||
|
||||
- name: Tests
|
||||
run: make test-unit
|
||||
100
.github/workflows/validate.yaml
vendored
Normal file
100
.github/workflows/validate.yaml
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
name: Validate
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.16
|
||||
GOLANGCI_LINT_VERSION: v1.41.1
|
||||
MISSSPELL_VERSION: v0.3.4
|
||||
PRE_TARGET: ""
|
||||
|
||||
jobs:
|
||||
|
||||
validate:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ github.workspace }}/go/src/github.com/traefik/traefik
|
||||
|
||||
steps:
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: go/src/github.com/traefik/traefik
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
~/.cache/go-build
|
||||
key: ${{ runner.os }}-validate-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-validate-go-
|
||||
|
||||
- name: Installing dependencies
|
||||
run: go install github.com/containous/go-bindata/go-bindata@v1.0.0
|
||||
|
||||
- name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }}
|
||||
run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION}
|
||||
|
||||
- name: Install missspell ${{ env.MISSSPELL_VERSION }}
|
||||
run: curl -sfL https://raw.githubusercontent.com/client9/misspell/master/install-misspell.sh | sh -s -- -b $(go env GOPATH)/bin ${MISSSPELL_VERSION}
|
||||
|
||||
- name: Validate
|
||||
run: make validate
|
||||
|
||||
validate-generate:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ github.workspace }}/go/src/github.com/traefik/traefik
|
||||
|
||||
steps:
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: go/src/github.com/traefik/traefik
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
~/.cache/go-build
|
||||
key: ${{ runner.os }}-validate-generate-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-validate-generate-go-
|
||||
|
||||
- name: Installing dependencies
|
||||
run: go install github.com/containous/go-bindata/go-bindata@v1.0.0
|
||||
|
||||
- name: go generate
|
||||
run: |
|
||||
go generate
|
||||
git diff --exit-code
|
||||
|
||||
- name: go mod tidy
|
||||
run: |
|
||||
go mod tidy
|
||||
git diff --exit-code
|
||||
|
||||
- name: make generate-crd
|
||||
run: |
|
||||
make generate-crd
|
||||
git diff --exit-code
|
||||
@@ -67,6 +67,7 @@
|
||||
"scopelint", # Deprecated
|
||||
"interfacer", # Deprecated
|
||||
"maligned", # Deprecated
|
||||
"golint", # Deprecated
|
||||
"sqlclosecheck", # Not relevant (SQL)
|
||||
"rowserrcheck", # Not relevant (SQL)
|
||||
"lll", # Not relevant
|
||||
@@ -97,6 +98,7 @@
|
||||
"unparam", # Too strict
|
||||
"godox", # Too strict
|
||||
"forcetypeassert", # Too strict
|
||||
"tagliatelle", # Not compatible with current tags.
|
||||
]
|
||||
|
||||
[issues]
|
||||
|
||||
100
.semaphore/semaphore.yml
Normal file
100
.semaphore/semaphore.yml
Normal file
@@ -0,0 +1,100 @@
|
||||
version: v1.0
|
||||
name: Traefik
|
||||
agent:
|
||||
machine:
|
||||
type: e1-standard-4
|
||||
os_image: ubuntu1804
|
||||
|
||||
fail_fast:
|
||||
stop:
|
||||
when: "branch != 'master'"
|
||||
|
||||
auto_cancel:
|
||||
queued:
|
||||
when: "branch != 'master'"
|
||||
running:
|
||||
when: "branch != 'master'"
|
||||
|
||||
global_job_config:
|
||||
prologue:
|
||||
commands:
|
||||
- curl -sSfL https://raw.githubusercontent.com/ldez/semgo/master/godownloader.sh | sudo sh -s -- -b "/usr/local/bin"
|
||||
- sudo semgo go1.16
|
||||
- export "GOPATH=$(go env GOPATH)"
|
||||
- export "SEMAPHORE_GIT_DIR=${GOPATH}/src/github.com/traefik/${SEMAPHORE_PROJECT_NAME}"
|
||||
- export "PATH=${GOPATH}/bin:${PATH}"
|
||||
- mkdir -vp "${SEMAPHORE_GIT_DIR}" "${GOPATH}/bin"
|
||||
- export GOPROXY=https://proxy.golang.org,direct
|
||||
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "${GOPATH}/bin" v1.41.1
|
||||
- curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | bash -s -- -b "${GOPATH}/bin"
|
||||
- go install github.com/containous/go-bindata/go-bindata@v1.0.0
|
||||
- checkout
|
||||
- cache restore traefik-$(checksum go.sum)
|
||||
|
||||
blocks:
|
||||
- name: Test Integration Container
|
||||
dependencies: []
|
||||
run:
|
||||
when: "branch =~ '.*' OR pull_request =~'.*'"
|
||||
task:
|
||||
jobs:
|
||||
- name: Test Integration Container
|
||||
commands:
|
||||
- make pull-images
|
||||
- mkdir -p static # Avoid to generate webui
|
||||
- PRE_TARGET="" make binary
|
||||
- make test-integration-container
|
||||
- df -h
|
||||
epilogue:
|
||||
always:
|
||||
commands:
|
||||
- cache store traefik-$(checksum go.sum) $HOME/go/pkg/mod
|
||||
|
||||
- name: Test Integration Host
|
||||
dependencies: []
|
||||
run:
|
||||
when: "branch =~ '.*' OR pull_request =~'.*'"
|
||||
task:
|
||||
env_vars:
|
||||
- name: PRE_TARGET
|
||||
value: ""
|
||||
jobs:
|
||||
- name: Test Integration Host
|
||||
commands:
|
||||
- mkdir -p static # Avoid to generate webui
|
||||
- make test-integration-host
|
||||
epilogue:
|
||||
always:
|
||||
commands:
|
||||
- cache store traefik-$(checksum go.sum) $HOME/go/pkg/mod
|
||||
|
||||
- name: Release
|
||||
dependencies: []
|
||||
run:
|
||||
when: "tag =~ '.*'"
|
||||
task:
|
||||
agent:
|
||||
machine:
|
||||
type: e1-standard-8
|
||||
os_image: ubuntu1804
|
||||
secrets:
|
||||
- name: traefik
|
||||
env_vars:
|
||||
- name: GH_VERSION
|
||||
value: 1.12.1
|
||||
- name: CODENAME
|
||||
value: "livarot"
|
||||
- name: PRE_TARGET
|
||||
value: ""
|
||||
prologue:
|
||||
commands:
|
||||
- export VERSION=${SEMAPHORE_GIT_TAG_NAME}
|
||||
- curl -sSL -o /tmp/gh_${GH_VERSION}_linux_amd64.tar.gz https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz
|
||||
- tar -zxvf /tmp/gh_${GH_VERSION}_linux_amd64.tar.gz -C /tmp
|
||||
- sudo mv /tmp/gh_${GH_VERSION}_linux_amd64/bin/gh /usr/local/bin/gh
|
||||
jobs:
|
||||
- name: Release
|
||||
commands:
|
||||
- make release-packages
|
||||
- gh release create ${SEMAPHORE_GIT_TAG_NAME} ./dist/traefik*.* --repo traefik/traefik --title ${SEMAPHORE_GIT_TAG_NAME} --notes ${SEMAPHORE_GIT_TAG_NAME}
|
||||
- ./script/deploy.sh
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
sudo rm -rf static
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
curl -O https://dl.google.com/go/go"${GO_VERSION}".linux-amd64.tar.gz
|
||||
|
||||
tar -xvf go"${GO_VERSION}".linux-amd64.tar.gz
|
||||
rm -rf go"${GO_VERSION}".linux-amd64.tar.gz
|
||||
|
||||
sudo mkdir -p /usr/local/golang/"${GO_VERSION}"/go
|
||||
sudo mv go /usr/local/golang/"${GO_VERSION}"/
|
||||
|
||||
sudo rm /usr/local/bin/go
|
||||
sudo chmod +x /usr/local/golang/"${GO_VERSION}"/go/bin/go
|
||||
sudo ln -s /usr/local/golang/"${GO_VERSION}"/go/bin/go /usr/local/bin/go
|
||||
|
||||
export GOROOT="/usr/local/golang/${GO_VERSION}/go"
|
||||
export GOTOOLDIR="/usr/local/golang/${GO_VERSION}/go/pkg/tool/linux_amd64"
|
||||
|
||||
go version
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
if [ -n "$SHOULD_TEST" ]; then ci_retry make pull-images; fi
|
||||
|
||||
if [ -n "$SHOULD_TEST" ]; then ci_retry make test-integration; fi
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
ci_retry make validate
|
||||
|
||||
if [ -n "$SHOULD_TEST" ]; then ci_retry make test-unit; fi
|
||||
|
||||
if [ -n "$SHOULD_TEST" ]; then make -j"${N_MAKE_JOBS}" crossbinary-default-parallel; fi
|
||||
@@ -1,35 +0,0 @@
|
||||
# For personnal CI
|
||||
# mv /home/runner/workspace/src/github.com/<username>/ /home/runner/workspace/src/github.com/traefik/
|
||||
# cd /home/runner/workspace/src/github.com/traefik/traefik/
|
||||
for s in apache2 cassandra elasticsearch memcached mysql mongod postgresql sphinxsearch rethinkdb rabbitmq-server redis-server; do sudo service $s stop; done
|
||||
sudo swapoff -a
|
||||
sudo dd if=/dev/zero of=/swapfile bs=1M count=3072
|
||||
sudo mkswap /swapfile
|
||||
sudo swapon /swapfile
|
||||
sudo rm -rf /home/runner/.rbenv
|
||||
sudo rm -rf /usr/local/golang/{1.4.3,1.5.4,1.6.4,1.7.6,1.8.6,1.9.7,1.10.3,1.11}
|
||||
#export DOCKER_VERSION=18.06.3
|
||||
source .semaphoreci/vars
|
||||
if [ -z "${PULL_REQUEST_NUMBER}" ]; then SHOULD_TEST="-*-"; else TEMP_STORAGE=$(curl --silent https://patch-diff.githubusercontent.com/raw/traefik/traefik/pull/${PULL_REQUEST_NUMBER}.diff | patch --dry-run -p1 -R || true); fi
|
||||
echo ${SHOULD_TEST}
|
||||
if [ -n "$TEMP_STORAGE" ]; then SHOULD_TEST=$(echo "$TEMP_STORAGE" | grep -Ev '(.md|.yaml|.yml)' || :); fi
|
||||
echo ${TEMP_STORAGE}
|
||||
echo ${SHOULD_TEST}
|
||||
#if [ -n "$SHOULD_TEST" ]; then sudo -E apt-get -yq update; fi
|
||||
#if [ -n "$SHOULD_TEST" ]; then sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install docker-ce=${DOCKER_VERSION}*; fi
|
||||
if [ -n "$SHOULD_TEST" ]; then docker version; fi
|
||||
export GO_VERSION=1.13
|
||||
if [ -f "./go.mod" ]; then GO_VERSION="$(grep '^go .*' go.mod | awk '{print $2}')"; export GO_VERSION; fi
|
||||
#if [ "${GO_VERSION}" == '1.15' ]; then export GO_VERSION=1.15rc2; fi
|
||||
echo "Selected Go version: ${GO_VERSION}"
|
||||
|
||||
if [ -f "./.semaphoreci/golang.sh" ]; then ./.semaphoreci/golang.sh; fi
|
||||
if [ -f "./.semaphoreci/golang.sh" ]; then export GOROOT="/usr/local/golang/${GO_VERSION}/go"; fi
|
||||
if [ -f "./.semaphoreci/golang.sh" ]; then export GOTOOLDIR="/usr/local/golang/${GO_VERSION}/go/pkg/tool/linux_amd64"; fi
|
||||
go version
|
||||
|
||||
if [ -f "./go.mod" ]; then export GO111MODULE=on; fi
|
||||
if [ -f "./go.mod" ]; then export GOPROXY=https://proxy.golang.org; fi
|
||||
if [ -f "./go.mod" ]; then go mod download; fi
|
||||
|
||||
df
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
export REPO='traefik/traefik'
|
||||
|
||||
if VERSION=$(git describe --exact-match --abbrev=0 --tags);
|
||||
then
|
||||
export VERSION
|
||||
else
|
||||
export VERSION=''
|
||||
fi
|
||||
|
||||
export CODENAME=livarot
|
||||
|
||||
export N_MAKE_JOBS=2
|
||||
|
||||
|
||||
function ci_retry {
|
||||
|
||||
local NRETRY=3
|
||||
local NSLEEP=5
|
||||
local n=0
|
||||
|
||||
until [ $n -ge $NRETRY ]
|
||||
do
|
||||
"$@" && break
|
||||
n=$((n+1))
|
||||
echo "${*} failed, attempt ${n}/${NRETRY}"
|
||||
sleep $NSLEEP
|
||||
done
|
||||
|
||||
[ $n -lt $NRETRY ]
|
||||
|
||||
}
|
||||
|
||||
export -f ci_retry
|
||||
49
.travis.yml
49
.travis.yml
@@ -1,49 +0,0 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
git:
|
||||
depth: false
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
env:
|
||||
global:
|
||||
- REPO=$TRAVIS_REPO_SLUG
|
||||
- VERSION=$TRAVIS_TAG
|
||||
- CODENAME=livarot
|
||||
- GO111MODULE=on
|
||||
|
||||
script:
|
||||
- echo "Skipping tests... (Tests are executed on SemaphoreCI)"
|
||||
|
||||
before_deploy:
|
||||
- >
|
||||
if ! [ "$BEFORE_DEPLOY_RUN" ]; then
|
||||
export BEFORE_DEPLOY_RUN=1;
|
||||
sudo -E apt-get -yq update;
|
||||
sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install docker-ce=${DOCKER_VERSION}*;
|
||||
docker version;
|
||||
echo "${DOCKERHUB_PASSWORD}" | docker login -u "${DOCKERHUB_USERNAME}" --password-stdin;
|
||||
make build-image;
|
||||
if [ "$TRAVIS_TAG" ]; then
|
||||
make release-packages;
|
||||
fi;
|
||||
fi
|
||||
|
||||
deploy:
|
||||
- provider: releases
|
||||
api_key: ${GITHUB_TOKEN}
|
||||
file: dist/traefik*
|
||||
skip_cleanup: true
|
||||
file_glob: true
|
||||
on:
|
||||
repo: traefik/traefik
|
||||
tags: true
|
||||
- provider: script
|
||||
script: sh script/deploy.sh
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: traefik/traefik
|
||||
tags: true
|
||||
|
||||
Binary file not shown.
49
CHANGELOG.md
49
CHANGELOG.md
@@ -1,3 +1,52 @@
|
||||
## [v2.4.14](https://github.com/traefik/traefik/tree/v2.4.14) (2021-08-16)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.4.13...v2.4.14)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[k8s/crd,k8s]** Avoid unauthorized middleware cross namespace reference ([#8322](https://github.com/traefik/traefik/pull/8322) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[kv]** Remove unwanted trailing slash in key ([#8335](https://github.com/traefik/traefik/pull/8335) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[middleware]** Redirect: fix comparison when explicit port request and implicit redirect port ([#8348](https://github.com/traefik/traefik/pull/8348) by [tcolgate](https://github.com/tcolgate))
|
||||
|
||||
**Documentation:**
|
||||
- **[kv]** Fix a router's entryPoint definition example for KV provider ([#8357](https://github.com/traefik/traefik/pull/8357) by [avtion](https://github.com/avtion))
|
||||
|
||||
## [v2.4.13](https://github.com/traefik/traefik/tree/v2.4.13) (2021-07-30)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.4.12...v2.4.13)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[authentication,middleware]** Remove hop-by-hop headers define in connection header beore some middleware ([#8319](https://github.com/traefik/traefik/pull/8319) by [ldez](https://github.com/ldez))
|
||||
|
||||
## [v2.4.12](https://github.com/traefik/traefik/tree/v2.4.12) (2021-07-26)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.4.11...v2.4.12)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[k8s,k8s/ingress]** Get Kubernetes server version early ([#8286](https://github.com/traefik/traefik/pull/8286) by [rtribotte](https://github.com/rtribotte))
|
||||
- **[k8s,k8s/ingress]** Don't remove ingress config on API call failure ([#8185](https://github.com/traefik/traefik/pull/8185) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[middleware]** Ratelimiter: use correct ttlSeconds value, and always call Set ([#8254](https://github.com/traefik/traefik/pull/8254) by [mpl](https://github.com/mpl))
|
||||
- **[tls]** Check if defaultcertificate is defined in store ([#8274](https://github.com/traefik/traefik/pull/8274) by [dtomcej](https://github.com/dtomcej))
|
||||
|
||||
## [v2.4.11](https://github.com/traefik/traefik/tree/v2.4.11) (2021-07-15)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.4.9...v2.4.11)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[k8s,k8s/crd,k8s/ingress]** Disable ExternalName Services by default on Kubernetes providers ([#8261](https://github.com/traefik/traefik/pull/8261) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[k8s,k8s/crd,k8s/ingress]** Fix: malformed Kubernetes resource names and references in tests ([#8226](https://github.com/traefik/traefik/pull/8226) by [rtribotte](https://github.com/rtribotte))
|
||||
- **[k8s,k8s/crd]** Disable Cross-Namespace by default for IngressRoute provider ([#8260](https://github.com/traefik/traefik/pull/8260) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[logs,middleware]** Accesslog: support multiple values for a given header ([#8258](https://github.com/traefik/traefik/pull/8258) by [ldez](https://github.com/ldez))
|
||||
- **[logs]** Ignore http 1.0 request host missing errors ([#8252](https://github.com/traefik/traefik/pull/8252) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[middleware]** Headers Middleware: support http.CloseNotifier interface ([#8238](https://github.com/traefik/traefik/pull/8238) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[tls]** Detect certificates content modifications ([#8243](https://github.com/traefik/traefik/pull/8243) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
|
||||
**Documentation:**
|
||||
- **[middleware,k8s]** Fix invalid subdomain ([#8212](https://github.com/traefik/traefik/pull/8212) by [WLun001](https://github.com/WLun001))
|
||||
- Add the list of available provider names ([#8225](https://github.com/traefik/traefik/pull/8225) by [WLun001](https://github.com/WLun001))
|
||||
- Fix maintainers-guidelines page title ([#8216](https://github.com/traefik/traefik/pull/8216) by [kubopanda](https://github.com/kubopanda))
|
||||
- Typos in contributing section ([#8215](https://github.com/traefik/traefik/pull/8215) by [kubopanda](https://github.com/kubopanda))
|
||||
|
||||
## [v2.4.10](https://github.com/traefik/traefik/tree/v2.4.10) (2021-07-13)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.4.9...v2.4.10)
|
||||
|
||||
Release canceled.
|
||||
|
||||
## [v2.4.9](https://github.com/traefik/traefik/tree/v2.4.9) (2021-06-21)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.4.8...v2.4.9)
|
||||
|
||||
|
||||
27
Makefile
27
Makefile
@@ -58,8 +58,9 @@ build-webui-image:
|
||||
docker build -t traefik-webui --build-arg ARG_PLATFORM_URL=$(PLATFORM_URL) -f webui/Dockerfile webui
|
||||
|
||||
## Generate WebUI
|
||||
generate-webui: build-webui-image
|
||||
generate-webui:
|
||||
if [ ! -d "static" ]; then \
|
||||
$(MAKE) build-webui-image; \
|
||||
mkdir -p static; \
|
||||
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui npm run build:nc; \
|
||||
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui chown -R $(shell id -u):$(shell id -g) ../static; \
|
||||
@@ -92,8 +93,16 @@ pull-images:
|
||||
grep --no-filename -E '^\s+image:' ./integration/resources/compose/*.yml | awk '{print $$2}' | sort | uniq | xargs -P 6 -n 1 docker pull
|
||||
|
||||
## Run the integration tests
|
||||
test-integration: $(PRE_TARGET)
|
||||
$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK),TEST_CONTAINER=1) ./script/make.sh generate binary test-integration
|
||||
test-integration: $(PRE_TARGET) binary
|
||||
$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK),TEST_CONTAINER=1) ./script/make.sh test-integration
|
||||
TEST_HOST=1 ./script/make.sh test-integration
|
||||
|
||||
## Run the container integration tests
|
||||
test-integration-container: $(PRE_TARGET) binary
|
||||
$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK),TEST_CONTAINER=1) ./script/make.sh test-integration
|
||||
|
||||
## Run the host integration tests
|
||||
test-integration-host: $(PRE_TARGET) binary
|
||||
TEST_HOST=1 ./script/make.sh test-integration
|
||||
|
||||
## Validate code and docs
|
||||
@@ -123,7 +132,7 @@ shell: build-dev-image
|
||||
docs:
|
||||
make -C ./docs docs
|
||||
|
||||
## Serve the documentation site localy
|
||||
## Serve the documentation site locally
|
||||
docs-serve:
|
||||
make -C ./docs docs-serve
|
||||
|
||||
@@ -133,20 +142,20 @@ docs-pull-images:
|
||||
|
||||
## Generate CRD clientset
|
||||
generate-crd:
|
||||
./script/update-generated-crd-code.sh
|
||||
@$(CURDIR)/script/code-gen.sh
|
||||
|
||||
## Create packages for the release
|
||||
release-packages: generate-webui build-dev-image
|
||||
release-packages: generate-webui $(PRE_TARGET)
|
||||
rm -rf dist
|
||||
$(DOCKER_RUN_TRAEFIK_NOTTY) goreleaser release --skip-publish --timeout="60m"
|
||||
$(DOCKER_RUN_TRAEFIK_NOTTY) tar cfz dist/traefik-${VERSION}.src.tar.gz \
|
||||
$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK_NOTTY)) goreleaser release --skip-publish --timeout="60m"
|
||||
$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK_NOTTY)) tar cfz dist/traefik-${VERSION}.src.tar.gz \
|
||||
--exclude-vcs \
|
||||
--exclude .idea \
|
||||
--exclude .travis \
|
||||
--exclude .semaphoreci \
|
||||
--exclude .github \
|
||||
--exclude dist .
|
||||
$(DOCKER_RUN_TRAEFIK_NOTTY) chown -R $(shell id -u):$(shell id -g) dist/
|
||||
$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK_NOTTY)) chown -R $(shell id -u):$(shell id -g) dist/
|
||||
|
||||
## Format the Code
|
||||
fmt:
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
[](https://semaphoreci.com/containous/traefik)
|
||||
[](https://doc.traefik.io/traefik)
|
||||
[](https://goreportcard.com/report/traefik/traefik)
|
||||
[](https://microbadger.com/images/traefik)
|
||||
[](https://github.com/traefik/traefik/blob/master/LICENSE.md)
|
||||
[](https://community.traefik.io/)
|
||||
[](https://twitter.com/intent/follow?screen_name=traefik)
|
||||
|
||||
@@ -19,7 +19,7 @@ RUN mkdir -p /usr/local/bin \
|
||||
&& chmod +x /usr/local/bin/go-bindata
|
||||
|
||||
# Download golangci-lint binary to bin folder in $GOPATH
|
||||
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.39.0
|
||||
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.41.1
|
||||
|
||||
# Download misspell binary to bin folder in $GOPATH
|
||||
RUN curl -sfL https://raw.githubusercontent.com/client9/misspell/master/install-misspell.sh | bash -s -- -b $GOPATH/bin v0.3.4
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# The Maintainers Guidelines
|
||||
# Maintainer's Guidelines
|
||||
|
||||

|
||||

|
||||
|
||||
Note: the document is a work in progress.
|
||||
|
||||
@@ -13,14 +13,14 @@ and firmly standing against the elitist closed approach.
|
||||
Being part of the core team should be accessible to anyone motivated
|
||||
and wants to be part of that journey!
|
||||
|
||||
## Onboarding process
|
||||
## Onboarding Process
|
||||
|
||||
If you consider joining our community please drop us a line using Twitter or leave a note in the issue.
|
||||
We will schedule a quick call to meet you and learn more about your motivation.
|
||||
During the call, the team will discuss the process of becoming a maintainer.
|
||||
We will be happy to answer any questions and explain all your doubts.
|
||||
|
||||
## Maintainers requirements
|
||||
## Maintainer's Requirements
|
||||
|
||||
Note: you do not have to meet all the listed requirements,
|
||||
but must have achieved several.
|
||||
@@ -34,7 +34,7 @@ but must have achieved several.
|
||||
or other technical forums/boards such as K8S slack, Reddit, StackOverflow, hacker news.
|
||||
- Have read and accepted the contributor guidelines.
|
||||
|
||||
## Maintainers responsibilities and privileges
|
||||
## Maintainer's Responsibilities and Privileges
|
||||
|
||||
There are lots of areas where you can contribute to the project,
|
||||
but we can suggest you start with activities such as:
|
||||
@@ -103,7 +103,7 @@ maintainers' activity and involvement will be reviewed on a regular basis.
|
||||
non-threatening,
|
||||
and friendly behavior towards other people on the maintainer team and with our community?
|
||||
|
||||
## Additional comments for (not only) maintainers
|
||||
## Additional Comments for (not only) Maintainers
|
||||
|
||||
- Be able to put yourself in users’ shoes.
|
||||
- Be open-minded and respectful with other maintainers and other community members.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainers
|
||||
|
||||
## The team
|
||||
## The Team
|
||||
|
||||
* Emile Vauge [@emilevauge](https://github.com/emilevauge)
|
||||
* Vincent Demeester [@vdemeester](https://github.com/vdemeester)
|
||||
@@ -20,15 +20,15 @@
|
||||
* Kevin Pollet [@kevinpollet](https://github.com/kevinpollet)
|
||||
* Harold Ozouf [@jspdown](https://github.com/jspdown)
|
||||
|
||||
## Maintainers guidelines
|
||||
## Maintainer's Guidelines
|
||||
|
||||
Please read the [maintainers guidelines](maintainers-guidelines.md)
|
||||
Please read the [maintainer's guidelines](maintainers-guidelines.md)
|
||||
|
||||
## Issue Triage
|
||||
|
||||
Issues and PRs are triaged daily and the process for triaging may be found under [triaging issues](https://github.com/traefik/contributors-guide/blob/master/issue_triage.md) in our [contributors guide repository](https://github.com/traefik/contributors-guide).
|
||||
|
||||
## PR review process:
|
||||
## PR Review Process
|
||||
|
||||
The process for reviewing PRs may be found under [review guidelines](https://github.com/traefik/contributors-guide/blob/master/review_guidelines.md) in our contributors guide repository.
|
||||
|
||||
@@ -118,7 +118,7 @@ The `status/*` labels represent the desired state in the workflow.
|
||||
* `priority/P2`: need to be fixed in the future.
|
||||
* `priority/P3`: maybe.
|
||||
|
||||
### PR size
|
||||
### PR Size
|
||||
|
||||
Automatically set by a bot.
|
||||
|
||||
|
||||
@@ -124,3 +124,16 @@ http:
|
||||
If there is a need for a response code other than a `503` and/or a custom message,
|
||||
the principle of the above example above (a catchall router) still stands,
|
||||
but the `unavailable` service should be adapted to fit such a need.
|
||||
|
||||
## Why Is My TLS Certificate Not Reloaded When Its Contents Change ?
|
||||
|
||||
With the file provider,
|
||||
a configuration update is only triggered when one of the [watched](../providers/file.md#provider-configuration) configuration files is modified.
|
||||
|
||||
Which is why, when a certificate is defined by path,
|
||||
and the actual contents of this certificate change,
|
||||
a configuration update is _not_ triggered.
|
||||
|
||||
To take into account the new certificate contents, the update of the dynamic configuration must be forced.
|
||||
One way to achieve that, is to trigger a file notification,
|
||||
for example, by using the `touch` command on the configuration file.
|
||||
|
||||
@@ -23,7 +23,7 @@ labels:
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: testHeader
|
||||
name: test-header
|
||||
spec:
|
||||
headers:
|
||||
customRequestHeaders:
|
||||
@@ -86,7 +86,7 @@ labels:
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: testHeader
|
||||
name: test-header
|
||||
spec:
|
||||
headers:
|
||||
customRequestHeaders:
|
||||
@@ -154,7 +154,7 @@ labels:
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: testHeader
|
||||
name: test-header
|
||||
spec:
|
||||
headers:
|
||||
frameDeny: true
|
||||
@@ -212,7 +212,7 @@ labels:
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: testHeader
|
||||
name: test-header
|
||||
spec:
|
||||
headers:
|
||||
accessControlAllowMethods:
|
||||
|
||||
@@ -92,8 +92,8 @@ The `depth` option tells Traefik to use the `X-Forwarded-For` header and take th
|
||||
```yaml tab="Docker"
|
||||
# Whitelisting Based on `X-Forwarded-For` with `depth=2`
|
||||
labels:
|
||||
- "traefik.http.middlewares.testIPwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7"
|
||||
- "traefik.http.middlewares.testIPwhitelist.ipwhitelist.ipstrategy.depth=2"
|
||||
- "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7"
|
||||
- "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.ipstrategy.depth=2"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
@@ -101,7 +101,7 @@ labels:
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: testIPwhitelist
|
||||
name: test-ipwhitelist
|
||||
spec:
|
||||
ipWhiteList:
|
||||
sourceRange:
|
||||
@@ -113,22 +113,22 @@ spec:
|
||||
|
||||
```yaml tab="Consul Catalog"
|
||||
# Whitelisting Based on `X-Forwarded-For` with `depth=2`
|
||||
- "traefik.http.middlewares.testIPwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7"
|
||||
- "traefik.http.middlewares.testIPwhitelist.ipwhitelist.ipstrategy.depth=2"
|
||||
- "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7"
|
||||
- "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.ipstrategy.depth=2"
|
||||
```
|
||||
|
||||
```json tab="Marathon"
|
||||
"labels": {
|
||||
"traefik.http.middlewares.testIPwhitelist.ipwhitelist.sourcerange": "127.0.0.1/32, 192.168.1.7",
|
||||
"traefik.http.middlewares.testIPwhitelist.ipwhitelist.ipstrategy.depth": "2"
|
||||
"traefik.http.middlewares.test-ipwhitelist.ipwhitelist.sourcerange": "127.0.0.1/32, 192.168.1.7",
|
||||
"traefik.http.middlewares.test-ipwhitelist.ipwhitelist.ipstrategy.depth": "2"
|
||||
}
|
||||
```
|
||||
|
||||
```yaml tab="Rancher"
|
||||
# Whitelisting Based on `X-Forwarded-For` with `depth=2`
|
||||
labels:
|
||||
- "traefik.http.middlewares.testIPwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7"
|
||||
- "traefik.http.middlewares.testIPwhitelist.ipwhitelist.ipstrategy.depth=2"
|
||||
- "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7"
|
||||
- "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.ipstrategy.depth=2"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
|
||||
@@ -441,7 +441,7 @@ To apply a redirection:
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: http-redirect-ingressRoute
|
||||
name: http-redirect-ingressroute
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
@@ -459,7 +459,7 @@ To apply a redirection:
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: https-ingressRoute
|
||||
name: https-ingressroute
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
@@ -595,7 +595,7 @@ with the path `/admin` stripped, e.g. to `http://<IP>:<port>/`. In this case, yo
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: http-redirect-ingressRoute
|
||||
name: http-redirect-ingressroute
|
||||
namespace: admin-web
|
||||
spec:
|
||||
entryPoints:
|
||||
|
||||
@@ -189,7 +189,7 @@ metadata:
|
||||
|
||||
spec:
|
||||
tls:
|
||||
- secretName: myTlsSecret
|
||||
- secretName: my-tls-secret
|
||||
|
||||
rules:
|
||||
- host: example.com
|
||||
@@ -256,7 +256,7 @@ metadata:
|
||||
|
||||
spec:
|
||||
tls:
|
||||
- secretName: myTlsSecret
|
||||
- secretName: my-tls-secret
|
||||
|
||||
rules:
|
||||
- host: example.com
|
||||
@@ -364,3 +364,14 @@ For more information, please read the [HTTP routers rule](../routing/routers/ind
|
||||
### Tracing Span
|
||||
|
||||
In `v2.4.9`, we changed span error to log only server errors (>= 500).
|
||||
|
||||
## v2.4.9 to v2.4.10
|
||||
|
||||
### K8S CrossNamespace
|
||||
|
||||
In `v2.4.10`, the default value for `allowCrossNamespace` has been changed to `false`.
|
||||
|
||||
### K8S ExternalName Service
|
||||
|
||||
In `v2.4.10`, by default, it is no longer authorized to reference Kubernetes ExternalName services.
|
||||
To allow it, the `allowExternalNameServices` option should be set to `true`.
|
||||
|
||||
@@ -260,29 +260,48 @@ providers:
|
||||
|
||||
### `allowCrossNamespace`
|
||||
|
||||
_Optional, Default: true_
|
||||
_Optional, Default: false_
|
||||
|
||||
If the parameter is set to `false`, IngressRoutes are not able to reference any resources in other namespaces than theirs.
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
Please note that the default value for this option will be set to `false` in a future version.
|
||||
If the parameter is set to `true`, IngressRoutes are able to reference resources in other namespaces than theirs.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
kubernetesCRD:
|
||||
allowCrossNamespace: false
|
||||
allowCrossNamespace: true
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.kubernetesCRD]
|
||||
allowCrossNamespace = false
|
||||
allowCrossNamespace = true
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetescrd.allowCrossNamespace=false
|
||||
--providers.kubernetescrd.allowCrossNamespace=true
|
||||
```
|
||||
|
||||
### `allowExternalNameServices`
|
||||
|
||||
_Optional, Default: false_
|
||||
|
||||
If the parameter is set to `true`, IngressRoutes are able to reference ExternalName services.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
kubernetesCRD:
|
||||
allowExternalNameServices: true
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.kubernetesCRD]
|
||||
allowExternalNameServices = true
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetescrd.allowexternalnameservices=true
|
||||
```
|
||||
|
||||
## Full Example
|
||||
|
||||
@@ -73,17 +73,17 @@ This provider is proposed as an experimental feature and partially supports the
|
||||
|
||||
The Kubernetes Gateway API project provides several guides on how to use the APIs.
|
||||
These guides can help you to go further than the example above.
|
||||
The [getting started guide](https://gateway-api.sigs.k8s.io/guides/getting-started/) details how to install the CRDs from their repository.
|
||||
The [getting started guide](https://gateway-api.sigs.k8s.io/v1alpha1/guides/getting-started/) details how to install the CRDs from their repository.
|
||||
|
||||
!!! note ""
|
||||
|
||||
Keep in mind that the Traefik Gateway provider only supports the `v0.1.0`.
|
||||
Keep in mind that the Traefik Gateway provider only supports the `v0.1.0` (v1alpha1).
|
||||
|
||||
For now, the Traefik Gateway Provider can be used while following the below guides:
|
||||
|
||||
* [Simple Gateway](https://gateway-api.sigs.k8s.io/guides/simple-gateway/)
|
||||
* [HTTP routing](https://gateway-api.sigs.k8s.io/guides/http-routing/)
|
||||
* [TLS](https://gateway-api.sigs.k8s.io/guides/tls/) (Partial support: only on listeners with terminate mode)
|
||||
* [Simple Gateway](https://gateway-api.sigs.k8s.io/v1alpha1/guides/simple-gateway/)
|
||||
* [HTTP routing](https://gateway-api.sigs.k8s.io/v1alpha1/guides/http-routing/)
|
||||
* [TLS](https://gateway-api.sigs.k8s.io/v1alpha1/guides/tls/) (Partial support: only on listeners with terminate mode)
|
||||
|
||||
## Resource Configuration
|
||||
|
||||
|
||||
@@ -375,6 +375,29 @@ providers:
|
||||
--providers.kubernetesingress.throttleDuration=10s
|
||||
```
|
||||
|
||||
### `allowExternalNameServices`
|
||||
|
||||
_Optional, Default: false_
|
||||
|
||||
If the parameter is set to `true`, Ingresses are able to reference ExternalName services.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
kubernetesIngress:
|
||||
allowExternalNameServices: true
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.kubernetesIngress]
|
||||
allowExternalNameServices = true
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetesingress.allowexternalnameservices=true
|
||||
```
|
||||
|
||||
### Further
|
||||
|
||||
To learn more about the various aspects of the Ingress specification that Traefik supports,
|
||||
|
||||
@@ -30,6 +30,8 @@ If you use multiple providers and wish to reference such an object declared in a
|
||||
(e.g. referencing a cross-provider object like middleware), then the object name should be suffixed by the `@`
|
||||
separator, and the provider name.
|
||||
|
||||
For the list of the providers names, see the [supported providers](#supported-providers) table below.
|
||||
|
||||
```text
|
||||
<resource-name>@<provider-name>
|
||||
```
|
||||
@@ -125,20 +127,22 @@ separator, and the provider name.
|
||||
|
||||
Below is the list of the currently supported providers in Traefik.
|
||||
|
||||
| Provider | Type | Configuration Type |
|
||||
|---------------------------------------|--------------|----------------------------|
|
||||
| [Docker](./docker.md) | Orchestrator | Label |
|
||||
| [Kubernetes](./kubernetes-crd.md) | Orchestrator | Custom Resource or Ingress |
|
||||
| [Consul Catalog](./consul-catalog.md) | Orchestrator | Label |
|
||||
| [ECS](./ecs.md) | Orchestrator | Label |
|
||||
| [Marathon](./marathon.md) | Orchestrator | Label |
|
||||
| [Rancher](./rancher.md) | Orchestrator | Label |
|
||||
| [File](./file.md) | Manual | YAML/TOML format |
|
||||
| [Consul](./consul.md) | KV | KV |
|
||||
| [Etcd](./etcd.md) | KV | KV |
|
||||
| [ZooKeeper](./zookeeper.md) | KV | KV |
|
||||
| [Redis](./redis.md) | KV | KV |
|
||||
| [HTTP](./http.md) | Manual | JSON format |
|
||||
| Provider | Type | Configuration Type | Provider Name |
|
||||
|---------------------------------------------------|--------------|----------------------|---------------------|
|
||||
| [Docker](./docker.md) | Orchestrator | Label | `docker` |
|
||||
| [Kubernetes IngressRoute](./kubernetes-crd.md) | Orchestrator | Custom Resource | `kubernetescrd` |
|
||||
| [Kubernetes Ingress](./kubernetes-ingress.md) | Orchestrator | Ingress | `kubernetes` |
|
||||
| [Kubernetes Gateway API](./kubernetes-gateway.md) | Orchestrator | Gateway API Resource | `kubernetesgateway` |
|
||||
| [Consul Catalog](./consul-catalog.md) | Orchestrator | Label | `consulcatalog` |
|
||||
| [ECS](./ecs.md) | Orchestrator | Label | `ecs` |
|
||||
| [Marathon](./marathon.md) | Orchestrator | Label | `marathon` |
|
||||
| [Rancher](./rancher.md) | Orchestrator | Label | `rancher` |
|
||||
| [File](./file.md) | Manual | YAML/TOML format | `file` |
|
||||
| [Consul](./consul.md) | KV | KV | `consul` |
|
||||
| [Etcd](./etcd.md) | KV | KV | `etcd` |
|
||||
| [ZooKeeper](./zookeeper.md) | KV | KV | `zookeeper` |
|
||||
| [Redis](./redis.md) | KV | KV | `redis` |
|
||||
| [HTTP](./http.md) | Manual | JSON format | `http` |
|
||||
|
||||
!!! info "More Providers"
|
||||
|
||||
|
||||
@@ -556,7 +556,10 @@ TLS key
|
||||
Enable Kubernetes backend with default settings. (Default: ```false```)
|
||||
|
||||
`--providers.kubernetescrd.allowcrossnamespace`:
|
||||
Allow cross namespace resource reference. (Default: ```true```)
|
||||
Allow cross namespace resource reference. (Default: ```false```)
|
||||
|
||||
`--providers.kubernetescrd.allowexternalnameservices`:
|
||||
Allow ExternalName services. (Default: ```false```)
|
||||
|
||||
`--providers.kubernetescrd.certauthfilepath`:
|
||||
Kubernetes certificate authority file path (not needed for in-cluster client).
|
||||
@@ -603,6 +606,9 @@ Kubernetes bearer token (not needed for in-cluster client).
|
||||
`--providers.kubernetesingress`:
|
||||
Enable Kubernetes backend with default settings. (Default: ```false```)
|
||||
|
||||
`--providers.kubernetesingress.allowexternalnameservices`:
|
||||
Allow ExternalName services. (Default: ```false```)
|
||||
|
||||
`--providers.kubernetesingress.certauthfilepath`:
|
||||
Kubernetes certificate authority file path (not needed for in-cluster client).
|
||||
|
||||
|
||||
@@ -556,7 +556,10 @@ TLS key
|
||||
Enable Kubernetes backend with default settings. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_KUBERNETESCRD_ALLOWCROSSNAMESPACE`:
|
||||
Allow cross namespace resource reference. (Default: ```true```)
|
||||
Allow cross namespace resource reference. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_KUBERNETESCRD_ALLOWEXTERNALNAMESERVICES`:
|
||||
Allow ExternalName services. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_KUBERNETESCRD_CERTAUTHFILEPATH`:
|
||||
Kubernetes certificate authority file path (not needed for in-cluster client).
|
||||
@@ -603,6 +606,9 @@ Kubernetes bearer token (not needed for in-cluster client).
|
||||
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS`:
|
||||
Enable Kubernetes backend with default settings. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_ALLOWEXTERNALNAMESERVICES`:
|
||||
Allow ExternalName services. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_CERTAUTHFILEPATH`:
|
||||
Kubernetes certificate authority file path (not needed for in-cluster client).
|
||||
|
||||
|
||||
@@ -385,7 +385,7 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: testName
|
||||
name: test-name
|
||||
namespace: default
|
||||
spec:
|
||||
entryPoints:
|
||||
@@ -1345,8 +1345,8 @@ or referencing TLS options in the [`IngressRoute`](#kind-ingressroute) / [`Ingre
|
||||
- TLS_RSA_WITH_AES_256_GCM_SHA384
|
||||
clientAuth: # [5]
|
||||
secretNames: # [6]
|
||||
- secretCA1
|
||||
- secretCA2
|
||||
- secret-ca1
|
||||
- secret-ca2
|
||||
clientAuthType: VerifyClientCertIfGiven # [7]
|
||||
sniStrict: true # [8]
|
||||
```
|
||||
@@ -1379,8 +1379,8 @@ or referencing TLS options in the [`IngressRoute`](#kind-ingressroute) / [`Ingre
|
||||
- TLS_RSA_WITH_AES_256_GCM_SHA384
|
||||
clientAuth:
|
||||
secretNames:
|
||||
- secretCA1
|
||||
- secretCA2
|
||||
- secret-ca1
|
||||
- secret-ca2
|
||||
clientAuthType: VerifyClientCertIfGiven
|
||||
```
|
||||
|
||||
@@ -1409,7 +1409,7 @@ or referencing TLS options in the [`IngressRoute`](#kind-ingressroute) / [`Ingre
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secretCA1
|
||||
name: secret-ca1
|
||||
namespace: default
|
||||
|
||||
data:
|
||||
@@ -1419,7 +1419,7 @@ or referencing TLS options in the [`IngressRoute`](#kind-ingressroute) / [`Ingre
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secretCA2
|
||||
name: secret-ca2
|
||||
namespace: default
|
||||
|
||||
data:
|
||||
@@ -1459,7 +1459,7 @@ or referencing TLS stores in the [`IngressRoute`](#kind-ingressroute) / [`Ingres
|
||||
|
||||
spec:
|
||||
defaultCertificate:
|
||||
secretName: mySecret # [1]
|
||||
secretName: my-secret # [1]
|
||||
```
|
||||
|
||||
| Ref | Attribute | Purpose |
|
||||
|
||||
@@ -35,14 +35,14 @@ You can find an excerpt of the supported Kubernetes Gateway API resources in the
|
||||
|
||||
| Kind | Purpose | Concept Behind |
|
||||
|------------------------------------|---------------------------------------------------------------------------|-----------------------------------------------------------------------------|
|
||||
| [GatewayClass](#kind-gatewayclass) | Defines a set of Gateways that share a common configuration and behaviour | [GatewayClass](https://gateway-api.sigs.k8s.io/api-types/gatewayclass) |
|
||||
| [Gateway](#kind-gateway) | Describes how traffic can be translated to Services within the cluster | [Gateway](https://gateway-api.sigs.k8s.io/api-types/gateway) |
|
||||
| [HTTPRoute](#kind-httproute) | HTTP rules for mapping requests from a Gateway to Kubernetes Services | [Route](https://gateway-api.sigs.k8s.io/api-types/httproute) |
|
||||
| [GatewayClass](#kind-gatewayclass) | Defines a set of Gateways that share a common configuration and behaviour | [GatewayClass](https://gateway-api.sigs.k8s.io/v1alpha1/api-types/gatewayclass) |
|
||||
| [Gateway](#kind-gateway) | Describes how traffic can be translated to Services within the cluster | [Gateway](https://gateway-api.sigs.k8s.io/v1alpha1/api-types/gateway) |
|
||||
| [HTTPRoute](#kind-httproute) | HTTP rules for mapping requests from a Gateway to Kubernetes Services | [Route](https://gateway-api.sigs.k8s.io/v1alpha1/api-types/httproute) |
|
||||
|
||||
### Kind: `GatewayClass`
|
||||
|
||||
`GatewayClass` is cluster-scoped resource defined by the infrastructure provider. This resource represents a class of Gateways that can be instantiated.
|
||||
More details on the GatewayClass [official documentation](https://gateway-api.sigs.k8s.io/api-types/gatewayclass/).
|
||||
More details on the GatewayClass [official documentation](https://gateway-api.sigs.k8s.io/v1alpha1/api-types/gatewayclass/).
|
||||
|
||||
The `GatewayClass` should be declared by the infrastructure provider, otherwise please register the `GatewayClass`
|
||||
[definition](../../reference/dynamic-configuration/kubernetes-gateway.md#definitions) in the Kubernetes cluster before
|
||||
@@ -65,7 +65,7 @@ creating `GatewayClass` objects.
|
||||
|
||||
A `Gateway` is 1:1 with the life cycle of the configuration of infrastructure. When a user creates a Gateway,
|
||||
some load balancing infrastructure is provisioned or configured by the GatewayClass controller.
|
||||
More details on the Gateway [official documentation](https://gateway-api.sigs.k8s.io/api-types/gateway/).
|
||||
More details on the Gateway [official documentation](https://gateway-api.sigs.k8s.io/v1alpha1/api-types/gateway/).
|
||||
|
||||
Register the `Gateway` [definition](../../reference/dynamic-configuration/kubernetes-gateway.md#definitions) in the
|
||||
Kubernetes cluster before creating `Gateway` objects.
|
||||
|
||||
@@ -28,8 +28,8 @@ A Story of key & values
|
||||
|
||||
| Key (Path) | Value |
|
||||
|-----------------------------------------------|-------------|
|
||||
| `traefik.http.routers.myrouter.entrypoints/0` | `web` |
|
||||
| `traefik.http.routers.myrouter.entrypoints/1` | `websecure` |
|
||||
| `traefik/http/routers/myrouter/entrypoints/0` | `web` |
|
||||
| `traefik/http/routers/myrouter/entrypoints/1` | `websecure` |
|
||||
|
||||
??? info "`traefik/http/routers/<router_name>/middlewares`"
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
mkdocs==1.1
|
||||
mkdocs==1.2.2
|
||||
pymdown-extensions==7.0
|
||||
mkdocs-bootswatch==1.0
|
||||
mkdocs-traefiklabs>=100.0.7
|
||||
markdown-include==0.5.1
|
||||
mkdocs-exclude==1.0.2
|
||||
Jinja2==3.0.0
|
||||
|
||||
@@ -22,7 +22,7 @@ find "${PATH_TO_SITE}" -type f -not -path "/app/site/theme/*" \
|
||||
--alt_ignore="/traefikproxy-vertical-logo-color.svg/" \
|
||||
--http_status_ignore="0,500,501,503" \
|
||||
--file_ignore="/404.html/" \
|
||||
--url_ignore="/https://groups.google.com/a/traefik.io/forum/#!forum/security/,/localhost:/,/127.0.0.1:/,/fonts.gstatic.com/,/.minikube/,/github.com\/traefik\/traefik\/*edit*/,/github.com\/traefik\/traefik/,/doc.traefik.io/,/github\.com\/golang\/oauth2\/blob\/36a7019397c4c86cf59eeab3bc0d188bac444277\/.+/,/www.akamai.com/,/pilot.traefik.io\/profile/,/traefik.io/,/doc.traefik.io\/traefik-mesh/,/www.mkdocs.org/,/squidfunk.github.io/,/ietf.org/,/www.namesilo.com/,/www.youtube.com/,/www.linode.com/" \
|
||||
--url_ignore="/https://groups.google.com/a/traefik.io/forum/#!forum/security/,/localhost:/,/127.0.0.1:/,/fonts.gstatic.com/,/.minikube/,/github.com\/traefik\/traefik\/*edit*/,/github.com\/traefik\/traefik/,/doc.traefik.io/,/github\.com\/golang\/oauth2\/blob\/36a7019397c4c86cf59eeab3bc0d188bac444277\/.+/,/www.akamai.com/,/pilot.traefik.io\/profile/,/traefik.io/,/doc.traefik.io\/traefik-mesh/,/www.mkdocs.org/,/squidfunk.github.io/,/ietf.org/,/www.namesilo.com/,/www.youtube.com/,/www.linode.com/,/www.alibabacloud.com/" \
|
||||
'{}' 1>/dev/null
|
||||
## HTML-proofer options at https://github.com/gjtorikian/html-proofer#configuration
|
||||
|
||||
|
||||
@@ -17,3 +17,4 @@
|
||||
|
||||
[providers.kubernetesCRD]
|
||||
allowCrossNamespace = false
|
||||
allowExternalNameServices = true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
server:
|
||||
image: rancher/k3s:v1.17.2-k3s1
|
||||
image: rancher/k3s:v1.18.20-k3s1
|
||||
command: server --disable-agent --no-deploy coredns --no-deploy servicelb --no-deploy traefik --no-deploy local-storage --no-deploy metrics-server --log /output/k3s.log
|
||||
environment:
|
||||
- K3S_CLUSTER_SECRET=somethingtotallyrandom
|
||||
@@ -12,7 +12,7 @@ server:
|
||||
- 6443:6443
|
||||
|
||||
node:
|
||||
image: rancher/k3s:v1.17.2-k3s1
|
||||
image: rancher/k3s:v1.18.20-k3s1
|
||||
privileged: true
|
||||
links:
|
||||
- server
|
||||
|
||||
@@ -24,7 +24,7 @@ func DecodeToNode(pairs []*store.KVPair, rootName string, filters ...string) (*p
|
||||
return nil, fmt.Errorf("invalid label root %s", rootName)
|
||||
}
|
||||
|
||||
split := strings.Split(pair.Key[len(rootName)+1:], "/")
|
||||
split := strings.FieldsFunc(pair.Key[len(rootName)+1:], func(c rune) bool { return c == '/' })
|
||||
|
||||
parts := []string{rootName}
|
||||
for _, fragment := range split {
|
||||
|
||||
@@ -28,6 +28,7 @@ func TestDecode(t *testing.T) {
|
||||
"traefik/fieldf/Test2": "B",
|
||||
"traefik/fieldg/0/name": "A",
|
||||
"traefik/fieldg/1/name": "B",
|
||||
"traefik/fieldh/": "foo",
|
||||
},
|
||||
expected: &sample{
|
||||
FieldA: "bar",
|
||||
@@ -45,6 +46,7 @@ func TestDecode(t *testing.T) {
|
||||
{Name: "A"},
|
||||
{Name: "B"},
|
||||
},
|
||||
FieldH: "foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -61,6 +63,7 @@ func TestDecode(t *testing.T) {
|
||||
"foo/bar/traefik/fieldf/Test2": "B",
|
||||
"foo/bar/traefik/fieldg/0/name": "A",
|
||||
"foo/bar/traefik/fieldg/1/name": "B",
|
||||
"foo/bar/traefik/fieldh/": "foo",
|
||||
},
|
||||
expected: &sample{
|
||||
FieldA: "bar",
|
||||
@@ -78,6 +81,7 @@ func TestDecode(t *testing.T) {
|
||||
{Name: "A"},
|
||||
{Name: "B"},
|
||||
},
|
||||
FieldH: "foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -107,6 +111,7 @@ type sample struct {
|
||||
} `label:"allowEmpty"`
|
||||
FieldF map[string]string
|
||||
FieldG []sub
|
||||
FieldH string
|
||||
}
|
||||
|
||||
type sub struct {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -344,7 +345,7 @@ func (h *Handler) redactHeaders(headers http.Header, fields logrus.Fields, prefi
|
||||
for k := range headers {
|
||||
v := h.config.Fields.KeepHeader(k)
|
||||
if v == types.AccessLogKeep {
|
||||
fields[prefix+k] = headers.Get(k)
|
||||
fields[prefix+k] = strings.Join(headers.Values(k), ",")
|
||||
} else if v == types.AccessLogRedact {
|
||||
fields[prefix+k] = "REDACTED"
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ func lineCount(t *testing.T, fileName string) int {
|
||||
}
|
||||
|
||||
func TestLoggerHeaderFields(t *testing.T) {
|
||||
expectedValue := "expectedValue"
|
||||
expectedValues := []string{"AAA", "BBB"}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
@@ -191,7 +191,10 @@ func TestLoggerHeaderFields(t *testing.T) {
|
||||
Path: testPath,
|
||||
},
|
||||
}
|
||||
req.Header.Set(test.header, expectedValue)
|
||||
|
||||
for _, s := range expectedValues {
|
||||
req.Header.Add(test.header, s)
|
||||
}
|
||||
|
||||
logger.ServeHTTP(httptest.NewRecorder(), req, http.HandlerFunc(func(writer http.ResponseWriter, r *http.Request) {
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
@@ -201,9 +204,9 @@ func TestLoggerHeaderFields(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
if test.expected == types.AccessLogDrop {
|
||||
assert.NotContains(t, string(logData), expectedValue)
|
||||
assert.NotContains(t, string(logData), strings.Join(expectedValues, ","))
|
||||
} else {
|
||||
assert.Contains(t, string(logData), expectedValue)
|
||||
assert.Contains(t, string(logData), strings.Join(expectedValues, ","))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/connectionheader"
|
||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||
"github.com/vulcand/oxy/forward"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
@@ -89,7 +90,7 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
|
||||
fa.authResponseHeadersRegex = re
|
||||
}
|
||||
|
||||
return fa, nil
|
||||
return connectionheader.Remover(fa), nil
|
||||
}
|
||||
|
||||
func (fa *forwardAuth) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
|
||||
46
pkg/middlewares/connectionheader/connectionheader.go
Normal file
46
pkg/middlewares/connectionheader/connectionheader.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package connectionheader
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
const (
|
||||
connectionHeader = "Connection"
|
||||
upgradeHeader = "Upgrade"
|
||||
)
|
||||
|
||||
// Remover removes hop-by-hop headers listed in the "Connection" header.
|
||||
// See RFC 7230, section 6.1.
|
||||
func Remover(next http.Handler) http.HandlerFunc {
|
||||
return func(rw http.ResponseWriter, req *http.Request) {
|
||||
var reqUpType string
|
||||
if httpguts.HeaderValuesContainsToken(req.Header[connectionHeader], upgradeHeader) {
|
||||
reqUpType = req.Header.Get(upgradeHeader)
|
||||
}
|
||||
|
||||
removeConnectionHeaders(req.Header)
|
||||
|
||||
if reqUpType != "" {
|
||||
req.Header.Set(connectionHeader, upgradeHeader)
|
||||
req.Header.Set(upgradeHeader, reqUpType)
|
||||
} else {
|
||||
req.Header.Del(connectionHeader)
|
||||
}
|
||||
|
||||
next.ServeHTTP(rw, req)
|
||||
}
|
||||
}
|
||||
|
||||
func removeConnectionHeaders(h http.Header) {
|
||||
for _, f := range h[connectionHeader] {
|
||||
for _, sf := range strings.Split(f, ",") {
|
||||
if sf = textproto.TrimString(sf); sf != "" {
|
||||
h.Del(sf)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
71
pkg/middlewares/connectionheader/connectionheader_test.go
Normal file
71
pkg/middlewares/connectionheader/connectionheader_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package connectionheader
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRemover(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
reqHeaders map[string]string
|
||||
expected http.Header
|
||||
}{
|
||||
{
|
||||
desc: "simple remove",
|
||||
reqHeaders: map[string]string{
|
||||
"Foo": "bar",
|
||||
connectionHeader: "foo",
|
||||
},
|
||||
expected: http.Header{},
|
||||
},
|
||||
{
|
||||
desc: "remove and Upgrade",
|
||||
reqHeaders: map[string]string{
|
||||
upgradeHeader: "test",
|
||||
"Foo": "bar",
|
||||
connectionHeader: "Upgrade,foo",
|
||||
},
|
||||
expected: http.Header{
|
||||
upgradeHeader: []string{"test"},
|
||||
connectionHeader: []string{"Upgrade"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "no remove",
|
||||
reqHeaders: map[string]string{
|
||||
"Foo": "bar",
|
||||
connectionHeader: "fii",
|
||||
},
|
||||
expected: http.Header{
|
||||
"Foo": []string{"bar"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {})
|
||||
|
||||
h := Remover(next)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "https://localhost", nil)
|
||||
|
||||
for k, v := range test.reqHeaders {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
h.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, test.expected, req.Header)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/connectionheader"
|
||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||
)
|
||||
|
||||
@@ -58,11 +59,12 @@ func New(ctx context.Context, next http.Handler, cfg dynamic.Headers, name strin
|
||||
|
||||
if hasCustomHeaders || hasCorsHeaders {
|
||||
logger.Debugf("Setting up customHeaders/Cors from %v", cfg)
|
||||
var err error
|
||||
handler, err = NewHeader(nextHandler, cfg)
|
||||
h, err := NewHeader(nextHandler, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
handler = connectionheader.Remover(h)
|
||||
}
|
||||
|
||||
return &headers{
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
)
|
||||
|
||||
type responseModifier struct {
|
||||
r *http.Request
|
||||
w http.ResponseWriter
|
||||
req *http.Request
|
||||
rw http.ResponseWriter
|
||||
|
||||
headersSent bool // whether headers have already been sent
|
||||
code int // status code, must default to 200
|
||||
@@ -24,71 +24,76 @@ type responseModifier struct {
|
||||
// modifier can be nil.
|
||||
func newResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(*http.Response) error) *responseModifier {
|
||||
return &responseModifier{
|
||||
r: r,
|
||||
w: w,
|
||||
req: r,
|
||||
rw: w,
|
||||
modifier: modifier,
|
||||
code: http.StatusOK,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *responseModifier) WriteHeader(code int) {
|
||||
if w.headersSent {
|
||||
func (r *responseModifier) WriteHeader(code int) {
|
||||
if r.headersSent {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
w.code = code
|
||||
w.headersSent = true
|
||||
r.code = code
|
||||
r.headersSent = true
|
||||
}()
|
||||
|
||||
if w.modifier == nil || w.modified {
|
||||
w.w.WriteHeader(code)
|
||||
if r.modifier == nil || r.modified {
|
||||
r.rw.WriteHeader(code)
|
||||
return
|
||||
}
|
||||
|
||||
resp := http.Response{
|
||||
Header: w.w.Header(),
|
||||
Request: w.r,
|
||||
Header: r.rw.Header(),
|
||||
Request: r.req,
|
||||
}
|
||||
|
||||
if err := w.modifier(&resp); err != nil {
|
||||
w.modifierErr = err
|
||||
if err := r.modifier(&resp); err != nil {
|
||||
r.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)
|
||||
r.rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.modified = true
|
||||
w.w.WriteHeader(code)
|
||||
r.modified = true
|
||||
r.rw.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (w *responseModifier) Header() http.Header {
|
||||
return w.w.Header()
|
||||
func (r *responseModifier) Header() http.Header {
|
||||
return r.rw.Header()
|
||||
}
|
||||
|
||||
func (w *responseModifier) Write(b []byte) (int, error) {
|
||||
w.WriteHeader(w.code)
|
||||
if w.modifierErr != nil {
|
||||
return 0, w.modifierErr
|
||||
func (r *responseModifier) Write(b []byte) (int, error) {
|
||||
r.WriteHeader(r.code)
|
||||
if r.modifierErr != nil {
|
||||
return 0, r.modifierErr
|
||||
}
|
||||
|
||||
return w.w.Write(b)
|
||||
return r.rw.Write(b)
|
||||
}
|
||||
|
||||
// Hijack hijacks the connection.
|
||||
func (w *responseModifier) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if h, ok := w.w.(http.Hijacker); ok {
|
||||
func (r *responseModifier) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if h, ok := r.rw.(http.Hijacker); ok {
|
||||
return h.Hijack()
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("not a hijacker: %T", w.w)
|
||||
return nil, nil, fmt.Errorf("not a hijacker: %T", r.rw)
|
||||
}
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
func (w *responseModifier) Flush() {
|
||||
if flusher, ok := w.w.(http.Flusher); ok {
|
||||
func (r *responseModifier) Flush() {
|
||||
if flusher, ok := r.rw.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// CloseNotify implements http.CloseNotifier.
|
||||
func (r *responseModifier) CloseNotify() <-chan bool {
|
||||
return r.rw.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
@@ -30,7 +30,12 @@ type rateLimiter struct {
|
||||
burst int64
|
||||
// maxDelay is the maximum duration we're willing to wait for a bucket reservation to become effective, in nanoseconds.
|
||||
// For now it is somewhat arbitrarily set to 1/(2*rate).
|
||||
maxDelay time.Duration
|
||||
maxDelay time.Duration
|
||||
// each rate limiter for a given source is stored in the buckets ttlmap.
|
||||
// To keep this ttlmap constrained in size,
|
||||
// each ratelimiter is "garbage collected" when it is considered expired.
|
||||
// It is considered expired after it hasn't been used for ttl seconds.
|
||||
ttl int
|
||||
sourceMatcher utils.SourceExtractor
|
||||
next http.Handler
|
||||
|
||||
@@ -66,12 +71,15 @@ func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name
|
||||
}
|
||||
|
||||
period := time.Duration(config.Period)
|
||||
if period < 0 {
|
||||
return nil, fmt.Errorf("negative value not valid for period: %v", period)
|
||||
}
|
||||
if period == 0 {
|
||||
period = time.Second
|
||||
}
|
||||
|
||||
// Logically, we should set maxDelay to infinity when config.Average == 0 (because it means no rate limiting),
|
||||
// but since the reservation will give us a delay = 0 anyway in this case, we're good even with any maxDelay >= 0.
|
||||
// if config.Average == 0, in that case,
|
||||
// the value of maxDelay does not matter since the reservation will (buggily) give us a delay of 0 anyway.
|
||||
var maxDelay time.Duration
|
||||
var rtl float64
|
||||
if config.Average > 0 {
|
||||
@@ -86,6 +94,17 @@ func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name
|
||||
}
|
||||
}
|
||||
|
||||
// Make the ttl inversely proportional to how often a rate limiter is supposed to see any activity (when maxed out),
|
||||
// for low rate limiters.
|
||||
// Otherwise just make it a second for all the high rate limiters.
|
||||
// Add an extra second in both cases for continuity between the two cases.
|
||||
ttl := 1
|
||||
if rtl >= 1 {
|
||||
ttl++
|
||||
} else if rtl > 0 {
|
||||
ttl += int(1 / rtl)
|
||||
}
|
||||
|
||||
return &rateLimiter{
|
||||
name: name,
|
||||
rate: rate.Limit(rtl),
|
||||
@@ -94,6 +113,7 @@ func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name
|
||||
next: next,
|
||||
sourceMatcher: sourceMatcher,
|
||||
buckets: buckets,
|
||||
ttl: ttl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -121,13 +141,21 @@ func (rl *rateLimiter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
bucket = rlSource.(*rate.Limiter)
|
||||
} else {
|
||||
bucket = rate.NewLimiter(rl.rate, int(rl.burst))
|
||||
if err := rl.buckets.Set(source, bucket, int(rl.maxDelay)*10+1); err != nil {
|
||||
logger.Errorf("could not insert bucket: %v", err)
|
||||
http.Error(w, "could not insert bucket", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// We Set even in the case where the source already exists,
|
||||
// because we want to update the expiryTime everytime we get the source,
|
||||
// as the expiryTime is supposed to reflect the activity (or lack thereof) on that source.
|
||||
if err := rl.buckets.Set(source, bucket, rl.ttl); err != nil {
|
||||
logger.Errorf("could not insert/update bucket: %v", err)
|
||||
http.Error(w, "could not insert/update bucket", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// time/rate is bugged, since a rate.Limiter with a 0 Limit not only allows a Reservation to take place,
|
||||
// but also gives a 0 delay below (because of a division by zero, followed by a multiplication that flips into the negatives),
|
||||
// regardless of the current load.
|
||||
// However, for now we take advantage of this behavior to provide the no-limit ratelimiter when config.Average is 0.
|
||||
res := bucket.Reserve()
|
||||
if !res.OK() {
|
||||
http.Error(w, "No bursty traffic allowed", http.StatusTooManyRequests)
|
||||
|
||||
@@ -4,13 +4,17 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
schemeHTTP = "http"
|
||||
schemeHTTPS = "https"
|
||||
)
|
||||
|
||||
type redirect struct {
|
||||
next http.Handler
|
||||
regex *regexp.Regexp
|
||||
@@ -18,10 +22,11 @@ type redirect struct {
|
||||
permanent bool
|
||||
errHandler utils.ErrorHandler
|
||||
name string
|
||||
rawURL func(*http.Request) string
|
||||
}
|
||||
|
||||
// New creates a Redirect middleware.
|
||||
func newRedirect(next http.Handler, regex, replacement string, permanent bool, name string) (http.Handler, error) {
|
||||
func newRedirect(next http.Handler, regex, replacement string, permanent bool, rawURL func(*http.Request) string, name string) (http.Handler, error) {
|
||||
re, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -34,6 +39,7 @@ func newRedirect(next http.Handler, regex, replacement string, permanent bool, n
|
||||
errHandler: utils.DefaultHandler,
|
||||
next: next,
|
||||
name: name,
|
||||
rawURL: rawURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -42,7 +48,7 @@ func (r *redirect) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
}
|
||||
|
||||
func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
oldURL := rawURL(req)
|
||||
oldURL := r.rawURL(req)
|
||||
|
||||
// If the Regexp doesn't match, skip to the next handler.
|
||||
if !r.regex.MatchString(oldURL) {
|
||||
@@ -98,33 +104,3 @@ func (m *moveHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func rawURL(req *http.Request) string {
|
||||
scheme := "http"
|
||||
host := req.Host
|
||||
port := ""
|
||||
uri := req.RequestURI
|
||||
|
||||
schemeRegex := `^(https?):\/\/(\[[\w:.]+\]|[\w\._-]+)?(:\d+)?(.*)$`
|
||||
re, _ := regexp.Compile(schemeRegex)
|
||||
if re.Match([]byte(req.RequestURI)) {
|
||||
match := re.FindStringSubmatch(req.RequestURI)
|
||||
scheme = match[1]
|
||||
|
||||
if len(match[2]) > 0 {
|
||||
host = match[2]
|
||||
}
|
||||
|
||||
if len(match[3]) > 0 {
|
||||
port = match[3]
|
||||
}
|
||||
|
||||
uri = match[4]
|
||||
}
|
||||
|
||||
if req.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
return strings.Join([]string{scheme, "://", host, port, uri}, "")
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package redirect
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
@@ -19,5 +21,35 @@ func NewRedirectRegex(ctx context.Context, next http.Handler, conf dynamic.Redir
|
||||
logger.Debug("Creating middleware")
|
||||
logger.Debugf("Setting up redirection from %s to %s", conf.Regex, conf.Replacement)
|
||||
|
||||
return newRedirect(next, conf.Regex, conf.Replacement, conf.Permanent, name)
|
||||
return newRedirect(next, conf.Regex, conf.Replacement, conf.Permanent, rawURL, name)
|
||||
}
|
||||
|
||||
func rawURL(req *http.Request) string {
|
||||
scheme := schemeHTTP
|
||||
host := req.Host
|
||||
port := ""
|
||||
uri := req.RequestURI
|
||||
|
||||
schemeRegex := `^(https?):\/\/(\[[\w:.]+\]|[\w\._-]+)?(:\d+)?(.*)$`
|
||||
re, _ := regexp.Compile(schemeRegex)
|
||||
if re.Match([]byte(req.RequestURI)) {
|
||||
match := re.FindStringSubmatch(req.RequestURI)
|
||||
scheme = match[1]
|
||||
|
||||
if len(match[2]) > 0 {
|
||||
host = match[2]
|
||||
}
|
||||
|
||||
if len(match[3]) > 0 {
|
||||
port = match[3]
|
||||
}
|
||||
|
||||
uri = match[4]
|
||||
}
|
||||
|
||||
if req.TLS != nil {
|
||||
scheme = schemeHTTPS
|
||||
}
|
||||
|
||||
return strings.Join([]string{scheme, "://", host, port, uri}, "")
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ package redirect
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
@@ -26,9 +29,47 @@ func NewRedirectScheme(ctx context.Context, next http.Handler, conf dynamic.Redi
|
||||
}
|
||||
|
||||
port := ""
|
||||
if len(conf.Port) > 0 && !(conf.Scheme == "http" && conf.Port == "80" || conf.Scheme == "https" && conf.Port == "443") {
|
||||
if len(conf.Port) > 0 && !(conf.Scheme == schemeHTTP && conf.Port == "80" || conf.Scheme == schemeHTTPS && conf.Port == "443") {
|
||||
port = ":" + conf.Port
|
||||
}
|
||||
|
||||
return newRedirect(next, schemeRedirectRegex, conf.Scheme+"://${2}"+port+"${4}", conf.Permanent, name)
|
||||
return newRedirect(next, schemeRedirectRegex, conf.Scheme+"://${2}"+port+"${4}", conf.Permanent, rawURLScheme, name)
|
||||
}
|
||||
|
||||
func rawURLScheme(req *http.Request) string {
|
||||
scheme := schemeHTTP
|
||||
host, port, err := net.SplitHostPort(req.Host)
|
||||
if err != nil {
|
||||
host = req.Host
|
||||
} else {
|
||||
port = ":" + port
|
||||
}
|
||||
uri := req.RequestURI
|
||||
|
||||
schemeRegex := `^(https?):\/\/(\[[\w:.]+\]|[\w\._-]+)?(:\d+)?(.*)$`
|
||||
re, _ := regexp.Compile(schemeRegex)
|
||||
if re.Match([]byte(req.RequestURI)) {
|
||||
match := re.FindStringSubmatch(req.RequestURI)
|
||||
scheme = match[1]
|
||||
|
||||
if len(match[2]) > 0 {
|
||||
host = match[2]
|
||||
}
|
||||
|
||||
if len(match[3]) > 0 {
|
||||
port = match[3]
|
||||
}
|
||||
|
||||
uri = match[4]
|
||||
}
|
||||
|
||||
if req.TLS != nil {
|
||||
scheme = schemeHTTPS
|
||||
}
|
||||
|
||||
if scheme == schemeHTTP && port == ":80" || scheme == schemeHTTPS && port == ":443" || port == "" {
|
||||
port = ""
|
||||
}
|
||||
|
||||
return strings.Join([]string{scheme, "://", host, port, uri}, "")
|
||||
}
|
||||
|
||||
@@ -127,8 +127,18 @@ func TestRedirectSchemeHandler(t *testing.T) {
|
||||
Port: "80",
|
||||
},
|
||||
url: "http://foo:80",
|
||||
expectedURL: "http://foo",
|
||||
expectedStatus: http.StatusFound,
|
||||
expectedURL: "http://foo:80",
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "to HTTPS 443",
|
||||
config: dynamic.RedirectScheme{
|
||||
Scheme: "https",
|
||||
Port: "443",
|
||||
},
|
||||
url: "https://foo:443",
|
||||
expectedURL: "https://foo:443",
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "HTTP to wss",
|
||||
@@ -248,6 +258,7 @@ func TestRedirectSchemeHandler(t *testing.T) {
|
||||
if test.method != "" {
|
||||
method = test.method
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(method, test.url, nil)
|
||||
|
||||
for k, v := range test.headers {
|
||||
|
||||
@@ -56,7 +56,7 @@ func (n MockSpan) Tracer() opentracing.Tracer { retu
|
||||
func (n MockSpan) LogEvent(event string) {}
|
||||
func (n MockSpan) LogEventWithPayload(event string, payload interface{}) {}
|
||||
func (n MockSpan) Log(data opentracing.LogData) {}
|
||||
func (n MockSpan) Reset() {
|
||||
func (n *MockSpan) Reset() {
|
||||
n.Tags = make(map[string]interface{})
|
||||
}
|
||||
|
||||
|
||||
@@ -167,6 +167,90 @@ func (p *Provider) loadFileConfig(ctx context.Context, filename string, parseTem
|
||||
|
||||
if configuration.TLS != nil {
|
||||
configuration.TLS.Certificates = flattenCertificates(ctx, configuration.TLS)
|
||||
|
||||
// TLS Options
|
||||
if configuration.TLS.Options != nil {
|
||||
for name, options := range configuration.TLS.Options {
|
||||
var caCerts []tls.FileOrContent
|
||||
|
||||
for _, caFile := range options.ClientAuth.CAFiles {
|
||||
content, err := caFile.Read()
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
caCerts = append(caCerts, tls.FileOrContent(content))
|
||||
}
|
||||
options.ClientAuth.CAFiles = caCerts
|
||||
|
||||
configuration.TLS.Options[name] = options
|
||||
}
|
||||
}
|
||||
|
||||
// TLS stores
|
||||
if len(configuration.TLS.Stores) > 0 {
|
||||
for name, store := range configuration.TLS.Stores {
|
||||
if store.DefaultCertificate == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
content, err := store.DefaultCertificate.CertFile.Read()
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Error(err)
|
||||
continue
|
||||
}
|
||||
store.DefaultCertificate.CertFile = tls.FileOrContent(content)
|
||||
|
||||
content, err = store.DefaultCertificate.KeyFile.Read()
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Error(err)
|
||||
continue
|
||||
}
|
||||
store.DefaultCertificate.KeyFile = tls.FileOrContent(content)
|
||||
|
||||
configuration.TLS.Stores[name] = store
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ServersTransport
|
||||
if configuration.HTTP != nil && len(configuration.HTTP.ServersTransports) > 0 {
|
||||
for name, st := range configuration.HTTP.ServersTransports {
|
||||
var certificates []tls.Certificate
|
||||
for _, cert := range st.Certificates {
|
||||
content, err := cert.CertFile.Read()
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Error(err)
|
||||
continue
|
||||
}
|
||||
cert.CertFile = tls.FileOrContent(content)
|
||||
|
||||
content, err = cert.KeyFile.Read()
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Error(err)
|
||||
continue
|
||||
}
|
||||
cert.KeyFile = tls.FileOrContent(content)
|
||||
|
||||
certificates = append(certificates, cert)
|
||||
}
|
||||
|
||||
configuration.HTTP.ServersTransports[name].Certificates = certificates
|
||||
|
||||
var rootCAs []tls.FileOrContent
|
||||
for _, rootCA := range st.RootCAs {
|
||||
content, err := rootCA.Read()
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
rootCAs = append(rootCAs, tls.FileOrContent(content))
|
||||
}
|
||||
|
||||
st.RootCAs = rootCAs
|
||||
}
|
||||
}
|
||||
|
||||
return configuration, nil
|
||||
|
||||
@@ -25,19 +25,35 @@ type ProvideTestCase struct {
|
||||
expectedNumTLSOptions int
|
||||
}
|
||||
|
||||
func TestTLSContent(t *testing.T) {
|
||||
func TestTLSCertificateContent(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
fileTLS, err := createTempFile("./fixtures/toml/tls_file.cert", tempDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
fileTLSKey, err := createTempFile("./fixtures/toml/tls_file_key.cert", tempDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
fileConfig, err := os.CreateTemp(tempDir, "temp*.toml")
|
||||
require.NoError(t, err)
|
||||
|
||||
content := `
|
||||
[[tls.certificates]]
|
||||
certFile = "` + fileTLS.Name() + `"
|
||||
keyFile = "` + fileTLS.Name() + `"
|
||||
keyFile = "` + fileTLSKey.Name() + `"
|
||||
|
||||
[tls.options.default.clientAuth]
|
||||
caFiles = ["` + fileTLS.Name() + `"]
|
||||
|
||||
[tls.stores.default.defaultCertificate]
|
||||
certFile = "` + fileTLS.Name() + `"
|
||||
keyFile = "` + fileTLSKey.Name() + `"
|
||||
|
||||
[http.serversTransports.default]
|
||||
rootCAs = ["` + fileTLS.Name() + `"]
|
||||
[[http.serversTransports.default.certificates]]
|
||||
certFile = "` + fileTLS.Name() + `"
|
||||
keyFile = "` + fileTLSKey.Name() + `"
|
||||
`
|
||||
|
||||
_, err = fileConfig.Write([]byte(content))
|
||||
@@ -48,7 +64,16 @@ func TestTLSContent(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "CONTENT", configuration.TLS.Certificates[0].Certificate.CertFile.String())
|
||||
require.Equal(t, "CONTENT", configuration.TLS.Certificates[0].Certificate.KeyFile.String())
|
||||
require.Equal(t, "CONTENTKEY", configuration.TLS.Certificates[0].Certificate.KeyFile.String())
|
||||
|
||||
require.Equal(t, "CONTENT", configuration.TLS.Options["default"].ClientAuth.CAFiles[0].String())
|
||||
|
||||
require.Equal(t, "CONTENT", configuration.TLS.Stores["default"].DefaultCertificate.CertFile.String())
|
||||
require.Equal(t, "CONTENTKEY", configuration.TLS.Stores["default"].DefaultCertificate.KeyFile.String())
|
||||
|
||||
require.Equal(t, "CONTENT", configuration.HTTP.ServersTransports["default"].Certificates[0].CertFile.String())
|
||||
require.Equal(t, "CONTENTKEY", configuration.HTTP.ServersTransports["default"].Certificates[0].KeyFile.String())
|
||||
require.Equal(t, "CONTENT", configuration.HTTP.ServersTransports["default"].RootCAs[0].String())
|
||||
}
|
||||
|
||||
func TestErrorWhenEmptyConfig(t *testing.T) {
|
||||
@@ -237,6 +262,13 @@ func getTestCases() []ProvideTestCase {
|
||||
expectedNumRouter: 20,
|
||||
expectedNumService: 20,
|
||||
},
|
||||
{
|
||||
desc: "simple file with empty store yaml",
|
||||
filePath: "./fixtures/yaml/simple_empty_store.yml",
|
||||
expectedNumRouter: 0,
|
||||
expectedNumService: 0,
|
||||
expectedNumTLSConf: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1
pkg/provider/file/fixtures/toml/tls_file_key.cert
Normal file
1
pkg/provider/file/fixtures/toml/tls_file_key.cert
Normal file
@@ -0,0 +1 @@
|
||||
CONTENTKEY
|
||||
3
pkg/provider/file/fixtures/yaml/simple_empty_store.yml
Normal file
3
pkg/provider/file/fixtures/yaml/simple_empty_store.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
tls:
|
||||
stores:
|
||||
default: {}
|
||||
@@ -1,7 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secretCA1
|
||||
name: secret-ca1
|
||||
namespace: default
|
||||
|
||||
data:
|
||||
@@ -11,7 +11,7 @@ data:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secretCA2
|
||||
name: secret-ca2
|
||||
namespace: default
|
||||
|
||||
data:
|
||||
@@ -32,9 +32,9 @@ spec:
|
||||
- TLS_RSA_WITH_AES_256_GCM_SHA384
|
||||
clientAuth:
|
||||
secretNames:
|
||||
- secretCA1
|
||||
- secretUnknown
|
||||
- emptySecret
|
||||
- secret-ca1
|
||||
- secret-unknown
|
||||
- empty-secret
|
||||
clientAuthType: VerifyClientCertIfGiven
|
||||
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secretCA1
|
||||
name: secret-ca1
|
||||
namespace: default
|
||||
|
||||
data:
|
||||
@@ -11,7 +11,7 @@ data:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secretCA2
|
||||
name: secret-ca2
|
||||
namespace: default
|
||||
|
||||
data:
|
||||
@@ -32,8 +32,8 @@ spec:
|
||||
- TLS_RSA_WITH_AES_256_GCM_SHA384
|
||||
clientAuth:
|
||||
secretNames:
|
||||
- secretCA1
|
||||
- secretCA2
|
||||
- secret-ca1
|
||||
- secret-ca2
|
||||
clientAuthType: VerifyClientCertIfGiven
|
||||
preferServerCipherSuites: true
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secretCA1
|
||||
name: secret-ca1
|
||||
namespace: myns
|
||||
|
||||
data:
|
||||
@@ -11,7 +11,7 @@ data:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secretCA2
|
||||
name: secret-ca2
|
||||
namespace: myns
|
||||
|
||||
data:
|
||||
@@ -32,8 +32,8 @@ spec:
|
||||
- TLS_RSA_WITH_AES_256_GCM_SHA384
|
||||
clientAuth:
|
||||
secretNames:
|
||||
- secretCA1
|
||||
- secretCA2
|
||||
- secret-ca1
|
||||
- secret-ca2
|
||||
clientAuthType: VerifyClientCertIfGiven
|
||||
|
||||
---
|
||||
|
||||
@@ -160,3 +160,16 @@ subsets:
|
||||
ports:
|
||||
- name: myapp
|
||||
port: 8000
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: external.service.with.port
|
||||
namespace: default
|
||||
spec:
|
||||
externalName: external.domain
|
||||
type: ExternalName
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRouteUDP
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- foo
|
||||
|
||||
routes:
|
||||
- services:
|
||||
- name: external.service.with.port
|
||||
port: 80
|
||||
@@ -1,7 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secretCA1
|
||||
name: secret-ca1
|
||||
namespace: default
|
||||
|
||||
data:
|
||||
@@ -11,7 +11,7 @@ data:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: badSecret
|
||||
name: bad-secret
|
||||
namespace: default
|
||||
|
||||
data:
|
||||
@@ -32,9 +32,9 @@ spec:
|
||||
- TLS_RSA_WITH_AES_256_GCM_SHA384
|
||||
clientAuth:
|
||||
secretNames:
|
||||
- secretCA1
|
||||
- secretUnknown
|
||||
- emptySecret
|
||||
- secret-ca1
|
||||
- secret-unknown
|
||||
- empty-secret
|
||||
clientAuthType: VerifyClientCertIfGiven
|
||||
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secretCAdefault1
|
||||
name: secret-ca-default1
|
||||
namespace: foo
|
||||
|
||||
data:
|
||||
@@ -11,7 +11,7 @@ data:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secretCAdefault2
|
||||
name: secret-ca-default2
|
||||
namespace: foo
|
||||
|
||||
data:
|
||||
@@ -32,8 +32,8 @@ spec:
|
||||
- TLS_RSA_WITH_AES_256_GCM_SHA384
|
||||
clientAuth:
|
||||
secretNames:
|
||||
- secretCAdefault1
|
||||
- secretCAdefault2
|
||||
- secret-ca-default1
|
||||
- secret-ca-default2
|
||||
clientAuthType: VerifyClientCertIfGiven
|
||||
preferServerCipherSuites: true
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secretCA1
|
||||
name: secret-ca1
|
||||
namespace: default
|
||||
|
||||
data:
|
||||
@@ -11,7 +11,7 @@ data:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secretCA2
|
||||
name: secret-ca2
|
||||
namespace: default
|
||||
|
||||
data:
|
||||
@@ -32,8 +32,8 @@ spec:
|
||||
- TLS_RSA_WITH_AES_256_GCM_SHA384
|
||||
clientAuth:
|
||||
secretNames:
|
||||
- secretCA1
|
||||
- secretCA2
|
||||
- secret-ca1
|
||||
- secret-ca2
|
||||
clientAuthType: VerifyClientCertIfGiven
|
||||
preferServerCipherSuites: true
|
||||
|
||||
|
||||
@@ -28,6 +28,15 @@ spec:
|
||||
port: 80
|
||||
middlewares:
|
||||
- name: test-errorpage
|
||||
- match: Host(`foo.com`) && PathPrefix(`/bur`)
|
||||
kind: Rule
|
||||
priority: 12
|
||||
services:
|
||||
- name: whoami
|
||||
namespace: default
|
||||
port: 80
|
||||
middlewares:
|
||||
- name: cross-ns-stripprefix@kubernetescrd
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: rootCas1
|
||||
name: root-ca1
|
||||
namespace: foo
|
||||
|
||||
data:
|
||||
@@ -11,7 +11,7 @@ data:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: rootCas2
|
||||
name: root-ca2
|
||||
namespace: foo
|
||||
|
||||
data:
|
||||
@@ -51,8 +51,8 @@ spec:
|
||||
insecureSkipVerify: true
|
||||
maxIdleConnsPerHost: 42
|
||||
rootCAsSecrets:
|
||||
- rootCas1
|
||||
- rootCas2
|
||||
- root-ca1
|
||||
- root-ca2
|
||||
certificatesSecrets:
|
||||
- mtls1
|
||||
- mtls2
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secretCA1
|
||||
name: secret-ca1
|
||||
namespace: default
|
||||
|
||||
data:
|
||||
@@ -11,7 +11,7 @@ data:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secretCA2
|
||||
name: secret-ca2
|
||||
namespace: default
|
||||
|
||||
data:
|
||||
@@ -32,8 +32,8 @@ spec:
|
||||
- TLS_RSA_WITH_AES_256_GCM_SHA384
|
||||
clientAuth:
|
||||
secretNames:
|
||||
- secretCA1
|
||||
- secretCA2
|
||||
- secret-ca1
|
||||
- secret-ca2
|
||||
clientAuthType: VerifyClientCertIfGiven
|
||||
preferServerCipherSuites: true
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secretCA1
|
||||
name: secret-ca1
|
||||
namespace: myns
|
||||
|
||||
data:
|
||||
@@ -11,7 +11,7 @@ data:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secretCA2
|
||||
name: secret-ca2
|
||||
namespace: myns
|
||||
|
||||
data:
|
||||
@@ -32,8 +32,8 @@ spec:
|
||||
- TLS_RSA_WITH_AES_256_GCM_SHA384
|
||||
clientAuth:
|
||||
secretNames:
|
||||
- secretCA1
|
||||
- secretCA2
|
||||
- secret-ca1
|
||||
- secret-ca2
|
||||
clientAuthType: VerifyClientCertIfGiven
|
||||
|
||||
---
|
||||
|
||||
@@ -38,20 +38,16 @@ const (
|
||||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
||||
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"`
|
||||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"`
|
||||
Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"`
|
||||
AllowCrossNamespace *bool `description:"Allow cross namespace resource reference." json:"allowCrossNamespace,omitempty" toml:"allowCrossNamespace,omitempty" yaml:"allowCrossNamespace,omitempty" export:"true"`
|
||||
LabelSelector string `description:"Kubernetes label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"`
|
||||
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"`
|
||||
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
|
||||
lastConfiguration safe.Safe
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (p *Provider) SetDefaults() {
|
||||
p.AllowCrossNamespace = func(b bool) *bool { return &b }(true)
|
||||
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
||||
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"`
|
||||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"`
|
||||
Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"`
|
||||
AllowCrossNamespace bool `description:"Allow cross namespace resource reference." json:"allowCrossNamespace,omitempty" toml:"allowCrossNamespace,omitempty" yaml:"allowCrossNamespace,omitempty" export:"true"`
|
||||
AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,omitempty" export:"true"`
|
||||
LabelSelector string `description:"Kubernetes label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"`
|
||||
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"`
|
||||
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
|
||||
lastConfiguration safe.Safe
|
||||
}
|
||||
|
||||
func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) {
|
||||
@@ -103,10 +99,14 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
||||
return err
|
||||
}
|
||||
|
||||
if p.AllowCrossNamespace == nil || *p.AllowCrossNamespace {
|
||||
if p.AllowCrossNamespace {
|
||||
logger.Warn("Cross-namespace reference between IngressRoutes and resources is enabled, please ensure that this is expected (see AllowCrossNamespace option)")
|
||||
}
|
||||
|
||||
if p.AllowExternalNameServices {
|
||||
logger.Warn("ExternalName service loading is enabled, please ensure that this is expected (see AllowExternalNameServices option)")
|
||||
}
|
||||
|
||||
pool.GoCtx(func(ctxPool context.Context) {
|
||||
operation := func() error {
|
||||
eventsChan, err := k8sClient.WatchAll(p.Namespaces, ctxPool.Done())
|
||||
@@ -245,7 +245,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
|
||||
}
|
||||
}
|
||||
|
||||
cb := configBuilder{client, p.AllowCrossNamespace}
|
||||
cb := configBuilder{client: client, allowCrossNamespace: p.AllowCrossNamespace, allowExternalNameServices: p.AllowExternalNameServices}
|
||||
|
||||
for _, service := range client.GetTraefikServices() {
|
||||
err := cb.buildTraefikService(ctx, service, conf.HTTP.Services)
|
||||
@@ -365,7 +365,7 @@ func (p *Provider) createErrorPageMiddleware(client Client, namespace string, er
|
||||
Query: errorPage.Query,
|
||||
}
|
||||
|
||||
balancerServerHTTP, err := configBuilder{client, p.AllowCrossNamespace}.buildServersLB(namespace, errorPage.Service.LoadBalancerSpec)
|
||||
balancerServerHTTP, err := configBuilder{client: client, allowCrossNamespace: p.AllowCrossNamespace, allowExternalNameServices: p.AllowExternalNameServices}.buildServersLB(namespace, errorPage.Service.LoadBalancerSpec)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -826,7 +826,7 @@ func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *s
|
||||
return eventsChanBuffered
|
||||
}
|
||||
|
||||
func isNamespaceAllowed(allowCrossNamespace *bool, parentNamespace, namespace string) bool {
|
||||
func isNamespaceAllowed(allowCrossNamespace bool, parentNamespace, namespace string) bool {
|
||||
// If allowCrossNamespace option is not defined the default behavior is to allow cross namespace references.
|
||||
return allowCrossNamespace == nil || *allowCrossNamespace || parentNamespace == namespace
|
||||
return allowCrossNamespace || parentNamespace == namespace
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
||||
ingressName = ingressRoute.GenerateName
|
||||
}
|
||||
|
||||
cb := configBuilder{client, p.AllowCrossNamespace}
|
||||
cb := configBuilder{client: client, allowCrossNamespace: p.AllowCrossNamespace, allowExternalNameServices: p.AllowExternalNameServices}
|
||||
|
||||
for _, route := range ingressRoute.Spec.Routes {
|
||||
if route.Kind != "Rule" {
|
||||
@@ -146,13 +146,23 @@ func (p *Provider) makeMiddlewareKeys(ctx context.Context, ingRouteNamespace str
|
||||
var mds []string
|
||||
|
||||
for _, mi := range middlewares {
|
||||
if strings.Contains(mi.Name, providerNamespaceSeparator) {
|
||||
name := mi.Name
|
||||
|
||||
if !p.AllowCrossNamespace && strings.HasSuffix(mi.Name, providerNamespaceSeparator+providerName) {
|
||||
// Since we are not able to know if another namespace is in the name (namespace-name@kubernetescrd),
|
||||
// if the provider namespace kubernetescrd is used,
|
||||
// we don't allow this format to avoid cross namespace references.
|
||||
return nil, fmt.Errorf("invalid reference to middleware %s: with crossnamespace disallowed, the namespace field needs to be explicitly specified", mi.Name)
|
||||
}
|
||||
|
||||
if strings.Contains(name, providerNamespaceSeparator) {
|
||||
if len(mi.Namespace) > 0 {
|
||||
log.FromContext(ctx).
|
||||
WithField(log.MiddlewareName, mi.Name).
|
||||
Warnf("namespace %q is ignored in cross-provider context", mi.Namespace)
|
||||
}
|
||||
mds = append(mds, mi.Name)
|
||||
|
||||
mds = append(mds, name)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -165,15 +175,16 @@ func (p *Provider) makeMiddlewareKeys(ctx context.Context, ingRouteNamespace str
|
||||
ns = mi.Namespace
|
||||
}
|
||||
|
||||
mds = append(mds, makeID(ns, mi.Name))
|
||||
mds = append(mds, makeID(ns, name))
|
||||
}
|
||||
|
||||
return mds, nil
|
||||
}
|
||||
|
||||
type configBuilder struct {
|
||||
client Client
|
||||
allowCrossNamespace *bool
|
||||
client Client
|
||||
allowCrossNamespace bool
|
||||
allowExternalNameServices bool
|
||||
}
|
||||
|
||||
// buildTraefikService creates the configuration for the traefik service defined in tService,
|
||||
@@ -322,6 +333,10 @@ func (c configBuilder) loadServers(parentNamespace string, svc v1alpha1.LoadBala
|
||||
|
||||
var servers []dynamic.Server
|
||||
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||
if !c.allowExternalNameServices {
|
||||
return nil, fmt.Errorf("externalName services not allowed: %s/%s", namespace, sanitizedName)
|
||||
}
|
||||
|
||||
protocol, err := parseServiceProtocol(svc.Scheme, svcPort.Name, svcPort.Port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -135,7 +135,7 @@ func (p *Provider) createLoadBalancerServerTCP(client Client, parentNamespace st
|
||||
ns = service.Namespace
|
||||
}
|
||||
|
||||
servers, err := loadTCPServers(client, ns, service)
|
||||
servers, err := p.loadTCPServers(client, ns, service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -162,7 +162,7 @@ func (p *Provider) createLoadBalancerServerTCP(client Client, parentNamespace st
|
||||
return tcpService, nil
|
||||
}
|
||||
|
||||
func loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([]dynamic.TCPServer, error) {
|
||||
func (p *Provider) loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([]dynamic.TCPServer, error) {
|
||||
service, exists, err := client.GetService(namespace, svc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -172,6 +172,10 @@ func loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([
|
||||
return nil, errors.New("service not found")
|
||||
}
|
||||
|
||||
if service.Spec.Type == corev1.ServiceTypeExternalName && !p.AllowExternalNameServices {
|
||||
return nil, fmt.Errorf("externalName services not allowed: %s/%s", namespace, svc.Name)
|
||||
}
|
||||
|
||||
svcPort, err := getServicePort(service, svc.Port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1153,8 +1153,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
p := Provider{IngressClass: test.ingressClass}
|
||||
p.SetDefaults()
|
||||
p := Provider{IngressClass: test.ingressClass, AllowCrossNamespace: true, AllowExternalNameServices: true}
|
||||
|
||||
clientMock := newClientMock(test.paths...)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
|
||||
@@ -3338,8 +3337,7 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
p := Provider{IngressClass: test.ingressClass}
|
||||
p.SetDefaults()
|
||||
p := Provider{IngressClass: test.ingressClass, AllowCrossNamespace: true, AllowExternalNameServices: true}
|
||||
|
||||
clientMock := newClientMock(test.paths...)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
|
||||
@@ -3655,8 +3653,7 @@ func TestLoadIngressRouteUDPs(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
p := Provider{IngressClass: test.ingressClass}
|
||||
p.SetDefaults()
|
||||
p := Provider{IngressClass: test.ingressClass, AllowCrossNamespace: true}
|
||||
|
||||
clientMock := newClientMock(test.paths...)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
|
||||
@@ -3989,6 +3986,13 @@ func TestCrossNamespace(t *testing.T) {
|
||||
Priority: 12,
|
||||
Middlewares: []string{"default-test-errorpage"},
|
||||
},
|
||||
"default-test-crossnamespace-route-a1963878aac7331b7950": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-crossnamespace-route-a1963878aac7331b7950",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bur`)",
|
||||
Priority: 12,
|
||||
Middlewares: []string{"cross-ns-stripprefix@kubernetescrd"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"cross-ns-stripprefix": {
|
||||
@@ -4045,6 +4049,19 @@ func TestCrossNamespace(t *testing.T) {
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"default-test-crossnamespace-route-a1963878aac7331b7950": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
@@ -4438,10 +4455,292 @@ func TestCrossNamespace(t *testing.T) {
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
p := Provider{}
|
||||
p.SetDefaults()
|
||||
p := Provider{AllowCrossNamespace: test.allowCrossNamespace}
|
||||
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), client)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExternalNameService(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
allowExternalNameService bool
|
||||
ingressClass string
|
||||
paths []string
|
||||
expected *dynamic.Configuration
|
||||
}{
|
||||
{
|
||||
desc: "Empty",
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HTTP ExternalName services allowed",
|
||||
paths: []string{"services.yml", "with_externalname_with_http.yml"},
|
||||
allowExternalNameService: true,
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-test-route-6f97418635c7e18853da": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-route-6f97418635c7e18853da",
|
||||
Rule: "Host(`foo.com`)",
|
||||
Priority: 0,
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-test-route-6f97418635c7e18853da": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://external.domain:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HTTP Externalname services disallowed",
|
||||
paths: []string{"services.yml", "with_externalname_with_http.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TCP ExternalName services allowed",
|
||||
paths: []string{"tcp/services.yml", "tcp/with_externalname_with_port.yml"},
|
||||
allowExternalNameService: true,
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{
|
||||
"default-test.route-fdd3e9338e47a45efefc": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-fdd3e9338e47a45efefc",
|
||||
Rule: "HostSNI(`foo.com`)",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.TCPService{
|
||||
"default-test.route-fdd3e9338e47a45efefc": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "external.domain:80",
|
||||
Port: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TCP ExternalName services disallowed",
|
||||
paths: []string{"tcp/services.yml", "tcp/with_externalname_with_port.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
// The router that references the invalid service will be discarded.
|
||||
Routers: map[string]*dynamic.TCPRouter{
|
||||
"default-test.route-fdd3e9338e47a45efefc": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-fdd3e9338e47a45efefc",
|
||||
Rule: "HostSNI(`foo.com`)",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "UDP ExternalName services allowed",
|
||||
paths: []string{"udp/services.yml", "udp/with_externalname_service.yml"},
|
||||
allowExternalNameService: true,
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{
|
||||
"default-test.route-0": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-0",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.UDPService{
|
||||
"default-test.route-0": {
|
||||
LoadBalancer: &dynamic.UDPServersLoadBalancer{
|
||||
Servers: []dynamic.UDPServer{
|
||||
{
|
||||
Address: "external.domain:80",
|
||||
Port: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "UDP ExternalName service disallowed",
|
||||
paths: []string{"udp/services.yml", "udp/with_externalname_service.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
// The router that references the invalid service will be discarded.
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{
|
||||
"default-test.route-0": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-0",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var k8sObjects []runtime.Object
|
||||
var crdObjects []runtime.Object
|
||||
for _, path := range test.paths {
|
||||
yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
objects := k8s.MustParseYaml(yamlContent)
|
||||
for _, obj := range objects {
|
||||
switch o := obj.(type) {
|
||||
case *corev1.Service, *corev1.Endpoints, *corev1.Secret:
|
||||
k8sObjects = append(k8sObjects, o)
|
||||
case *v1alpha1.IngressRoute:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.IngressRouteTCP:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.IngressRouteUDP:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.Middleware:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.TraefikService:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.TLSOption:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.TLSStore:
|
||||
crdObjects = append(crdObjects, o)
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
crdClient := crdfake.NewSimpleClientset(crdObjects...)
|
||||
|
||||
client := newClientImpl(kubeClient, crdClient)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
|
||||
eventCh, err := client.WatchAll([]string{"default", "cross-ns"}, stopCh)
|
||||
require.NoError(t, err)
|
||||
|
||||
if k8sObjects != nil || crdObjects != nil {
|
||||
// just wait for the first event
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
p := Provider{AllowExternalNameServices: test.allowExternalNameService}
|
||||
|
||||
p.AllowCrossNamespace = func(b bool) *bool { return &b }(test.allowCrossNamespace)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), client)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
|
||||
@@ -87,7 +87,7 @@ func (p *Provider) createLoadBalancerServerUDP(client Client, parentNamespace st
|
||||
ns = service.Namespace
|
||||
}
|
||||
|
||||
servers, err := loadUDPServers(client, ns, service)
|
||||
servers, err := p.loadUDPServers(client, ns, service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -101,7 +101,7 @@ func (p *Provider) createLoadBalancerServerUDP(client Client, parentNamespace st
|
||||
return udpService, nil
|
||||
}
|
||||
|
||||
func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([]dynamic.UDPServer, error) {
|
||||
func (p *Provider) loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([]dynamic.UDPServer, error) {
|
||||
service, exists, err := client.GetService(namespace, svc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -111,6 +111,10 @@ func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([
|
||||
return nil, errors.New("service not found")
|
||||
}
|
||||
|
||||
if service.Spec.Type == corev1.ServiceTypeExternalName && !p.AllowExternalNameServices {
|
||||
return nil, fmt.Errorf("externalName services not allowed: %s/%s", namespace, svc.Name)
|
||||
}
|
||||
|
||||
var portSpec *corev1.ServicePort
|
||||
for _, p := range service.Spec.Ports {
|
||||
p := p
|
||||
|
||||
@@ -60,7 +60,7 @@ type Client interface {
|
||||
GetSecret(namespace, name string) (*corev1.Secret, bool, error)
|
||||
GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error)
|
||||
UpdateIngressStatus(ing *networkingv1beta1.Ingress, ingStatus []corev1.LoadBalancerIngress) error
|
||||
GetServerVersion() (*version.Version, error)
|
||||
GetServerVersion() *version.Version
|
||||
}
|
||||
|
||||
type clientWrapper struct {
|
||||
@@ -72,6 +72,7 @@ type clientWrapper struct {
|
||||
ingressLabelSelector string
|
||||
isNamespaceAll bool
|
||||
watchedNamespaces []string
|
||||
serverVersion *version.Version
|
||||
}
|
||||
|
||||
// newInClusterClient returns a new Provider client that is expected to run
|
||||
@@ -149,6 +150,19 @@ func newClientImpl(clientset kubernetes.Interface) *clientWrapper {
|
||||
|
||||
// WatchAll starts namespace-specific controllers for all relevant kinds.
|
||||
func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) {
|
||||
// Get and store the serverVersion for future use.
|
||||
serverVersionInfo, err := c.clientset.Discovery().ServerVersion()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not retrieve server version: %w", err)
|
||||
}
|
||||
|
||||
serverVersion, err := version.NewVersion(serverVersionInfo.GitVersion)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse server version: %w", err)
|
||||
}
|
||||
|
||||
c.serverVersion = serverVersion
|
||||
|
||||
eventCh := make(chan interface{}, 1)
|
||||
eventHandler := &resourceEventHandler{eventCh}
|
||||
|
||||
@@ -208,12 +222,6 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
|
||||
}
|
||||
}
|
||||
|
||||
serverVersion, err := c.GetServerVersion()
|
||||
if err != nil {
|
||||
log.WithoutContext().Errorf("Failed to get server version: %v", err)
|
||||
return eventCh, nil
|
||||
}
|
||||
|
||||
if supportsIngressClass(serverVersion) {
|
||||
c.clusterFactory = informers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod)
|
||||
c.clusterFactory.Networking().V1beta1().IngressClasses().Informer().AddEventHandler(eventHandler)
|
||||
@@ -426,13 +434,8 @@ func (c *clientWrapper) lookupNamespace(ns string) string {
|
||||
}
|
||||
|
||||
// GetServerVersion returns the cluster server version, or an error.
|
||||
func (c *clientWrapper) GetServerVersion() (*version.Version, error) {
|
||||
serverVersion, err := c.clientset.Discovery().ServerVersion()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not retrieve server version: %w", err)
|
||||
}
|
||||
|
||||
return version.NewVersion(serverVersion.GitVersion)
|
||||
func (c *clientWrapper) GetServerVersion() *version.Version {
|
||||
return c.serverVersion
|
||||
}
|
||||
|
||||
// eventHandlerFunc will pass the obj on to the events channel or drop it.
|
||||
|
||||
@@ -73,8 +73,8 @@ func (c clientMock) GetIngresses() []*networkingv1beta1.Ingress {
|
||||
return c.ingresses
|
||||
}
|
||||
|
||||
func (c clientMock) GetServerVersion() (*version.Version, error) {
|
||||
return c.serverVersion, nil
|
||||
func (c clientMock) GetServerVersion() *version.Version {
|
||||
return c.serverVersion
|
||||
}
|
||||
|
||||
func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, error) {
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
kubeerror "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
fakediscovery "k8s.io/client-go/discovery/fake"
|
||||
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
@@ -149,6 +151,15 @@ func TestClientIgnoresHelmOwnedSecrets(t *testing.T) {
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(helmSecret, secret)
|
||||
|
||||
fakeDiscovery, ok := kubeClient.Discovery().(*fakediscovery.FakeDiscovery)
|
||||
if !ok {
|
||||
t.Fatalf("couldn't convert Discovery() to *FakeDiscovery")
|
||||
}
|
||||
|
||||
fakeDiscovery.FakedServerVersion = &version.Info{
|
||||
GitVersion: "1.17.0",
|
||||
}
|
||||
|
||||
client := newClientImpl(kubeClient)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: example.com
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /foo
|
||||
backend:
|
||||
serviceName: service-foo
|
||||
servicePort: 8080
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service-foo
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
type: ExternalName
|
||||
externalName: "2001:0db8:3c4d:0015:0000:0000:1a2f:2a3b"
|
||||
@@ -0,0 +1,15 @@
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: ""
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
rules:
|
||||
- host: traefik.tchouk
|
||||
http:
|
||||
paths:
|
||||
- path: /bar
|
||||
backend:
|
||||
serviceName: service1
|
||||
servicePort: 8080
|
||||
@@ -0,0 +1,13 @@
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 8080
|
||||
clusterIP: 10.0.0.1
|
||||
type: ExternalName
|
||||
externalName: traefik.wtf
|
||||
|
||||
@@ -8,7 +8,7 @@ metadata:
|
||||
|
||||
spec:
|
||||
tls:
|
||||
- secretName: myTlsSecret
|
||||
- secretName: my-tls-secret
|
||||
rules:
|
||||
- host: example.com
|
||||
http:
|
||||
@@ -27,7 +27,7 @@ metadata:
|
||||
|
||||
spec:
|
||||
tls:
|
||||
- secretName: myUndefinedSecret
|
||||
- secretName: my-undefined-secret
|
||||
rules:
|
||||
- host: example.fail
|
||||
http:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: myTlsSecret
|
||||
name: my-tls-secret
|
||||
namespace: testing
|
||||
|
||||
data:
|
||||
|
||||
@@ -37,15 +37,16 @@ const (
|
||||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
||||
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"`
|
||||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"`
|
||||
Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"`
|
||||
LabelSelector string `description:"Kubernetes Ingress label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"`
|
||||
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"`
|
||||
IngressEndpoint *EndpointIngress `description:"Kubernetes Ingress Endpoint." json:"ingressEndpoint,omitempty" toml:"ingressEndpoint,omitempty" yaml:"ingressEndpoint,omitempty" export:"true"`
|
||||
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
|
||||
lastConfiguration safe.Safe
|
||||
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
||||
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"`
|
||||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"`
|
||||
Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"`
|
||||
LabelSelector string `description:"Kubernetes Ingress label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"`
|
||||
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"`
|
||||
IngressEndpoint *EndpointIngress `description:"Kubernetes Ingress Endpoint." json:"ingressEndpoint,omitempty" toml:"ingressEndpoint,omitempty" yaml:"ingressEndpoint,omitempty" export:"true"`
|
||||
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
|
||||
AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,omitempty" export:"true"`
|
||||
lastConfiguration safe.Safe
|
||||
}
|
||||
|
||||
// EndpointIngress holds the endpoint information for the Kubernetes provider.
|
||||
@@ -107,6 +108,10 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
||||
return err
|
||||
}
|
||||
|
||||
if p.AllowExternalNameServices {
|
||||
logger.Warn("ExternalName service loading is enabled, please ensure that this is expected (see AllowExternalNameServices option)")
|
||||
}
|
||||
|
||||
pool.GoCtx(func(ctxPool context.Context) {
|
||||
operation := func() error {
|
||||
eventsChan, err := k8sClient.WatchAll(p.Namespaces, ctxPool.Done())
|
||||
@@ -184,11 +189,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
}
|
||||
|
||||
serverVersion, err := client.GetServerVersion()
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Errorf("Failed to get server version: %v", err)
|
||||
return conf
|
||||
}
|
||||
serverVersion := client.GetServerVersion()
|
||||
|
||||
var ingressClasses []*networkingv1beta1.IngressClass
|
||||
|
||||
@@ -228,7 +229,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
||||
continue
|
||||
}
|
||||
|
||||
service, err := loadService(client, ingress.Namespace, *ingress.Spec.Backend)
|
||||
service, err := p.loadService(client, ingress.Namespace, *ingress.Spec.Backend)
|
||||
if err != nil {
|
||||
log.FromContext(ctx).
|
||||
WithField("serviceName", ingress.Spec.Backend.ServiceName).
|
||||
@@ -265,7 +266,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
||||
}
|
||||
|
||||
for _, pa := range rule.HTTP.Paths {
|
||||
service, err := loadService(client, ingress.Namespace, pa.Backend)
|
||||
service, err := p.loadService(client, ingress.Namespace, pa.Backend)
|
||||
if err != nil {
|
||||
log.FromContext(ctx).
|
||||
WithField("serviceName", pa.Backend.ServiceName).
|
||||
@@ -460,7 +461,7 @@ func getTLSConfig(tlsConfigs map[string]*tls.CertAndStores) []*tls.CertAndStores
|
||||
return configs
|
||||
}
|
||||
|
||||
func loadService(client Client, namespace string, backend networkingv1beta1.IngressBackend) (*dynamic.Service, error) {
|
||||
func (p *Provider) loadService(client Client, namespace string, backend networkingv1beta1.IngressBackend) (*dynamic.Service, error) {
|
||||
service, exists, err := client.GetService(namespace, backend.ServiceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -470,6 +471,10 @@ func loadService(client Client, namespace string, backend networkingv1beta1.Ingr
|
||||
return nil, errors.New("service not found")
|
||||
}
|
||||
|
||||
if !p.AllowExternalNameServices && service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||
return nil, fmt.Errorf("externalName services not allowed: %s/%s", namespace, backend.ServiceName)
|
||||
}
|
||||
|
||||
var portName string
|
||||
var portSpec corev1.ServicePort
|
||||
var match bool
|
||||
|
||||
@@ -732,33 +732,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Ingress with service with externalName",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"testing-traefik-tchouk-bar": {
|
||||
Rule: "Host(`traefik.tchouk`) && PathPrefix(`/bar`)",
|
||||
Service: "testing-service1-8080",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service1-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://traefik.wtf:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Ingress with port invalid for one service",
|
||||
expected: &dynamic.Configuration{
|
||||
@@ -786,47 +759,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Ingress with IPv6 endpoints",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"example-com-testing-bar": {
|
||||
Rule: "PathPrefix(`/bar`)",
|
||||
Service: "testing-service-bar-8080",
|
||||
},
|
||||
"example-com-testing-foo": {
|
||||
Rule: "PathPrefix(`/foo`)",
|
||||
Service: "testing-service-foo-8080",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service-bar-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://[2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b]:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"testing-service-foo-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://[2001:0db8:3c4d:0015:0000:0000:1a2f:2a3b]:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TLS support",
|
||||
expected: &dynamic.Configuration{
|
||||
@@ -1332,6 +1264,152 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var paths []string
|
||||
_, err := os.Stat(generateTestFilename("_ingress", test.desc))
|
||||
if err == nil {
|
||||
paths = append(paths, generateTestFilename("_ingress", test.desc))
|
||||
}
|
||||
_, err = os.Stat(generateTestFilename("_endpoint", test.desc))
|
||||
if err == nil {
|
||||
paths = append(paths, generateTestFilename("_endpoint", test.desc))
|
||||
}
|
||||
_, err = os.Stat(generateTestFilename("_service", test.desc))
|
||||
if err == nil {
|
||||
paths = append(paths, generateTestFilename("_service", test.desc))
|
||||
}
|
||||
_, err = os.Stat(generateTestFilename("_secret", test.desc))
|
||||
if err == nil {
|
||||
paths = append(paths, generateTestFilename("_secret", test.desc))
|
||||
}
|
||||
_, err = os.Stat(generateTestFilename("_ingressclass", test.desc))
|
||||
if err == nil {
|
||||
paths = append(paths, generateTestFilename("_ingressclass", test.desc))
|
||||
}
|
||||
|
||||
serverVersion := test.serverVersion
|
||||
if serverVersion == "" {
|
||||
serverVersion = "v1.17"
|
||||
}
|
||||
|
||||
clientMock := newClientMock(serverVersion, paths...)
|
||||
p := Provider{IngressClass: test.ingressClass}
|
||||
conf := p.loadConfigurationFromIngresses(context.Background(), clientMock)
|
||||
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigurationFromIngressesWithExternalNameServices(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
ingressClass string
|
||||
serverVersion string
|
||||
allowExternalNameServices bool
|
||||
expected *dynamic.Configuration
|
||||
}{
|
||||
{
|
||||
desc: "Ingress with service with externalName",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Ingress with service with externalName enabled",
|
||||
allowExternalNameServices: true,
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"testing-traefik-tchouk-bar": {
|
||||
Rule: "Host(`traefik.tchouk`) && PathPrefix(`/bar`)",
|
||||
Service: "testing-service1-8080",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service1-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://traefik.wtf:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Ingress with IPv6 endpoints",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"example-com-testing-bar": {
|
||||
Rule: "PathPrefix(`/bar`)",
|
||||
Service: "testing-service-bar-8080",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service-bar-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://[2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b]:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Ingress with IPv6 endpoints externalname enabled",
|
||||
allowExternalNameServices: true,
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"example-com-testing-foo": {
|
||||
Rule: "PathPrefix(`/foo`)",
|
||||
Service: "testing-service-foo-8080",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service-foo-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://[2001:0db8:3c4d:0015:0000:0000:1a2f:2a3b]:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
@@ -1368,6 +1446,7 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||
clientMock := newClientMock(serverVersion, paths...)
|
||||
|
||||
p := Provider{IngressClass: test.ingressClass}
|
||||
p.AllowExternalNameServices = test.allowExternalNameServices
|
||||
conf := p.loadConfigurationFromIngresses(context.Background(), clientMock)
|
||||
|
||||
assert.Equal(t, test.expected, conf)
|
||||
|
||||
@@ -113,7 +113,11 @@ func host(route *mux.Route, hosts ...string) error {
|
||||
route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
|
||||
reqHost := requestdecorator.GetCanonizedHost(req.Context())
|
||||
if len(reqHost) == 0 {
|
||||
log.FromContext(req.Context()).Warnf("Could not retrieve CanonizedHost, rejecting %s", req.Host)
|
||||
// If the request is an HTTP/1.0 request, then a Host may not be defined.
|
||||
if req.ProtoAtLeast(1, 1) {
|
||||
log.FromContext(req.Context()).Warnf("Could not retrieve CanonizedHost, rejecting %s", req.Host)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/provider"
|
||||
"github.com/traefik/traefik/v2/pkg/safe"
|
||||
"github.com/traefik/traefik/v2/pkg/tls"
|
||||
)
|
||||
|
||||
// ConfigurationWatcher watches configuration changes.
|
||||
@@ -164,6 +165,16 @@ func (c *ConfigurationWatcher) preLoadConfiguration(configMsg dynamic.Message) {
|
||||
if copyConf.TLS != nil {
|
||||
copyConf.TLS.Certificates = nil
|
||||
|
||||
if copyConf.TLS.Options != nil {
|
||||
cleanedOptions := make(map[string]tls.Options, len(copyConf.TLS.Options))
|
||||
for name, option := range copyConf.TLS.Options {
|
||||
option.ClientAuth.CAFiles = []tls.FileOrContent{}
|
||||
cleanedOptions[name] = option
|
||||
}
|
||||
|
||||
copyConf.TLS.Options = cleanedOptions
|
||||
}
|
||||
|
||||
for k := range copyConf.TLS.Stores {
|
||||
st := copyConf.TLS.Stores[k]
|
||||
st.DefaultCertificate = nil
|
||||
@@ -171,6 +182,13 @@ func (c *ConfigurationWatcher) preLoadConfiguration(configMsg dynamic.Message) {
|
||||
}
|
||||
}
|
||||
|
||||
if copyConf.HTTP != nil {
|
||||
for _, transport := range copyConf.HTTP.ServersTransports {
|
||||
transport.Certificates = tls.Certificates{}
|
||||
transport.RootCAs = []tls.FileOrContent{}
|
||||
}
|
||||
}
|
||||
|
||||
jsonConf, err := json.Marshal(copyConf)
|
||||
if err != nil {
|
||||
logger.Errorf("Could not marshal dynamic configuration: %v", err)
|
||||
|
||||
24
script/code-gen.sh
Executable file
24
script/code-gen.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
PROJECT_MODULE="github.com/traefik/traefik"
|
||||
MODULE_VERSION="v2"
|
||||
IMAGE_NAME="kubernetes-codegen:latest"
|
||||
|
||||
echo "Building codegen Docker image..."
|
||||
docker build --build-arg KUBE_VERSION=v0.20.2 --build-arg USER=$USER --build-arg UID=$(id -u) --build-arg GID=$(id -g) -f "./script/codegen.Dockerfile" \
|
||||
-t "${IMAGE_NAME}" \
|
||||
"."
|
||||
|
||||
cmd="/go/src/k8s.io/code-generator/generate-groups.sh all ${PROJECT_MODULE}/${MODULE_VERSION}/pkg/provider/kubernetes/crd/generated ${PROJECT_MODULE}/${MODULE_VERSION}/pkg/provider/kubernetes/crd traefik:v1alpha1 --go-header-file=/go/src/${PROJECT_MODULE}/script/boilerplate.go.tmpl"
|
||||
|
||||
echo "Generating Traefik clientSet code ..."
|
||||
echo $(pwd)
|
||||
docker run --rm \
|
||||
-v "$(pwd):/go/src/${PROJECT_MODULE}" \
|
||||
-w "/go/src/${PROJECT_MODULE}" \
|
||||
"${IMAGE_NAME}" $cmd
|
||||
|
||||
cp -r $(pwd)/${MODULE_VERSION}/* $(pwd)
|
||||
rm -rf $(pwd)/${MODULE_VERSION}
|
||||
19
script/codegen.Dockerfile
Normal file
19
script/codegen.Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
FROM golang:1.16
|
||||
|
||||
ARG USER=$USER
|
||||
ARG UID=$UID
|
||||
ARG GID=$GID
|
||||
RUN useradd -m ${USER} --uid=${UID} && echo "${USER}:" chpasswd
|
||||
USER ${UID}:${GID}
|
||||
|
||||
ARG KUBE_VERSION
|
||||
|
||||
RUN go get k8s.io/code-generator@$KUBE_VERSION; exit 0
|
||||
RUN go get k8s.io/apimachinery@$KUBE_VERSION; exit 0
|
||||
|
||||
RUN mkdir -p $GOPATH/src/k8s.io/{code-generator,apimachinery}
|
||||
RUN cp -R $GOPATH/pkg/mod/k8s.io/code-generator@$KUBE_VERSION $GOPATH/src/k8s.io/code-generator
|
||||
RUN cp -R $GOPATH/pkg/mod/k8s.io/apimachinery@$KUBE_VERSION $GOPATH/src/k8s.io/apimachinery
|
||||
RUN chmod +x $GOPATH/src/k8s.io/code-generator/generate-groups.sh
|
||||
|
||||
WORKDIR $GOPATH/src/k8s.io/code-generator
|
||||
@@ -1,33 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
if [ -n "$TRAVIS_TAG" ]; then
|
||||
if [ -n "${SEMAPHORE_GIT_TAG_NAME}" ]; then
|
||||
echo "Deploying..."
|
||||
else
|
||||
echo "Skipping deploy"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git config --global user.email "$TRAEFIKER_EMAIL"
|
||||
git config --global user.email "${TRAEFIKER_EMAIL}"
|
||||
git config --global user.name "Traefiker"
|
||||
|
||||
# load ssh key
|
||||
: "${encrypted_83c521e11abe_key:?}" # ensures variable is defined
|
||||
: "${encrypted_83c521e11abe_iv:?}" # same
|
||||
echo "Loading key..."
|
||||
openssl aes-256-cbc -K "$encrypted_83c521e11abe_key" -iv "$encrypted_83c521e11abe_iv" -in .travis/traefiker_rsa.enc -out ~/.ssh/traefiker_rsa -d
|
||||
eval "$(ssh-agent -s)"
|
||||
chmod 600 ~/.ssh/traefiker_rsa
|
||||
ssh-add ~/.ssh/traefiker_rsa
|
||||
chmod 600 /home/semaphore/.ssh/traefiker_rsa
|
||||
ssh-add /home/semaphore/.ssh/traefiker_rsa
|
||||
|
||||
# update traefik-library-image repo (official Docker image)
|
||||
echo "Updating traefik-library-imag repo..."
|
||||
git clone git@github.com:traefik/traefik-library-image.git
|
||||
cd traefik-library-image
|
||||
./updatev2.sh "$VERSION"
|
||||
./updatev2.sh "${VERSION}"
|
||||
git add -A
|
||||
echo "$VERSION" | git commit --file -
|
||||
echo "$VERSION" | git tag -a "$VERSION" --file -
|
||||
echo "${VERSION}" | git commit --file -
|
||||
echo "${VERSION}" | git tag -a "${VERSION}" --file -
|
||||
git push -q --follow-tags -u origin master > /dev/null 2>&1
|
||||
|
||||
cd ..
|
||||
|
||||
@@ -4,11 +4,11 @@ RepositoryName = "traefik"
|
||||
OutputType = "file"
|
||||
FileName = "traefik_changelog.md"
|
||||
|
||||
# example new bugfix v2.4.9
|
||||
# example new bugfix v2.4.14
|
||||
CurrentRef = "v2.4"
|
||||
PreviousRef = "v2.4.8"
|
||||
PreviousRef = "v2.4.13"
|
||||
BaseBranch = "v2.4"
|
||||
FutureCurrentRefName = "v2.4.9"
|
||||
FutureCurrentRefName = "v2.4.14"
|
||||
|
||||
ThresholdPreviousRef = 10
|
||||
ThresholdCurrentRef = 10
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
HACK_DIR="$( cd "$( dirname "${0}" )" && pwd -P)"; export HACK_DIR
|
||||
REPO_ROOT=${HACK_DIR}/..
|
||||
TRAEFIK_MODULE_VERSION=v2
|
||||
|
||||
rm -rf "${REPO_ROOT}"/vendor
|
||||
go mod vendor
|
||||
chmod +x "${REPO_ROOT}"/vendor/k8s.io/code-generator/*.sh
|
||||
|
||||
"${REPO_ROOT}"/vendor/k8s.io/code-generator/generate-groups.sh \
|
||||
all \
|
||||
github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/provider/kubernetes/crd/generated \
|
||||
github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/provider/kubernetes/crd \
|
||||
traefik:v1alpha1 \
|
||||
--go-header-file "${HACK_DIR}"/boilerplate.go.tmpl \
|
||||
"$@"
|
||||
|
||||
deepcopy-gen \
|
||||
--input-dirs github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/config/dynamic \
|
||||
--input-dirs github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/tls \
|
||||
--input-dirs github.com/traefik/traefik/${TRAEFIK_MODULE_VERSION}/pkg/types \
|
||||
--output-package github.com/traefik/traefik \
|
||||
-O zz_generated.deepcopy --go-header-file "${HACK_DIR}"/boilerplate.go.tmpl
|
||||
|
||||
cp -r "${REPO_ROOT}"/"${TRAEFIK_MODULE_VERSION:?}"/* "${REPO_ROOT}"; rm -rf "${REPO_ROOT}"/"${TRAEFIK_MODULE_VERSION:?}"
|
||||
|
||||
rm -rf "${REPO_ROOT}"/vendor
|
||||
Reference in New Issue
Block a user