mirror of
https://github.com/caddyserver/caddy.git
synced 2026-05-26 08:42:31 -04:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ac8411d69a | |||
| 74739bc1a4 | |||
| 577f8ba811 |
+6
-16
@@ -1,7 +1,7 @@
|
||||
Contributing to Caddy
|
||||
=====================
|
||||
|
||||
Welcome! Thank you for choosing to be a part of our community. Caddy wouldn't be nearly as excellent without your involvement!
|
||||
Welcome! Thank you for choosing to be a part of our community. Caddy wouldn't be great without your involvement!
|
||||
|
||||
For starters, we invite you to join [the Caddy forum](https://caddy.community) where you can hang out with other Caddy users and developers.
|
||||
|
||||
@@ -35,29 +35,19 @@ Here are some of the expectations we have of contributors:
|
||||
|
||||
- **Keep related commits together in a PR.** We do want pull requests to be small, but you should also keep multiple related commits in the same PR if they rely on each other.
|
||||
|
||||
- **Write tests.** Good, automated tests are very valuable! Written properly, they ensure your change works, and that other changes in the future won't break your change. CI checks should pass.
|
||||
- **Write tests.** Tests are essential! Written properly, they ensure your change works, and that other changes in the future won't break your change. CI checks should pass.
|
||||
|
||||
- **Benchmarks should be included for optimizations.** Optimizations sometimes make code harder to read or have changes that are less than obvious. They should be proven with benchmarks and profiling.
|
||||
- **Benchmarks should be included for optimizations.** Optimizations sometimes make code harder to read or have changes that are less than obvious. They should be proven with benchmarks or profiling.
|
||||
|
||||
- **[Squash](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) insignificant commits.** Every commit should be significant. Commits which merely rewrite a comment or fix a typo can be combined into another commit that has more substance. Interactive rebase can do this, or a simpler way is `git reset --soft <diverging-commit>` then `git commit -s`.
|
||||
|
||||
- **Be responsible for and maintain your contributions.** Caddy is a growing project, and it's much better when individual contributors help maintain their change after it is merged.
|
||||
- **Own your contributions.** Caddy is a growing project, and it's much better when individual contributors help maintain their change after it is merged.
|
||||
|
||||
- **Use comments properly.** We expect good godoc comments for package-level functions, types, and values. Comments are also useful whenever the purpose for a line of code is not obvious.
|
||||
|
||||
- **Pull requests may still get closed.** The longer a PR stays open and idle, the more likely it is to be closed. If we haven't reviewed it in a while, it probably means the change is not a priority. Please don't take this personally, we're trying to balance a lot of tasks! If nobody else has commented or reacted to the PR, it likely means your change is useful only to you. The reality is this happens quite a lot. We don't tend to accept PRs that aren't generally helpful. For these reasons or others, the PR may get closed even after a review. We are not obligated to accept all proposed changes, even if the best justification we can give is something vague like, "It doesn't sit right." Sometimes PRs are just the wrong thing or the wrong time. Because it is open source, you can always build your own modified version of Caddy with a change you need, even if we reject it in the official repo. Plus, because Caddy is extensible, it's possible your feature could make a great plugin instead!
|
||||
- **Pull requests may still get closed.** The longer a PR stays open and idle, the more likely it is to be closed. If we haven't reviewed it in a while, it probably means the change is not a priority. Please don't take this personally, we're trying to balance a lot of tasks! If nobody else has commented or reacted to the PR, it likely means your change is useful only to you. The reality is this happens quite a bit. We don't tend to accept PRs that aren't generally helpful. For these reasons or others, the PR may get closed even after a review. We are not obligated to accept all proposed changes, even if the best justification we can give is something vague like, "It doesn't sit right." Sometimes PRs are just the wrong thing or the wrong time. Because it is open source, you can always build your own modified version of Caddy with a change you need, even if we reject it in the official repo.
|
||||
|
||||
- **You certify that you wrote and comprehend the code you submit.** The Caddy project welcomes original contributions that comply with [our CLA](https://cla-assistant.io/caddyserver/caddy), meaning that authors must be able to certify that they created or have rights to the code they are contributing. In addition, we require that code is not simply copy-pasted from Q/A sites or AI language models without full comprehension and rigorous testing. In other words: contributors are allowed to refer to communities for assistance and use AI tools such as language models for inspiration, but code which originates from or is assisted by these resources MUST be:
|
||||
|
||||
- Licensed for you to freely share
|
||||
- Fully comprehended by you (be able to explain every line of code)
|
||||
- Verified by automated tests when feasible, or thorough manual tests otherwise
|
||||
|
||||
We have found that current language models (LLMs, like ChatGPT) may understand code syntax and even problem spaces to an extent, but often fail in subtle ways to convey true knowledge and produce correct algorithms. Integrated tools such as GitHub Copilot and Sourcegraph Cody may be used for inspiration, but code generated by these tools still needs to meet our criteria for licensing, human comprehension, and testing. These tools may be used to help write code comments and tests as long as you can certify they are accurate and correct. Note that it is often more trouble than it's worth to certify that Copilot (for example) is not giving you code that is possibly plagiarised, unlicensed, or licensed with incompatible terms -- as the Caddy project cannot accept such contributions. If that's too difficult for you (or impossible), then we recommend using these resources only for inspiration and write your own code. Ultimately, you (the contributor) are responsible for the code you're submitting.
|
||||
|
||||
As a courtesy to reviewers, we kindly ask that you disclose when contributing code that was generated by an AI tool or copied from another website so we can be aware of what to look for in code review.
|
||||
|
||||
We often grant [collaborator status](#collaborator-instructions) to contributors who author one or more significant, high-quality PRs that are merged into the code base.
|
||||
We often grant [collaborator status](#collaborator-instructions) to contributors who author one or more significant, high-quality PRs that are merged into the code base!
|
||||
|
||||
|
||||
#### HOW TO MAKE A PULL REQUEST TO CADDY
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ The Caddy project would like to make sure that it stays on top of all practicall
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.x | ✔️ |
|
||||
| 2.x | :white_check_mark: |
|
||||
| 1.x | :x: |
|
||||
| < 1.x | :x: |
|
||||
|
||||
@@ -24,7 +24,7 @@ We do not accept reports if the steps imply or require a compromised system or t
|
||||
|
||||
Client-side exploits are out of scope. In other words, it is not a bug in Caddy if the web browser does something unsafe, even if the downloaded content was served by Caddy. (Those kinds of exploits can generally be mitigated by proper configuration of HTTP headers.) As a general rule, the content served by Caddy is not considered in scope because content is configurable by the site owner or the associated web application.
|
||||
|
||||
Security bugs in code dependencies (including Go's standard library) are out of scope. Instead, if a dependency has patched a relevant security bug, please feel free to open a public issue or pull request to update that dependency in our code.
|
||||
Security bugs in code dependencies are out of scope. Instead, if a dependency has patched a relevant security bug, please feel free to open a public issue or pull request to update that dependency in our code.
|
||||
|
||||
|
||||
## Reporting a Vulnerability
|
||||
@@ -42,7 +42,7 @@ We'll need enough information to verify the bug and make a patch. To speed thing
|
||||
- Specific minimal steps to reproduce the issue from scratch
|
||||
- A working patch
|
||||
|
||||
Please DO NOT use containers, VMs, cloud instances or services, or any other complex infrastructure in your steps. Always prefer `curl -v` instead of web browsers.
|
||||
Please DO NOT use containers, VMs, cloud instances or services, or any other complex infrastructure in your steps. Always prefer `curl` instead of web browsers.
|
||||
|
||||
We consider publicly-registered domain names to be public information. This necessary in order to maintain the integrity of certificate transparency, public DNS, and other public trust systems. Do not redact domain names from your reports. The actual content of your domain name affects Caddy's behavior, so we need the exact domain name(s) to reproduce with, or your report will be ignored.
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
+40
-30
@@ -18,22 +18,17 @@ jobs:
|
||||
# Default is true, cancels jobs for other platforms in the matrix if one fails
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
go:
|
||||
- '1.20'
|
||||
- '1.21'
|
||||
os: [ ubuntu-latest, macos-latest, windows-latest ]
|
||||
go: [ '1.18', '1.19' ]
|
||||
|
||||
include:
|
||||
# Set the minimum Go patch version for the given Go minor
|
||||
# Usable via ${{ matrix.GO_SEMVER }}
|
||||
- go: '1.20'
|
||||
GO_SEMVER: '~1.20.6'
|
||||
- go: '1.18'
|
||||
GO_SEMVER: '~1.18.4'
|
||||
|
||||
- go: '1.21'
|
||||
GO_SEMVER: '~1.21.0'
|
||||
- go: '1.19'
|
||||
GO_SEMVER: '~1.19.0'
|
||||
|
||||
# Set some variables per OS, usable via ${{ matrix.VAR }}
|
||||
# CADDY_BIN_PATH: the path to the compiled Caddy binary, for artifact publishing
|
||||
@@ -53,15 +48,15 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.GO_SEMVER }}
|
||||
check-latest: true
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# These tools would be useful if we later decide to reinvestigate
|
||||
# publishing test/coverage reports to some tool for easier consumption
|
||||
# - name: Install test and coverage analysis tools
|
||||
@@ -69,11 +64,10 @@ jobs:
|
||||
# go get github.com/axw/gocov/gocov
|
||||
# go get github.com/AlekSi/gocov-xml
|
||||
# go get -u github.com/jstemmer/go-junit-report
|
||||
# echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
# echo "::add-path::$(go env GOPATH)/bin"
|
||||
|
||||
- name: Print Go version and environment
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
printf "Using go at: $(which go)\n"
|
||||
printf "Go version: $(go version)\n"
|
||||
@@ -83,7 +77,24 @@ jobs:
|
||||
env
|
||||
printf "Git version: $(git version)\n\n"
|
||||
# Calculate the short SHA1 hash of the git commit
|
||||
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
|
||||
|
||||
- name: Cache the build cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
# In order:
|
||||
# * Module download cache
|
||||
# * Build cache (Linux)
|
||||
# * Build cache (Mac)
|
||||
# * Build cache (Windows)
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
~/.cache/go-build
|
||||
~/Library/Caches/go-build
|
||||
~\AppData\Local\go-build
|
||||
key: ${{ runner.os }}-${{ matrix.go }}-go-ci-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.go }}-go-ci
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
@@ -98,7 +109,7 @@ jobs:
|
||||
go build -trimpath -ldflags="-w -s" -v
|
||||
|
||||
- name: Publish Build Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: caddy_${{ runner.os }}_go${{ matrix.go }}_${{ steps.vars.outputs.short_sha }}
|
||||
path: ${{ matrix.CADDY_BIN_PATH }}
|
||||
@@ -112,7 +123,7 @@ jobs:
|
||||
run: |
|
||||
# (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out
|
||||
go test -v -coverprofile="cover-profile.out" -short -race ./...
|
||||
# echo "status=$?" >> $GITHUB_OUTPUT
|
||||
# echo "::set-output name=status::$?"
|
||||
|
||||
# Relevant step if we reinvestigate publishing test/coverage reports
|
||||
# - name: Prepare coverage reports
|
||||
@@ -132,11 +143,11 @@ jobs:
|
||||
s390x-test:
|
||||
name: test (s390x on IBM Z)
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]'
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
continue-on-error: true # August 2020: s390x VM is down due to weather and power issues
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
- name: Run Tests
|
||||
run: |
|
||||
mkdir -p ~/.ssh && echo -e "${SSH_KEY//_/\\n}" > ~/.ssh/id_ecdsa && chmod og-rwx ~/.ssh/id_ecdsa
|
||||
@@ -145,26 +156,25 @@ jobs:
|
||||
short_sha=$(git rev-parse --short HEAD)
|
||||
|
||||
# The environment is fresh, so there's no point in keeping accepting and adding the key.
|
||||
rsync -arz -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" --progress --delete --exclude '.git' . "$CI_USER"@ci-s390x.caddyserver.com:/var/tmp/"$short_sha"
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -t "$CI_USER"@ci-s390x.caddyserver.com "cd /var/tmp/$short_sha; go version; go env; printf "\n\n";CGO_ENABLED=0 go test -v ./..."
|
||||
rsync -arz -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" --progress --delete --exclude '.git' . caddy-ci@ci-s390x.caddyserver.com:/var/tmp/"$short_sha"
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -t caddy-ci@ci-s390x.caddyserver.com "cd /var/tmp/$short_sha; go version; go env; printf "\n\n";CGO_ENABLED=0 go test -v ./..."
|
||||
test_result=$?
|
||||
|
||||
# There's no need leaving the files around
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "$CI_USER"@ci-s390x.caddyserver.com "rm -rf /var/tmp/'$short_sha'"
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null caddy-ci@ci-s390x.caddyserver.com "rm -rf /var/tmp/'$short_sha'"
|
||||
|
||||
echo "Test exit code: $test_result"
|
||||
exit $test_result
|
||||
env:
|
||||
SSH_KEY: ${{ secrets.S390X_SSH_KEY }}
|
||||
CI_USER: ${{ secrets.CI_USER }}
|
||||
|
||||
goreleaser-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: goreleaser/goreleaser-action@v5
|
||||
- uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
version: latest
|
||||
args: check
|
||||
|
||||
@@ -15,35 +15,20 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
goos:
|
||||
- 'android'
|
||||
- 'linux'
|
||||
- 'solaris'
|
||||
- 'illumos'
|
||||
- 'dragonfly'
|
||||
- 'freebsd'
|
||||
- 'openbsd'
|
||||
- 'plan9'
|
||||
- 'windows'
|
||||
- 'darwin'
|
||||
- 'netbsd'
|
||||
go:
|
||||
- '1.21'
|
||||
goos: ['android', 'linux', 'solaris', 'illumos', 'dragonfly', 'freebsd', 'openbsd', 'plan9', 'windows', 'darwin', 'netbsd']
|
||||
go: [ '1.19' ]
|
||||
|
||||
include:
|
||||
# Set the minimum Go patch version for the given Go minor
|
||||
# Usable via ${{ matrix.GO_SEMVER }}
|
||||
- go: '1.21'
|
||||
GO_SEMVER: '~1.21.0'
|
||||
- go: '1.19'
|
||||
GO_SEMVER: '~1.19.0'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.GO_SEMVER }}
|
||||
check-latest: true
|
||||
@@ -58,6 +43,22 @@ jobs:
|
||||
printf "\n\nSystem environment:\n\n"
|
||||
env
|
||||
|
||||
- name: Cache the build cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
# In order:
|
||||
# * Module download cache
|
||||
# * Build cache (Linux)
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
~/.cache/go-build
|
||||
key: cross-build-go${{ matrix.go }}-${{ matrix.goos }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
cross-build-go${{ matrix.go }}-${{ matrix.goos }}
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run Build
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
@@ -10,52 +10,26 @@ on:
|
||||
- master
|
||||
- 2.*
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# From https://github.com/golangci/golangci-lint-action
|
||||
golangci:
|
||||
permissions:
|
||||
contents: read # for actions/checkout to fetch code
|
||||
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
|
||||
name: lint
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '~1.21.0'
|
||||
go-version: '~1.18.4'
|
||||
check-latest: true
|
||||
|
||||
# Workaround for https://github.com/golangci/golangci-lint-action/issues/135
|
||||
skip-pkg-cache: true
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: v1.54
|
||||
|
||||
# Workaround for https://github.com/golangci/golangci-lint-action/issues/135
|
||||
skip-pkg-cache: true
|
||||
|
||||
version: v1.47
|
||||
# Windows times out frequently after about 5m50s if we don't set a longer timeout.
|
||||
args: --timeout 10m
|
||||
|
||||
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||
# only-new-issues: true
|
||||
|
||||
govulncheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: govulncheck
|
||||
uses: golang/govulncheck-action@v1
|
||||
with:
|
||||
go-version-input: '~1.21.0'
|
||||
check-latest: true
|
||||
|
||||
@@ -10,16 +10,14 @@ jobs:
|
||||
name: Release
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
go:
|
||||
- '1.21'
|
||||
os: [ ubuntu-latest ]
|
||||
go: [ '1.19' ]
|
||||
|
||||
include:
|
||||
# Set the minimum Go patch version for the given Go minor
|
||||
# Usable via ${{ matrix.GO_SEMVER }}
|
||||
- go: '1.21'
|
||||
GO_SEMVER: '~1.21.0'
|
||||
- go: '1.19'
|
||||
GO_SEMVER: '~1.19.0'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
|
||||
@@ -31,19 +29,19 @@ jobs:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.GO_SEMVER }}
|
||||
check-latest: true
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Force fetch upstream tags -- because 65 minutes
|
||||
# tl;dr: actions/checkout@v4 runs this line:
|
||||
# tl;dr: actions/checkout@v3 runs this line:
|
||||
# git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/
|
||||
# which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran:
|
||||
# git fetch --prune --unshallow
|
||||
@@ -63,8 +61,8 @@ jobs:
|
||||
go env
|
||||
printf "\n\nSystem environment:\n\n"
|
||||
env
|
||||
echo "version_tag=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
|
||||
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
echo "::set-output name=version_tag::${GITHUB_REF/refs\/tags\//}"
|
||||
echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
|
||||
|
||||
# Add "pip install" CLI tools to PATH
|
||||
echo ~/.local/bin >> $GITHUB_PATH
|
||||
@@ -76,10 +74,10 @@ jobs:
|
||||
TAG_MINOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\2#"`
|
||||
TAG_PATCH=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\3#"`
|
||||
TAG_SPECIAL=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\4#"`
|
||||
echo "tag_major=${TAG_MAJOR}" >> $GITHUB_OUTPUT
|
||||
echo "tag_minor=${TAG_MINOR}" >> $GITHUB_OUTPUT
|
||||
echo "tag_patch=${TAG_PATCH}" >> $GITHUB_OUTPUT
|
||||
echo "tag_special=${TAG_SPECIAL}" >> $GITHUB_OUTPUT
|
||||
echo "::set-output name=tag_major::${TAG_MAJOR}"
|
||||
echo "::set-output name=tag_minor::${TAG_MINOR}"
|
||||
echo "::set-output name=tag_patch::${TAG_PATCH}"
|
||||
echo "::set-output name=tag_special::${TAG_SPECIAL}"
|
||||
|
||||
# Cloudsmith CLI tooling for pushing releases
|
||||
# See https://help.cloudsmith.io/docs/cli
|
||||
@@ -96,6 +94,18 @@ jobs:
|
||||
# tags are only accepted if signed by Matt's key
|
||||
git verify-tag "${{ steps.vars.outputs.version_tag }}" || exit 1
|
||||
|
||||
- name: Cache the build cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
# In order:
|
||||
# * Module download cache
|
||||
# * Build cache (Linux)
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
~/.cache/go-build
|
||||
key: ${{ runner.os }}-go${{ matrix.go }}-release-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go${{ matrix.go }}-release
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@main
|
||||
- name: Cosign version
|
||||
@@ -106,10 +116,10 @@ jobs:
|
||||
run: syft version
|
||||
# GoReleaser will take care of publishing those artifacts into the release
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
version: latest
|
||||
args: release --clean --timeout 60m
|
||||
args: release --rm-dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ steps.vars.outputs.version_tag }}
|
||||
|
||||
@@ -10,15 +10,14 @@ jobs:
|
||||
name: Release Published
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
os: [ ubuntu-latest ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
|
||||
# See https://github.com/peter-evans/repository-dispatch
|
||||
- name: Trigger event on caddyserver/dist
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
uses: peter-evans/repository-dispatch@v1
|
||||
with:
|
||||
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
||||
repository: caddyserver/dist
|
||||
@@ -26,7 +25,7 @@ jobs:
|
||||
client-payload: '{"tag": "${{ github.event.release.tag_name }}"}'
|
||||
|
||||
- name: Trigger event on caddyserver/caddy-docker
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
uses: peter-evans/repository-dispatch@v1
|
||||
with:
|
||||
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
||||
repository: caddyserver/caddy-docker
|
||||
|
||||
@@ -11,8 +11,6 @@ Caddyfile.*
|
||||
# build artifacts and helpers
|
||||
cmd/caddy/caddy
|
||||
cmd/caddy/caddy.exe
|
||||
cmd/caddy/tmp/*.exe
|
||||
cmd/caddy/.env
|
||||
|
||||
# mac specific
|
||||
.DS_Store
|
||||
|
||||
+9
-23
@@ -2,27 +2,15 @@ linters-settings:
|
||||
errcheck:
|
||||
ignore: fmt:.*,go.uber.org/zap/zapcore:^Add.*
|
||||
ignoretests: true
|
||||
gci:
|
||||
sections:
|
||||
- standard # Standard section: captures all standard packages.
|
||||
- default # Default section: contains all imports that could not be matched to another section type.
|
||||
- prefix(github.com/caddyserver/caddy/v2/cmd) # ensure that this is always at the top and always has a line break.
|
||||
- prefix(github.com/caddyserver/caddy) # Custom section: groups all imports with the specified Prefix.
|
||||
# Skip generated files.
|
||||
# Default: true
|
||||
skip-generated: true
|
||||
# Enable custom order of sections.
|
||||
# If `true`, make the section order the same as the order of `sections`.
|
||||
# Default: false
|
||||
custom-order: true
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- bodyclose
|
||||
- deadcode
|
||||
- errcheck
|
||||
- gci
|
||||
- gofumpt
|
||||
- gofmt
|
||||
- goimports
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
@@ -30,9 +18,11 @@ linters:
|
||||
- misspell
|
||||
- prealloc
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unused
|
||||
- varcheck
|
||||
# these are implicitly disabled:
|
||||
# - asciicheck
|
||||
# - depguard
|
||||
@@ -90,23 +80,19 @@ output:
|
||||
issues:
|
||||
exclude-rules:
|
||||
# we aren't calling unknown URL
|
||||
- text: 'G107' # G107: Url provided to HTTP request as taint input
|
||||
- text: "G107" # G107: Url provided to HTTP request as taint input
|
||||
linters:
|
||||
- gosec
|
||||
# as a web server that's expected to handle any template, this is totally in the hands of the user.
|
||||
- text: 'G203' # G203: Use of unescaped data in HTML templates
|
||||
- text: "G203" # G203: Use of unescaped data in HTML templates
|
||||
linters:
|
||||
- gosec
|
||||
# we're shelling out to known commands, not relying on user-defined input.
|
||||
- text: 'G204' # G204: Audit use of command execution
|
||||
- text: "G204" # G204: Audit use of command execution
|
||||
linters:
|
||||
- gosec
|
||||
# the choice of weakrand is deliberate, hence the named import "weakrand"
|
||||
- path: modules/caddyhttp/reverseproxy/selectionpolicies.go
|
||||
text: 'G404' # G404: Insecure random number source (rand)
|
||||
linters:
|
||||
- gosec
|
||||
- path: modules/caddyhttp/reverseproxy/streaming.go
|
||||
text: 'G404' # G404: Insecure random number source (rand)
|
||||
text: "G404" # G404: Insecure random number source (rand)
|
||||
linters:
|
||||
- gosec
|
||||
|
||||
+9
-64
@@ -4,9 +4,6 @@ before:
|
||||
# This is so we can run goreleaser on tag without Git complaining of being dirty. The main.go in cmd/caddy directory
|
||||
# cannot be built within that directory due to changes necessary for the build causing Git to be dirty, which
|
||||
# subsequently causes gorleaser to refuse running.
|
||||
- rm -rf caddy-build caddy-dist vendor
|
||||
# vendor Caddy deps
|
||||
- go mod vendor
|
||||
- mkdir -p caddy-build
|
||||
- cp cmd/caddy/main.go caddy-build/main.go
|
||||
- /bin/sh -c 'cd ./caddy-build && go mod init caddy'
|
||||
@@ -16,8 +13,6 @@ before:
|
||||
# as of Go 1.16, `go` commands no longer automatically change go.{mod,sum}. We now have to explicitly
|
||||
# run `go mod tidy`. The `/bin/sh -c '...'` is because goreleaser can't find cd in PATH without shell invocation.
|
||||
- /bin/sh -c 'cd ./caddy-build && go mod tidy'
|
||||
# vendor the deps of the prepared to-build module
|
||||
- /bin/sh -c 'cd ./caddy-build && go mod vendor'
|
||||
- git clone --depth 1 https://github.com/caddyserver/dist caddy-dist
|
||||
- mkdir -p caddy-dist/man
|
||||
- go mod download
|
||||
@@ -43,7 +38,6 @@ builds:
|
||||
- arm64
|
||||
- s390x
|
||||
- ppc64le
|
||||
- riscv64
|
||||
goarm:
|
||||
- "5"
|
||||
- "6"
|
||||
@@ -55,20 +49,14 @@ builds:
|
||||
goarch: ppc64le
|
||||
- goos: darwin
|
||||
goarch: s390x
|
||||
- goos: darwin
|
||||
goarch: riscv64
|
||||
- goos: windows
|
||||
goarch: ppc64le
|
||||
- goos: windows
|
||||
goarch: s390x
|
||||
- goos: windows
|
||||
goarch: riscv64
|
||||
- goos: freebsd
|
||||
goarch: ppc64le
|
||||
- goos: freebsd
|
||||
goarch: s390x
|
||||
- goos: freebsd
|
||||
goarch: riscv64
|
||||
- goos: freebsd
|
||||
goarch: arm
|
||||
goarm: "5"
|
||||
@@ -77,69 +65,25 @@ builds:
|
||||
- -mod=readonly
|
||||
ldflags:
|
||||
- -s -w
|
||||
|
||||
signs:
|
||||
- cmd: cosign
|
||||
signature: "${artifact}.sig"
|
||||
certificate: '{{ trimsuffix (trimsuffix .Env.artifact ".zip") ".tar.gz" }}.pem'
|
||||
args: ["sign-blob", "--yes", "--output-signature=${signature}", "--output-certificate", "${certificate}", "${artifact}"]
|
||||
certificate: '{{ trimsuffix .Env.artifact ".tar.gz" }}.pem'
|
||||
args: ["sign-blob", "--output-signature=${signature}", "--output-certificate", "${certificate}", "${artifact}"]
|
||||
artifacts: all
|
||||
|
||||
sboms:
|
||||
- artifacts: binary
|
||||
documents:
|
||||
- >-
|
||||
{{ .ProjectName }}_
|
||||
{{- .Version }}_
|
||||
{{- if eq .Os "darwin" }}mac{{ else }}{{ .Os }}{{ end }}_
|
||||
{{- .Arch }}
|
||||
{{- with .Arm }}v{{ . }}{{ end }}
|
||||
{{- with .Mips }}_{{ . }}{{ end }}
|
||||
{{- if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}.sbom
|
||||
# defaults to
|
||||
# documents:
|
||||
# - "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}.sbom"
|
||||
cmd: syft
|
||||
args: ["$artifact", "--file", "${document}", "--output", "cyclonedx-json"]
|
||||
|
||||
archives:
|
||||
- id: default
|
||||
format_overrides:
|
||||
- format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_
|
||||
{{- .Version }}_
|
||||
{{- if eq .Os "darwin" }}mac{{ else }}{{ .Os }}{{ end }}_
|
||||
{{- .Arch }}
|
||||
{{- with .Arm }}v{{ . }}{{ end }}
|
||||
{{- with .Mips }}_{{ . }}{{ end }}
|
||||
{{- if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}
|
||||
|
||||
# package the 'caddy-build' directory into a tarball,
|
||||
# allowing users to build the exact same set of files as ours.
|
||||
- id: source
|
||||
meta: true
|
||||
name_template: "{{ .ProjectName }}_{{ .Version }}_buildable-artifact"
|
||||
files:
|
||||
- src: LICENSE
|
||||
dst: ./LICENSE
|
||||
- src: README.md
|
||||
dst: ./README.md
|
||||
- src: AUTHORS
|
||||
dst: ./AUTHORS
|
||||
- src: ./caddy-build
|
||||
dst: ./
|
||||
|
||||
source:
|
||||
enabled: true
|
||||
name_template: '{{ .ProjectName }}_{{ .Version }}_src'
|
||||
format: 'tar.gz'
|
||||
|
||||
# Additional files/template/globs you want to add to the source archive.
|
||||
#
|
||||
# Default: empty.
|
||||
files:
|
||||
- vendor
|
||||
|
||||
|
||||
replacements:
|
||||
darwin: mac
|
||||
checksum:
|
||||
algorithm: sha512
|
||||
|
||||
@@ -184,6 +128,7 @@ nfpms:
|
||||
preremove: ./caddy-dist/scripts/preremove.sh
|
||||
postremove: ./caddy-dist/scripts/postremove.sh
|
||||
|
||||
|
||||
release:
|
||||
github:
|
||||
owner: caddyserver
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
<p align="center">
|
||||
<a href="https://caddyserver.com">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/1128849/210187358-e2c39003-9a5e-4dd5-a783-6deb6483ee72.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1128849/210187356-dfb7f1c5-ac2e-43aa-bb23-fc014280ae1f.svg">
|
||||
<img src="https://user-images.githubusercontent.com/1128849/210187356-dfb7f1c5-ac2e-43aa-bb23-fc014280ae1f.svg" alt="Caddy" width="550">
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://caddyserver.com"><img src="https://user-images.githubusercontent.com/1128849/36338535-05fb646a-136f-11e8-987b-e6901e717d5a.png" alt="Caddy" width="450"></a>
|
||||
<br>
|
||||
<h3 align="center">a <a href="https://zerossl.com"><img src="https://user-images.githubusercontent.com/55066419/208327323-2770dc16-ec09-43a0-9035-c5b872c2ad7f.svg" height="28" style="vertical-align: -7.7px" valign="middle"></a> project</h3>
|
||||
<h3 align="center">a <a href="https://zerossl.com"><img src="https://caddyserver.com/resources/images/zerossl-logo.svg" height="28" valign="middle"></a> project</h3>
|
||||
</p>
|
||||
<hr>
|
||||
<h3 align="center">Every site on HTTPS</h3>
|
||||
<p align="center">Caddy is an extensible server platform that uses TLS by default.</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/caddyserver/caddy/actions/workflows/ci.yml"><img src="https://github.com/caddyserver/caddy/actions/workflows/ci.yml/badge.svg"></a>
|
||||
<a href="https://github.com/caddyserver/caddy/actions?query=workflow%3ACross-Platform"><img src="https://github.com/caddyserver/caddy/workflows/Cross-Platform/badge.svg"></a>
|
||||
<a href="https://pkg.go.dev/github.com/caddyserver/caddy/v2"><img src="https://img.shields.io/badge/godoc-reference-%23007d9c.svg"></a>
|
||||
<br>
|
||||
<a href="https://twitter.com/caddyserver" title="@caddyserver on Twitter"><img src="https://img.shields.io/badge/twitter-@caddyserver-55acee.svg" alt="@caddyserver on Twitter"></a>
|
||||
@@ -46,13 +40,7 @@
|
||||
<p align="center">
|
||||
<b>Powered by</b>
|
||||
<br>
|
||||
<a href="https://github.com/caddyserver/certmagic">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/55066419/206946718-740b6371-3df3-4d72-a822-47e4c48af999.png">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png">
|
||||
<img src="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png" alt="CertMagic" width="250">
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/caddyserver/certmagic"><img src="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png" alt="CertMagic" width="250"></a>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -70,7 +58,7 @@
|
||||
- **Stays up when other servers go down** due to TLS/OCSP/certificate-related issues
|
||||
- **Production-ready** after serving trillions of requests and managing millions of TLS certificates
|
||||
- **Scales to hundreds of thousands of sites** as proven in production
|
||||
- **HTTP/1.1, HTTP/2, and HTTP/3** all supported by default
|
||||
- **HTTP/1.1, HTTP/2, and HTTP/3** supported all by default
|
||||
- **Highly extensible** [modular architecture](https://caddyserver.com/docs/architecture) lets Caddy do anything without bloat
|
||||
- **Runs anywhere** with **no external dependencies** (not even libc)
|
||||
- Written in Go, a language with higher **memory safety guarantees** than other servers
|
||||
@@ -87,10 +75,10 @@ See [our online documentation](https://caddyserver.com/docs/install) for other i
|
||||
|
||||
Requirements:
|
||||
|
||||
- [Go 1.20 or newer](https://golang.org/dl/)
|
||||
- [Go 1.18 or newer](https://golang.org/dl/)
|
||||
|
||||
### For development
|
||||
|
||||
|
||||
_**Note:** These steps [will not embed proper version information](https://github.com/golang/go/issues/29228). For that, please follow the instructions in the next section._
|
||||
|
||||
```bash
|
||||
@@ -197,4 +185,4 @@ Matthew Holt began developing Caddy in 2014 while studying computer science at B
|
||||
|
||||
Caddy is a project of [ZeroSSL](https://zerossl.com), a Stack Holdings company.
|
||||
|
||||
Debian package repository hosting is graciously provided by [Cloudsmith](https://cloudsmith.com). Cloudsmith is the only fully hosted, cloud-native, universal package management solution, that enables your organization to create, store and share packages in any format, to any place, with total confidence.
|
||||
Debian package repository hosting is graciously provided by [Cloudsmith](https://cloudsmith.com). Cloudsmith is the only fully hosted, cloud-native, universal package management solution, that enables your organization to create, store and share packages in any format, to any place, with total confidence.
|
||||
@@ -46,17 +46,6 @@ import (
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// The hard-coded default `DefaultAdminListen` can be overidden
|
||||
// by setting the `CADDY_ADMIN` environment variable.
|
||||
// The environment variable may be used by packagers to change
|
||||
// the default admin address to something more appropriate for
|
||||
// that platform. See #5317 for discussion.
|
||||
if env, exists := os.LookupEnv("CADDY_ADMIN"); exists {
|
||||
DefaultAdminListen = env
|
||||
}
|
||||
}
|
||||
|
||||
// AdminConfig configures Caddy's API endpoint, which is used
|
||||
// to manage Caddy while it is running.
|
||||
type AdminConfig struct {
|
||||
@@ -68,14 +57,7 @@ type AdminConfig struct {
|
||||
|
||||
// The address to which the admin endpoint's listener should
|
||||
// bind itself. Can be any single network address that can be
|
||||
// parsed by Caddy. Accepts placeholders.
|
||||
// Default: the value of the `CADDY_ADMIN` environment variable,
|
||||
// or `localhost:2019` otherwise.
|
||||
//
|
||||
// Remember: When changing this value through a config reload,
|
||||
// be sure to use the `--address` CLI flag to specify the current
|
||||
// admin address if the currently-running admin endpoint is not
|
||||
// the default address.
|
||||
// parsed by Caddy. Default: localhost:2019
|
||||
Listen string `json:"listen,omitempty"`
|
||||
|
||||
// If true, CORS headers will be emitted, and requests to the
|
||||
@@ -174,7 +156,7 @@ type IdentityConfig struct {
|
||||
//
|
||||
// EXPERIMENTAL: Subject to change.
|
||||
type RemoteAdmin struct {
|
||||
// The address on which to start the secure listener. Accepts placeholders.
|
||||
// The address on which to start the secure listener.
|
||||
// Default: :2021
|
||||
Listen string `json:"listen,omitempty"`
|
||||
|
||||
@@ -318,32 +300,7 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
|
||||
// messages. If the requested URI does not include an Internet host
|
||||
// name for the service being requested, then the Host header field MUST
|
||||
// be given with an empty value."
|
||||
//
|
||||
// UPDATE July 2023: Go broke this by patching a minor security bug in 1.20.6.
|
||||
// Understandable, but frustrating. See:
|
||||
// https://github.com/golang/go/issues/60374
|
||||
// See also the discussion here:
|
||||
// https://github.com/golang/go/issues/61431
|
||||
//
|
||||
// We can no longer conform to RFC 2616 Section 14.26 from either Go or curl
|
||||
// in purity. (Curl allowed no host between 7.40 and 7.50, but now requires a
|
||||
// bogus host; see https://superuser.com/a/925610.) If we disable Host/Origin
|
||||
// security checks, the infosec community assures me that it is secure to do
|
||||
// so, because:
|
||||
// 1) Browsers do not allow access to unix sockets
|
||||
// 2) DNS is irrelevant to unix sockets
|
||||
//
|
||||
// I am not quite ready to trust either of those external factors, so instead
|
||||
// of disabling Host/Origin checks, we now allow specific Host values when
|
||||
// accessing the admin endpoint over unix sockets. I definitely don't trust
|
||||
// DNS (e.g. I don't trust 'localhost' to always resolve to the local host),
|
||||
// and IP shouldn't even be used, but if it is for some reason, I think we can
|
||||
// at least be reasonably assured that 127.0.0.1 and ::1 route to the local
|
||||
// machine, meaning that a hypothetical browser origin would have to be on the
|
||||
// local machine as well.
|
||||
uniqueOrigins[""] = struct{}{}
|
||||
uniqueOrigins["127.0.0.1"] = struct{}{}
|
||||
uniqueOrigins["::1"] = struct{}{}
|
||||
} else {
|
||||
uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
|
||||
uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
|
||||
@@ -425,7 +382,7 @@ func replaceLocalAdminServer(cfg *Config) error {
|
||||
|
||||
handler := cfg.Admin.newAdminHandler(addr, false)
|
||||
|
||||
ln, err := addr.Listen(context.TODO(), 0, net.ListenConfig{})
|
||||
ln, err := Listen(addr.Network, addr.JoinHostPort(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -446,7 +403,7 @@ func replaceLocalAdminServer(cfg *Config) error {
|
||||
serverMu.Lock()
|
||||
server := localAdminServer
|
||||
serverMu.Unlock()
|
||||
if err := server.Serve(ln.(net.Listener)); !errors.Is(err, http.ErrServerClosed) {
|
||||
if err := server.Serve(ln); !errors.Is(err, http.ErrServerClosed) {
|
||||
adminLogger.Error("admin server shutdown for unknown reason", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
@@ -592,11 +549,10 @@ func replaceRemoteAdminServer(ctx Context, cfg *Config) error {
|
||||
serverMu.Unlock()
|
||||
|
||||
// start listener
|
||||
lnAny, err := addr.Listen(ctx, 0, net.ListenConfig{})
|
||||
ln, err := Listen(addr.Network, addr.JoinHostPort(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ln := lnAny.(net.Listener)
|
||||
ln = tls.NewListener(ln, tlsConfig)
|
||||
|
||||
go func() {
|
||||
@@ -615,13 +571,12 @@ func replaceRemoteAdminServer(ctx Context, cfg *Config) error {
|
||||
}
|
||||
|
||||
func (ident *IdentityConfig) certmagicConfig(logger *zap.Logger, makeCache bool) *certmagic.Config {
|
||||
var cmCfg *certmagic.Config
|
||||
if ident == nil {
|
||||
// user might not have configured identity; that's OK, we can still make a
|
||||
// certmagic config, although it'll be mostly useless for remote management
|
||||
ident = new(IdentityConfig)
|
||||
}
|
||||
template := certmagic.Config{
|
||||
cmCfg := &certmagic.Config{
|
||||
Storage: DefaultStorage, // do not act as part of a cluster (this is for the server's local identity)
|
||||
Logger: logger,
|
||||
Issuers: ident.issuers,
|
||||
@@ -631,11 +586,9 @@ func (ident *IdentityConfig) certmagicConfig(logger *zap.Logger, makeCache bool)
|
||||
GetConfigForCert: func(certmagic.Certificate) (*certmagic.Config, error) {
|
||||
return cmCfg, nil
|
||||
},
|
||||
Logger: logger.Named("cache"),
|
||||
})
|
||||
}
|
||||
cmCfg = certmagic.New(identityCertCache, template)
|
||||
return cmCfg
|
||||
return certmagic.New(identityCertCache, *cmCfg)
|
||||
}
|
||||
|
||||
// IdentityCredentials returns this instance's configured, managed identity credentials
|
||||
@@ -1041,9 +994,9 @@ func handleConfigID(w http.ResponseWriter, r *http.Request) error {
|
||||
id := parts[2]
|
||||
|
||||
// map the ID to the expanded path
|
||||
rawCfgMu.RLock()
|
||||
currentCtxMu.RLock()
|
||||
expanded, ok := rawCfgIndex[id]
|
||||
rawCfgMu.RUnlock()
|
||||
defer currentCtxMu.RUnlock()
|
||||
if !ok {
|
||||
return APIError{
|
||||
HTTPStatus: http.StatusNotFound,
|
||||
@@ -1196,27 +1149,15 @@ traverseLoop:
|
||||
}
|
||||
case http.MethodPut:
|
||||
if _, ok := v[part]; ok {
|
||||
return APIError{
|
||||
HTTPStatus: http.StatusConflict,
|
||||
Err: fmt.Errorf("[%s] key already exists: %s", path, part),
|
||||
}
|
||||
return fmt.Errorf("[%s] key already exists: %s", path, part)
|
||||
}
|
||||
v[part] = val
|
||||
case http.MethodPatch:
|
||||
if _, ok := v[part]; !ok {
|
||||
return APIError{
|
||||
HTTPStatus: http.StatusNotFound,
|
||||
Err: fmt.Errorf("[%s] key does not exist: %s", path, part),
|
||||
}
|
||||
return fmt.Errorf("[%s] key does not exist: %s", path, part)
|
||||
}
|
||||
v[part] = val
|
||||
case http.MethodDelete:
|
||||
if _, ok := v[part]; !ok {
|
||||
return APIError{
|
||||
HTTPStatus: http.StatusNotFound,
|
||||
Err: fmt.Errorf("[%s] key does not exist: %s", path, part),
|
||||
}
|
||||
}
|
||||
delete(v, part)
|
||||
default:
|
||||
return fmt.Errorf("unrecognized method %s", method)
|
||||
@@ -1305,10 +1246,7 @@ func (e APIError) Error() string {
|
||||
// parseAdminListenAddr extracts a singular listen address from either addr
|
||||
// or defaultAddr, returning the network and the address of the listener.
|
||||
func parseAdminListenAddr(addr string, defaultAddr string) (NetworkAddress, error) {
|
||||
input, err := NewReplacer().ReplaceOrErr(addr, true, true)
|
||||
if err != nil {
|
||||
return NetworkAddress{}, fmt.Errorf("replacing listen address: %v", err)
|
||||
}
|
||||
input := addr
|
||||
if input == "" {
|
||||
input = defaultAddr
|
||||
}
|
||||
@@ -1358,7 +1296,7 @@ var (
|
||||
// will get deleted before the process gracefully exits.
|
||||
func PIDFile(filename string) error {
|
||||
pid := []byte(strconv.Itoa(os.Getpid()) + "\n")
|
||||
err := os.WriteFile(filename, pid, 0o600)
|
||||
err := os.WriteFile(filename, pid, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+1
-7
@@ -75,12 +75,6 @@ func TestUnsyncedConfigAccess(t *testing.T) {
|
||||
path: "/bar/qq",
|
||||
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c"]}`,
|
||||
},
|
||||
{
|
||||
method: "DELETE",
|
||||
path: "/bar/qq",
|
||||
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c"]}`,
|
||||
shouldErr: true,
|
||||
},
|
||||
{
|
||||
method: "POST",
|
||||
path: "/list",
|
||||
@@ -167,7 +161,7 @@ func (fooModule) Stop() error { return nil }
|
||||
func TestETags(t *testing.T) {
|
||||
RegisterModule(fooModule{})
|
||||
|
||||
if err := Load([]byte(`{"admin": {"listen": "localhost:2999"}, "apps": {"foo": {"strField": "abc", "intField": 0}}}`), true); err != nil {
|
||||
if err := Load([]byte(`{"apps": {"foo": {"strField": "abc", "intField": 0}}}`), true); err != nil {
|
||||
t.Fatalf("loading: %s", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,14 +31,12 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/notify"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/notify"
|
||||
)
|
||||
|
||||
// Config is the top (or beginning) of the Caddy configuration structure.
|
||||
@@ -157,8 +155,8 @@ func changeConfig(method, path string, input []byte, ifMatchHeader string, force
|
||||
return fmt.Errorf("method not allowed")
|
||||
}
|
||||
|
||||
rawCfgMu.Lock()
|
||||
defer rawCfgMu.Unlock()
|
||||
currentCtxMu.Lock()
|
||||
defer currentCtxMu.Unlock()
|
||||
|
||||
if ifMatchHeader != "" {
|
||||
// expect the first and last character to be quotes
|
||||
@@ -258,8 +256,8 @@ func changeConfig(method, path string, input []byte, ifMatchHeader string, force
|
||||
// readConfig traverses the current config to path
|
||||
// and writes its JSON encoding to out.
|
||||
func readConfig(path string, out io.Writer) error {
|
||||
rawCfgMu.RLock()
|
||||
defer rawCfgMu.RUnlock()
|
||||
currentCtxMu.RLock()
|
||||
defer currentCtxMu.RUnlock()
|
||||
return unsyncedConfigAccess(http.MethodGet, path, nil, out)
|
||||
}
|
||||
|
||||
@@ -306,7 +304,7 @@ func indexConfigObjects(ptr any, configPath string, index map[string]string) err
|
||||
// it as the new config, replacing any other current config.
|
||||
// It does NOT update the raw config state, as this is a
|
||||
// lower-level function; most callers will want to use Load
|
||||
// instead. A write lock on rawCfgMu is required! If
|
||||
// instead. A write lock on currentCtxMu is required! If
|
||||
// allowPersist is false, it will not be persisted to disk,
|
||||
// even if it is configured to.
|
||||
func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
||||
@@ -315,7 +313,7 @@ func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
||||
strippedCfgJSON := RemoveMetaFields(cfgJSON)
|
||||
|
||||
var newCfg *Config
|
||||
err := StrictUnmarshalJSON(strippedCfgJSON, &newCfg)
|
||||
err := strictUnmarshalJSON(strippedCfgJSON, &newCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -341,10 +339,8 @@ func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
||||
}
|
||||
|
||||
// swap old context (including its config) with the new one
|
||||
currentCtxMu.Lock()
|
||||
oldCtx := currentCtx
|
||||
currentCtx = ctx
|
||||
currentCtxMu.Unlock()
|
||||
|
||||
// Stop, Cleanup each old app
|
||||
unsyncedStop(oldCtx)
|
||||
@@ -357,13 +353,13 @@ func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
||||
newCfg.Admin.Config.Persist == nil ||
|
||||
*newCfg.Admin.Config.Persist) {
|
||||
dir := filepath.Dir(ConfigAutosavePath)
|
||||
err := os.MkdirAll(dir, 0o700)
|
||||
err := os.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
Log().Error("unable to create folder for config autosave",
|
||||
zap.String("dir", dir),
|
||||
zap.Error(err))
|
||||
} else {
|
||||
err := os.WriteFile(ConfigAutosavePath, cfgJSON, 0o600)
|
||||
err := os.WriteFile(ConfigAutosavePath, cfgJSON, 0600)
|
||||
if err == nil {
|
||||
Log().Info("autosaved config (load with --resume flag)", zap.String("file", ConfigAutosavePath))
|
||||
} else {
|
||||
@@ -630,35 +626,22 @@ type ConfigLoader interface {
|
||||
// stop the others. Stop should only be called
|
||||
// if not replacing with a new config.
|
||||
func Stop() error {
|
||||
currentCtxMu.RLock()
|
||||
ctx := currentCtx
|
||||
currentCtxMu.RUnlock()
|
||||
|
||||
rawCfgMu.Lock()
|
||||
unsyncedStop(ctx)
|
||||
|
||||
currentCtxMu.Lock()
|
||||
defer currentCtxMu.Unlock()
|
||||
unsyncedStop(currentCtx)
|
||||
currentCtx = Context{}
|
||||
currentCtxMu.Unlock()
|
||||
|
||||
rawCfgJSON = nil
|
||||
rawCfgIndex = nil
|
||||
rawCfg[rawConfigKey] = nil
|
||||
rawCfgMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// unsyncedStop stops ctx from running, but has
|
||||
// no locking around ctx. It is a no-op if ctx has a
|
||||
// nil cfg. If any app returns an error when stopping,
|
||||
// unsyncedStop stops cfg from running, but has
|
||||
// no locking around cfg. It is a no-op if cfg is
|
||||
// nil. If any app returns an error when stopping,
|
||||
// it is logged and the function continues stopping
|
||||
// the next app. This function assumes all apps in
|
||||
// ctx were successfully started first.
|
||||
//
|
||||
// A lock on rawCfgMu is required, even though this
|
||||
// function does not access rawCfg, that lock
|
||||
// synchronizes the stop/start of apps.
|
||||
// cfg were successfully started first.
|
||||
func unsyncedStop(ctx Context) {
|
||||
if ctx.cfg == nil {
|
||||
return
|
||||
@@ -693,10 +676,6 @@ func Validate(cfg *Config) error {
|
||||
// Errors are logged along the way, and an appropriate exit
|
||||
// code is emitted.
|
||||
func exitProcess(ctx context.Context, logger *zap.Logger) {
|
||||
// let the rest of the program know we're quitting
|
||||
atomic.StoreInt32(exiting, 1)
|
||||
|
||||
// give the OS or service/process manager our 2 weeks' notice: we quit
|
||||
if err := notify.Stopping(); err != nil {
|
||||
Log().Error("unable to notify service manager of stopping state", zap.Error(err))
|
||||
}
|
||||
@@ -760,12 +739,6 @@ func exitProcess(ctx context.Context, logger *zap.Logger) {
|
||||
}()
|
||||
}
|
||||
|
||||
var exiting = new(int32) // accessed atomically
|
||||
|
||||
// Exiting returns true if the process is exiting.
|
||||
// EXPERIMENTAL API: subject to change or removal.
|
||||
func Exiting() bool { return atomic.LoadInt32(exiting) == 1 }
|
||||
|
||||
// Duration can be an integer or a string. An integer is
|
||||
// interpreted as nanoseconds. If a string, it is a Go
|
||||
// time.Duration value such as `300ms`, `1.5h`, or `2h45m`;
|
||||
@@ -790,12 +763,8 @@ func (d *Duration) UnmarshalJSON(b []byte) error {
|
||||
|
||||
// ParseDuration parses a duration string, adding
|
||||
// support for the "d" unit meaning number of days,
|
||||
// where a day is assumed to be 24h. The maximum
|
||||
// input string length is 1024.
|
||||
// where a day is assumed to be 24h.
|
||||
func ParseDuration(s string) (time.Duration, error) {
|
||||
if len(s) > 1024 {
|
||||
return 0, fmt.Errorf("parsing duration: input string too long")
|
||||
}
|
||||
var inNumber bool
|
||||
var numStart int
|
||||
for i := 0; i < len(s); i++ {
|
||||
@@ -832,7 +801,7 @@ func InstanceID() (uuid.UUID, error) {
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
err = os.WriteFile(uuidFilePath, []byte(uuid.String()), 0o600)
|
||||
err = os.WriteFile(uuidFilePath, []byte(uuid.String()), 0600)
|
||||
return uuid, err
|
||||
} else if err != nil {
|
||||
return [16]byte{}, err
|
||||
@@ -840,20 +809,6 @@ func InstanceID() (uuid.UUID, error) {
|
||||
return uuid.ParseBytes(uuidFileBytes)
|
||||
}
|
||||
|
||||
// CustomVersion is an optional string that overrides Caddy's
|
||||
// reported version. It can be helpful when downstream packagers
|
||||
// need to manually set Caddy's version. If no other version
|
||||
// information is available, the short form version (see
|
||||
// Version()) will be set to CustomVersion, and the full version
|
||||
// will include CustomVersion at the beginning.
|
||||
//
|
||||
// Set this variable during `go build` with `-ldflags`:
|
||||
//
|
||||
// -ldflags '-X github.com/caddyserver/caddy/v2.CustomVersion=v2.6.2'
|
||||
//
|
||||
// for example.
|
||||
var CustomVersion string
|
||||
|
||||
// Version returns the Caddy version in a simple/short form, and
|
||||
// a full version string. The short form will not have spaces and
|
||||
// is intended for User-Agent strings and similar, but may be
|
||||
@@ -863,10 +818,8 @@ var CustomVersion string
|
||||
// build info provided by go.mod dependencies; then it tries to
|
||||
// get info from embedded VCS information, which requires having
|
||||
// built Caddy from a git repository. If no version is available,
|
||||
// this function returns "(devel)" because Go uses that, but for
|
||||
// the simple form we change it to "unknown". If still no version
|
||||
// is available (e.g. no VCS repo), then it will use CustomVersion;
|
||||
// CustomVersion is always prepended to the full version string.
|
||||
// this function returns "(devel)" becaise Go uses that, but for
|
||||
// the simple form we change it to "unknown".
|
||||
//
|
||||
// See relevant Go issues: https://github.com/golang/go/issues/29228
|
||||
// and https://github.com/golang/go/issues/50603.
|
||||
@@ -880,21 +833,13 @@ func Version() (simple, full string) {
|
||||
// bi.Main... hopefully.
|
||||
var module *debug.Module
|
||||
bi, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
if CustomVersion != "" {
|
||||
full = CustomVersion
|
||||
simple = CustomVersion
|
||||
return
|
||||
}
|
||||
full = "unknown"
|
||||
simple = "unknown"
|
||||
return
|
||||
}
|
||||
// find the Caddy module in the dependency list
|
||||
for _, dep := range bi.Deps {
|
||||
if dep.Path == ImportPath {
|
||||
module = dep
|
||||
break
|
||||
if ok {
|
||||
// find the Caddy module in the dependency list
|
||||
for _, dep := range bi.Deps {
|
||||
if dep.Path == ImportPath {
|
||||
module = dep
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if module != nil {
|
||||
@@ -950,22 +895,8 @@ func Version() (simple, full string) {
|
||||
}
|
||||
}
|
||||
|
||||
if full == "" {
|
||||
if CustomVersion != "" {
|
||||
full = CustomVersion
|
||||
} else {
|
||||
full = "unknown"
|
||||
}
|
||||
} else if CustomVersion != "" {
|
||||
full = CustomVersion + " " + full
|
||||
}
|
||||
|
||||
if simple == "" || simple == "(devel)" {
|
||||
if CustomVersion != "" {
|
||||
simple = CustomVersion
|
||||
} else {
|
||||
simple = "unknown"
|
||||
}
|
||||
simple = "unknown"
|
||||
}
|
||||
|
||||
return
|
||||
@@ -985,12 +916,14 @@ type CtxKey string
|
||||
|
||||
// This group of variables pertains to the current configuration.
|
||||
var (
|
||||
// currentCtxMu protects everything in this var block.
|
||||
currentCtxMu sync.RWMutex
|
||||
|
||||
// currentCtx is the root context for the currently-running
|
||||
// configuration, which can be accessed through this value.
|
||||
// If the Config contained in this value is not nil, then
|
||||
// a config is currently active/running.
|
||||
currentCtx Context
|
||||
currentCtxMu sync.RWMutex
|
||||
currentCtx Context
|
||||
|
||||
// rawCfg is the current, generic-decoded configuration;
|
||||
// we initialize it as a map with one field ("config")
|
||||
@@ -1008,10 +941,6 @@ var (
|
||||
// rawCfgIndex is the map of user-assigned ID to expanded
|
||||
// path, for converting /id/ paths to /config/ paths.
|
||||
rawCfgIndex map[string]string
|
||||
|
||||
// rawCfgMu protects all the rawCfg fields and also
|
||||
// essentially synchronizes config changes/reloads.
|
||||
rawCfgMu sync.RWMutex
|
||||
)
|
||||
|
||||
// errSameConfig is returned if the new config is the same
|
||||
|
||||
@@ -54,7 +54,7 @@ func (a Adapter) Adapt(body []byte, options map[string]any) ([]byte, []caddyconf
|
||||
|
||||
// lint check: see if input was properly formatted; sometimes messy files files parse
|
||||
// successfully but result in logical errors (the Caddyfile is a bad format, I'm sorry)
|
||||
if warning, different := FormattingDifference(filename, body); different {
|
||||
if warning, different := formattingDifference(filename, body); different {
|
||||
warnings = append(warnings, warning)
|
||||
}
|
||||
|
||||
@@ -63,10 +63,10 @@ func (a Adapter) Adapt(body []byte, options map[string]any) ([]byte, []caddyconf
|
||||
return result, warnings, err
|
||||
}
|
||||
|
||||
// FormattingDifference returns a warning and true if the formatted version
|
||||
// formattingDifference returns a warning and true if the formatted version
|
||||
// is any different from the input; empty warning and false otherwise.
|
||||
// TODO: also perform this check on imported files
|
||||
func FormattingDifference(filename string, body []byte) (caddyconfig.Warning, bool) {
|
||||
func formattingDifference(filename string, body []byte) (caddyconfig.Warning, bool) {
|
||||
// replace windows-style newlines to normalize comparison
|
||||
normalizedBody := bytes.Replace(body, []byte("\r\n"), []byte("\n"), -1)
|
||||
|
||||
@@ -88,7 +88,7 @@ func FormattingDifference(filename string, body []byte) (caddyconfig.Warning, bo
|
||||
return caddyconfig.Warning{
|
||||
File: filename,
|
||||
Line: line,
|
||||
Message: "Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies",
|
||||
Message: "Caddyfile input is not formatted; run the 'caddy fmt' command to fix inconsistencies",
|
||||
}, true
|
||||
}
|
||||
|
||||
|
||||
@@ -101,12 +101,12 @@ func (d *Dispenser) nextOnSameLine() bool {
|
||||
d.cursor++
|
||||
return true
|
||||
}
|
||||
if d.cursor >= len(d.tokens)-1 {
|
||||
if d.cursor >= len(d.tokens) {
|
||||
return false
|
||||
}
|
||||
curr := d.tokens[d.cursor]
|
||||
next := d.tokens[d.cursor+1]
|
||||
if !isNextOnNewLine(curr, next) {
|
||||
if d.cursor < len(d.tokens)-1 &&
|
||||
d.tokens[d.cursor].File == d.tokens[d.cursor+1].File &&
|
||||
d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) == d.tokens[d.cursor+1].Line {
|
||||
d.cursor++
|
||||
return true
|
||||
}
|
||||
@@ -122,12 +122,12 @@ func (d *Dispenser) NextLine() bool {
|
||||
d.cursor++
|
||||
return true
|
||||
}
|
||||
if d.cursor >= len(d.tokens)-1 {
|
||||
if d.cursor >= len(d.tokens) {
|
||||
return false
|
||||
}
|
||||
curr := d.tokens[d.cursor]
|
||||
next := d.tokens[d.cursor+1]
|
||||
if isNextOnNewLine(curr, next) {
|
||||
if d.cursor < len(d.tokens)-1 &&
|
||||
(d.tokens[d.cursor].File != d.tokens[d.cursor+1].File ||
|
||||
d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) < d.tokens[d.cursor+1].Line) {
|
||||
d.cursor++
|
||||
return true
|
||||
}
|
||||
@@ -203,17 +203,14 @@ func (d *Dispenser) Val() string {
|
||||
}
|
||||
|
||||
// ValRaw gets the raw text of the current token (including quotes).
|
||||
// If the token was a heredoc, then the delimiter is not included,
|
||||
// because that is not relevant to any unmarshaling logic at this time.
|
||||
// If there is no token loaded, it returns empty string.
|
||||
func (d *Dispenser) ValRaw() string {
|
||||
if d.cursor < 0 || d.cursor >= len(d.tokens) {
|
||||
return ""
|
||||
}
|
||||
quote := d.tokens[d.cursor].wasQuoted
|
||||
if quote > 0 && quote != '<' {
|
||||
// string literal
|
||||
return string(quote) + d.tokens[d.cursor].Text + string(quote)
|
||||
if quote > 0 {
|
||||
return string(quote) + d.tokens[d.cursor].Text + string(quote) // string literal
|
||||
}
|
||||
return d.tokens[d.cursor].Text
|
||||
}
|
||||
@@ -391,22 +388,22 @@ func (d *Dispenser) Reset() {
|
||||
// an argument.
|
||||
func (d *Dispenser) ArgErr() error {
|
||||
if d.Val() == "{" {
|
||||
return d.Err("unexpected token '{', expecting argument")
|
||||
return d.Err("Unexpected token '{', expecting argument")
|
||||
}
|
||||
return d.Errf("wrong argument count or unexpected line ending after '%s'", d.Val())
|
||||
return d.Errf("Wrong argument count or unexpected line ending after '%s'", d.Val())
|
||||
}
|
||||
|
||||
// SyntaxErr creates a generic syntax error which explains what was
|
||||
// found and what was expected.
|
||||
func (d *Dispenser) SyntaxErr(expected string) error {
|
||||
msg := fmt.Sprintf("syntax error: unexpected token '%s', expecting '%s', at %s:%d import chain: ['%s']", d.Val(), expected, d.File(), d.Line(), strings.Join(d.Token().imports, "','"))
|
||||
msg := fmt.Sprintf("%s:%d - Syntax error: Unexpected token '%s', expecting '%s'", d.File(), d.Line(), d.Val(), expected)
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
// EOFErr returns an error indicating that the dispenser reached
|
||||
// the end of the input when searching for the next token.
|
||||
func (d *Dispenser) EOFErr() error {
|
||||
return d.Errf("unexpected EOF")
|
||||
return d.Errf("Unexpected EOF")
|
||||
}
|
||||
|
||||
// Err generates a custom parse-time error with a message of msg.
|
||||
@@ -421,10 +418,7 @@ func (d *Dispenser) Errf(format string, args ...any) error {
|
||||
|
||||
// WrapErr takes an existing error and adds the Caddyfile file and line number.
|
||||
func (d *Dispenser) WrapErr(err error) error {
|
||||
if len(d.Token().imports) > 0 {
|
||||
return fmt.Errorf("%w, at %s:%d import chain ['%s']", err, d.File(), d.Line(), strings.Join(d.Token().imports, "','"))
|
||||
}
|
||||
return fmt.Errorf("%w, at %s:%d", err, d.File(), d.Line())
|
||||
return fmt.Errorf("%s:%d - Error during parsing: %w", d.File(), d.Line(), err)
|
||||
}
|
||||
|
||||
// Delete deletes the current token and returns the updated slice
|
||||
@@ -444,14 +438,14 @@ func (d *Dispenser) Delete() []Token {
|
||||
return d.tokens
|
||||
}
|
||||
|
||||
// DeleteN is the same as Delete, but can delete many tokens at once.
|
||||
// If there aren't N tokens available to delete, none are deleted.
|
||||
func (d *Dispenser) DeleteN(amount int) []Token {
|
||||
if amount > 0 && d.cursor >= (amount-1) && d.cursor <= len(d.tokens)-1 {
|
||||
d.tokens = append(d.tokens[:d.cursor-(amount-1)], d.tokens[d.cursor+1:]...)
|
||||
d.cursor -= amount
|
||||
// numLineBreaks counts how many line breaks are in the token
|
||||
// value given by the token index tknIdx. It returns 0 if the
|
||||
// token does not exist or there are no line breaks.
|
||||
func (d *Dispenser) numLineBreaks(tknIdx int) int {
|
||||
if tknIdx < 0 || tknIdx >= len(d.tokens) {
|
||||
return 0
|
||||
}
|
||||
return d.tokens
|
||||
return strings.Count(d.tokens[tknIdx].Text, "\n")
|
||||
}
|
||||
|
||||
// isNewLine determines whether the current token is on a different
|
||||
@@ -467,7 +461,25 @@ func (d *Dispenser) isNewLine() bool {
|
||||
|
||||
prev := d.tokens[d.cursor-1]
|
||||
curr := d.tokens[d.cursor]
|
||||
return isNextOnNewLine(prev, curr)
|
||||
|
||||
// If the previous token is from a different file,
|
||||
// we can assume it's from a different line
|
||||
if prev.File != curr.File {
|
||||
return true
|
||||
}
|
||||
|
||||
// The previous token may contain line breaks if
|
||||
// it was quoted and spanned multiple lines. e.g:
|
||||
//
|
||||
// dir "foo
|
||||
// bar
|
||||
// baz"
|
||||
prevLineBreaks := d.numLineBreaks(d.cursor - 1)
|
||||
|
||||
// If the previous token (incl line breaks) ends
|
||||
// on a line earlier than the current token,
|
||||
// then the current token is on a new line
|
||||
return prev.Line+prevLineBreaks < curr.Line
|
||||
}
|
||||
|
||||
// isNextOnNewLine determines whether the current token is on a different
|
||||
@@ -483,5 +495,23 @@ func (d *Dispenser) isNextOnNewLine() bool {
|
||||
|
||||
curr := d.tokens[d.cursor]
|
||||
next := d.tokens[d.cursor+1]
|
||||
return isNextOnNewLine(curr, next)
|
||||
|
||||
// If the next token is from a different file,
|
||||
// we can assume it's from a different line
|
||||
if curr.File != next.File {
|
||||
return true
|
||||
}
|
||||
|
||||
// The current token may contain line breaks if
|
||||
// it was quoted and spanned multiple lines. e.g:
|
||||
//
|
||||
// dir "foo
|
||||
// bar
|
||||
// baz"
|
||||
currLineBreaks := d.numLineBreaks(d.cursor)
|
||||
|
||||
// If the current token (incl line breaks) ends
|
||||
// on a line earlier than the next token,
|
||||
// then the next token is on a new line
|
||||
return curr.Line+currLineBreaks < next.Line
|
||||
}
|
||||
|
||||
@@ -153,10 +153,7 @@ func Format(input []byte) []byte {
|
||||
openBraceWritten = true
|
||||
nextLine()
|
||||
newLines = 0
|
||||
// prevent infinite nesting from ridiculous inputs (issue #4169)
|
||||
if nesting < 10 {
|
||||
nesting++
|
||||
}
|
||||
nesting++
|
||||
}
|
||||
|
||||
switch {
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package caddyfile
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
// parseVariadic determines if the token is a variadic placeholder,
|
||||
// and if so, determines the index range (start/end) of args to use.
|
||||
// Returns a boolean signaling whether a variadic placeholder was found,
|
||||
// and the start and end indices.
|
||||
func parseVariadic(token Token, argCount int) (bool, int, int) {
|
||||
if !strings.HasPrefix(token.Text, "{args[") {
|
||||
return false, 0, 0
|
||||
}
|
||||
if !strings.HasSuffix(token.Text, "]}") {
|
||||
return false, 0, 0
|
||||
}
|
||||
|
||||
argRange := strings.TrimSuffix(strings.TrimPrefix(token.Text, "{args["), "]}")
|
||||
if argRange == "" {
|
||||
caddy.Log().Named("caddyfile").Warn(
|
||||
"Placeholder "+token.Text+" cannot have an empty index",
|
||||
zap.String("file", token.File+":"+strconv.Itoa(token.Line)), zap.Strings("import_chain", token.imports))
|
||||
return false, 0, 0
|
||||
}
|
||||
|
||||
start, end, found := strings.Cut(argRange, ":")
|
||||
|
||||
// If no ":" delimiter is found, this is not a variadic.
|
||||
// The replacer will pick this up.
|
||||
if !found {
|
||||
return false, 0, 0
|
||||
}
|
||||
|
||||
var (
|
||||
startIndex = 0
|
||||
endIndex = argCount
|
||||
err error
|
||||
)
|
||||
if start != "" {
|
||||
startIndex, err = strconv.Atoi(start)
|
||||
if err != nil {
|
||||
caddy.Log().Named("caddyfile").Warn(
|
||||
"Variadic placeholder "+token.Text+" has an invalid start index",
|
||||
zap.String("file", token.File+":"+strconv.Itoa(token.Line)), zap.Strings("import_chain", token.imports))
|
||||
return false, 0, 0
|
||||
}
|
||||
}
|
||||
if end != "" {
|
||||
endIndex, err = strconv.Atoi(end)
|
||||
if err != nil {
|
||||
caddy.Log().Named("caddyfile").Warn(
|
||||
"Variadic placeholder "+token.Text+" has an invalid end index",
|
||||
zap.String("file", token.File+":"+strconv.Itoa(token.Line)), zap.Strings("import_chain", token.imports))
|
||||
return false, 0, 0
|
||||
}
|
||||
}
|
||||
|
||||
// bound check
|
||||
if startIndex < 0 || startIndex > endIndex || endIndex > argCount {
|
||||
caddy.Log().Named("caddyfile").Warn(
|
||||
"Variadic placeholder "+token.Text+" indices are out of bounds, only "+strconv.Itoa(argCount)+" argument(s) exist",
|
||||
zap.String("file", token.File+":"+strconv.Itoa(token.Line)), zap.Strings("import_chain", token.imports))
|
||||
return false, 0, 0
|
||||
}
|
||||
return true, startIndex, endIndex
|
||||
}
|
||||
|
||||
// makeArgsReplacer prepares a Replacer which can replace
|
||||
// non-variadic args placeholders in imported tokens.
|
||||
func makeArgsReplacer(args []string) *caddy.Replacer {
|
||||
repl := caddy.NewEmptyReplacer()
|
||||
repl.Map(func(key string) (any, bool) {
|
||||
// TODO: Remove the deprecated {args.*} placeholder
|
||||
// support at some point in the future
|
||||
if matches := argsRegexpIndexDeprecated.FindStringSubmatch(key); len(matches) > 0 {
|
||||
// What's matched may be a substring of the key
|
||||
if matches[0] != key {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
value, err := strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
caddy.Log().Named("caddyfile").Warn(
|
||||
"Placeholder {args." + matches[1] + "} has an invalid index")
|
||||
return nil, false
|
||||
}
|
||||
if value >= len(args) {
|
||||
caddy.Log().Named("caddyfile").Warn(
|
||||
"Placeholder {args." + matches[1] + "} index is out of bounds, only " + strconv.Itoa(len(args)) + " argument(s) exist")
|
||||
return nil, false
|
||||
}
|
||||
caddy.Log().Named("caddyfile").Warn(
|
||||
"Placeholder {args." + matches[1] + "} deprecated, use {args[" + matches[1] + "]} instead")
|
||||
return args[value], true
|
||||
}
|
||||
|
||||
// Handle args[*] form
|
||||
if matches := argsRegexpIndex.FindStringSubmatch(key); len(matches) > 0 {
|
||||
// What's matched may be a substring of the key
|
||||
if matches[0] != key {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if strings.Contains(matches[1], ":") {
|
||||
caddy.Log().Named("caddyfile").Warn(
|
||||
"Variadic placeholder {args[" + matches[1] + "]} must be a token on its own")
|
||||
return nil, false
|
||||
}
|
||||
value, err := strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
caddy.Log().Named("caddyfile").Warn(
|
||||
"Placeholder {args[" + matches[1] + "]} has an invalid index")
|
||||
return nil, false
|
||||
}
|
||||
if value >= len(args) {
|
||||
caddy.Log().Named("caddyfile").Warn(
|
||||
"Placeholder {args[" + matches[1] + "]} index is out of bounds, only " + strconv.Itoa(len(args)) + " argument(s) exist")
|
||||
return nil, false
|
||||
}
|
||||
return args[value], true
|
||||
}
|
||||
|
||||
// Not an args placeholder, ignore
|
||||
return nil, false
|
||||
})
|
||||
return repl
|
||||
}
|
||||
|
||||
var (
|
||||
argsRegexpIndexDeprecated = regexp.MustCompile(`args\.(.+)`)
|
||||
argsRegexpIndex = regexp.MustCompile(`args\[(.+)]`)
|
||||
)
|
||||
@@ -34,7 +34,6 @@ func (i *importGraph) addNode(name string) {
|
||||
}
|
||||
i.nodes[name] = true
|
||||
}
|
||||
|
||||
func (i *importGraph) addNodes(names []string) {
|
||||
for _, name := range names {
|
||||
i.addNode(name)
|
||||
@@ -44,7 +43,6 @@ func (i *importGraph) addNodes(names []string) {
|
||||
func (i *importGraph) removeNode(name string) {
|
||||
delete(i.nodes, name)
|
||||
}
|
||||
|
||||
func (i *importGraph) removeNodes(names []string) {
|
||||
for _, name := range names {
|
||||
i.removeNode(name)
|
||||
@@ -75,7 +73,6 @@ func (i *importGraph) addEdge(from, to string) error {
|
||||
i.edges[from] = append(i.edges[from], to)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *importGraph) addEdges(from string, tos []string) error {
|
||||
for _, to := range tos {
|
||||
err := i.addEdge(from, to)
|
||||
|
||||
+32
-214
@@ -17,10 +17,7 @@ package caddyfile
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
@@ -38,41 +35,15 @@ type (
|
||||
|
||||
// Token represents a single parsable unit.
|
||||
Token struct {
|
||||
File string
|
||||
imports []string
|
||||
Line int
|
||||
Text string
|
||||
wasQuoted rune // enclosing quote character, if any
|
||||
heredocMarker string
|
||||
snippetName string
|
||||
File string
|
||||
Line int
|
||||
Text string
|
||||
wasQuoted rune // enclosing quote character, if any
|
||||
inSnippet bool
|
||||
snippetName string
|
||||
}
|
||||
)
|
||||
|
||||
// Tokenize takes bytes as input and lexes it into
|
||||
// a list of tokens that can be parsed as a Caddyfile.
|
||||
// Also takes a filename to fill the token's File as
|
||||
// the source of the tokens, which is important to
|
||||
// determine relative paths for `import` directives.
|
||||
func Tokenize(input []byte, filename string) ([]Token, error) {
|
||||
l := lexer{}
|
||||
if err := l.load(bytes.NewReader(input)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tokens []Token
|
||||
for {
|
||||
found, err := l.next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
l.token.File = filename
|
||||
tokens = append(tokens, l.token)
|
||||
}
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
// load prepares the lexer to scan an input for tokens.
|
||||
// It discards any leading byte order mark.
|
||||
func (l *lexer) load(input io.Reader) error {
|
||||
@@ -104,107 +75,28 @@ func (l *lexer) load(input io.Reader) error {
|
||||
// may be escaped. The rest of the line is skipped
|
||||
// if a "#" character is read in. Returns true if
|
||||
// a token was loaded; false otherwise.
|
||||
func (l *lexer) next() (bool, error) {
|
||||
func (l *lexer) next() bool {
|
||||
var val []rune
|
||||
var comment, quoted, btQuoted, inHeredoc, heredocEscaped, escaped bool
|
||||
var heredocMarker string
|
||||
var comment, quoted, btQuoted, escaped bool
|
||||
|
||||
makeToken := func(quoted rune) bool {
|
||||
l.token.Text = string(val)
|
||||
l.token.wasQuoted = quoted
|
||||
l.token.heredocMarker = heredocMarker
|
||||
return true
|
||||
}
|
||||
|
||||
for {
|
||||
// Read a character in; if err then if we had
|
||||
// read some characters, make a token. If we
|
||||
// reached EOF, then no more tokens to read.
|
||||
// If no EOF, then we had a problem.
|
||||
ch, _, err := l.reader.ReadRune()
|
||||
if err != nil {
|
||||
if len(val) > 0 {
|
||||
if inHeredoc {
|
||||
return false, fmt.Errorf("incomplete heredoc <<%s on line #%d, expected ending marker %s", heredocMarker, l.line+l.skippedLines, heredocMarker)
|
||||
}
|
||||
|
||||
return makeToken(0), nil
|
||||
return makeToken(0)
|
||||
}
|
||||
if err == io.EOF {
|
||||
return false, nil
|
||||
return false
|
||||
}
|
||||
return false, err
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// detect whether we have the start of a heredoc
|
||||
if !(quoted || btQuoted) && !(inHeredoc || heredocEscaped) &&
|
||||
len(val) > 1 && string(val[:2]) == "<<" {
|
||||
// a space means it's just a regular token and not a heredoc
|
||||
if ch == ' ' {
|
||||
return makeToken(0), nil
|
||||
}
|
||||
|
||||
// skip CR, we only care about LF
|
||||
if ch == '\r' {
|
||||
continue
|
||||
}
|
||||
|
||||
// after hitting a newline, we know that the heredoc marker
|
||||
// is the characters after the two << and the newline.
|
||||
// we reset the val because the heredoc is syntax we don't
|
||||
// want to keep.
|
||||
if ch == '\n' {
|
||||
if len(val) == 2 {
|
||||
return false, fmt.Errorf("missing opening heredoc marker on line #%d; must contain only alpha-numeric characters, dashes and underscores; got empty string", l.line)
|
||||
}
|
||||
|
||||
// check if there's too many <
|
||||
if string(val[:3]) == "<<<" {
|
||||
return false, fmt.Errorf("too many '<' for heredoc on line #%d; only use two, for example <<END", l.line)
|
||||
}
|
||||
|
||||
heredocMarker = string(val[2:])
|
||||
if !heredocMarkerRegexp.Match([]byte(heredocMarker)) {
|
||||
return false, fmt.Errorf("heredoc marker on line #%d must contain only alpha-numeric characters, dashes and underscores; got '%s'", l.line, heredocMarker)
|
||||
}
|
||||
|
||||
inHeredoc = true
|
||||
l.skippedLines++
|
||||
val = nil
|
||||
continue
|
||||
}
|
||||
val = append(val, ch)
|
||||
continue
|
||||
}
|
||||
|
||||
// if we're in a heredoc, all characters are read as-is
|
||||
if inHeredoc {
|
||||
val = append(val, ch)
|
||||
|
||||
if ch == '\n' {
|
||||
l.skippedLines++
|
||||
}
|
||||
|
||||
// check if we're done, i.e. that the last few characters are the marker
|
||||
if len(val) > len(heredocMarker) && heredocMarker == string(val[len(val)-len(heredocMarker):]) {
|
||||
// set the final value
|
||||
val, err = l.finalizeHeredoc(val, heredocMarker)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// set the line counter, and make the token
|
||||
l.line += l.skippedLines
|
||||
l.skippedLines = 0
|
||||
return makeToken('<'), nil
|
||||
}
|
||||
|
||||
// stay in the heredoc until we find the ending marker
|
||||
continue
|
||||
}
|
||||
|
||||
// track whether we found an escape '\' for the next
|
||||
// iteration to be contextually aware
|
||||
if !escaped && !btQuoted && ch == '\\' {
|
||||
escaped = true
|
||||
continue
|
||||
@@ -219,29 +111,26 @@ func (l *lexer) next() (bool, error) {
|
||||
}
|
||||
escaped = false
|
||||
} else {
|
||||
if (quoted && ch == '"') || (btQuoted && ch == '`') {
|
||||
return makeToken(ch), nil
|
||||
if quoted && ch == '"' {
|
||||
return makeToken('"')
|
||||
}
|
||||
if btQuoted && ch == '`' {
|
||||
return makeToken('`')
|
||||
}
|
||||
}
|
||||
// allow quoted text to wrap continue on multiple lines
|
||||
if ch == '\n' {
|
||||
l.line += 1 + l.skippedLines
|
||||
l.skippedLines = 0
|
||||
}
|
||||
// collect this character as part of the quoted token
|
||||
val = append(val, ch)
|
||||
continue
|
||||
}
|
||||
|
||||
if unicode.IsSpace(ch) {
|
||||
// ignore CR altogether, we only actually care about LF (\n)
|
||||
if ch == '\r' {
|
||||
continue
|
||||
}
|
||||
// end of the line
|
||||
if ch == '\n' {
|
||||
// newlines can be escaped to chain arguments
|
||||
// onto multiple lines; else, increment the line count
|
||||
if escaped {
|
||||
l.skippedLines++
|
||||
escaped = false
|
||||
@@ -249,18 +138,14 @@ func (l *lexer) next() (bool, error) {
|
||||
l.line += 1 + l.skippedLines
|
||||
l.skippedLines = 0
|
||||
}
|
||||
// comments (#) are single-line only
|
||||
comment = false
|
||||
}
|
||||
// any kind of space means we're at the end of this token
|
||||
if len(val) > 0 {
|
||||
return makeToken(0), nil
|
||||
return makeToken(0)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// comments must be at the start of a token,
|
||||
// in other words, preceded by space or newline
|
||||
if ch == '#' && len(val) == 0 {
|
||||
comment = true
|
||||
}
|
||||
@@ -281,12 +166,7 @@ func (l *lexer) next() (bool, error) {
|
||||
}
|
||||
|
||||
if escaped {
|
||||
// allow escaping the first < to skip the heredoc syntax
|
||||
if ch == '<' {
|
||||
heredocEscaped = true
|
||||
} else {
|
||||
val = append(val, '\\')
|
||||
}
|
||||
val = append(val, '\\')
|
||||
escaped = false
|
||||
}
|
||||
|
||||
@@ -294,86 +174,24 @@ func (l *lexer) next() (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// finalizeHeredoc takes the runes read as the heredoc text and the marker,
|
||||
// and processes the text to strip leading whitespace, returning the final
|
||||
// value without the leading whitespace.
|
||||
func (l *lexer) finalizeHeredoc(val []rune, marker string) ([]rune, error) {
|
||||
stringVal := string(val)
|
||||
|
||||
// find the last newline of the heredoc, which is where the contents end
|
||||
lastNewline := strings.LastIndex(stringVal, "\n")
|
||||
|
||||
// collapse the content, then split into separate lines
|
||||
lines := strings.Split(stringVal[:lastNewline+1], "\n")
|
||||
|
||||
// figure out how much whitespace we need to strip from the front of every line
|
||||
// by getting the string that precedes the marker, on the last line
|
||||
paddingToStrip := stringVal[lastNewline+1 : len(stringVal)-len(marker)]
|
||||
|
||||
// iterate over each line and strip the whitespace from the front
|
||||
var out string
|
||||
for lineNum, lineText := range lines[:len(lines)-1] {
|
||||
// find an exact match for the padding
|
||||
index := strings.Index(lineText, paddingToStrip)
|
||||
|
||||
// if the padding doesn't match exactly at the start then we can't safely strip
|
||||
if index != 0 {
|
||||
return nil, fmt.Errorf("mismatched leading whitespace in heredoc <<%s on line #%d [%s], expected whitespace [%s] to match the closing marker", marker, l.line+lineNum+1, lineText, paddingToStrip)
|
||||
}
|
||||
|
||||
// strip, then append the line, with the newline, to the output.
|
||||
// also removes all "\r" because Windows.
|
||||
out += strings.ReplaceAll(lineText[len(paddingToStrip):]+"\n", "\r", "")
|
||||
// Tokenize takes bytes as input and lexes it into
|
||||
// a list of tokens that can be parsed as a Caddyfile.
|
||||
// Also takes a filename to fill the token's File as
|
||||
// the source of the tokens, which is important to
|
||||
// determine relative paths for `import` directives.
|
||||
func Tokenize(input []byte, filename string) ([]Token, error) {
|
||||
l := lexer{}
|
||||
if err := l.load(bytes.NewReader(input)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Remove the trailing newline from the loop
|
||||
if len(out) > 0 && out[len(out)-1] == '\n' {
|
||||
out = out[:len(out)-1]
|
||||
var tokens []Token
|
||||
for l.next() {
|
||||
l.token.File = filename
|
||||
tokens = append(tokens, l.token)
|
||||
}
|
||||
|
||||
// return the final value
|
||||
return []rune(out), nil
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
func (t Token) Quoted() bool {
|
||||
return t.wasQuoted > 0
|
||||
}
|
||||
|
||||
// NumLineBreaks counts how many line breaks are in the token text.
|
||||
func (t Token) NumLineBreaks() int {
|
||||
lineBreaks := strings.Count(t.Text, "\n")
|
||||
if t.wasQuoted == '<' {
|
||||
// heredocs have an extra linebreak because the opening
|
||||
// delimiter is on its own line and is not included in the
|
||||
// token Text itself, and the trailing newline is removed.
|
||||
lineBreaks += 2
|
||||
}
|
||||
return lineBreaks
|
||||
}
|
||||
|
||||
var heredocMarkerRegexp = regexp.MustCompile("^[A-Za-z0-9_-]+$")
|
||||
|
||||
// isNextOnNewLine tests whether t2 is on a different line from t1
|
||||
func isNextOnNewLine(t1, t2 Token) bool {
|
||||
// If the second token is from a different file,
|
||||
// we can assume it's from a different line
|
||||
if t1.File != t2.File {
|
||||
return true
|
||||
}
|
||||
|
||||
// If the second token is from a different import chain,
|
||||
// we can assume it's from a different line
|
||||
if len(t1.imports) != len(t2.imports) {
|
||||
return true
|
||||
}
|
||||
for i, im := range t1.imports {
|
||||
if im != t2.imports[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// If the first token (incl line breaks) ends
|
||||
// on a line earlier than the next token,
|
||||
// then the second token is on a new line
|
||||
return t1.Line+t1.NumLineBreaks() < t2.Line
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
type lexerTestCase struct {
|
||||
input []byte
|
||||
expected []Token
|
||||
}
|
||||
|
||||
func TestLexer(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input []byte
|
||||
expected []Token
|
||||
expectErr bool
|
||||
errorMessage string
|
||||
}{
|
||||
testCases := []lexerTestCase{
|
||||
{
|
||||
input: []byte(`host:123`),
|
||||
expected: []Token{
|
||||
@@ -249,219 +249,12 @@ func TestLexer(t *testing.T) {
|
||||
{Line: 1, Text: `quotes`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`heredoc <<EOF
|
||||
content
|
||||
EOF same-line-arg
|
||||
`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `heredoc`},
|
||||
{Line: 1, Text: "content"},
|
||||
{Line: 3, Text: `same-line-arg`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`heredoc <<VERY-LONG-MARKER
|
||||
content
|
||||
VERY-LONG-MARKER same-line-arg
|
||||
`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `heredoc`},
|
||||
{Line: 1, Text: "content"},
|
||||
{Line: 3, Text: `same-line-arg`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`heredoc <<EOF
|
||||
extra-newline
|
||||
|
||||
EOF same-line-arg
|
||||
`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `heredoc`},
|
||||
{Line: 1, Text: "extra-newline\n"},
|
||||
{Line: 4, Text: `same-line-arg`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`heredoc <<EOF
|
||||
EOF same-line-arg
|
||||
`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `heredoc`},
|
||||
{Line: 1, Text: ""},
|
||||
{Line: 2, Text: `same-line-arg`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`heredoc <<EOF
|
||||
content
|
||||
EOF same-line-arg
|
||||
`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `heredoc`},
|
||||
{Line: 1, Text: "content"},
|
||||
{Line: 3, Text: `same-line-arg`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`prev-line
|
||||
heredoc <<EOF
|
||||
multi
|
||||
line
|
||||
content
|
||||
EOF same-line-arg
|
||||
next-line
|
||||
`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `prev-line`},
|
||||
{Line: 2, Text: `heredoc`},
|
||||
{Line: 2, Text: "\tmulti\n\tline\n\tcontent"},
|
||||
{Line: 6, Text: `same-line-arg`},
|
||||
{Line: 7, Text: `next-line`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`escaped-heredoc \<< >>`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `escaped-heredoc`},
|
||||
{Line: 1, Text: `<<`},
|
||||
{Line: 1, Text: `>>`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`not-a-heredoc <EOF
|
||||
content
|
||||
`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `not-a-heredoc`},
|
||||
{Line: 1, Text: `<EOF`},
|
||||
{Line: 2, Text: `content`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`not-a-heredoc <<<EOF content`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `not-a-heredoc`},
|
||||
{Line: 1, Text: `<<<EOF`},
|
||||
{Line: 1, Text: `content`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`not-a-heredoc "<<" ">>"`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `not-a-heredoc`},
|
||||
{Line: 1, Text: `<<`},
|
||||
{Line: 1, Text: `>>`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`not-a-heredoc << >>`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `not-a-heredoc`},
|
||||
{Line: 1, Text: `<<`},
|
||||
{Line: 1, Text: `>>`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`not-a-heredoc <<HERE SAME LINE
|
||||
content
|
||||
HERE same-line-arg
|
||||
`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `not-a-heredoc`},
|
||||
{Line: 1, Text: `<<HERE`},
|
||||
{Line: 1, Text: `SAME`},
|
||||
{Line: 1, Text: `LINE`},
|
||||
{Line: 2, Text: `content`},
|
||||
{Line: 3, Text: `HERE`},
|
||||
{Line: 3, Text: `same-line-arg`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`heredoc <<s
|
||||
�
|
||||
s
|
||||
`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `heredoc`},
|
||||
{Line: 1, Text: "�"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte("\u000Aheredoc \u003C\u003C\u0073\u0073\u000A\u00BF\u0057\u0001\u0000\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u003D\u001F\u000A\u0073\u0073\u000A\u00BF\u0057\u0001\u0000\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u003D\u001F\u000A\u00BF\u00BF\u0057\u0001\u0000\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u003D\u001F"),
|
||||
expected: []Token{
|
||||
{
|
||||
Line: 2,
|
||||
Text: "heredoc",
|
||||
},
|
||||
{
|
||||
Line: 2,
|
||||
Text: "\u00BF\u0057\u0001\u0000\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u003D\u001F",
|
||||
},
|
||||
{
|
||||
Line: 5,
|
||||
Text: "\u00BF\u0057\u0001\u0000\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u003D\u001F",
|
||||
},
|
||||
{
|
||||
Line: 6,
|
||||
Text: "\u00BF\u00BF\u0057\u0001\u0000\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u003D\u001F",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte("not-a-heredoc <<\n"),
|
||||
expectErr: true,
|
||||
errorMessage: "missing opening heredoc marker on line #1; must contain only alpha-numeric characters, dashes and underscores; got empty string",
|
||||
},
|
||||
{
|
||||
input: []byte(`heredoc <<<EOF
|
||||
content
|
||||
EOF same-line-arg
|
||||
`),
|
||||
expectErr: true,
|
||||
errorMessage: "too many '<' for heredoc on line #1; only use two, for example <<END",
|
||||
},
|
||||
{
|
||||
input: []byte(`heredoc <<EOF
|
||||
content
|
||||
`),
|
||||
expectErr: true,
|
||||
errorMessage: "incomplete heredoc <<EOF on line #3, expected ending marker EOF",
|
||||
},
|
||||
{
|
||||
input: []byte(`heredoc <<EOF
|
||||
content
|
||||
EOF
|
||||
`),
|
||||
expectErr: true,
|
||||
errorMessage: "mismatched leading whitespace in heredoc <<EOF on line #2 [\tcontent], expected whitespace [\t\t] to match the closing marker",
|
||||
},
|
||||
{
|
||||
input: []byte(`heredoc <<EOF
|
||||
content
|
||||
EOF
|
||||
`),
|
||||
expectErr: true,
|
||||
errorMessage: "mismatched leading whitespace in heredoc <<EOF on line #2 [ content], expected whitespace [\t\t] to match the closing marker",
|
||||
},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
actual, err := Tokenize(testCase.input, "")
|
||||
if testCase.expectErr {
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got actual: %v", actual)
|
||||
continue
|
||||
}
|
||||
if err.Error() != testCase.errorMessage {
|
||||
t.Fatalf("expected error '%v', got: %v", testCase.errorMessage, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
lexerCompare(t, i, testCase.expected, actual)
|
||||
}
|
||||
@@ -469,17 +262,17 @@ EOF same-line-arg
|
||||
|
||||
func lexerCompare(t *testing.T, n int, expected, actual []Token) {
|
||||
if len(expected) != len(actual) {
|
||||
t.Fatalf("Test case %d: expected %d token(s) but got %d", n, len(expected), len(actual))
|
||||
t.Errorf("Test case %d: expected %d token(s) but got %d", n, len(expected), len(actual))
|
||||
}
|
||||
|
||||
for i := 0; i < len(actual) && i < len(expected); i++ {
|
||||
if actual[i].Line != expected[i].Line {
|
||||
t.Fatalf("Test case %d token %d ('%s'): expected line %d but was line %d",
|
||||
t.Errorf("Test case %d token %d ('%s'): expected line %d but was line %d",
|
||||
n, i, expected[i].Text, expected[i].Line, actual[i].Line)
|
||||
break
|
||||
}
|
||||
if actual[i].Text != expected[i].Text {
|
||||
t.Fatalf("Test case %d token %d: expected text '%s' but was '%s'",
|
||||
t.Errorf("Test case %d token %d: expected text '%s' but was '%s'",
|
||||
n, i, expected[i].Text, actual[i].Text)
|
||||
break
|
||||
}
|
||||
|
||||
+41
-141
@@ -20,11 +20,11 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Parse parses the input just enough to group tokens, in
|
||||
@@ -61,12 +61,20 @@ func Parse(filename string, input []byte) ([]ServerBlock, error) {
|
||||
// It returns all the tokens from the input, unstructured
|
||||
// and in order. It may mutate input as it expands env vars.
|
||||
func allTokens(filename string, input []byte) ([]Token, error) {
|
||||
return Tokenize(replaceEnvVars(input), filename)
|
||||
inputCopy, err := replaceEnvVars(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokens, err := Tokenize(inputCopy, filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
// replaceEnvVars replaces all occurrences of environment variables.
|
||||
// It mutates the underlying array and returns the updated slice.
|
||||
func replaceEnvVars(input []byte) []byte {
|
||||
func replaceEnvVars(input []byte) ([]byte, error) {
|
||||
var offset int
|
||||
for {
|
||||
begin := bytes.Index(input[offset:], spanOpen)
|
||||
@@ -107,7 +115,7 @@ func replaceEnvVars(input []byte) []byte {
|
||||
// continue at the end of the replacement
|
||||
offset = begin + len(envVarBytes)
|
||||
}
|
||||
return input
|
||||
return input, nil
|
||||
}
|
||||
|
||||
type parser struct {
|
||||
@@ -149,6 +157,7 @@ func (p *parser) begin() error {
|
||||
}
|
||||
|
||||
err := p.addresses()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -159,25 +168,6 @@ func (p *parser) begin() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if ok, name := p.isNamedRoute(); ok {
|
||||
// named routes only have one key, the route name
|
||||
p.block.Keys = []string{name}
|
||||
p.block.IsNamedRoute = true
|
||||
|
||||
// we just need a dummy leading token to ease parsing later
|
||||
nameToken := p.Token()
|
||||
nameToken.Text = name
|
||||
|
||||
// get all the tokens from the block, including the braces
|
||||
tokens, err := p.blockTokens(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tokens = append([]Token{nameToken}, tokens...)
|
||||
p.block.Segments = []Segment{tokens}
|
||||
return nil
|
||||
}
|
||||
|
||||
if ok, name := p.isSnippet(); ok {
|
||||
if p.definedSnippets == nil {
|
||||
p.definedSnippets = map[string][]Token{}
|
||||
@@ -186,15 +176,16 @@ func (p *parser) begin() error {
|
||||
return p.Errf("redeclaration of previously declared snippet %s", name)
|
||||
}
|
||||
// consume all tokens til matched close brace
|
||||
tokens, err := p.blockTokens(false)
|
||||
tokens, err := p.snippetTokens()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Just as we need to track which file the token comes from, we need to
|
||||
// keep track of which snippet the token comes from. This is helpful
|
||||
// in tracking import cycles across files/snippets by namespacing them.
|
||||
// Without this, we end up with false-positives in cycle-detection.
|
||||
// keep track of which snippets do the tokens come from. This is helpful
|
||||
// in tracking import cycles across files/snippets by namespacing them. Without
|
||||
// this we end up with false-positives in cycle-detection.
|
||||
for k, v := range tokens {
|
||||
v.inSnippet = true
|
||||
v.snippetName = name
|
||||
tokens[k] = v
|
||||
}
|
||||
@@ -215,7 +206,7 @@ func (p *parser) addresses() error {
|
||||
|
||||
// special case: import directive replaces tokens during parse-time
|
||||
if tkn == "import" && p.isNewLine() {
|
||||
err := p.doImport(0)
|
||||
err := p.doImport()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -315,7 +306,7 @@ func (p *parser) directives() error {
|
||||
|
||||
// special case: import directive replaces tokens during parse-time
|
||||
if p.Val() == "import" {
|
||||
err := p.doImport(1)
|
||||
err := p.doImport()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -341,7 +332,7 @@ func (p *parser) directives() error {
|
||||
// is on the token before where the import directive was. In
|
||||
// other words, call Next() to access the first token that was
|
||||
// imported.
|
||||
func (p *parser) doImport(nesting int) error {
|
||||
func (p *parser) doImport() error {
|
||||
// syntax checks
|
||||
if !p.NextArg() {
|
||||
return p.ArgErr()
|
||||
@@ -354,8 +345,11 @@ func (p *parser) doImport(nesting int) error {
|
||||
// grab remaining args as placeholder replacements
|
||||
args := p.RemainingArgs()
|
||||
|
||||
// set up a replacer for non-variadic args replacement
|
||||
repl := makeArgsReplacer(args)
|
||||
// add args to the replacer
|
||||
repl := caddy.NewEmptyReplacer()
|
||||
for index, arg := range args {
|
||||
repl.Set("args."+strconv.Itoa(index), arg)
|
||||
}
|
||||
|
||||
// splice out the import directive and its arguments
|
||||
// (2 tokens, plus the length of args)
|
||||
@@ -403,20 +397,6 @@ func (p *parser) doImport(nesting int) error {
|
||||
} else {
|
||||
return p.Errf("File to import not found: %s", importPattern)
|
||||
}
|
||||
} else {
|
||||
// See issue #5295 - should skip any files that start with a . when iterating over them.
|
||||
sep := string(filepath.Separator)
|
||||
segGlobPattern := strings.Split(globPattern, sep)
|
||||
if strings.HasPrefix(segGlobPattern[len(segGlobPattern)-1], "*") {
|
||||
var tmpMatches []string
|
||||
for _, m := range matches {
|
||||
seg := strings.Split(m, sep)
|
||||
if !strings.HasPrefix(seg[len(seg)-1], ".") {
|
||||
tmpMatches = append(tmpMatches, m)
|
||||
}
|
||||
}
|
||||
matches = tmpMatches
|
||||
}
|
||||
}
|
||||
|
||||
// collect all the imported tokens
|
||||
@@ -431,7 +411,7 @@ func (p *parser) doImport(nesting int) error {
|
||||
}
|
||||
|
||||
nodeName := p.File()
|
||||
if p.Token().snippetName != "" {
|
||||
if p.Token().inSnippet {
|
||||
nodeName += fmt.Sprintf(":%s", p.Token().snippetName)
|
||||
}
|
||||
p.importGraph.addNode(nodeName)
|
||||
@@ -442,69 +422,13 @@ func (p *parser) doImport(nesting int) error {
|
||||
}
|
||||
|
||||
// copy the tokens so we don't overwrite p.definedSnippets
|
||||
tokensCopy := make([]Token, 0, len(importedTokens))
|
||||
|
||||
var (
|
||||
maybeSnippet bool
|
||||
maybeSnippetId bool
|
||||
index int
|
||||
)
|
||||
tokensCopy := make([]Token, len(importedTokens))
|
||||
copy(tokensCopy, importedTokens)
|
||||
|
||||
// run the argument replacer on the tokens
|
||||
// golang for range slice return a copy of value
|
||||
// similarly, append also copy value
|
||||
for i, token := range importedTokens {
|
||||
// update the token's imports to refer to import directive filename, line number and snippet name if there is one
|
||||
if token.snippetName != "" {
|
||||
token.imports = append(token.imports, fmt.Sprintf("%s:%d (import %s)", p.File(), p.Line(), token.snippetName))
|
||||
} else {
|
||||
token.imports = append(token.imports, fmt.Sprintf("%s:%d (import)", p.File(), p.Line()))
|
||||
}
|
||||
|
||||
// naive way of determine snippets, as snippets definition can only follow name + block
|
||||
// format, won't check for nesting correctness or any other error, that's what parser does.
|
||||
if !maybeSnippet && nesting == 0 {
|
||||
// first of the line
|
||||
if i == 0 || isNextOnNewLine(tokensCopy[i-1], token) {
|
||||
index = 0
|
||||
} else {
|
||||
index++
|
||||
}
|
||||
|
||||
if index == 0 && len(token.Text) >= 3 && strings.HasPrefix(token.Text, "(") && strings.HasSuffix(token.Text, ")") {
|
||||
maybeSnippetId = true
|
||||
}
|
||||
}
|
||||
|
||||
switch token.Text {
|
||||
case "{":
|
||||
nesting++
|
||||
if index == 1 && maybeSnippetId && nesting == 1 {
|
||||
maybeSnippet = true
|
||||
maybeSnippetId = false
|
||||
}
|
||||
case "}":
|
||||
nesting--
|
||||
if nesting == 0 && maybeSnippet {
|
||||
maybeSnippet = false
|
||||
}
|
||||
}
|
||||
|
||||
if maybeSnippet {
|
||||
tokensCopy = append(tokensCopy, token)
|
||||
continue
|
||||
}
|
||||
|
||||
foundVariadic, startIndex, endIndex := parseVariadic(token, len(args))
|
||||
if foundVariadic {
|
||||
for _, arg := range args[startIndex:endIndex] {
|
||||
token.Text = arg
|
||||
tokensCopy = append(tokensCopy, token)
|
||||
}
|
||||
} else {
|
||||
token.Text = repl.ReplaceKnown(token.Text, "")
|
||||
tokensCopy = append(tokensCopy, token)
|
||||
}
|
||||
for index, token := range tokensCopy {
|
||||
token.Text = repl.ReplaceKnown(token.Text, "")
|
||||
tokensCopy[index] = token
|
||||
}
|
||||
|
||||
// splice the imported tokens in the place of the import statement
|
||||
@@ -535,12 +459,6 @@ func (p *parser) doSingleImport(importFile string) ([]Token, error) {
|
||||
return nil, p.Errf("Could not read imported file %s: %v", importFile, err)
|
||||
}
|
||||
|
||||
// only warning in case of empty files
|
||||
if len(input) == 0 || len(strings.TrimSpace(string(input))) == 0 {
|
||||
caddy.Log().Warn("Import file is empty", zap.String("file", importFile))
|
||||
return []Token{}, nil
|
||||
}
|
||||
|
||||
importedTokens, err := allTokens(importFile, input)
|
||||
if err != nil {
|
||||
return nil, p.Errf("Could not read tokens while importing %s: %v", importFile, err)
|
||||
@@ -566,6 +484,7 @@ func (p *parser) doSingleImport(importFile string) ([]Token, error) {
|
||||
// are loaded into the current server block for later use
|
||||
// by directive setup functions.
|
||||
func (p *parser) directive() error {
|
||||
|
||||
// a segment is a list of tokens associated with this directive
|
||||
var segment Segment
|
||||
|
||||
@@ -578,9 +497,6 @@ func (p *parser) directive() error {
|
||||
if !p.isNextOnNewLine() && p.Token().wasQuoted == 0 {
|
||||
return p.Err("Unexpected next token after '{' on same line")
|
||||
}
|
||||
if p.isNewLine() {
|
||||
return p.Err("Unexpected '{' on a new line; did you mean to place the '{' on the previous line?")
|
||||
}
|
||||
} else if p.Val() == "{}" {
|
||||
if p.isNextOnNewLine() && p.Token().wasQuoted == 0 {
|
||||
return p.Err("Unexpected '{}' at end of line")
|
||||
@@ -593,7 +509,7 @@ func (p *parser) directive() error {
|
||||
} else if p.Val() == "}" && p.nesting == 0 {
|
||||
return p.Err("Unexpected '}' because no matching opening brace")
|
||||
} else if p.Val() == "import" && p.isNewLine() {
|
||||
if err := p.doImport(1); err != nil {
|
||||
if err := p.doImport(); err != nil {
|
||||
return err
|
||||
}
|
||||
p.cursor-- // cursor is advanced when we continue, so roll back one more
|
||||
@@ -634,15 +550,6 @@ func (p *parser) closeCurlyBrace() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) isNamedRoute() (bool, string) {
|
||||
keys := p.block.Keys
|
||||
// A named route block is a single key with parens, prefixed with &.
|
||||
if len(keys) == 1 && strings.HasPrefix(keys[0], "&(") && strings.HasSuffix(keys[0], ")") {
|
||||
return true, strings.TrimSuffix(keys[0][2:], ")")
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func (p *parser) isSnippet() (bool, string) {
|
||||
keys := p.block.Keys
|
||||
// A snippet block is a single key with parens. Nothing else qualifies.
|
||||
@@ -653,24 +560,18 @@ func (p *parser) isSnippet() (bool, string) {
|
||||
}
|
||||
|
||||
// read and store everything in a block for later replay.
|
||||
func (p *parser) blockTokens(retainCurlies bool) ([]Token, error) {
|
||||
// block must have curlies.
|
||||
func (p *parser) snippetTokens() ([]Token, error) {
|
||||
// snippet must have curlies.
|
||||
err := p.openCurlyBrace()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nesting := 1 // count our own nesting
|
||||
nesting := 1 // count our own nesting in snippets
|
||||
tokens := []Token{}
|
||||
if retainCurlies {
|
||||
tokens = append(tokens, p.Token())
|
||||
}
|
||||
for p.Next() {
|
||||
if p.Val() == "}" {
|
||||
nesting--
|
||||
if nesting == 0 {
|
||||
if retainCurlies {
|
||||
tokens = append(tokens, p.Token())
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -690,10 +591,9 @@ func (p *parser) blockTokens(retainCurlies bool) ([]Token, error) {
|
||||
// head of the server block with tokens, which are
|
||||
// grouped by segments.
|
||||
type ServerBlock struct {
|
||||
HasBraces bool
|
||||
Keys []string
|
||||
Segments []Segment
|
||||
IsNamedRoute bool
|
||||
HasBraces bool
|
||||
Keys []string
|
||||
Segments []Segment
|
||||
}
|
||||
|
||||
// DispenseDirective returns a dispenser that contains
|
||||
|
||||
@@ -21,88 +21,6 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseVariadic(t *testing.T) {
|
||||
var args = make([]string, 10)
|
||||
for i, tc := range []struct {
|
||||
input string
|
||||
result bool
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
input: "{args[1",
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
input: "1]}",
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
input: "{args[:]}aaaaa",
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
input: "aaaaa{args[:]}",
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
input: "{args.}",
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
input: "{args.1}",
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
input: "{args[]}",
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
input: "{args[:]}",
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
input: "{args[:]}",
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
input: "{args[0:]}",
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
input: "{args[:0]}",
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
input: "{args[-1:]}",
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
input: "{args[:11]}",
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
input: "{args[10:0]}",
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
input: "{args[0:10]}",
|
||||
result: true,
|
||||
},
|
||||
} {
|
||||
token := Token{
|
||||
File: "test",
|
||||
Line: 1,
|
||||
Text: tc.input,
|
||||
}
|
||||
if v, _, _ := parseVariadic(token, len(args)); v != tc.result {
|
||||
t.Errorf("Test %d error expectation failed Expected: %t, got %t", i, tc.result, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllTokens(t *testing.T) {
|
||||
input := []byte("a b c\nd e")
|
||||
expected := []string{"a", "b", "c", "d", "e"}
|
||||
@@ -269,23 +187,6 @@ func TestParseOneAndImport(t *testing.T) {
|
||||
|
||||
{`import testdata/not_found.txt`, true, []string{}, []int{}},
|
||||
|
||||
// empty file should just log a warning, and result in no tokens
|
||||
{`import testdata/empty.txt`, false, []string{}, []int{}},
|
||||
|
||||
{`import testdata/only_white_space.txt`, false, []string{}, []int{}},
|
||||
|
||||
// import path/to/dir/* should skip any files that start with a . when iterating over them.
|
||||
{`localhost
|
||||
dir1 arg1
|
||||
import testdata/glob/*`, false, []string{
|
||||
"localhost",
|
||||
}, []int{2, 3, 1}},
|
||||
|
||||
// import path/to/dir/.* should continue to read all dotfiles in a dir.
|
||||
{`import testdata/glob/.*`, false, []string{
|
||||
"host1",
|
||||
}, []int{1, 2}},
|
||||
|
||||
{`""`, false, []string{}, []int{}},
|
||||
|
||||
{``, false, []string{}, []int{}},
|
||||
@@ -293,14 +194,6 @@ func TestParseOneAndImport(t *testing.T) {
|
||||
// Unexpected next token after '{' on same line
|
||||
{`localhost
|
||||
dir1 { a b }`, true, []string{"localhost"}, []int{}},
|
||||
|
||||
// Unexpected '{' on a new line
|
||||
{`localhost
|
||||
dir1
|
||||
{
|
||||
a b
|
||||
}`, true, []string{"localhost"}, []int{}},
|
||||
|
||||
// Workaround with quotes
|
||||
{`localhost
|
||||
dir1 "{" a b "}"`, false, []string{"localhost"}, []int{5}},
|
||||
@@ -711,38 +604,11 @@ func TestEnvironmentReplacement(t *testing.T) {
|
||||
expect: "}{$",
|
||||
},
|
||||
} {
|
||||
actual := replaceEnvVars([]byte(test.input))
|
||||
if !bytes.Equal(actual, []byte(test.expect)) {
|
||||
t.Errorf("Test %d: Expected: '%s' but got '%s'", i, test.expect, actual)
|
||||
actual, err := replaceEnvVars([]byte(test.input))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportReplacementInJSONWithBrace(t *testing.T) {
|
||||
for i, test := range []struct {
|
||||
args []string
|
||||
input string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
args: []string{"123"},
|
||||
input: "{args[0]}",
|
||||
expect: "123",
|
||||
},
|
||||
{
|
||||
args: []string{"123"},
|
||||
input: `{"key":"{args[0]}"}`,
|
||||
expect: `{"key":"123"}`,
|
||||
},
|
||||
{
|
||||
args: []string{"123", "123"},
|
||||
input: `{"key":[{args[0]},{args[1]}]}`,
|
||||
expect: `{"key":[123,123]}`,
|
||||
},
|
||||
} {
|
||||
repl := makeArgsReplacer(test.args)
|
||||
actual := repl.ReplaceKnown(test.input, "")
|
||||
if actual != test.expect {
|
||||
if !bytes.Equal(actual, []byte(test.expect)) {
|
||||
t.Errorf("Test %d: Expected: '%s' but got '%s'", i, test.expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
host1 {
|
||||
dir1
|
||||
dir2 arg1
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
dir2 arg1 arg2
|
||||
dir3
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{args[0]}
|
||||
{args.0}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{args[0]} {args[1]}
|
||||
{args.0} {args.1}
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -24,11 +24,10 @@ import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/certmagic"
|
||||
)
|
||||
|
||||
// mapAddressToServerBlocks returns a map of listener address to list of server
|
||||
@@ -37,12 +36,12 @@ import (
|
||||
// server block that share the same address stay grouped together so the config
|
||||
// isn't repeated unnecessarily. For example, this Caddyfile:
|
||||
//
|
||||
// example.com {
|
||||
// bind 127.0.0.1
|
||||
// }
|
||||
// www.example.com, example.net/path, localhost:9999 {
|
||||
// bind 127.0.0.1 1.2.3.4
|
||||
// }
|
||||
// example.com {
|
||||
// bind 127.0.0.1
|
||||
// }
|
||||
// www.example.com, example.net/path, localhost:9999 {
|
||||
// bind 127.0.0.1 1.2.3.4
|
||||
// }
|
||||
//
|
||||
// has two server blocks to start with. But expressed in this Caddyfile are
|
||||
// actually 4 listener addresses: 127.0.0.1:443, 1.2.3.4:443, 127.0.0.1:9999,
|
||||
@@ -78,8 +77,7 @@ import (
|
||||
// multiple addresses to the same lists of server blocks (a many:many mapping).
|
||||
// (Doing this is essentially a map-reduce technique.)
|
||||
func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock,
|
||||
options map[string]any,
|
||||
) (map[string][]serverBlock, error) {
|
||||
options map[string]any) (map[string][]serverBlock, error) {
|
||||
sbmap := make(map[string][]serverBlock)
|
||||
|
||||
for i, sblock := range originalServerBlocks {
|
||||
@@ -189,25 +187,13 @@ func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]se
|
||||
// listenerAddrsForServerBlockKey essentially converts the Caddyfile
|
||||
// site addresses to Caddy listener addresses for each server block.
|
||||
func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string,
|
||||
options map[string]any,
|
||||
) ([]string, error) {
|
||||
options map[string]any) ([]string, error) {
|
||||
addr, err := ParseAddress(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing key: %v", err)
|
||||
}
|
||||
addr = addr.Normalize()
|
||||
|
||||
switch addr.Scheme {
|
||||
case "wss":
|
||||
return nil, fmt.Errorf("the scheme wss:// is only supported in browsers; use https:// instead")
|
||||
case "ws":
|
||||
return nil, fmt.Errorf("the scheme ws:// is only supported in browsers; use http:// instead")
|
||||
case "https", "http", "":
|
||||
// Do nothing or handle the valid schemes
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported URL scheme %s://", addr.Scheme)
|
||||
}
|
||||
|
||||
// figure out the HTTP and HTTPS ports; either
|
||||
// use defaults, or override with user config
|
||||
httpPort, httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPPort), strconv.Itoa(caddyhttp.DefaultHTTPSPort)
|
||||
@@ -233,7 +219,7 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
|
||||
return nil, fmt.Errorf("[%s] scheme and port violate convention", key)
|
||||
}
|
||||
|
||||
// the bind directive specifies hosts (and potentially network), but is optional
|
||||
// the bind directive specifies hosts, but is optional
|
||||
lnHosts := make([]string, 0, len(sblock.pile["bind"]))
|
||||
for _, cfgVal := range sblock.pile["bind"] {
|
||||
lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
|
||||
@@ -248,23 +234,11 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
|
||||
|
||||
// use a map to prevent duplication
|
||||
listeners := make(map[string]struct{})
|
||||
for _, lnHost := range lnHosts {
|
||||
// normally we would simply append the port,
|
||||
// but if lnHost is IPv6, we need to ensure it
|
||||
// is enclosed in [ ]; net.JoinHostPort does
|
||||
// this for us, but lnHost might also have a
|
||||
// network type in front (e.g. "tcp/") leading
|
||||
// to "[tcp/::1]" which causes parsing failures
|
||||
// later; what we need is "tcp/[::1]", so we have
|
||||
// to split the network and host, then re-combine
|
||||
network, host, ok := strings.Cut(lnHost, "/")
|
||||
if !ok {
|
||||
host = network
|
||||
network = ""
|
||||
}
|
||||
host = strings.Trim(host, "[]") // IPv6
|
||||
networkAddr := caddy.JoinNetworkAddress(network, host, lnPort)
|
||||
addr, err := caddy.ParseNetworkAddress(networkAddr)
|
||||
for _, host := range lnHosts {
|
||||
// host can have network + host (e.g. "tcp6/localhost") but
|
||||
// will/should not have port information because this usually
|
||||
// comes from the bind directive, so we append the port
|
||||
addr, err := caddy.ParseNetworkAddress(host + ":" + lnPort)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing network address: %v", err)
|
||||
}
|
||||
|
||||
@@ -24,17 +24,15 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -49,14 +47,13 @@ func init() {
|
||||
RegisterHandlerDirective("route", parseRoute)
|
||||
RegisterHandlerDirective("handle", parseHandle)
|
||||
RegisterDirective("handle_errors", parseHandleErrors)
|
||||
RegisterHandlerDirective("invoke", parseInvoke)
|
||||
RegisterDirective("log", parseLog)
|
||||
RegisterHandlerDirective("skip_log", parseSkipLog)
|
||||
}
|
||||
|
||||
// parseBind parses the bind directive. Syntax:
|
||||
//
|
||||
// bind <addresses...>
|
||||
// bind <addresses...>
|
||||
//
|
||||
func parseBind(h Helper) ([]ConfigValue, error) {
|
||||
var lnHosts []string
|
||||
for h.Next() {
|
||||
@@ -67,34 +64,28 @@ func parseBind(h Helper) ([]ConfigValue, error) {
|
||||
|
||||
// parseTLS parses the tls directive. Syntax:
|
||||
//
|
||||
// tls [<email>|internal]|[<cert_file> <key_file>] {
|
||||
// protocols <min> [<max>]
|
||||
// ciphers <cipher_suites...>
|
||||
// curves <curves...>
|
||||
// client_auth {
|
||||
// mode [request|require|verify_if_given|require_and_verify]
|
||||
// trusted_ca_cert <base64_der>
|
||||
// trusted_ca_cert_file <filename>
|
||||
// trusted_leaf_cert <base64_der>
|
||||
// trusted_leaf_cert_file <filename>
|
||||
// }
|
||||
// alpn <values...>
|
||||
// load <paths...>
|
||||
// ca <acme_ca_endpoint>
|
||||
// ca_root <pem_file>
|
||||
// key_type [ed25519|p256|p384|rsa2048|rsa4096]
|
||||
// dns <provider_name> [...]
|
||||
// propagation_delay <duration>
|
||||
// propagation_timeout <duration>
|
||||
// resolvers <dns_servers...>
|
||||
// dns_ttl <duration>
|
||||
// dns_challenge_override_domain <domain>
|
||||
// on_demand
|
||||
// eab <key_id> <mac_key>
|
||||
// issuer <module_name> [...]
|
||||
// get_certificate <module_name> [...]
|
||||
// insecure_secrets_log <log_file>
|
||||
// }
|
||||
// tls [<email>|internal]|[<cert_file> <key_file>] {
|
||||
// protocols <min> [<max>]
|
||||
// ciphers <cipher_suites...>
|
||||
// curves <curves...>
|
||||
// client_auth {
|
||||
// mode [request|require|verify_if_given|require_and_verify]
|
||||
// trusted_ca_cert <base64_der>
|
||||
// trusted_ca_cert_file <filename>
|
||||
// trusted_leaf_cert <base64_der>
|
||||
// trusted_leaf_cert_file <filename>
|
||||
// }
|
||||
// alpn <values...>
|
||||
// load <paths...>
|
||||
// ca <acme_ca_endpoint>
|
||||
// ca_root <pem_file>
|
||||
// dns <provider_name> [...]
|
||||
// on_demand
|
||||
// eab <key_id> <mac_key>
|
||||
// issuer <module_name> [...]
|
||||
// get_certificate <module_name> [...]
|
||||
// }
|
||||
//
|
||||
func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
cp := new(caddytls.ConnectionPolicy)
|
||||
var fileLoader caddytls.FileLoader
|
||||
@@ -181,17 +172,17 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
case "protocols":
|
||||
args := h.RemainingArgs()
|
||||
if len(args) == 0 {
|
||||
return nil, h.Errf("protocols requires one or two arguments")
|
||||
return nil, h.SyntaxErr("one or two protocols")
|
||||
}
|
||||
if len(args) > 0 {
|
||||
if _, ok := caddytls.SupportedProtocols[args[0]]; !ok {
|
||||
return nil, h.Errf("wrong protocol name or protocol not supported: '%s'", args[0])
|
||||
return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[0])
|
||||
}
|
||||
cp.ProtocolMin = args[0]
|
||||
}
|
||||
if len(args) > 1 {
|
||||
if _, ok := caddytls.SupportedProtocols[args[1]]; !ok {
|
||||
return nil, h.Errf("wrong protocol name or protocol not supported: '%s'", args[1])
|
||||
return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[1])
|
||||
}
|
||||
cp.ProtocolMax = args[1]
|
||||
}
|
||||
@@ -199,7 +190,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
case "ciphers":
|
||||
for h.NextArg() {
|
||||
if !caddytls.CipherSuiteNameSupported(h.Val()) {
|
||||
return nil, h.Errf("wrong cipher suite name or cipher suite not supported: '%s'", h.Val())
|
||||
return nil, h.Errf("Wrong cipher suite name or cipher suite not supported: '%s'", h.Val())
|
||||
}
|
||||
cp.CipherSuites = append(cp.CipherSuites, h.Val())
|
||||
}
|
||||
@@ -372,75 +363,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
}
|
||||
acmeIssuer.Challenges.DNS.Resolvers = args
|
||||
|
||||
case "propagation_delay":
|
||||
arg := h.RemainingArgs()
|
||||
if len(arg) != 1 {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
delayStr := arg[0]
|
||||
delay, err := caddy.ParseDuration(delayStr)
|
||||
if err != nil {
|
||||
return nil, h.Errf("invalid propagation_delay duration %s: %v", delayStr, err)
|
||||
}
|
||||
if acmeIssuer == nil {
|
||||
acmeIssuer = new(caddytls.ACMEIssuer)
|
||||
}
|
||||
if acmeIssuer.Challenges == nil {
|
||||
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
||||
}
|
||||
if acmeIssuer.Challenges.DNS == nil {
|
||||
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
||||
}
|
||||
acmeIssuer.Challenges.DNS.PropagationDelay = caddy.Duration(delay)
|
||||
|
||||
case "propagation_timeout":
|
||||
arg := h.RemainingArgs()
|
||||
if len(arg) != 1 {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
timeoutStr := arg[0]
|
||||
var timeout time.Duration
|
||||
if timeoutStr == "-1" {
|
||||
timeout = time.Duration(-1)
|
||||
} else {
|
||||
var err error
|
||||
timeout, err = caddy.ParseDuration(timeoutStr)
|
||||
if err != nil {
|
||||
return nil, h.Errf("invalid propagation_timeout duration %s: %v", timeoutStr, err)
|
||||
}
|
||||
}
|
||||
if acmeIssuer == nil {
|
||||
acmeIssuer = new(caddytls.ACMEIssuer)
|
||||
}
|
||||
if acmeIssuer.Challenges == nil {
|
||||
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
||||
}
|
||||
if acmeIssuer.Challenges.DNS == nil {
|
||||
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
||||
}
|
||||
acmeIssuer.Challenges.DNS.PropagationTimeout = caddy.Duration(timeout)
|
||||
|
||||
case "dns_ttl":
|
||||
arg := h.RemainingArgs()
|
||||
if len(arg) != 1 {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
ttlStr := arg[0]
|
||||
ttl, err := caddy.ParseDuration(ttlStr)
|
||||
if err != nil {
|
||||
return nil, h.Errf("invalid dns_ttl duration %s: %v", ttlStr, err)
|
||||
}
|
||||
if acmeIssuer == nil {
|
||||
acmeIssuer = new(caddytls.ACMEIssuer)
|
||||
}
|
||||
if acmeIssuer.Challenges == nil {
|
||||
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
||||
}
|
||||
if acmeIssuer.Challenges.DNS == nil {
|
||||
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
||||
}
|
||||
acmeIssuer.Challenges.DNS.TTL = caddy.Duration(ttl)
|
||||
|
||||
case "dns_challenge_override_domain":
|
||||
arg := h.RemainingArgs()
|
||||
if len(arg) != 1 {
|
||||
@@ -473,12 +395,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
}
|
||||
onDemand = true
|
||||
|
||||
case "insecure_secrets_log":
|
||||
if !h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
cp.InsecureSecretsLog = h.Val()
|
||||
|
||||
default:
|
||||
return nil, h.Errf("unknown subdirective: %s", h.Val())
|
||||
}
|
||||
@@ -599,7 +515,8 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
|
||||
// parseRoot parses the root directive. Syntax:
|
||||
//
|
||||
// root [<matcher>] <path>
|
||||
// root [<matcher>] <path>
|
||||
//
|
||||
func parseRoot(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
var root string
|
||||
for h.Next() {
|
||||
@@ -733,20 +650,29 @@ func parseError(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
|
||||
// parseRoute parses the route directive.
|
||||
func parseRoute(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
sr := new(caddyhttp.Subroute)
|
||||
|
||||
allResults, err := parseSegmentAsConfig(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, result := range allResults {
|
||||
switch result.Value.(type) {
|
||||
case caddyhttp.Route, caddyhttp.Subroute:
|
||||
switch handler := result.Value.(type) {
|
||||
case caddyhttp.Route:
|
||||
sr.Routes = append(sr.Routes, handler)
|
||||
case caddyhttp.Subroute:
|
||||
// directives which return a literal subroute instead of a route
|
||||
// means they intend to keep those handlers together without
|
||||
// them being reordered; we're doing that anyway since we're in
|
||||
// the route directive, so just append its handlers
|
||||
sr.Routes = append(sr.Routes, handler.Routes...)
|
||||
default:
|
||||
return nil, h.Errf("%s directive returned something other than an HTTP route or subroute: %#v (only handler directives can be used in routes)", result.directive, result.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return buildSubroute(allResults, h.groupCounter, false)
|
||||
return sr, nil
|
||||
}
|
||||
|
||||
func parseHandle(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
@@ -766,35 +692,14 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parseInvoke parses the invoke directive.
|
||||
func parseInvoke(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
h.Next() // consume directive
|
||||
if !h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
for h.Next() || h.NextBlock(0) {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
|
||||
// remember that we're invoking this name
|
||||
// to populate the server with these named routes
|
||||
if h.State[namedRouteKey] == nil {
|
||||
h.State[namedRouteKey] = map[string]struct{}{}
|
||||
}
|
||||
h.State[namedRouteKey].(map[string]struct{})[h.Val()] = struct{}{}
|
||||
|
||||
// return the handler
|
||||
return &caddyhttp.Invoke{Name: h.Val()}, nil
|
||||
}
|
||||
|
||||
// parseLog parses the log directive. Syntax:
|
||||
//
|
||||
// log <logger_name> {
|
||||
// hostnames <hostnames...>
|
||||
// output <writer_module> ...
|
||||
// format <encoder_module> ...
|
||||
// level <level>
|
||||
// }
|
||||
// log {
|
||||
// output <writer_module> ...
|
||||
// format <encoder_module> ...
|
||||
// level <level>
|
||||
// }
|
||||
//
|
||||
func parseLog(h Helper) ([]ConfigValue, error) {
|
||||
return parseLogHelper(h, nil)
|
||||
}
|
||||
@@ -811,13 +716,11 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||
var configValues []ConfigValue
|
||||
for h.Next() {
|
||||
// Logic below expects that a name is always present when a
|
||||
// global option is being parsed; or an optional override
|
||||
// is supported for access logs.
|
||||
var logName string
|
||||
|
||||
// global option is being parsed.
|
||||
var globalLogName string
|
||||
if parseAsGlobalOption {
|
||||
if h.NextArg() {
|
||||
logName = h.Val()
|
||||
globalLogName = h.Val()
|
||||
|
||||
// Only a single argument is supported.
|
||||
if h.NextArg() {
|
||||
@@ -828,47 +731,26 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||
// reference the default logger. See the
|
||||
// setupNewDefault function in the logging
|
||||
// package for where this is configured.
|
||||
logName = caddy.DefaultLoggerName
|
||||
globalLogName = "default"
|
||||
}
|
||||
|
||||
// Verify this name is unused.
|
||||
_, used := globalLogNames[logName]
|
||||
_, used := globalLogNames[globalLogName]
|
||||
if used {
|
||||
return nil, h.Err("duplicate global log option for: " + logName)
|
||||
return nil, h.Err("duplicate global log option for: " + globalLogName)
|
||||
}
|
||||
globalLogNames[logName] = struct{}{}
|
||||
globalLogNames[globalLogName] = struct{}{}
|
||||
} else {
|
||||
// An optional override of the logger name can be provided;
|
||||
// otherwise a default will be used, like "log0", "log1", etc.
|
||||
// No arguments are supported for the server block log directive
|
||||
if h.NextArg() {
|
||||
logName = h.Val()
|
||||
|
||||
// Only a single argument is supported.
|
||||
if h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
}
|
||||
|
||||
cl := new(caddy.CustomLog)
|
||||
|
||||
// allow overriding the current site block's hostnames for this logger;
|
||||
// this is useful for setting up loggers per subdomain in a site block
|
||||
// with a wildcard domain
|
||||
customHostnames := []string{}
|
||||
|
||||
for h.NextBlock(0) {
|
||||
switch h.Val() {
|
||||
case "hostnames":
|
||||
if parseAsGlobalOption {
|
||||
return nil, h.Err("hostnames is not allowed in the log global options")
|
||||
}
|
||||
args := h.RemainingArgs()
|
||||
if len(args) == 0 {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
customHostnames = append(customHostnames, args...)
|
||||
|
||||
case "output":
|
||||
if !h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
@@ -927,16 +809,18 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||
}
|
||||
|
||||
case "include":
|
||||
// This configuration is only allowed in the global options
|
||||
if !parseAsGlobalOption {
|
||||
return nil, h.Err("include is not allowed in the log directive")
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
for h.NextArg() {
|
||||
cl.Include = append(cl.Include, h.Val())
|
||||
}
|
||||
|
||||
case "exclude":
|
||||
// This configuration is only allowed in the global options
|
||||
if !parseAsGlobalOption {
|
||||
return nil, h.Err("exclude is not allowed in the log directive")
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
for h.NextArg() {
|
||||
cl.Exclude = append(cl.Exclude, h.Val())
|
||||
@@ -948,34 +832,24 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||
}
|
||||
|
||||
var val namedCustomLog
|
||||
val.hostnames = customHostnames
|
||||
|
||||
isEmptyConfig := reflect.DeepEqual(cl, new(caddy.CustomLog))
|
||||
|
||||
// Skip handling of empty logging configs
|
||||
|
||||
if parseAsGlobalOption {
|
||||
// Use indicated name for global log options
|
||||
val.name = logName
|
||||
} else {
|
||||
if logName != "" {
|
||||
val.name = logName
|
||||
} else if !isEmptyConfig {
|
||||
if !reflect.DeepEqual(cl, new(caddy.CustomLog)) {
|
||||
if parseAsGlobalOption {
|
||||
// Use indicated name for global log options
|
||||
val.name = globalLogName
|
||||
val.log = cl
|
||||
} else {
|
||||
// Construct a log name for server log streams
|
||||
logCounter, ok := h.State["logCounter"].(int)
|
||||
if !ok {
|
||||
logCounter = 0
|
||||
}
|
||||
val.name = fmt.Sprintf("log%d", logCounter)
|
||||
cl.Include = []string{"http.log.access." + val.name}
|
||||
val.log = cl
|
||||
logCounter++
|
||||
h.State["logCounter"] = logCounter
|
||||
}
|
||||
if val.name != "" {
|
||||
cl.Include = []string{"http.log.access." + val.name}
|
||||
}
|
||||
}
|
||||
if !isEmptyConfig {
|
||||
val.log = cl
|
||||
}
|
||||
configValues = append(configValues, ConfigValue{
|
||||
Class: "custom_log",
|
||||
@@ -984,15 +858,3 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||
}
|
||||
return configValues, nil
|
||||
}
|
||||
|
||||
// parseSkipLog parses the skip_log directive. Syntax:
|
||||
//
|
||||
// skip_log [<matcher>]
|
||||
func parseSkipLog(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
for h.Next() {
|
||||
if h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
}
|
||||
return caddyhttp.VarsMiddleware{"skip_log": true}, nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package httpcaddyfile
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
@@ -52,13 +51,12 @@ func TestLogDirectiveSyntax(t *testing.T) {
|
||||
},
|
||||
{
|
||||
input: `:8080 {
|
||||
log name-override {
|
||||
log invalid {
|
||||
output file foo.log
|
||||
}
|
||||
}
|
||||
`,
|
||||
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.name-override"]},"name-override":{"writer":{"filename":"foo.log","output":"file"},"include":["http.log.access.name-override"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"name-override"}}}}}}`,
|
||||
expectError: false,
|
||||
expectError: true,
|
||||
},
|
||||
} {
|
||||
|
||||
@@ -215,139 +213,3 @@ func TestRedirDirectiveSyntax(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportErrorLine(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
input string
|
||||
errorFunc func(err error) bool
|
||||
}{
|
||||
{
|
||||
input: `(t1) {
|
||||
abort {args[:]}
|
||||
}
|
||||
:8080 {
|
||||
import t1
|
||||
import t1 true
|
||||
}`,
|
||||
errorFunc: func(err error) bool {
|
||||
return err != nil && strings.Contains(err.Error(), "Caddyfile:6 (import t1)")
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `(t1) {
|
||||
abort {args[:]}
|
||||
}
|
||||
:8080 {
|
||||
import t1 true
|
||||
}`,
|
||||
errorFunc: func(err error) bool {
|
||||
return err != nil && strings.Contains(err.Error(), "Caddyfile:5 (import t1)")
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `
|
||||
import testdata/import_variadic_snippet.txt
|
||||
:8080 {
|
||||
import t1 true
|
||||
}`,
|
||||
errorFunc: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `
|
||||
import testdata/import_variadic_with_import.txt
|
||||
:8080 {
|
||||
import t1 true
|
||||
import t2 true
|
||||
}`,
|
||||
errorFunc: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
} {
|
||||
adapter := caddyfile.Adapter{
|
||||
ServerType: ServerType{},
|
||||
}
|
||||
|
||||
_, _, err := adapter.Adapt([]byte(tc.input), nil)
|
||||
|
||||
if !tc.errorFunc(err) {
|
||||
t.Errorf("Test %d error expectation failed, got %s", i, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNestedImport(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
input string
|
||||
errorFunc func(err error) bool
|
||||
}{
|
||||
{
|
||||
input: `(t1) {
|
||||
respond {args[0]} {args[1]}
|
||||
}
|
||||
|
||||
(t2) {
|
||||
import t1 {args[0]} 202
|
||||
}
|
||||
|
||||
:8080 {
|
||||
handle {
|
||||
import t2 "foobar"
|
||||
}
|
||||
}`,
|
||||
errorFunc: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `(t1) {
|
||||
respond {args[:]}
|
||||
}
|
||||
|
||||
(t2) {
|
||||
import t1 {args[0]} {args[1]}
|
||||
}
|
||||
|
||||
:8080 {
|
||||
handle {
|
||||
import t2 "foobar" 202
|
||||
}
|
||||
}`,
|
||||
errorFunc: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `(t1) {
|
||||
respond {args[0]} {args[1]}
|
||||
}
|
||||
|
||||
(t2) {
|
||||
import t1 {args[:]}
|
||||
}
|
||||
|
||||
:8080 {
|
||||
handle {
|
||||
import t2 "foobar" 202
|
||||
}
|
||||
}`,
|
||||
errorFunc: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
} {
|
||||
adapter := caddyfile.Adapter{
|
||||
ServerType: ServerType{},
|
||||
}
|
||||
|
||||
_, _, err := adapter.Adapt([]byte(tc.input), nil)
|
||||
|
||||
if !tc.errorFunc(err) {
|
||||
t.Errorf("Test %d error expectation failed, got %s", i, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ var directiveOrder = []string{
|
||||
"map",
|
||||
"vars",
|
||||
"root",
|
||||
"skip_log",
|
||||
|
||||
"header",
|
||||
"copy_response_headers", // only in reverse_proxy's handle_response
|
||||
@@ -65,7 +64,6 @@ var directiveOrder = []string{
|
||||
"templates",
|
||||
|
||||
// special routing & dispatching directives
|
||||
"invoke",
|
||||
"handle",
|
||||
"handle_path",
|
||||
"route",
|
||||
@@ -173,7 +171,6 @@ func (h Helper) Caddyfiles() []string {
|
||||
for file := range files {
|
||||
filesSlice = append(filesSlice, file)
|
||||
}
|
||||
sort.Strings(filesSlice)
|
||||
return filesSlice
|
||||
}
|
||||
|
||||
@@ -217,8 +214,7 @@ func (h Helper) ExtractMatcherSet() (caddy.ModuleMap, error) {
|
||||
|
||||
// NewRoute returns config values relevant to creating a new HTTP route.
|
||||
func (h Helper) NewRoute(matcherSet caddy.ModuleMap,
|
||||
handler caddyhttp.MiddlewareHandler,
|
||||
) []ConfigValue {
|
||||
handler caddyhttp.MiddlewareHandler) []ConfigValue {
|
||||
mod, err := caddy.GetModule(caddy.GetModuleID(handler))
|
||||
if err != nil {
|
||||
*h.warnings = append(*h.warnings, caddyconfig.Warning{
|
||||
@@ -292,7 +288,7 @@ func ParseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buildSubroute(allResults, h.groupCounter, true)
|
||||
return buildSubroute(allResults, h.groupCounter)
|
||||
}
|
||||
|
||||
// parseSegmentAsConfig parses the segment such that its subdirectives
|
||||
@@ -430,16 +426,26 @@ func sortRoutes(routes []ConfigValue) {
|
||||
jPathLen = len(jPM[0])
|
||||
}
|
||||
|
||||
sortByPath := func() bool {
|
||||
// some directives involve setting values which can overwrite
|
||||
// each other, so it makes most sense to reverse the order so
|
||||
// that the lease specific matcher is first; everything else
|
||||
// has most-specific matcher first
|
||||
if iDir == "vars" {
|
||||
// we can only confidently compare path lengths if both
|
||||
// directives have a single path to match (issue #5037)
|
||||
if iPathLen > 0 && jPathLen > 0 {
|
||||
// if both paths are the same except for a trailing wildcard,
|
||||
// sort by the shorter path first (which is more specific)
|
||||
if strings.TrimSuffix(iPM[0], "*") == strings.TrimSuffix(jPM[0], "*") {
|
||||
return iPathLen < jPathLen
|
||||
}
|
||||
// sort least-specific (shortest) path first
|
||||
return iPathLen < jPathLen
|
||||
}
|
||||
|
||||
// if both directives don't have a single path to compare,
|
||||
// sort whichever one has no matcher first; if both have
|
||||
// no matcher, sort equally (stable sort preserves order)
|
||||
return len(iRoute.MatcherSetsRaw) == 0 && len(jRoute.MatcherSetsRaw) > 0
|
||||
} else {
|
||||
// we can only confidently compare path lengths if both
|
||||
// directives have a single path to match (issue #5037)
|
||||
if iPathLen > 0 && jPathLen > 0 {
|
||||
// sort most-specific (longest) path first
|
||||
return iPathLen > jPathLen
|
||||
}
|
||||
@@ -448,18 +454,7 @@ func sortRoutes(routes []ConfigValue) {
|
||||
// sort whichever one has a matcher first; if both have
|
||||
// a matcher, sort equally (stable sort preserves order)
|
||||
return len(iRoute.MatcherSetsRaw) > 0 && len(jRoute.MatcherSetsRaw) == 0
|
||||
}()
|
||||
|
||||
// some directives involve setting values which can overwrite
|
||||
// each other, so it makes most sense to reverse the order so
|
||||
// that the least-specific matcher is first, allowing the last
|
||||
// matching one to win
|
||||
if iDir == "vars" {
|
||||
return !sortByPath
|
||||
}
|
||||
|
||||
// everything else is most-specific matcher first
|
||||
return sortByPath
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -18,19 +18,18 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -49,13 +48,12 @@ type App struct {
|
||||
}
|
||||
|
||||
// ServerType can set up a config from an HTTP Caddyfile.
|
||||
type ServerType struct{}
|
||||
type ServerType struct {
|
||||
}
|
||||
|
||||
// Setup makes a config from the tokens.
|
||||
func (st ServerType) Setup(
|
||||
inputServerBlocks []caddyfile.ServerBlock,
|
||||
options map[string]any,
|
||||
) (*caddy.Config, []caddyconfig.Warning, error) {
|
||||
func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
||||
options map[string]any) (*caddy.Config, []caddyconfig.Warning, error) {
|
||||
var warnings []caddyconfig.Warning
|
||||
gc := counter{new(int)}
|
||||
state := make(map[string]any)
|
||||
@@ -81,18 +79,41 @@ func (st ServerType) Setup(
|
||||
return nil, warnings, err
|
||||
}
|
||||
|
||||
// this will replace both static and user-defined placeholder shorthands
|
||||
// with actual identifiers used by Caddy
|
||||
replacer := NewShorthandReplacer()
|
||||
// replace shorthand placeholders (which are convenient
|
||||
// when writing a Caddyfile) with their actual placeholder
|
||||
// identifiers or variable names
|
||||
replacer := strings.NewReplacer(placeholderShorthands()...)
|
||||
|
||||
originalServerBlocks, err = st.extractNamedRoutes(originalServerBlocks, options, &warnings, replacer)
|
||||
if err != nil {
|
||||
return nil, warnings, err
|
||||
// these are placeholders that allow a user-defined final
|
||||
// parameters, but we still want to provide a shorthand
|
||||
// for those, so we use a regexp to replace
|
||||
regexpReplacements := []struct {
|
||||
search *regexp.Regexp
|
||||
replace string
|
||||
}{
|
||||
{regexp.MustCompile(`{header\.([\w-]*)}`), "{http.request.header.$1}"},
|
||||
{regexp.MustCompile(`{cookie\.([\w-]*)}`), "{http.request.cookie.$1}"},
|
||||
{regexp.MustCompile(`{labels\.([\w-]*)}`), "{http.request.host.labels.$1}"},
|
||||
{regexp.MustCompile(`{path\.([\w-]*)}`), "{http.request.uri.path.$1}"},
|
||||
{regexp.MustCompile(`{file\.([\w-]*)}`), "{http.request.uri.path.file.$1}"},
|
||||
{regexp.MustCompile(`{query\.([\w-]*)}`), "{http.request.uri.query.$1}"},
|
||||
{regexp.MustCompile(`{re\.([\w-]*)\.([\w-]*)}`), "{http.regexp.$1.$2}"},
|
||||
{regexp.MustCompile(`{vars\.([\w-]*)}`), "{http.vars.$1}"},
|
||||
{regexp.MustCompile(`{rp\.([\w-\.]*)}`), "{http.reverse_proxy.$1}"},
|
||||
{regexp.MustCompile(`{err\.([\w-\.]*)}`), "{http.error.$1}"},
|
||||
{regexp.MustCompile(`{file_match\.([\w-]*)}`), "{http.matchers.file.$1}"},
|
||||
}
|
||||
|
||||
for _, sb := range originalServerBlocks {
|
||||
for i := range sb.block.Segments {
|
||||
replacer.ApplyToSegment(&sb.block.Segments[i])
|
||||
for _, segment := range sb.block.Segments {
|
||||
for i := 0; i < len(segment); i++ {
|
||||
// simple string replacements
|
||||
segment[i].Text = replacer.Replace(segment[i].Text)
|
||||
// complex regexp replacements
|
||||
for _, r := range regexpReplacements {
|
||||
segment[i].Text = r.search.ReplaceAllString(segment[i].Text, r.replace)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(sb.block.Keys) == 0 {
|
||||
@@ -151,18 +172,6 @@ func (st ServerType) Setup(
|
||||
result.directive = dir
|
||||
sb.pile[result.Class] = append(sb.pile[result.Class], result)
|
||||
}
|
||||
|
||||
// specially handle named routes that were pulled out from
|
||||
// the invoke directive, which could be nested anywhere within
|
||||
// some subroutes in this directive; we add them to the pile
|
||||
// for this server block
|
||||
if state[namedRouteKey] != nil {
|
||||
for name := range state[namedRouteKey].(map[string]struct{}) {
|
||||
result := ConfigValue{Class: namedRouteKey, Value: name}
|
||||
sb.pile[result.Class] = append(sb.pile[result.Class], result)
|
||||
}
|
||||
state[namedRouteKey] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,11 +219,11 @@ func (st ServerType) Setup(
|
||||
if ncl.name == "" {
|
||||
return
|
||||
}
|
||||
if ncl.name == caddy.DefaultLoggerName {
|
||||
if ncl.name == "default" {
|
||||
hasDefaultLog = true
|
||||
}
|
||||
if _, ok := options["debug"]; ok && ncl.log != nil && ncl.log.Level == "" {
|
||||
ncl.log.Level = zap.DebugLevel.CapitalString()
|
||||
if _, ok := options["debug"]; ok && ncl.log.Level == "" {
|
||||
ncl.log.Level = "DEBUG"
|
||||
}
|
||||
customLogs = append(customLogs, ncl)
|
||||
}
|
||||
@@ -231,10 +240,8 @@ func (st ServerType) Setup(
|
||||
// configure it with any applicable options
|
||||
if _, ok := options["debug"]; ok {
|
||||
customLogs = append(customLogs, namedCustomLog{
|
||||
name: caddy.DefaultLoggerName,
|
||||
log: &caddy.CustomLog{
|
||||
BaseLog: caddy.BaseLog{Level: zap.DebugLevel.CapitalString()},
|
||||
},
|
||||
name: "default",
|
||||
log: &caddy.CustomLog{Level: "DEBUG"},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -279,61 +286,28 @@ func (st ServerType) Setup(
|
||||
if adminConfig, ok := options["admin"].(*caddy.AdminConfig); ok && adminConfig != nil {
|
||||
cfg.Admin = adminConfig
|
||||
}
|
||||
|
||||
if pc, ok := options["persist_config"].(string); ok && pc == "off" {
|
||||
if cfg.Admin == nil {
|
||||
cfg.Admin = new(caddy.AdminConfig)
|
||||
}
|
||||
if cfg.Admin.Config == nil {
|
||||
cfg.Admin.Config = new(caddy.ConfigSettings)
|
||||
}
|
||||
cfg.Admin.Config.Persist = new(bool)
|
||||
}
|
||||
|
||||
if len(customLogs) > 0 {
|
||||
if cfg.Logging == nil {
|
||||
cfg.Logging = &caddy.Logging{
|
||||
Logs: make(map[string]*caddy.CustomLog),
|
||||
}
|
||||
}
|
||||
|
||||
// Add the default log first if defined, so that it doesn't
|
||||
// accidentally get re-created below due to the Exclude logic
|
||||
for _, ncl := range customLogs {
|
||||
if ncl.name == caddy.DefaultLoggerName && ncl.log != nil {
|
||||
cfg.Logging.Logs[caddy.DefaultLoggerName] = ncl.log
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Add the rest of the custom logs
|
||||
for _, ncl := range customLogs {
|
||||
if ncl.log == nil || ncl.name == caddy.DefaultLoggerName {
|
||||
continue
|
||||
}
|
||||
if ncl.name != "" {
|
||||
cfg.Logging.Logs[ncl.name] = ncl.log
|
||||
}
|
||||
// most users seem to prefer not writing access logs
|
||||
// to the default log when they are directed to a
|
||||
// file or have any other special customization
|
||||
if ncl.name != caddy.DefaultLoggerName && len(ncl.log.Include) > 0 {
|
||||
defaultLog, ok := cfg.Logging.Logs[caddy.DefaultLoggerName]
|
||||
if ncl.name != "default" && len(ncl.log.Include) > 0 {
|
||||
defaultLog, ok := cfg.Logging.Logs["default"]
|
||||
if !ok {
|
||||
defaultLog = new(caddy.CustomLog)
|
||||
cfg.Logging.Logs[caddy.DefaultLoggerName] = defaultLog
|
||||
cfg.Logging.Logs["default"] = defaultLog
|
||||
}
|
||||
defaultLog.Exclude = append(defaultLog.Exclude, ncl.log.Include...)
|
||||
|
||||
// avoid duplicates by sorting + compacting
|
||||
sort.Strings(defaultLog.Exclude)
|
||||
defaultLog.Exclude = slices.Compact[[]string, string](defaultLog.Exclude)
|
||||
}
|
||||
}
|
||||
// we may have not actually added anything, so remove if empty
|
||||
if len(cfg.Logging.Logs) == 0 {
|
||||
cfg.Logging = nil
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, warnings, nil
|
||||
@@ -416,81 +390,6 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options
|
||||
return serverBlocks[1:], nil
|
||||
}
|
||||
|
||||
// extractNamedRoutes pulls out any named route server blocks
|
||||
// so they don't get parsed as sites, and stores them in options
|
||||
// for later.
|
||||
func (ServerType) extractNamedRoutes(
|
||||
serverBlocks []serverBlock,
|
||||
options map[string]any,
|
||||
warnings *[]caddyconfig.Warning,
|
||||
replacer ShorthandReplacer,
|
||||
) ([]serverBlock, error) {
|
||||
namedRoutes := map[string]*caddyhttp.Route{}
|
||||
|
||||
gc := counter{new(int)}
|
||||
state := make(map[string]any)
|
||||
|
||||
// copy the server blocks so we can
|
||||
// splice out the named route ones
|
||||
filtered := append([]serverBlock{}, serverBlocks...)
|
||||
index := -1
|
||||
|
||||
for _, sb := range serverBlocks {
|
||||
index++
|
||||
if !sb.block.IsNamedRoute {
|
||||
continue
|
||||
}
|
||||
|
||||
// splice out this block, because we know it's not a real server
|
||||
filtered = append(filtered[:index], filtered[index+1:]...)
|
||||
index--
|
||||
|
||||
if len(sb.block.Segments) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
wholeSegment := caddyfile.Segment{}
|
||||
for i := range sb.block.Segments {
|
||||
// replace user-defined placeholder shorthands in extracted named routes
|
||||
replacer.ApplyToSegment(&sb.block.Segments[i])
|
||||
|
||||
// zip up all the segments since ParseSegmentAsSubroute
|
||||
// was designed to take a directive+
|
||||
wholeSegment = append(wholeSegment, sb.block.Segments[i]...)
|
||||
}
|
||||
|
||||
h := Helper{
|
||||
Dispenser: caddyfile.NewDispenser(wholeSegment),
|
||||
options: options,
|
||||
warnings: warnings,
|
||||
matcherDefs: nil,
|
||||
parentBlock: sb.block,
|
||||
groupCounter: gc,
|
||||
State: state,
|
||||
}
|
||||
|
||||
handler, err := ParseSegmentAsSubroute(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subroute := handler.(*caddyhttp.Subroute)
|
||||
route := caddyhttp.Route{}
|
||||
|
||||
if len(subroute.Routes) == 1 && len(subroute.Routes[0].MatcherSetsRaw) == 0 {
|
||||
// if there's only one route with no matcher, then we can simplify
|
||||
route.HandlersRaw = append(route.HandlersRaw, subroute.Routes[0].HandlersRaw[0])
|
||||
} else {
|
||||
// otherwise we need the whole subroute
|
||||
route.HandlersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(handler, "handler", subroute.CaddyModule().ID.Name(), h.warnings)}
|
||||
}
|
||||
|
||||
namedRoutes[sb.block.Keys[0]] = &route
|
||||
}
|
||||
options["named_routes"] = namedRoutes
|
||||
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
// serversFromPairings creates the servers for each pairing of addresses
|
||||
// to server blocks. Each pairing is essentially a server definition.
|
||||
func (st *ServerType) serversFromPairings(
|
||||
@@ -501,7 +400,6 @@ func (st *ServerType) serversFromPairings(
|
||||
) (map[string]*caddyhttp.Server, error) {
|
||||
servers := make(map[string]*caddyhttp.Server)
|
||||
defaultSNI := tryString(options["default_sni"], warnings)
|
||||
fallbackSNI := tryString(options["fallback_sni"], warnings)
|
||||
|
||||
httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
|
||||
if hp, ok := options["http_port"].(int); ok {
|
||||
@@ -620,6 +518,15 @@ func (st *ServerType) serversFromPairings(
|
||||
var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool
|
||||
autoHTTPSWillAddConnPolicy := autoHTTPS != "off"
|
||||
|
||||
// if a catch-all server block (one which accepts all hostnames) exists in this pairing,
|
||||
// we need to know that so that we can configure logs properly (see #3878)
|
||||
var catchAllSblockExists bool
|
||||
for _, sblock := range p.serverBlocks {
|
||||
if len(sblock.hostsFromKeys(false)) == 0 {
|
||||
catchAllSblockExists = true
|
||||
}
|
||||
}
|
||||
|
||||
// if needed, the ServerLogConfig is initialized beforehand so
|
||||
// that all server blocks can populate it with data, even when not
|
||||
// coming with a log directive
|
||||
@@ -630,24 +537,6 @@ func (st *ServerType) serversFromPairings(
|
||||
}
|
||||
}
|
||||
|
||||
// add named routes to the server if 'invoke' was used inside of it
|
||||
configuredNamedRoutes := options["named_routes"].(map[string]*caddyhttp.Route)
|
||||
for _, sblock := range p.serverBlocks {
|
||||
if len(sblock.pile[namedRouteKey]) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, value := range sblock.pile[namedRouteKey] {
|
||||
if srv.NamedRoutes == nil {
|
||||
srv.NamedRoutes = map[string]*caddyhttp.Route{}
|
||||
}
|
||||
name := value.Value.(string)
|
||||
if configuredNamedRoutes[name] == nil {
|
||||
return nil, fmt.Errorf("cannot invoke named route '%s', which was not defined", name)
|
||||
}
|
||||
srv.NamedRoutes[name] = configuredNamedRoutes[name]
|
||||
}
|
||||
}
|
||||
|
||||
// create a subroute for each site in the server block
|
||||
for _, sblock := range p.serverBlocks {
|
||||
matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock)
|
||||
@@ -677,21 +566,14 @@ func (st *ServerType) serversFromPairings(
|
||||
cp.DefaultSNI = defaultSNI
|
||||
break
|
||||
}
|
||||
if h == fallbackSNI {
|
||||
hosts = append(hosts, "")
|
||||
cp.FallbackSNI = fallbackSNI
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(hosts) > 0 {
|
||||
slices.Sort(hosts) // for deterministic JSON output
|
||||
cp.MatchersRaw = caddy.ModuleMap{
|
||||
"sni": caddyconfig.JSON(hosts, warnings), // make sure to match all hosts, not just auto-HTTPS-qualified ones
|
||||
}
|
||||
} else {
|
||||
cp.DefaultSNI = defaultSNI
|
||||
cp.FallbackSNI = fallbackSNI
|
||||
}
|
||||
|
||||
// only append this policy if it actually changes something
|
||||
@@ -717,20 +599,10 @@ func (st *ServerType) serversFromPairings(
|
||||
}
|
||||
}
|
||||
|
||||
// If TLS is specified as directive, it will also result in 1 or more connection policy being created
|
||||
// Thus, catch-all address with non-standard port, e.g. :8443, can have TLS enabled without
|
||||
// specifying prefix "https://"
|
||||
// Second part of the condition is to allow creating TLS conn policy even though `auto_https` has been disabled
|
||||
// ensuring compatibility with behavior described in below link
|
||||
// https://caddy.community/t/making-sense-of-auto-https-and-why-disabling-it-still-serves-https-instead-of-http/9761
|
||||
createdTLSConnPolicies, ok := sblock.pile["tls.connection_policy"]
|
||||
hasTLSEnabled := (ok && len(createdTLSConnPolicies) > 0) ||
|
||||
(addr.Host != "" && srv.AutoHTTPS != nil && !sliceContains(srv.AutoHTTPS.Skip, addr.Host))
|
||||
|
||||
// we'll need to remember if the address qualifies for auto-HTTPS, so we
|
||||
// can add a TLS conn policy if necessary
|
||||
if addr.Scheme == "https" ||
|
||||
(addr.Scheme != "http" && addr.Port != httpPort && hasTLSEnabled) {
|
||||
(addr.Scheme != "http" && addr.Host != "" && addr.Port != httpPort) {
|
||||
addressQualifiesForTLS = true
|
||||
}
|
||||
// predict whether auto-HTTPS will add the conn policy for us; if so, we
|
||||
@@ -755,7 +627,7 @@ func (st *ServerType) serversFromPairings(
|
||||
|
||||
// set up each handler directive, making sure to honor directive order
|
||||
dirRoutes := sblock.pile["route"]
|
||||
siteSubroute, err := buildSubroute(dirRoutes, groupCounter, true)
|
||||
siteSubroute, err := buildSubroute(dirRoutes, groupCounter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -779,25 +651,25 @@ func (st *ServerType) serversFromPairings(
|
||||
sblockLogHosts := sblock.hostsFromKeys(true)
|
||||
for _, cval := range sblock.pile["custom_log"] {
|
||||
ncl := cval.Value.(namedCustomLog)
|
||||
if sblock.hasHostCatchAllKey() && len(ncl.hostnames) == 0 {
|
||||
if sblock.hasHostCatchAllKey() {
|
||||
// all requests for hosts not able to be listed should use
|
||||
// this log because it's a catch-all-hosts server block
|
||||
srv.Logs.DefaultLoggerName = ncl.name
|
||||
} else if len(ncl.hostnames) > 0 {
|
||||
// if the logger overrides the hostnames, map that to the logger name
|
||||
for _, h := range ncl.hostnames {
|
||||
if srv.Logs.LoggerNames == nil {
|
||||
srv.Logs.LoggerNames = make(map[string]string)
|
||||
}
|
||||
srv.Logs.LoggerNames[h] = ncl.name
|
||||
}
|
||||
} else {
|
||||
// otherwise, map each host to the logger name
|
||||
// map each host to the user's desired logger name
|
||||
for _, h := range sblockLogHosts {
|
||||
if srv.Logs.LoggerNames == nil {
|
||||
srv.Logs.LoggerNames = make(map[string]string)
|
||||
// if the custom logger name is non-empty, add it to the map;
|
||||
// otherwise, only map to an empty logger name if this or
|
||||
// another site block on this server has a catch-all host (in
|
||||
// which case only requests with mapped hostnames will be
|
||||
// access-logged, so it'll be necessary to add them to the
|
||||
// map even if they use default logger)
|
||||
if ncl.name != "" || catchAllSblockExists {
|
||||
if srv.Logs.LoggerNames == nil {
|
||||
srv.Logs.LoggerNames = make(map[string]string)
|
||||
}
|
||||
srv.Logs.LoggerNames[h] = ncl.name
|
||||
}
|
||||
srv.Logs.LoggerNames[h] = ncl.name
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -835,8 +707,8 @@ func (st *ServerType) serversFromPairings(
|
||||
// policy missing for any HTTPS-enabled hosts, if so, add it... maybe?
|
||||
if addressQualifiesForTLS &&
|
||||
!hasCatchAllTLSConnPolicy &&
|
||||
(len(srv.TLSConnPolicies) > 0 || !autoHTTPSWillAddConnPolicy || defaultSNI != "" || fallbackSNI != "") {
|
||||
srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{DefaultSNI: defaultSNI, FallbackSNI: fallbackSNI})
|
||||
(len(srv.TLSConnPolicies) > 0 || !autoHTTPSWillAddConnPolicy || defaultSNI != "") {
|
||||
srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{DefaultSNI: defaultSNI})
|
||||
}
|
||||
|
||||
// tidy things up a bit
|
||||
@@ -851,7 +723,7 @@ func (st *ServerType) serversFromPairings(
|
||||
|
||||
err := applyServerOptions(servers, options, warnings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("applying global server options: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return servers, nil
|
||||
@@ -1045,39 +917,18 @@ func appendSubrouteToRouteList(routeList caddyhttp.RouteList,
|
||||
subroute *caddyhttp.Subroute,
|
||||
matcherSetsEnc []caddy.ModuleMap,
|
||||
p sbAddrAssociation,
|
||||
warnings *[]caddyconfig.Warning,
|
||||
) caddyhttp.RouteList {
|
||||
warnings *[]caddyconfig.Warning) caddyhttp.RouteList {
|
||||
|
||||
// nothing to do if... there's nothing to do
|
||||
if len(matcherSetsEnc) == 0 && len(subroute.Routes) == 0 && subroute.Errors == nil {
|
||||
return routeList
|
||||
}
|
||||
|
||||
// No need to wrap the handlers in a subroute if this is the only server block
|
||||
// and there is no matcher for it (doing so would produce unnecessarily nested
|
||||
// JSON), *unless* there is a host matcher within this site block; if so, then
|
||||
// we still need to wrap in a subroute because otherwise the host matcher from
|
||||
// the inside of the site block would be a top-level host matcher, which is
|
||||
// subject to auto-HTTPS (cert management), and using a host matcher within
|
||||
// a site block is a valid, common pattern for excluding domains from cert
|
||||
// management, leading to unexpected behavior; see issue #5124.
|
||||
wrapInSubroute := true
|
||||
if len(matcherSetsEnc) == 0 && len(p.serverBlocks) == 1 {
|
||||
var hasHostMatcher bool
|
||||
outer:
|
||||
for _, route := range subroute.Routes {
|
||||
for _, ms := range route.MatcherSetsRaw {
|
||||
for matcherName := range ms {
|
||||
if matcherName == "host" {
|
||||
hasHostMatcher = true
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
wrapInSubroute = hasHostMatcher
|
||||
}
|
||||
|
||||
if wrapInSubroute {
|
||||
// no need to wrap the handlers in a subroute if this is
|
||||
// the only server block and there is no matcher for it
|
||||
routeList = append(routeList, subroute.Routes...)
|
||||
} else {
|
||||
route := caddyhttp.Route{
|
||||
// the semantics of a site block in the Caddyfile dictate
|
||||
// that only the first matching one is evaluated, since
|
||||
@@ -1095,26 +946,21 @@ func appendSubrouteToRouteList(routeList caddyhttp.RouteList,
|
||||
if len(route.MatcherSetsRaw) > 0 || len(route.HandlersRaw) > 0 {
|
||||
routeList = append(routeList, route)
|
||||
}
|
||||
} else {
|
||||
routeList = append(routeList, subroute.Routes...)
|
||||
}
|
||||
|
||||
return routeList
|
||||
}
|
||||
|
||||
// buildSubroute turns the config values, which are expected to be routes
|
||||
// into a clean and orderly subroute that has all the routes within it.
|
||||
func buildSubroute(routes []ConfigValue, groupCounter counter, needsSorting bool) (*caddyhttp.Subroute, error) {
|
||||
if needsSorting {
|
||||
for _, val := range routes {
|
||||
if !directiveIsOrdered(val.directive) {
|
||||
return nil, fmt.Errorf("directive '%s' is not an ordered HTTP handler, so it cannot be used here - try placing within a route block or using the order global option", val.directive)
|
||||
}
|
||||
func buildSubroute(routes []ConfigValue, groupCounter counter) (*caddyhttp.Subroute, error) {
|
||||
for _, val := range routes {
|
||||
if !directiveIsOrdered(val.directive) {
|
||||
return nil, fmt.Errorf("directive '%s' is not an ordered HTTP handler, so it cannot be used here", val.directive)
|
||||
}
|
||||
|
||||
sortRoutes(routes)
|
||||
}
|
||||
|
||||
sortRoutes(routes)
|
||||
|
||||
subroute := new(caddyhttp.Subroute)
|
||||
|
||||
// some directives are mutually exclusive (only first matching
|
||||
@@ -1435,6 +1281,36 @@ func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (caddy.Modul
|
||||
return msEncoded, nil
|
||||
}
|
||||
|
||||
// placeholderShorthands returns a slice of old-new string pairs,
|
||||
// where the left of the pair is a placeholder shorthand that may
|
||||
// be used in the Caddyfile, and the right is the replacement.
|
||||
func placeholderShorthands() []string {
|
||||
return []string{
|
||||
"{dir}", "{http.request.uri.path.dir}",
|
||||
"{file}", "{http.request.uri.path.file}",
|
||||
"{host}", "{http.request.host}",
|
||||
"{hostport}", "{http.request.hostport}",
|
||||
"{port}", "{http.request.port}",
|
||||
"{method}", "{http.request.method}",
|
||||
"{path}", "{http.request.uri.path}",
|
||||
"{query}", "{http.request.uri.query}",
|
||||
"{remote}", "{http.request.remote}",
|
||||
"{remote_host}", "{http.request.remote.host}",
|
||||
"{remote_port}", "{http.request.remote.port}",
|
||||
"{scheme}", "{http.request.scheme}",
|
||||
"{uri}", "{http.request.uri}",
|
||||
"{tls_cipher}", "{http.request.tls.cipher_suite}",
|
||||
"{tls_version}", "{http.request.tls.version}",
|
||||
"{tls_client_fingerprint}", "{http.request.tls.client.fingerprint}",
|
||||
"{tls_client_issuer}", "{http.request.tls.client.issuer}",
|
||||
"{tls_client_serial}", "{http.request.tls.client.serial}",
|
||||
"{tls_client_subject}", "{http.request.tls.client.subject}",
|
||||
"{tls_client_certificate_pem}", "{http.request.tls.client.certificate_pem}",
|
||||
"{tls_client_certificate_der_base64}", "{http.request.tls.client.certificate_der_base64}",
|
||||
"{upstream_hostport}", "{http.reverse_proxy.upstream.hostport}",
|
||||
}
|
||||
}
|
||||
|
||||
// WasReplacedPlaceholderShorthand checks if a token string was
|
||||
// likely a replaced shorthand of the known Caddyfile placeholder
|
||||
// replacement outputs. Useful to prevent some user-defined map
|
||||
@@ -1550,9 +1426,8 @@ func (c counter) nextGroup() string {
|
||||
}
|
||||
|
||||
type namedCustomLog struct {
|
||||
name string
|
||||
hostnames []string
|
||||
log *caddy.CustomLog
|
||||
name string
|
||||
log *caddy.CustomLog
|
||||
}
|
||||
|
||||
// sbAddrAssociation is a mapping from a list of
|
||||
@@ -1563,10 +1438,7 @@ type sbAddrAssociation struct {
|
||||
serverBlocks []serverBlock
|
||||
}
|
||||
|
||||
const (
|
||||
matcherPrefix = "@"
|
||||
namedRouteKey = "named_route"
|
||||
)
|
||||
const matcherPrefix = "@"
|
||||
|
||||
// Interface guard
|
||||
var _ caddyfile.ServerType = (*ServerType)(nil)
|
||||
|
||||
@@ -17,13 +17,12 @@ package httpcaddyfile
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -34,7 +33,6 @@ func init() {
|
||||
RegisterGlobalOption("grace_period", parseOptDuration)
|
||||
RegisterGlobalOption("shutdown_delay", parseOptDuration)
|
||||
RegisterGlobalOption("default_sni", parseOptSingleString)
|
||||
RegisterGlobalOption("fallback_sni", parseOptSingleString)
|
||||
RegisterGlobalOption("order", parseOptOrder)
|
||||
RegisterGlobalOption("storage", parseOptStorage)
|
||||
RegisterGlobalOption("storage_clean_interval", parseOptDuration)
|
||||
@@ -56,7 +54,6 @@ func init() {
|
||||
RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions)
|
||||
RegisterGlobalOption("log", parseLogOptions)
|
||||
RegisterGlobalOption("preferred_chains", parseOptPreferredChains)
|
||||
RegisterGlobalOption("persist_config", parseOptPersistConfig)
|
||||
}
|
||||
|
||||
func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil }
|
||||
@@ -389,21 +386,6 @@ func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||
return ond, nil
|
||||
}
|
||||
|
||||
func parseOptPersistConfig(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||
d.Next() // consume parameter name
|
||||
if !d.Next() {
|
||||
return "", d.ArgErr()
|
||||
}
|
||||
val := d.Val()
|
||||
if d.Next() {
|
||||
return "", d.ArgErr()
|
||||
}
|
||||
if val != "off" {
|
||||
return "", d.Errf("persist_config must be 'off'")
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||
d.Next() // consume parameter name
|
||||
if !d.Next() {
|
||||
@@ -439,13 +421,13 @@ func parseOCSPStaplingOptions(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||
|
||||
// parseLogOptions parses the global log option. Syntax:
|
||||
//
|
||||
// log [name] {
|
||||
// output <writer_module> ...
|
||||
// format <encoder_module> ...
|
||||
// level <level>
|
||||
// include <namespaces...>
|
||||
// exclude <namespaces...>
|
||||
// }
|
||||
// log [name] {
|
||||
// output <writer_module> ...
|
||||
// format <encoder_module> ...
|
||||
// level <level>
|
||||
// include <namespaces...>
|
||||
// exclude <namespaces...>
|
||||
// }
|
||||
//
|
||||
// When the name argument is unspecified, this directive modifies the default
|
||||
// logger.
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package httpcaddyfile
|
||||
|
||||
import (
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||||
@@ -27,24 +26,23 @@ func init() {
|
||||
|
||||
// parsePKIApp parses the global log option. Syntax:
|
||||
//
|
||||
// pki {
|
||||
// ca [<id>] {
|
||||
// name <name>
|
||||
// root_cn <name>
|
||||
// intermediate_cn <name>
|
||||
// intermediate_lifetime <duration>
|
||||
// root {
|
||||
// cert <path>
|
||||
// key <path>
|
||||
// format <format>
|
||||
// }
|
||||
// intermediate {
|
||||
// cert <path>
|
||||
// key <path>
|
||||
// format <format>
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// pki {
|
||||
// ca [<id>] {
|
||||
// name <name>
|
||||
// root_cn <name>
|
||||
// intermediate_cn <name>
|
||||
// root {
|
||||
// cert <path>
|
||||
// key <path>
|
||||
// format <format>
|
||||
// }
|
||||
// intermediate {
|
||||
// cert <path>
|
||||
// key <path>
|
||||
// format <format>
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// When the CA ID is unspecified, 'local' is assumed.
|
||||
func parsePKIApp(d *caddyfile.Dispenser, existingVal any) (any, error) {
|
||||
@@ -85,16 +83,6 @@ func parsePKIApp(d *caddyfile.Dispenser, existingVal any) (any, error) {
|
||||
}
|
||||
pkiCa.IntermediateCommonName = d.Val()
|
||||
|
||||
case "intermediate_lifetime":
|
||||
if !d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
dur, err := caddy.ParseDuration(d.Val())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkiCa.IntermediateLifetime = caddy.Duration(dur)
|
||||
|
||||
case "root":
|
||||
if pkiCa.Root == nil {
|
||||
pkiCa.Root = new(caddypki.KeyPair)
|
||||
@@ -174,6 +162,7 @@ func (st ServerType) buildPKIApp(
|
||||
options map[string]any,
|
||||
warnings []caddyconfig.Warning,
|
||||
) (*caddypki.PKI, []caddyconfig.Warning, error) {
|
||||
|
||||
skipInstallTrust := false
|
||||
if _, ok := options["skip_install_trust"]; ok {
|
||||
skipInstallTrust = true
|
||||
|
||||
@@ -18,12 +18,11 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
// serverOptions collects server config overrides parsed from Caddyfile global options
|
||||
@@ -34,7 +33,6 @@ type serverOptions struct {
|
||||
ListenerAddress string
|
||||
|
||||
// These will all map 1:1 to the caddyhttp.Server struct
|
||||
Name string
|
||||
ListenerWrappersRaw []json.RawMessage
|
||||
ReadTimeout caddy.Duration
|
||||
ReadHeaderTimeout caddy.Duration
|
||||
@@ -42,13 +40,9 @@ type serverOptions struct {
|
||||
IdleTimeout caddy.Duration
|
||||
KeepAliveInterval caddy.Duration
|
||||
MaxHeaderBytes int
|
||||
EnableFullDuplex bool
|
||||
Protocols []string
|
||||
StrictSNIHost *bool
|
||||
TrustedProxiesRaw json.RawMessage
|
||||
ClientIPHeaders []string
|
||||
ShouldLogCredentials bool
|
||||
Metrics *caddyhttp.Metrics
|
||||
}
|
||||
|
||||
func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
||||
@@ -62,15 +56,6 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
||||
}
|
||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||
switch d.Val() {
|
||||
case "name":
|
||||
if serverOpts.ListenerAddress == "" {
|
||||
return nil, d.Errf("cannot set a name for a server without a listener address")
|
||||
}
|
||||
if !d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
serverOpts.Name = d.Val()
|
||||
|
||||
case "listener_wrappers":
|
||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||
modID := "caddy.listeners." + d.Val()
|
||||
@@ -159,12 +144,6 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
||||
}
|
||||
serverOpts.MaxHeaderBytes = int(size)
|
||||
|
||||
case "enable_full_duplex":
|
||||
if d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
serverOpts.EnableFullDuplex = true
|
||||
|
||||
case "log_credentials":
|
||||
if d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
@@ -182,7 +161,7 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
||||
}
|
||||
serverOpts.Protocols = append(serverOpts.Protocols, proto)
|
||||
}
|
||||
if nesting := d.Nesting(); d.NextBlock(nesting) {
|
||||
if d.NextBlock(0) {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
|
||||
@@ -196,48 +175,6 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
||||
}
|
||||
serverOpts.StrictSNIHost = &boolVal
|
||||
|
||||
case "trusted_proxies":
|
||||
if !d.NextArg() {
|
||||
return nil, d.Err("trusted_proxies expects an IP range source module name as its first argument")
|
||||
}
|
||||
modID := "http.ip_sources." + d.Val()
|
||||
unm, err := caddyfile.UnmarshalModule(d, modID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
source, ok := unm.(caddyhttp.IPRangeSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("module %s (%T) is not an IP range source", modID, unm)
|
||||
}
|
||||
jsonSource := caddyconfig.JSONModuleObject(
|
||||
source,
|
||||
"source",
|
||||
source.(caddy.Module).CaddyModule().ID.Name(),
|
||||
nil,
|
||||
)
|
||||
serverOpts.TrustedProxiesRaw = jsonSource
|
||||
|
||||
case "client_ip_headers":
|
||||
headers := d.RemainingArgs()
|
||||
for _, header := range headers {
|
||||
if sliceContains(serverOpts.ClientIPHeaders, header) {
|
||||
return nil, d.Errf("client IP header %s specified more than once", header)
|
||||
}
|
||||
serverOpts.ClientIPHeaders = append(serverOpts.ClientIPHeaders, header)
|
||||
}
|
||||
if nesting := d.Nesting(); d.NextBlock(nesting) {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
|
||||
case "metrics":
|
||||
if d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
if nesting := d.Nesting(); d.NextBlock(nesting) {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
serverOpts.Metrics = new(caddyhttp.Metrics)
|
||||
|
||||
// TODO: DEPRECATED. (August 2022)
|
||||
case "protocol":
|
||||
caddy.Log().Named("caddyfile").Warn("DEPRECATED: protocol sub-option will be removed soon")
|
||||
@@ -291,22 +228,7 @@ func applyServerOptions(
|
||||
return nil
|
||||
}
|
||||
|
||||
// check for duplicate names, which would clobber the config
|
||||
existingNames := map[string]bool{}
|
||||
for _, opts := range serverOpts {
|
||||
if opts.Name == "" {
|
||||
continue
|
||||
}
|
||||
if existingNames[opts.Name] {
|
||||
return fmt.Errorf("cannot use duplicate server name '%s'", opts.Name)
|
||||
}
|
||||
existingNames[opts.Name] = true
|
||||
}
|
||||
|
||||
// collect the server name overrides
|
||||
nameReplacements := map[string]string{}
|
||||
|
||||
for key, server := range servers {
|
||||
for _, server := range servers {
|
||||
// find the options that apply to this server
|
||||
opts := func() *serverOptions {
|
||||
for _, entry := range serverOpts {
|
||||
@@ -335,28 +257,14 @@ func applyServerOptions(
|
||||
server.IdleTimeout = opts.IdleTimeout
|
||||
server.KeepAliveInterval = opts.KeepAliveInterval
|
||||
server.MaxHeaderBytes = opts.MaxHeaderBytes
|
||||
server.EnableFullDuplex = opts.EnableFullDuplex
|
||||
server.Protocols = opts.Protocols
|
||||
server.StrictSNIHost = opts.StrictSNIHost
|
||||
server.TrustedProxiesRaw = opts.TrustedProxiesRaw
|
||||
server.ClientIPHeaders = opts.ClientIPHeaders
|
||||
server.Metrics = opts.Metrics
|
||||
if opts.ShouldLogCredentials {
|
||||
if server.Logs == nil {
|
||||
server.Logs = &caddyhttp.ServerLogConfig{}
|
||||
}
|
||||
server.Logs.ShouldLogCredentials = opts.ShouldLogCredentials
|
||||
}
|
||||
|
||||
if opts.Name != "" {
|
||||
nameReplacements[key] = opts.Name
|
||||
}
|
||||
}
|
||||
|
||||
// rename the servers if marked to do so
|
||||
for old, new := range nameReplacements {
|
||||
servers[new] = servers[old]
|
||||
delete(servers, old)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package httpcaddyfile
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
)
|
||||
|
||||
type ComplexShorthandReplacer struct {
|
||||
search *regexp.Regexp
|
||||
replace string
|
||||
}
|
||||
|
||||
type ShorthandReplacer struct {
|
||||
complex []ComplexShorthandReplacer
|
||||
simple *strings.Replacer
|
||||
}
|
||||
|
||||
func NewShorthandReplacer() ShorthandReplacer {
|
||||
// replace shorthand placeholders (which are convenient
|
||||
// when writing a Caddyfile) with their actual placeholder
|
||||
// identifiers or variable names
|
||||
replacer := strings.NewReplacer(placeholderShorthands()...)
|
||||
|
||||
// these are placeholders that allow a user-defined final
|
||||
// parameters, but we still want to provide a shorthand
|
||||
// for those, so we use a regexp to replace
|
||||
regexpReplacements := []ComplexShorthandReplacer{
|
||||
{regexp.MustCompile(`{header\.([\w-]*)}`), "{http.request.header.$1}"},
|
||||
{regexp.MustCompile(`{cookie\.([\w-]*)}`), "{http.request.cookie.$1}"},
|
||||
{regexp.MustCompile(`{labels\.([\w-]*)}`), "{http.request.host.labels.$1}"},
|
||||
{regexp.MustCompile(`{path\.([\w-]*)}`), "{http.request.uri.path.$1}"},
|
||||
{regexp.MustCompile(`{file\.([\w-]*)}`), "{http.request.uri.path.file.$1}"},
|
||||
{regexp.MustCompile(`{query\.([\w-]*)}`), "{http.request.uri.query.$1}"},
|
||||
{regexp.MustCompile(`{re\.([\w-]*)\.([\w-]*)}`), "{http.regexp.$1.$2}"},
|
||||
{regexp.MustCompile(`{vars\.([\w-]*)}`), "{http.vars.$1}"},
|
||||
{regexp.MustCompile(`{rp\.([\w-\.]*)}`), "{http.reverse_proxy.$1}"},
|
||||
{regexp.MustCompile(`{err\.([\w-\.]*)}`), "{http.error.$1}"},
|
||||
{regexp.MustCompile(`{file_match\.([\w-]*)}`), "{http.matchers.file.$1}"},
|
||||
}
|
||||
|
||||
return ShorthandReplacer{
|
||||
complex: regexpReplacements,
|
||||
simple: replacer,
|
||||
}
|
||||
}
|
||||
|
||||
// placeholderShorthands returns a slice of old-new string pairs,
|
||||
// where the left of the pair is a placeholder shorthand that may
|
||||
// be used in the Caddyfile, and the right is the replacement.
|
||||
func placeholderShorthands() []string {
|
||||
return []string{
|
||||
"{dir}", "{http.request.uri.path.dir}",
|
||||
"{file}", "{http.request.uri.path.file}",
|
||||
"{host}", "{http.request.host}",
|
||||
"{hostport}", "{http.request.hostport}",
|
||||
"{port}", "{http.request.port}",
|
||||
"{method}", "{http.request.method}",
|
||||
"{path}", "{http.request.uri.path}",
|
||||
"{query}", "{http.request.uri.query}",
|
||||
"{remote}", "{http.request.remote}",
|
||||
"{remote_host}", "{http.request.remote.host}",
|
||||
"{remote_port}", "{http.request.remote.port}",
|
||||
"{scheme}", "{http.request.scheme}",
|
||||
"{uri}", "{http.request.uri}",
|
||||
"{tls_cipher}", "{http.request.tls.cipher_suite}",
|
||||
"{tls_version}", "{http.request.tls.version}",
|
||||
"{tls_client_fingerprint}", "{http.request.tls.client.fingerprint}",
|
||||
"{tls_client_issuer}", "{http.request.tls.client.issuer}",
|
||||
"{tls_client_serial}", "{http.request.tls.client.serial}",
|
||||
"{tls_client_subject}", "{http.request.tls.client.subject}",
|
||||
"{tls_client_certificate_pem}", "{http.request.tls.client.certificate_pem}",
|
||||
"{tls_client_certificate_der_base64}", "{http.request.tls.client.certificate_der_base64}",
|
||||
"{upstream_hostport}", "{http.reverse_proxy.upstream.hostport}",
|
||||
"{client_ip}", "{http.vars.client_ip}",
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyToSegment replaces shorthand placeholder to its full placeholder, understandable by Caddy.
|
||||
func (s ShorthandReplacer) ApplyToSegment(segment *caddyfile.Segment) {
|
||||
if segment != nil {
|
||||
for i := 0; i < len(*segment); i++ {
|
||||
// simple string replacements
|
||||
(*segment)[i].Text = s.simple.Replace((*segment)[i].Text)
|
||||
// complex regexp replacements
|
||||
for _, r := range s.complex {
|
||||
(*segment)[i].Text = r.search.ReplaceAllString((*segment)[i].Text, r.replace)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
(t2) {
|
||||
respond 200 {
|
||||
body {args[:]}
|
||||
}
|
||||
}
|
||||
|
||||
:8082 {
|
||||
import t2 false
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
(t1) {
|
||||
respond 200 {
|
||||
body {args[:]}
|
||||
}
|
||||
}
|
||||
|
||||
:8081 {
|
||||
import t1 false
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
(t1) {
|
||||
respond 200 {
|
||||
body {args[:]}
|
||||
}
|
||||
}
|
||||
|
||||
:8081 {
|
||||
import t1 false
|
||||
}
|
||||
|
||||
import import_variadic.txt
|
||||
|
||||
:8083 {
|
||||
import t2 true
|
||||
}
|
||||
@@ -23,13 +23,12 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
)
|
||||
|
||||
func (st ServerType) buildTLSApp(
|
||||
@@ -37,6 +36,7 @@ func (st ServerType) buildTLSApp(
|
||||
options map[string]any,
|
||||
warnings []caddyconfig.Warning,
|
||||
) (*caddytls.TLS, []caddyconfig.Warning, error) {
|
||||
|
||||
tlsApp := &caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}
|
||||
var certLoaders []caddytls.CertificateLoader
|
||||
|
||||
@@ -44,32 +44,37 @@ func (st ServerType) buildTLSApp(
|
||||
if hp, ok := options["http_port"].(int); ok {
|
||||
httpPort = strconv.Itoa(hp)
|
||||
}
|
||||
autoHTTPS := "on"
|
||||
if ah, ok := options["auto_https"].(string); ok {
|
||||
autoHTTPS = ah
|
||||
httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort)
|
||||
if hsp, ok := options["https_port"].(int); ok {
|
||||
httpsPort = strconv.Itoa(hsp)
|
||||
}
|
||||
|
||||
// find all hosts that share a server block with a hostless
|
||||
// key, so that they don't get forgotten/omitted by auto-HTTPS
|
||||
// (since they won't appear in route matchers)
|
||||
// count how many server blocks have a TLS-enabled key with
|
||||
// no host, and find all hosts that share a server block with
|
||||
// a hostless key, so that they don't get forgotten/omitted
|
||||
// by auto-HTTPS (since they won't appear in route matchers)
|
||||
var serverBlocksWithTLSHostlessKey int
|
||||
httpsHostsSharedWithHostlessKey := make(map[string]struct{})
|
||||
if autoHTTPS != "off" {
|
||||
for _, pair := range pairings {
|
||||
for _, sb := range pair.serverBlocks {
|
||||
for _, addr := range sb.keys {
|
||||
if addr.Host == "" {
|
||||
// this server block has a hostless key, now
|
||||
// go through and add all the hosts to the set
|
||||
for _, otherAddr := range sb.keys {
|
||||
if otherAddr.Original == addr.Original {
|
||||
continue
|
||||
}
|
||||
if otherAddr.Host != "" && otherAddr.Scheme != "http" && otherAddr.Port != httpPort {
|
||||
httpsHostsSharedWithHostlessKey[otherAddr.Host] = struct{}{}
|
||||
}
|
||||
}
|
||||
break
|
||||
for _, pair := range pairings {
|
||||
for _, sb := range pair.serverBlocks {
|
||||
for _, addr := range sb.keys {
|
||||
if addr.Host == "" {
|
||||
// this address has no hostname, but if it's explicitly set
|
||||
// to HTTPS, then we need to count it as being TLS-enabled
|
||||
if addr.Scheme == "https" || addr.Port == httpsPort {
|
||||
serverBlocksWithTLSHostlessKey++
|
||||
}
|
||||
// this server block has a hostless key, now
|
||||
// go through and add all the hosts to the set
|
||||
for _, otherAddr := range sb.keys {
|
||||
if otherAddr.Original == addr.Original {
|
||||
continue
|
||||
}
|
||||
if otherAddr.Host != "" && otherAddr.Scheme != "http" && otherAddr.Port != httpPort {
|
||||
httpsHostsSharedWithHostlessKey[otherAddr.Host] = struct{}{}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,19 +134,6 @@ func (st ServerType) buildTLSApp(
|
||||
issuers = append(issuers, issuerVal.Value.(certmagic.Issuer))
|
||||
}
|
||||
if ap == catchAllAP && !reflect.DeepEqual(ap.Issuers, issuers) {
|
||||
// this more correctly implements an error check that was removed
|
||||
// below; try it with this config:
|
||||
//
|
||||
// :443 {
|
||||
// bind 127.0.0.1
|
||||
// }
|
||||
//
|
||||
// :443 {
|
||||
// bind ::1
|
||||
// tls {
|
||||
// issuer acme
|
||||
// }
|
||||
// }
|
||||
return nil, warnings, fmt.Errorf("automation policy from site block is also default/catch-all policy because of key without hostname, and the two are in conflict: %#v != %#v", ap.Issuers, issuers)
|
||||
}
|
||||
ap.Issuers = issuers
|
||||
@@ -184,30 +176,34 @@ func (st ServerType) buildTLSApp(
|
||||
}
|
||||
}
|
||||
|
||||
// we used to ensure this block is allowed to create an automation policy;
|
||||
// doing so was forbidden if it has a key with no host (i.e. ":443")
|
||||
// first make sure this block is allowed to create an automation policy;
|
||||
// doing so is forbidden if it has a key with no host (i.e. ":443")
|
||||
// and if there is a different server block that also has a key with no
|
||||
// host -- since a key with no host matches any host, we need its
|
||||
// associated automation policy to have an empty Subjects list, i.e. no
|
||||
// host filter, which is indistinguishable between the two server blocks
|
||||
// because automation is not done in the context of a particular server...
|
||||
// this is an example of a poor mapping from Caddyfile to JSON but that's
|
||||
// the least-leaky abstraction I could figure out -- however, this check
|
||||
// was preventing certain listeners, like those provided by plugins, from
|
||||
// being used as desired (see the Tailscale listener plugin), so I removed
|
||||
// the check: and I think since I originally wrote the check I added a new
|
||||
// check above which *properly* detects this ambiguity without breaking the
|
||||
// listener plugin; see the check above with a commented example config
|
||||
if len(sblockHosts) == 0 && catchAllAP == nil {
|
||||
// this server block has a key with no hosts, but there is not yet
|
||||
// a catch-all automation policy (probably because no global options
|
||||
// were set), so this one becomes it
|
||||
catchAllAP = ap
|
||||
// the least-leaky abstraction I could figure out
|
||||
if len(sblockHosts) == 0 {
|
||||
if serverBlocksWithTLSHostlessKey > 1 {
|
||||
// this server block and at least one other has a key with no host,
|
||||
// making the two indistinguishable; it is misleading to define such
|
||||
// a policy within one server block since it actually will apply to
|
||||
// others as well
|
||||
return nil, warnings, fmt.Errorf("cannot make a TLS automation policy from a server block that has a host-less address when there are other TLS-enabled server block addresses lacking a host")
|
||||
}
|
||||
if catchAllAP == nil {
|
||||
// this server block has a key with no hosts, but there is not yet
|
||||
// a catch-all automation policy (probably because no global options
|
||||
// were set), so this one becomes it
|
||||
catchAllAP = ap
|
||||
}
|
||||
}
|
||||
|
||||
// associate our new automation policy with this server block's hosts
|
||||
ap.SubjectsRaw = sblock.hostsFromKeysNotHTTP(httpPort)
|
||||
sort.Strings(ap.SubjectsRaw) // solely for deterministic test results
|
||||
ap.Subjects = sblock.hostsFromKeysNotHTTP(httpPort)
|
||||
sort.Strings(ap.Subjects) // solely for deterministic test results
|
||||
|
||||
// if a combination of public and internal names were given
|
||||
// for this same server block and no issuer was specified, we
|
||||
@@ -217,11 +213,7 @@ func (st ServerType) buildTLSApp(
|
||||
var ap2 *caddytls.AutomationPolicy
|
||||
if len(ap.Issuers) == 0 {
|
||||
var internal, external []string
|
||||
for _, s := range ap.SubjectsRaw {
|
||||
// do not create Issuers for Tailscale domains; they will be given a Manager instead
|
||||
if strings.HasSuffix(strings.ToLower(s), ".ts.net") {
|
||||
continue
|
||||
}
|
||||
for _, s := range ap.Subjects {
|
||||
if !certmagic.SubjectQualifiesForCert(s) {
|
||||
return nil, warnings, fmt.Errorf("subject does not qualify for certificate: '%s'", s)
|
||||
}
|
||||
@@ -239,10 +231,10 @@ func (st ServerType) buildTLSApp(
|
||||
}
|
||||
}
|
||||
if len(external) > 0 && len(internal) > 0 {
|
||||
ap.SubjectsRaw = external
|
||||
ap.Subjects = external
|
||||
apCopy := *ap
|
||||
ap2 = &apCopy
|
||||
ap2.SubjectsRaw = internal
|
||||
ap2.Subjects = internal
|
||||
ap2.IssuersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)}
|
||||
}
|
||||
}
|
||||
@@ -339,18 +331,16 @@ func (st ServerType) buildTLSApp(
|
||||
internalAP := &caddytls.AutomationPolicy{
|
||||
IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
|
||||
}
|
||||
if autoHTTPS != "off" {
|
||||
for h := range httpsHostsSharedWithHostlessKey {
|
||||
al = append(al, h)
|
||||
if !certmagic.SubjectQualifiesForPublicCert(h) {
|
||||
internalAP.SubjectsRaw = append(internalAP.SubjectsRaw, h)
|
||||
}
|
||||
for h := range httpsHostsSharedWithHostlessKey {
|
||||
al = append(al, h)
|
||||
if !certmagic.SubjectQualifiesForPublicCert(h) {
|
||||
internalAP.Subjects = append(internalAP.Subjects, h)
|
||||
}
|
||||
}
|
||||
if len(al) > 0 {
|
||||
tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings)
|
||||
}
|
||||
if len(internalAP.SubjectsRaw) > 0 {
|
||||
if len(internalAP.Subjects) > 0 {
|
||||
if tlsApp.Automation == nil {
|
||||
tlsApp.Automation = new(caddytls.AutomationConfig)
|
||||
}
|
||||
@@ -416,7 +406,7 @@ func (st ServerType) buildTLSApp(
|
||||
// for convenience)
|
||||
automationHostSet := make(map[string]struct{})
|
||||
for _, ap := range tlsApp.Automation.Policies {
|
||||
for _, s := range ap.SubjectsRaw {
|
||||
for _, s := range ap.Subjects {
|
||||
if _, ok := automationHostSet[s]; ok {
|
||||
return nil, warnings, fmt.Errorf("hostname appears in more than one automation policy, making certificate management ambiguous: %s", s)
|
||||
}
|
||||
@@ -537,7 +527,7 @@ func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls
|
||||
if automationPolicyIsSubset(aps[j], aps[i]) {
|
||||
return false
|
||||
}
|
||||
return len(aps[i].SubjectsRaw) > len(aps[j].SubjectsRaw)
|
||||
return len(aps[i].Subjects) > len(aps[j].Subjects)
|
||||
})
|
||||
|
||||
emptyAPCount := 0
|
||||
@@ -545,7 +535,7 @@ func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls
|
||||
// compute the number of empty policies (disregarding subjects) - see #4128
|
||||
emptyAP := new(caddytls.AutomationPolicy)
|
||||
for i := 0; i < len(aps); i++ {
|
||||
emptyAP.SubjectsRaw = aps[i].SubjectsRaw
|
||||
emptyAP.Subjects = aps[i].Subjects
|
||||
if reflect.DeepEqual(aps[i], emptyAP) {
|
||||
emptyAPCount++
|
||||
if !automationPolicyHasAllPublicNames(aps[i]) {
|
||||
@@ -587,7 +577,7 @@ outer:
|
||||
aps[i].KeyType == aps[j].KeyType &&
|
||||
aps[i].OnDemand == aps[j].OnDemand &&
|
||||
aps[i].RenewalWindowRatio == aps[j].RenewalWindowRatio {
|
||||
if len(aps[i].SubjectsRaw) > 0 && len(aps[j].SubjectsRaw) == 0 {
|
||||
if len(aps[i].Subjects) > 0 && len(aps[j].Subjects) == 0 {
|
||||
// later policy (at j) has no subjects ("catch-all"), so we can
|
||||
// remove the identical-but-more-specific policy that comes first
|
||||
// AS LONG AS it is not shadowed by another policy before it; e.g.
|
||||
@@ -602,9 +592,9 @@ outer:
|
||||
}
|
||||
} else {
|
||||
// avoid repeated subjects
|
||||
for _, subj := range aps[j].SubjectsRaw {
|
||||
if !sliceContains(aps[i].SubjectsRaw, subj) {
|
||||
aps[i].SubjectsRaw = append(aps[i].SubjectsRaw, subj)
|
||||
for _, subj := range aps[j].Subjects {
|
||||
if !sliceContains(aps[i].Subjects, subj) {
|
||||
aps[i].Subjects = append(aps[i].Subjects, subj)
|
||||
}
|
||||
}
|
||||
aps = append(aps[:j], aps[j+1:]...)
|
||||
@@ -620,15 +610,15 @@ outer:
|
||||
// automationPolicyIsSubset returns true if a's subjects are a subset
|
||||
// of b's subjects.
|
||||
func automationPolicyIsSubset(a, b *caddytls.AutomationPolicy) bool {
|
||||
if len(b.SubjectsRaw) == 0 {
|
||||
if len(b.Subjects) == 0 {
|
||||
return true
|
||||
}
|
||||
if len(a.SubjectsRaw) == 0 {
|
||||
if len(a.Subjects) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, aSubj := range a.SubjectsRaw {
|
||||
for _, aSubj := range a.Subjects {
|
||||
var inSuperset bool
|
||||
for _, bSubj := range b.SubjectsRaw {
|
||||
for _, bSubj := range b.Subjects {
|
||||
if certmagic.MatchWildcard(aSubj, bSubj) {
|
||||
inSuperset = true
|
||||
break
|
||||
@@ -666,7 +656,7 @@ func subjectQualifiesForPublicCert(ap *caddytls.AutomationPolicy, subj string) b
|
||||
}
|
||||
|
||||
func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
|
||||
for _, subj := range ap.SubjectsRaw {
|
||||
for _, subj := range ap.Subjects {
|
||||
if !subjectQualifiesForPublicCert(ap, subj) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ func TestAutomationPolicyIsSubset(t *testing.T) {
|
||||
expect: false,
|
||||
},
|
||||
} {
|
||||
apA := &caddytls.AutomationPolicy{SubjectsRaw: test.a}
|
||||
apB := &caddytls.AutomationPolicy{SubjectsRaw: test.b}
|
||||
apA := &caddytls.AutomationPolicy{Subjects: test.a}
|
||||
apB := &caddytls.AutomationPolicy{Subjects: test.b}
|
||||
if actual := automationPolicyIsSubset(apA, apB); actual != test.expect {
|
||||
t.Errorf("Test %d: Expected %t but got %t (A: %v B: %v)", i, test.expect, actual, test.a, test.b)
|
||||
}
|
||||
|
||||
@@ -30,14 +30,8 @@ func init() {
|
||||
caddy.RegisterModule(HTTPLoader{})
|
||||
}
|
||||
|
||||
// HTTPLoader can load Caddy configs over HTTP(S).
|
||||
//
|
||||
// If the response is not a JSON config, a config adapter must be specified
|
||||
// either in the loader config (`adapter`), or in the Content-Type HTTP header
|
||||
// returned in the HTTP response from the server. The Content-Type header is
|
||||
// read just like the admin API's `/load` endpoint. Uf you don't have control
|
||||
// over the HTTP server (but can still trust its response), you can override
|
||||
// the Content-Type header by setting the `adapter` property in this config.
|
||||
// HTTPLoader can load Caddy configs over HTTP(S). It can adapt the config
|
||||
// based on the Content-Type header of the HTTP response.
|
||||
type HTTPLoader struct {
|
||||
// The method for the request. Default: GET
|
||||
Method string `json:"method,omitempty"`
|
||||
@@ -51,11 +45,6 @@ type HTTPLoader struct {
|
||||
// Maximum time allowed for a complete connection and request.
|
||||
Timeout caddy.Duration `json:"timeout,omitempty"`
|
||||
|
||||
// The name of the config adapter to use, if any. Only needed
|
||||
// if the HTTP response is not a JSON config and if the server's
|
||||
// Content-Type header is missing or incorrect.
|
||||
Adapter string `json:"adapter,omitempty"`
|
||||
|
||||
TLS *struct {
|
||||
// Present this instance's managed remote identity credentials to the server.
|
||||
UseServerIdentity bool `json:"use_server_identity,omitempty"`
|
||||
@@ -105,7 +94,7 @@ func (hl HTTPLoader) LoadConfig(ctx caddy.Context) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := doHttpCallWithRetries(ctx, client, req)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -119,54 +108,17 @@ func (hl HTTPLoader) LoadConfig(ctx caddy.Context) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// adapt the config based on either manually-configured adapter or server's response header
|
||||
ct := resp.Header.Get("Content-Type")
|
||||
if hl.Adapter != "" {
|
||||
ct = "text/" + hl.Adapter
|
||||
}
|
||||
result, warnings, err := adaptByContentType(ct, body)
|
||||
result, warnings, err := adaptByContentType(resp.Header.Get("Content-Type"), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, warn := range warnings {
|
||||
ctx.Logger().Warn(warn.String())
|
||||
ctx.Logger(hl).Warn(warn.String())
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func attemptHttpCall(client *http.Client, request *http.Request) (*http.Response, error) {
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("problem calling http loader url: %v", err)
|
||||
} else if resp.StatusCode < 200 || resp.StatusCode > 499 {
|
||||
resp.Body.Close()
|
||||
return nil, fmt.Errorf("bad response status code from http loader url: %v", resp.StatusCode)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func doHttpCallWithRetries(ctx caddy.Context, client *http.Client, request *http.Request) (*http.Response, error) {
|
||||
var resp *http.Response
|
||||
var err error
|
||||
const maxAttempts = 10
|
||||
|
||||
for i := 0; i < maxAttempts; i++ {
|
||||
resp, err = attemptHttpCall(client, request)
|
||||
if err != nil && i < maxAttempts-1 {
|
||||
select {
|
||||
case <-time.After(time.Millisecond * 500):
|
||||
case <-ctx.Done():
|
||||
return resp, ctx.Err()
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (hl HTTPLoader) makeClient(ctx caddy.Context) (*http.Client, error) {
|
||||
client := &http.Client{
|
||||
Timeout: time.Duration(hl.Timeout),
|
||||
@@ -177,7 +129,7 @@ func (hl HTTPLoader) makeClient(ctx caddy.Context) (*http.Client, error) {
|
||||
|
||||
// client authentication
|
||||
if hl.TLS.UseServerIdentity {
|
||||
certs, err := ctx.IdentityCredentials(ctx.Logger())
|
||||
certs, err := ctx.IdentityCredentials(ctx.Logger(hl))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting server identity credentials: %v", err)
|
||||
}
|
||||
|
||||
+26
-28
@@ -22,10 +22,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/aryann/difflib"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
// plug in Caddy modules here
|
||||
_ "github.com/caddyserver/caddy/v2/modules/standard"
|
||||
)
|
||||
@@ -44,7 +43,7 @@ type Defaults struct {
|
||||
|
||||
// Default testing values
|
||||
var Default = Defaults{
|
||||
AdminPort: 2999, // different from what a real server also running on a developer's machine might be
|
||||
AdminPort: 2019,
|
||||
Certifcates: []string{"/caddy.localhost.crt", "/caddy.localhost.key"},
|
||||
TestRequestTimeout: 5 * time.Second,
|
||||
LoadRequestTimeout: 5 * time.Second,
|
||||
@@ -64,6 +63,7 @@ type Tester struct {
|
||||
|
||||
// NewTester will create a new testing client with an attached cookie jar
|
||||
func NewTester(t *testing.T) *Tester {
|
||||
|
||||
jar, err := cookiejar.New(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create cookiejar: %s", err)
|
||||
@@ -94,6 +94,7 @@ func timeElapsed(start time.Time, name string) {
|
||||
// InitServer this will configure the server with a configurion of a specific
|
||||
// type. The configType must be either "json" or the adapter type.
|
||||
func (tc *Tester) InitServer(rawConfig string, configType string) {
|
||||
|
||||
if err := tc.initServer(rawConfig, configType); err != nil {
|
||||
tc.t.Logf("failed to load config: %s", err)
|
||||
tc.t.Fail()
|
||||
@@ -107,12 +108,13 @@ func (tc *Tester) InitServer(rawConfig string, configType string) {
|
||||
// InitServer this will configure the server with a configurion of a specific
|
||||
// type. The configType must be either "json" or the adapter type.
|
||||
func (tc *Tester) initServer(rawConfig string, configType string) error {
|
||||
|
||||
if testing.Short() {
|
||||
tc.t.SkipNow()
|
||||
return nil
|
||||
}
|
||||
|
||||
err := validateTestPrerequisites(tc.t)
|
||||
err := validateTestPrerequisites()
|
||||
if err != nil {
|
||||
tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err)
|
||||
return nil
|
||||
@@ -212,24 +214,20 @@ func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error
|
||||
return actual
|
||||
}
|
||||
|
||||
for retries := 10; retries > 0; retries-- {
|
||||
for retries := 4; retries > 0; retries-- {
|
||||
if reflect.DeepEqual(expected, fetchConfig(client)) {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
tc.t.Errorf("POSTed configuration isn't active")
|
||||
return errors.New("EnsureConfigRunning: POSTed configuration isn't active")
|
||||
}
|
||||
|
||||
const initConfig = `{
|
||||
admin localhost:2999
|
||||
}
|
||||
`
|
||||
|
||||
// validateTestPrerequisites ensures the certificates are available in the
|
||||
// designated path and Caddy sub-process is running.
|
||||
func validateTestPrerequisites(t *testing.T) error {
|
||||
func validateTestPrerequisites() error {
|
||||
|
||||
// check certificates are found
|
||||
for _, certName := range Default.Certifcates {
|
||||
if _, err := os.Stat(getIntegrationDir() + certName); os.IsNotExist(err) {
|
||||
@@ -238,27 +236,15 @@ func validateTestPrerequisites(t *testing.T) error {
|
||||
}
|
||||
|
||||
if isCaddyAdminRunning() != nil {
|
||||
// setup the init config file, and set the cleanup afterwards
|
||||
f, err := os.CreateTemp("", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
os.Remove(f.Name())
|
||||
})
|
||||
if _, err := f.WriteString(initConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// start inprocess caddy server
|
||||
os.Args = []string{"caddy", "run", "--config", f.Name(), "--adapter", "caddyfile"}
|
||||
os.Args = []string{"caddy", "run"}
|
||||
go func() {
|
||||
caddycmd.Main()
|
||||
}()
|
||||
|
||||
// wait for caddy to start serving the initial config
|
||||
for retries := 10; retries > 0 && isCaddyAdminRunning() != nil; retries-- {
|
||||
time.Sleep(1 * time.Second)
|
||||
for retries := 4; retries > 0 && isCaddyAdminRunning() != nil; retries-- {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,6 +267,7 @@ func isCaddyAdminRunning() error {
|
||||
}
|
||||
|
||||
func getIntegrationDir() string {
|
||||
|
||||
_, filename, _, ok := runtime.Caller(1)
|
||||
if !ok {
|
||||
panic("unable to determine the current file path")
|
||||
@@ -300,6 +287,7 @@ func prependCaddyFilePath(rawConfig string) string {
|
||||
|
||||
// CreateTestingTransport creates a testing transport that forces call dialing connections to happen locally
|
||||
func CreateTestingTransport() *http.Transport {
|
||||
|
||||
dialer := net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
KeepAlive: 5 * time.Second,
|
||||
@@ -327,6 +315,7 @@ func CreateTestingTransport() *http.Transport {
|
||||
|
||||
// AssertLoadError will load a config and expect an error
|
||||
func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) {
|
||||
|
||||
tc := NewTester(t)
|
||||
|
||||
err := tc.initServer(rawConfig, configType)
|
||||
@@ -337,6 +326,7 @@ func AssertLoadError(t *testing.T, rawConfig string, configType string, expected
|
||||
|
||||
// AssertRedirect makes a request and asserts the redirection happens
|
||||
func (tc *Tester) AssertRedirect(requestURI string, expectedToLocation string, expectedStatusCode int) *http.Response {
|
||||
|
||||
redirectPolicyFunc := func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
@@ -374,6 +364,7 @@ func (tc *Tester) AssertRedirect(requestURI string, expectedToLocation string, e
|
||||
|
||||
// CompareAdapt adapts a config and then compares it against an expected result
|
||||
func CompareAdapt(t *testing.T, filename, rawConfig string, adapterName string, expectedResponse string) bool {
|
||||
|
||||
cfgAdapter := caddyconfig.GetAdapter(adapterName)
|
||||
if cfgAdapter == nil {
|
||||
t.Logf("unrecognized config adapter '%s'", adapterName)
|
||||
@@ -461,13 +452,14 @@ func applyHeaders(t *testing.T, req *http.Request, requestHeaders []string) {
|
||||
|
||||
// AssertResponseCode will execute the request and verify the status code, returns a response for additional assertions
|
||||
func (tc *Tester) AssertResponseCode(req *http.Request, expectedStatusCode int) *http.Response {
|
||||
|
||||
resp, err := tc.Client.Do(req)
|
||||
if err != nil {
|
||||
tc.t.Fatalf("failed to call server %s", err)
|
||||
}
|
||||
|
||||
if expectedStatusCode != resp.StatusCode {
|
||||
tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", req.URL.RequestURI(), expectedStatusCode, resp.StatusCode)
|
||||
tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", req.RequestURI, expectedStatusCode, resp.StatusCode)
|
||||
}
|
||||
|
||||
return resp
|
||||
@@ -475,6 +467,7 @@ func (tc *Tester) AssertResponseCode(req *http.Request, expectedStatusCode int)
|
||||
|
||||
// AssertResponse request a URI and assert the status code and the body contains a string
|
||||
func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
resp := tc.AssertResponseCode(req, expectedStatusCode)
|
||||
|
||||
defer resp.Body.Close()
|
||||
@@ -496,6 +489,7 @@ func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expe
|
||||
|
||||
// AssertGetResponse GET a URI and expect a statusCode and body text
|
||||
func (tc *Tester) AssertGetResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("GET", requestURI, nil)
|
||||
if err != nil {
|
||||
tc.t.Fatalf("unable to create request %s", err)
|
||||
@@ -506,6 +500,7 @@ func (tc *Tester) AssertGetResponse(requestURI string, expectedStatusCode int, e
|
||||
|
||||
// AssertDeleteResponse request a URI and expect a statusCode and body text
|
||||
func (tc *Tester) AssertDeleteResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("DELETE", requestURI, nil)
|
||||
if err != nil {
|
||||
tc.t.Fatalf("unable to create request %s", err)
|
||||
@@ -516,6 +511,7 @@ func (tc *Tester) AssertDeleteResponse(requestURI string, expectedStatusCode int
|
||||
|
||||
// AssertPostResponseBody POST to a URI and assert the response code and body
|
||||
func (tc *Tester) AssertPostResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("POST", requestURI, requestBody)
|
||||
if err != nil {
|
||||
tc.t.Errorf("failed to create request %s", err)
|
||||
@@ -529,6 +525,7 @@ func (tc *Tester) AssertPostResponseBody(requestURI string, requestHeaders []str
|
||||
|
||||
// AssertPutResponseBody PUT to a URI and assert the response code and body
|
||||
func (tc *Tester) AssertPutResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("PUT", requestURI, requestBody)
|
||||
if err != nil {
|
||||
tc.t.Errorf("failed to create request %s", err)
|
||||
@@ -542,6 +539,7 @@ func (tc *Tester) AssertPutResponseBody(requestURI string, requestHeaders []stri
|
||||
|
||||
// AssertPatchResponseBody PATCH to a URI and assert the response code and body
|
||||
func (tc *Tester) AssertPatchResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("PATCH", requestURI, requestBody)
|
||||
if err != nil {
|
||||
tc.t.Errorf("failed to create request %s", err)
|
||||
|
||||
@@ -11,8 +11,6 @@ func TestAutoHTTPtoHTTPSRedirectsImplicitPort(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
admin localhost:2999
|
||||
skip_install_trust
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
}
|
||||
@@ -27,8 +25,6 @@ func TestAutoHTTPtoHTTPSRedirectsExplicitPortSameAsHTTPSPort(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
}
|
||||
@@ -43,8 +39,6 @@ func TestAutoHTTPtoHTTPSRedirectsExplicitPortDifferentFromHTTPSPort(t *testing.T
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
}
|
||||
@@ -59,9 +53,6 @@ func TestAutoHTTPRedirectsWithHTTPListenerFirstInAddresses(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"admin": {
|
||||
"listen": "localhost:2999"
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
@@ -83,14 +74,7 @@ func TestAutoHTTPRedirectsWithHTTPListenerFirstInAddresses(t *testing.T) {
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities": {
|
||||
"local": {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
@@ -101,8 +85,6 @@ func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAll(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
local_certs
|
||||
@@ -126,8 +108,6 @@ func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAllWithNoExplicitHTTPSit
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
local_certs
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
{
|
||||
pki {
|
||||
ca internal {
|
||||
name "Internal"
|
||||
root_cn "Internal Root Cert"
|
||||
intermediate_cn "Internal Intermediate Cert"
|
||||
}
|
||||
ca internal-long-lived {
|
||||
name "Long-lived"
|
||||
root_cn "Internal Root Cert 2"
|
||||
intermediate_cn "Internal Intermediate Cert 2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
acme-internal.example.com {
|
||||
acme_server {
|
||||
ca internal
|
||||
}
|
||||
}
|
||||
|
||||
acme-long-lived.example.com {
|
||||
acme_server {
|
||||
ca internal-long-lived
|
||||
lifetime 7d
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"acme-long-lived.example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"ca": "internal-long-lived",
|
||||
"handler": "acme_server",
|
||||
"lifetime": 604800000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"acme-internal.example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"ca": "internal",
|
||||
"handler": "acme_server"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities": {
|
||||
"internal": {
|
||||
"name": "Internal",
|
||||
"root_common_name": "Internal Root Cert",
|
||||
"intermediate_common_name": "Internal Intermediate Cert"
|
||||
},
|
||||
"internal-long-lived": {
|
||||
"name": "Long-lived",
|
||||
"root_common_name": "Internal Root Cert 2",
|
||||
"intermediate_common_name": "Internal Intermediate Cert 2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
:8443 {
|
||||
tls internal {
|
||||
on_demand
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8443"
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"automation": {
|
||||
"policies": [
|
||||
{
|
||||
"issuers": [
|
||||
{
|
||||
"module": "internal"
|
||||
}
|
||||
],
|
||||
"on_demand": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ encode gzip zstd {
|
||||
header Content-Type application/xhtml+xml*
|
||||
header Content-Type application/atom+xml*
|
||||
header Content-Type application/rss+xml*
|
||||
header Content-Type application/wasm*
|
||||
header Content-Type image/svg+xml*
|
||||
}
|
||||
}
|
||||
@@ -48,7 +47,6 @@ encode {
|
||||
"application/xhtml+xml*",
|
||||
"application/atom+xml*",
|
||||
"application/rss+xml*",
|
||||
"application/wasm*",
|
||||
"image/svg+xml*"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -69,11 +69,11 @@
|
||||
}
|
||||
],
|
||||
"on_demand": {
|
||||
"ask": "https://example.com",
|
||||
"rate_limit": {
|
||||
"interval": 30000000000,
|
||||
"burst": 20
|
||||
}
|
||||
},
|
||||
"ask": "https://example.com"
|
||||
}
|
||||
},
|
||||
"disable_ocsp_stapling": true
|
||||
|
||||
@@ -78,11 +78,11 @@
|
||||
}
|
||||
],
|
||||
"on_demand": {
|
||||
"ask": "https://example.com",
|
||||
"rate_limit": {
|
||||
"interval": 30000000000,
|
||||
"burst": 20
|
||||
}
|
||||
},
|
||||
"ask": "https://example.com"
|
||||
},
|
||||
"ocsp_interval": 172800000000000,
|
||||
"renew_interval": 86400000000000,
|
||||
|
||||
@@ -71,11 +71,11 @@
|
||||
}
|
||||
],
|
||||
"on_demand": {
|
||||
"ask": "https://example.com",
|
||||
"rate_limit": {
|
||||
"interval": 30000000000,
|
||||
"burst": 20
|
||||
}
|
||||
},
|
||||
"ask": "https://example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
{
|
||||
http_port 8080
|
||||
persist_config off
|
||||
admin {
|
||||
origins localhost:2019 [::1]:2019 127.0.0.1:2019 192.168.10.128
|
||||
}
|
||||
}
|
||||
|
||||
:80
|
||||
----------
|
||||
{
|
||||
"admin": {
|
||||
"listen": "localhost:2019",
|
||||
"origins": [
|
||||
"localhost:2019",
|
||||
"[::1]:2019",
|
||||
"127.0.0.1:2019",
|
||||
"192.168.10.128"
|
||||
],
|
||||
"config": {
|
||||
"persist": false
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 8080,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
persist_config off
|
||||
}
|
||||
|
||||
:8881 {
|
||||
}
|
||||
----------
|
||||
{
|
||||
"admin": {
|
||||
"config": {
|
||||
"persist": false
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8881"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,4 +165,4 @@ acme-bar.example.com {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,9 @@
|
||||
timeouts {
|
||||
idle 90s
|
||||
}
|
||||
strict_sni_host insecure_off
|
||||
protocol {
|
||||
strict_sni_host insecure_off
|
||||
}
|
||||
}
|
||||
servers :80 {
|
||||
timeouts {
|
||||
@@ -14,7 +16,9 @@
|
||||
timeouts {
|
||||
idle 30s
|
||||
}
|
||||
strict_sni_host
|
||||
protocol {
|
||||
strict_sni_host
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,13 +11,9 @@
|
||||
idle 30s
|
||||
}
|
||||
max_header_size 100MB
|
||||
enable_full_duplex
|
||||
log_credentials
|
||||
protocols h1 h2 h2c h3
|
||||
strict_sni_host
|
||||
trusted_proxies static private_ranges
|
||||
client_ip_headers Custom-Real-Client-IP X-Forwarded-For
|
||||
client_ip_headers A-Third-One
|
||||
protocols h1 h2 h2c h3
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +42,6 @@ foo.com {
|
||||
"write_timeout": 30000000000,
|
||||
"idle_timeout": 30000000000,
|
||||
"max_header_bytes": 100000000,
|
||||
"enable_full_duplex": true,
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
@@ -60,22 +55,6 @@ foo.com {
|
||||
}
|
||||
],
|
||||
"strict_sni_host": true,
|
||||
"trusted_proxies": {
|
||||
"ranges": [
|
||||
"192.168.0.0/16",
|
||||
"172.16.0.0/12",
|
||||
"10.0.0.0/8",
|
||||
"127.0.0.1/8",
|
||||
"fd00::/8",
|
||||
"::1"
|
||||
],
|
||||
"source": "static"
|
||||
},
|
||||
"client_ip_headers": [
|
||||
"Custom-Real-Client-IP",
|
||||
"X-Forwarded-For",
|
||||
"A-Third-One"
|
||||
],
|
||||
"logs": {
|
||||
"should_log_credentials": true
|
||||
},
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
:8881 {
|
||||
route {
|
||||
handle /foo/* {
|
||||
respond "Foo"
|
||||
}
|
||||
handle {
|
||||
respond "Bar"
|
||||
}
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8881"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "Foo",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/foo/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "Bar",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,6 @@
|
||||
+Link "Foo"
|
||||
+Link "Bar"
|
||||
}
|
||||
header >Set Defer
|
||||
header >Replace Deferred Replacement
|
||||
}
|
||||
----------
|
||||
{
|
||||
@@ -138,31 +136,6 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"handler": "headers",
|
||||
"response": {
|
||||
"deferred": true,
|
||||
"set": {
|
||||
"Set": [
|
||||
"Defer"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"handler": "headers",
|
||||
"response": {
|
||||
"deferred": true,
|
||||
"replace": {
|
||||
"Replace": [
|
||||
{
|
||||
"replace": "Replacement",
|
||||
"search_regexp": "Deferred"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
example.com {
|
||||
respond <<EOF
|
||||
<html>
|
||||
<head><title>Foo</title>
|
||||
<body>Foo</body>
|
||||
</html>
|
||||
EOF 200
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "\u003chtml\u003e\n \u003chead\u003e\u003ctitle\u003eFoo\u003c/title\u003e\n \u003cbody\u003eFoo\u003c/body\u003e\n\u003c/html\u003e",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
(logging) {
|
||||
log {
|
||||
output file /var/log/caddy/{args[0]}.access.log
|
||||
output file /var/log/caddy/{args.0}.access.log
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
&(first) {
|
||||
@first path /first
|
||||
vars @first first 1
|
||||
respond "first"
|
||||
}
|
||||
|
||||
&(second) {
|
||||
respond "second"
|
||||
}
|
||||
|
||||
:8881 {
|
||||
invoke first
|
||||
route {
|
||||
invoke second
|
||||
}
|
||||
}
|
||||
|
||||
:8882 {
|
||||
handle {
|
||||
invoke second
|
||||
}
|
||||
}
|
||||
|
||||
:8883 {
|
||||
respond "no invoke"
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8881"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "invoke",
|
||||
"name": "first"
|
||||
},
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "invoke",
|
||||
"name": "second"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"named_routes": {
|
||||
"first": {
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"first": 1,
|
||||
"handler": "vars"
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/first"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "first",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"second": {
|
||||
"handle": [
|
||||
{
|
||||
"body": "second",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"srv1": {
|
||||
"listen": [
|
||||
":8882"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "invoke",
|
||||
"name": "second"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"named_routes": {
|
||||
"second": {
|
||||
"handle": [
|
||||
{
|
||||
"body": "second",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"srv2": {
|
||||
"listen": [
|
||||
":8883"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "no invoke",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
http://localhost:2020 {
|
||||
log
|
||||
skip_log /first-hidden*
|
||||
skip_log /second-hidden*
|
||||
respond 200
|
||||
}
|
||||
|
||||
@@ -30,36 +28,6 @@ http://localhost:2020 {
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "vars",
|
||||
"skip_log": true
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/second-hidden*"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "vars",
|
||||
"skip_log": true
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/first-hidden*"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
*.example.com {
|
||||
log {
|
||||
hostnames foo.example.com bar.example.com
|
||||
output file /foo-bar.txt
|
||||
}
|
||||
log {
|
||||
hostnames baz.example.com
|
||||
output file /baz.txt
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
"exclude": [
|
||||
"http.log.access.log0",
|
||||
"http.log.access.log1"
|
||||
]
|
||||
},
|
||||
"log0": {
|
||||
"writer": {
|
||||
"filename": "/foo-bar.txt",
|
||||
"output": "file"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.log0"
|
||||
]
|
||||
},
|
||||
"log1": {
|
||||
"writer": {
|
||||
"filename": "/baz.txt",
|
||||
"output": "file"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.log1"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"*.example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"bar.example.com": "log0",
|
||||
"baz.example.com": "log1",
|
||||
"foo.example.com": "log0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
{
|
||||
log access-console {
|
||||
include http.log.access.foo
|
||||
output file access-localhost.log
|
||||
format console
|
||||
}
|
||||
|
||||
log access-json {
|
||||
include http.log.access.foo
|
||||
output file access-localhost.json
|
||||
format json
|
||||
}
|
||||
}
|
||||
|
||||
http://localhost:8881 {
|
||||
log foo
|
||||
}
|
||||
----------
|
||||
{
|
||||
"logging": {
|
||||
"logs": {
|
||||
"access-console": {
|
||||
"writer": {
|
||||
"filename": "access-localhost.log",
|
||||
"output": "file"
|
||||
},
|
||||
"encoder": {
|
||||
"format": "console"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.foo"
|
||||
]
|
||||
},
|
||||
"access-json": {
|
||||
"writer": {
|
||||
"filename": "access-localhost.json",
|
||||
"output": "file"
|
||||
},
|
||||
"encoder": {
|
||||
"format": "json"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.foo"
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"exclude": [
|
||||
"http.log.access.foo"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8881"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"automatic_https": {
|
||||
"skip": [
|
||||
"localhost"
|
||||
]
|
||||
},
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"localhost:8881": "foo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
{
|
||||
debug
|
||||
|
||||
log access-console {
|
||||
include http.log.access.foo
|
||||
output file access-localhost.log
|
||||
format console
|
||||
}
|
||||
|
||||
log access-json {
|
||||
include http.log.access.foo
|
||||
output file access-localhost.json
|
||||
format json
|
||||
}
|
||||
}
|
||||
|
||||
http://localhost:8881 {
|
||||
log foo
|
||||
}
|
||||
----------
|
||||
{
|
||||
"logging": {
|
||||
"logs": {
|
||||
"access-console": {
|
||||
"writer": {
|
||||
"filename": "access-localhost.log",
|
||||
"output": "file"
|
||||
},
|
||||
"encoder": {
|
||||
"format": "console"
|
||||
},
|
||||
"level": "DEBUG",
|
||||
"include": [
|
||||
"http.log.access.foo"
|
||||
]
|
||||
},
|
||||
"access-json": {
|
||||
"writer": {
|
||||
"filename": "access-localhost.json",
|
||||
"output": "file"
|
||||
},
|
||||
"encoder": {
|
||||
"format": "json"
|
||||
},
|
||||
"level": "DEBUG",
|
||||
"include": [
|
||||
"http.log.access.foo"
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"level": "DEBUG",
|
||||
"exclude": [
|
||||
"http.log.access.foo"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8881"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"automatic_https": {
|
||||
"skip": [
|
||||
"localhost"
|
||||
]
|
||||
},
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"localhost:8881": "foo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,9 +62,6 @@ example.com {
|
||||
}
|
||||
],
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"one.example.com": ""
|
||||
},
|
||||
"skip_hosts": [
|
||||
"three.example.com",
|
||||
"two.example.com",
|
||||
|
||||
@@ -100,16 +100,16 @@ vars {
|
||||
],
|
||||
"source": "{http.request.host}"
|
||||
},
|
||||
{
|
||||
"foo": "bar",
|
||||
"handler": "vars"
|
||||
},
|
||||
{
|
||||
"abc": true,
|
||||
"def": 1,
|
||||
"ghi": 2.3,
|
||||
"handler": "vars",
|
||||
"jkl": "mn op"
|
||||
},
|
||||
{
|
||||
"foo": "bar",
|
||||
"handler": "vars"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -43,9 +43,6 @@
|
||||
|
||||
@matcher11 remote_ip private_ranges
|
||||
respond @matcher11 "remote_ip matcher with private ranges"
|
||||
|
||||
@matcher12 client_ip private_ranges
|
||||
respond @matcher12 "client_ip matcher with private ranges"
|
||||
}
|
||||
----------
|
||||
{
|
||||
@@ -253,28 +250,6 @@
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"client_ip": {
|
||||
"ranges": [
|
||||
"192.168.0.0/16",
|
||||
"172.16.0.0/12",
|
||||
"10.0.0.0/8",
|
||||
"127.0.0.1/8",
|
||||
"fd00::/8",
|
||||
"::1"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"body": "client_ip matcher with private ranges",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ route {
|
||||
}
|
||||
not path */
|
||||
}
|
||||
redir @canonicalPath {http.request.orig_uri.path}/ 308
|
||||
redir @canonicalPath {path}/ 308
|
||||
|
||||
# If the requested file does not exist, try index files
|
||||
@indexFiles {
|
||||
@@ -50,7 +50,7 @@ route {
|
||||
"handler": "static_response",
|
||||
"headers": {
|
||||
"Location": [
|
||||
"{http.request.orig_uri.path}/"
|
||||
"{http.request.uri.path}/"
|
||||
]
|
||||
},
|
||||
"status_code": 308
|
||||
@@ -74,7 +74,6 @@ route {
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "group0",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "rewrite",
|
||||
@@ -130,4 +129,4 @@ route {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@
|
||||
"handler": "static_response",
|
||||
"headers": {
|
||||
"Location": [
|
||||
"{http.request.orig_uri.path}/"
|
||||
"{http.request.uri.path}/"
|
||||
]
|
||||
},
|
||||
"status_code": 308
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
:8884
|
||||
|
||||
# the use of a host matcher here should cause this
|
||||
# site block to be wrapped in a subroute, even though
|
||||
# the site block does not have a hostname; this is
|
||||
# to prevent auto-HTTPS from picking up on this host
|
||||
# matcher because it is not a key on the site block
|
||||
@test host example.com
|
||||
php_fastcgi @test localhost:9000
|
||||
@api host example.com
|
||||
php_fastcgi @api localhost:9000
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
@@ -18,6 +13,13 @@ php_fastcgi @test localhost:9000
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
@@ -25,99 +27,82 @@ php_fastcgi @test localhost:9000
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
"handler": "static_response",
|
||||
"headers": {
|
||||
"Location": [
|
||||
"{http.request.uri.path}/"
|
||||
]
|
||||
},
|
||||
"status_code": 308
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"file": {
|
||||
"try_files": [
|
||||
"{http.request.uri.path}/index.php"
|
||||
]
|
||||
},
|
||||
"not": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "static_response",
|
||||
"headers": {
|
||||
"Location": [
|
||||
"{http.request.orig_uri.path}/"
|
||||
]
|
||||
},
|
||||
"status_code": 308
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"file": {
|
||||
"try_files": [
|
||||
"{http.request.uri.path}/index.php"
|
||||
]
|
||||
},
|
||||
"not": [
|
||||
{
|
||||
"path": [
|
||||
"*/"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
"path": [
|
||||
"*/"
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "rewrite",
|
||||
"uri": "{http.matchers.file.relative}"
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"file": {
|
||||
"split_path": [
|
||||
".php"
|
||||
],
|
||||
"try_files": [
|
||||
"{http.request.uri.path}",
|
||||
"{http.request.uri.path}/index.php",
|
||||
"index.php"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"transport": {
|
||||
"protocol": "fastcgi",
|
||||
"split_path": [
|
||||
".php"
|
||||
]
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "rewrite",
|
||||
"uri": "{http.matchers.file.relative}"
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"file": {
|
||||
"split_path": [
|
||||
".php"
|
||||
],
|
||||
"try_files": [
|
||||
"{http.request.uri.path}",
|
||||
"{http.request.uri.path}/index.php",
|
||||
"index.php"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"transport": {
|
||||
"protocol": "fastcgi",
|
||||
"split_path": [
|
||||
".php"
|
||||
]
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:9000"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"*.php"
|
||||
]
|
||||
}
|
||||
]
|
||||
"dial": "localhost:9000"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"example.com"
|
||||
"path": [
|
||||
"*.php"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ php_fastcgi localhost:9000 {
|
||||
"handler": "static_response",
|
||||
"headers": {
|
||||
"Location": [
|
||||
"{http.request.orig_uri.path}/"
|
||||
"{http.request.uri.path}/"
|
||||
]
|
||||
},
|
||||
"status_code": 308
|
||||
|
||||
@@ -46,7 +46,7 @@ php_fastcgi localhost:9000 {
|
||||
"handler": "static_response",
|
||||
"headers": {
|
||||
"Location": [
|
||||
"{http.request.orig_uri.path}/"
|
||||
"{http.request.uri.path}/"
|
||||
]
|
||||
},
|
||||
"status_code": 308
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
*.sandbox.localhost {
|
||||
@sandboxPort {
|
||||
header_regexp first_label Host ^([0-9]{3})\.sandbox\.
|
||||
}
|
||||
handle @sandboxPort {
|
||||
reverse_proxy {re.first_label.1}
|
||||
}
|
||||
handle {
|
||||
redir {scheme}://application.localhost
|
||||
}
|
||||
}
|
||||
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"*.sandbox.localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "{http.regexp.first_label.1}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"header_regexp": {
|
||||
"Host": {
|
||||
"name": "first_label",
|
||||
"pattern": "^([0-9]{3})\\.sandbox\\."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "static_response",
|
||||
"headers": {
|
||||
"Location": [
|
||||
"{http.request.scheme}://application.localhost"
|
||||
]
|
||||
},
|
||||
"status_code": 302
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
*.sandbox.localhost {
|
||||
@sandboxPort {
|
||||
header_regexp port Host ^([0-9]{3})\.sandbox\.
|
||||
}
|
||||
handle @sandboxPort {
|
||||
reverse_proxy app:6{re.port.1}
|
||||
}
|
||||
handle {
|
||||
redir {scheme}://application.localhost
|
||||
}
|
||||
}
|
||||
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"*.sandbox.localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "app:6{http.regexp.port.1}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"header_regexp": {
|
||||
"Host": {
|
||||
"name": "port",
|
||||
"pattern": "^([0-9]{3})\\.sandbox\\."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "static_response",
|
||||
"headers": {
|
||||
"Location": [
|
||||
"{http.request.scheme}://application.localhost"
|
||||
]
|
||||
},
|
||||
"status_code": 302
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
*.sandbox.localhost {
|
||||
@sandboxPort {
|
||||
header_regexp port Host ^([0-9]{3})\.sandbox\.
|
||||
}
|
||||
handle @sandboxPort {
|
||||
reverse_proxy app:{re.port.1}
|
||||
}
|
||||
handle {
|
||||
redir {scheme}://application.localhost
|
||||
}
|
||||
}
|
||||
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"*.sandbox.localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "app:{http.regexp.port.1}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"header_regexp": {
|
||||
"Host": {
|
||||
"name": "port",
|
||||
"pattern": "^([0-9]{3})\\.sandbox\\."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "static_response",
|
||||
"headers": {
|
||||
"Location": [
|
||||
"{http.request.scheme}://application.localhost"
|
||||
]
|
||||
},
|
||||
"status_code": 302
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
https://example.com {
|
||||
reverse_proxy https://localhost:54321 {
|
||||
request_buffers unlimited
|
||||
response_buffers unlimited
|
||||
}
|
||||
}
|
||||
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"request_buffers": -1,
|
||||
"response_buffers": -1,
|
||||
"transport": {
|
||||
"protocol": "http",
|
||||
"tls": {}
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:54321"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@
|
||||
resolvers 8.8.8.8 8.8.4.4
|
||||
dial_timeout 2s
|
||||
dial_fallback_delay 300ms
|
||||
versions ipv6
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,10 +66,7 @@
|
||||
"8.8.4.4"
|
||||
]
|
||||
},
|
||||
"source": "a",
|
||||
"versions": {
|
||||
"ipv6": true
|
||||
}
|
||||
"source": "a"
|
||||
},
|
||||
"handler": "reverse_proxy"
|
||||
}
|
||||
@@ -117,4 +113,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,6 @@ reverse_proxy 127.0.0.1:65535 {
|
||||
X-Header-Key 95ca39e3cbe7
|
||||
X-Header-Keys VbG4NZwWnipo 335Q9/MhqcNU3s2TO
|
||||
X-Empty-Value
|
||||
Same-Key 1
|
||||
Same-Key 2
|
||||
X-System-Hostname {system.hostname}
|
||||
}
|
||||
health_uri /health
|
||||
}
|
||||
@@ -32,10 +29,6 @@ reverse_proxy 127.0.0.1:65535 {
|
||||
"Host": [
|
||||
"example.com"
|
||||
],
|
||||
"Same-Key": [
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"X-Empty-Value": [
|
||||
""
|
||||
],
|
||||
@@ -45,9 +38,6 @@ reverse_proxy 127.0.0.1:65535 {
|
||||
"X-Header-Keys": [
|
||||
"VbG4NZwWnipo",
|
||||
"335Q9/MhqcNU3s2TO"
|
||||
],
|
||||
"X-System-Hostname": [
|
||||
"{system.hostname}"
|
||||
]
|
||||
},
|
||||
"uri": "/health"
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
:8884
|
||||
|
||||
reverse_proxy 127.0.0.1:65535 127.0.0.1:35535 {
|
||||
lb_policy weighted_round_robin 10 1
|
||||
lb_retries 5
|
||||
lb_try_duration 10s
|
||||
lb_try_interval 500ms
|
||||
lb_retry_match {
|
||||
path /foo*
|
||||
method POST
|
||||
}
|
||||
lb_retry_match path /bar*
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8884"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"load_balancing": {
|
||||
"retries": 5,
|
||||
"retry_match": [
|
||||
{
|
||||
"method": [
|
||||
"POST"
|
||||
],
|
||||
"path": [
|
||||
"/foo*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"/bar*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selection_policy": {
|
||||
"policy": "weighted_round_robin",
|
||||
"weights": [
|
||||
10,
|
||||
1
|
||||
]
|
||||
},
|
||||
"try_duration": 10000000000,
|
||||
"try_interval": 500000000
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "127.0.0.1:65535"
|
||||
},
|
||||
{
|
||||
"dial": "127.0.0.1:35535"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
https://example.com {
|
||||
reverse_proxy /path https://localhost:54321 {
|
||||
header_up Host {upstream_hostport}
|
||||
@@ -6,7 +7,7 @@ https://example.com {
|
||||
method GET
|
||||
rewrite /rewritten?uri={uri}
|
||||
|
||||
request_buffers 4KB
|
||||
buffer_requests
|
||||
|
||||
transport http {
|
||||
read_buffer 10MB
|
||||
@@ -23,12 +24,13 @@ https://example.com {
|
||||
max_conns_per_host 5
|
||||
keepalive_idle_conns_per_host 2
|
||||
keepalive_interval 30s
|
||||
|
||||
|
||||
tls_renegotiation freely
|
||||
tls_except_ports 8181 8182
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
@@ -54,6 +56,7 @@ https://example.com {
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"buffer_requests": true,
|
||||
"handler": "reverse_proxy",
|
||||
"headers": {
|
||||
"request": {
|
||||
@@ -67,7 +70,6 @@ https://example.com {
|
||||
}
|
||||
}
|
||||
},
|
||||
"request_buffers": 4000,
|
||||
"rewrite": {
|
||||
"method": "GET",
|
||||
"uri": "/rewritten?uri={http.request.uri}"
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
:8884 {
|
||||
# Port range
|
||||
reverse_proxy localhost:8001-8002
|
||||
|
||||
# Port range with placeholder
|
||||
reverse_proxy {host}:8001-8002
|
||||
|
||||
# Port range with scheme
|
||||
reverse_proxy https://localhost:8001-8002
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8884"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:8001"
|
||||
},
|
||||
{
|
||||
"dial": "localhost:8002"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "{http.request.host}:8001"
|
||||
},
|
||||
{
|
||||
"dial": "{http.request.host}:8002"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"transport": {
|
||||
"protocol": "http",
|
||||
"tls": {}
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:8001"
|
||||
},
|
||||
{
|
||||
"dial": "localhost:8002"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
{
|
||||
servers :443 {
|
||||
name https
|
||||
}
|
||||
|
||||
servers :8000 {
|
||||
name app1
|
||||
}
|
||||
|
||||
servers :8001 {
|
||||
name app2
|
||||
}
|
||||
|
||||
servers 123.123.123.123:8002 {
|
||||
name bind-server
|
||||
}
|
||||
}
|
||||
|
||||
example.com {
|
||||
}
|
||||
|
||||
:8000 {
|
||||
}
|
||||
|
||||
:8001, :8002 {
|
||||
}
|
||||
|
||||
:8002 {
|
||||
bind 123.123.123.123 222.222.222.222
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"app1": {
|
||||
"listen": [
|
||||
":8000"
|
||||
]
|
||||
},
|
||||
"app2": {
|
||||
"listen": [
|
||||
":8001"
|
||||
]
|
||||
},
|
||||
"bind-server": {
|
||||
"listen": [
|
||||
"123.123.123.123:8002",
|
||||
"222.222.222.222:8002"
|
||||
]
|
||||
},
|
||||
"https": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"srv4": {
|
||||
"listen": [
|
||||
":8002"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
*.example.com {
|
||||
@foo host foo.example.com
|
||||
handle @foo {
|
||||
handle_path /strip {
|
||||
handle_path /strip* {
|
||||
respond "this should be first"
|
||||
}
|
||||
handle_path /strip* {
|
||||
respond "this should be second"
|
||||
}
|
||||
handle {
|
||||
respond "this should be last"
|
||||
respond "this should be second"
|
||||
}
|
||||
}
|
||||
handle {
|
||||
@@ -38,13 +35,13 @@
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"group": "group6",
|
||||
"group": "group5",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"group": "group3",
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
@@ -68,39 +65,6 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/strip"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "group3",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "rewrite",
|
||||
"strip_path_prefix": "/strip"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "this should be second",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
@@ -110,7 +74,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "group3",
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
@@ -118,7 +82,7 @@
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "this should be last",
|
||||
"body": "this should be second",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
@@ -139,7 +103,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "group6",
|
||||
"group": "group5",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
:80
|
||||
|
||||
vars /foobar foo last
|
||||
vars /foo foo middle-last
|
||||
vars /foo* foo middle-first
|
||||
vars /foo foo middle
|
||||
vars * foo first
|
||||
----------
|
||||
{
|
||||
@@ -22,21 +21,6 @@ vars * foo first
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/foo*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"foo": "middle-first",
|
||||
"handler": "vars"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
@@ -47,7 +31,7 @@ vars * foo first
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"foo": "middle-last",
|
||||
"foo": "middle",
|
||||
"handler": "vars"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
# example from issue #4667
|
||||
{
|
||||
auto_https off
|
||||
}
|
||||
|
||||
https://, example.com {
|
||||
tls test.crt test.key
|
||||
respond "Hello World"
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "Hello World",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"certificate_selection": {
|
||||
"any_tag": [
|
||||
"cert0"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"automatic_https": {
|
||||
"disable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"certificates": {
|
||||
"load_files": [
|
||||
{
|
||||
"certificate": "test.crt",
|
||||
"key": "test.key",
|
||||
"tags": [
|
||||
"cert0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
localhost
|
||||
|
||||
respond "hello from localhost"
|
||||
tls {
|
||||
dns_ttl 5m10s
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from localhost",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"automation": {
|
||||
"policies": [
|
||||
{
|
||||
"subjects": [
|
||||
"localhost"
|
||||
],
|
||||
"issuers": [
|
||||
{
|
||||
"challenges": {
|
||||
"dns": {
|
||||
"ttl": 310000000000
|
||||
}
|
||||
},
|
||||
"module": "acme"
|
||||
},
|
||||
{
|
||||
"challenges": {
|
||||
"dns": {
|
||||
"ttl": 310000000000
|
||||
}
|
||||
},
|
||||
"module": "zerossl"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
localhost
|
||||
|
||||
respond "hello from localhost"
|
||||
tls {
|
||||
issuer acme {
|
||||
dns_ttl 5m10s
|
||||
}
|
||||
issuer zerossl {
|
||||
dns_ttl 10m20s
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from localhost",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"automation": {
|
||||
"policies": [
|
||||
{
|
||||
"subjects": [
|
||||
"localhost"
|
||||
],
|
||||
"issuers": [
|
||||
{
|
||||
"challenges": {
|
||||
"dns": {
|
||||
"ttl": 310000000000
|
||||
}
|
||||
},
|
||||
"module": "acme"
|
||||
},
|
||||
{
|
||||
"challenges": {
|
||||
"dns": {
|
||||
"ttl": 620000000000
|
||||
}
|
||||
},
|
||||
"module": "zerossl"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
localhost
|
||||
|
||||
respond "hello from localhost"
|
||||
tls {
|
||||
propagation_delay 5m10s
|
||||
propagation_timeout 10m20s
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from localhost",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"automation": {
|
||||
"policies": [
|
||||
{
|
||||
"subjects": [
|
||||
"localhost"
|
||||
],
|
||||
"issuers": [
|
||||
{
|
||||
"challenges": {
|
||||
"dns": {
|
||||
"propagation_delay": 310000000000,
|
||||
"propagation_timeout": 620000000000
|
||||
}
|
||||
},
|
||||
"module": "acme"
|
||||
},
|
||||
{
|
||||
"challenges": {
|
||||
"dns": {
|
||||
"propagation_delay": 310000000000,
|
||||
"propagation_timeout": 620000000000
|
||||
}
|
||||
},
|
||||
"module": "zerossl"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,8 @@ func TestRespond(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
grace_period 1ns
|
||||
}
|
||||
|
||||
localhost:9080 {
|
||||
@@ -37,10 +35,8 @@ func TestRedirect(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
grace_period 1ns
|
||||
}
|
||||
|
||||
localhost:9080 {
|
||||
@@ -88,11 +84,8 @@ func TestReadCookie(t *testing.T) {
|
||||
tester.Client.Jar.SetCookies(localhost, []*http.Cookie{&cookie})
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
grace_period 1ns
|
||||
}
|
||||
|
||||
localhost:9080 {
|
||||
@@ -114,11 +107,8 @@ func TestReplIndex(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
grace_period 1ns
|
||||
}
|
||||
|
||||
localhost:9080 {
|
||||
@@ -135,352 +125,3 @@ func TestReplIndex(t *testing.T) {
|
||||
// act and assert
|
||||
tester.AssertGetResponse("http://localhost:9080/", 200, "")
|
||||
}
|
||||
|
||||
func TestInvalidPrefix(t *testing.T) {
|
||||
type testCase struct {
|
||||
config, expectedError string
|
||||
}
|
||||
|
||||
failureCases := []testCase{
|
||||
{
|
||||
config: `wss://localhost`,
|
||||
expectedError: `the scheme wss:// is only supported in browsers; use https:// instead`,
|
||||
},
|
||||
{
|
||||
config: `ws://localhost`,
|
||||
expectedError: `the scheme ws:// is only supported in browsers; use http:// instead`,
|
||||
},
|
||||
{
|
||||
config: `someInvalidPrefix://localhost`,
|
||||
expectedError: "unsupported URL scheme someinvalidprefix://",
|
||||
},
|
||||
{
|
||||
config: `h2c://localhost`,
|
||||
expectedError: `unsupported URL scheme h2c://`,
|
||||
},
|
||||
{
|
||||
config: `localhost, wss://localhost`,
|
||||
expectedError: `the scheme wss:// is only supported in browsers; use https:// instead`,
|
||||
},
|
||||
{
|
||||
config: `localhost {
|
||||
reverse_proxy ws://localhost"
|
||||
}`,
|
||||
expectedError: `the scheme ws:// is only supported in browsers; use http:// instead`,
|
||||
},
|
||||
{
|
||||
config: `localhost {
|
||||
reverse_proxy someInvalidPrefix://localhost"
|
||||
}`,
|
||||
expectedError: `unsupported URL scheme someinvalidprefix://`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, failureCase := range failureCases {
|
||||
caddytest.AssertLoadError(t, failureCase.config, "caddyfile", failureCase.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidPrefix(t *testing.T) {
|
||||
type testCase struct {
|
||||
rawConfig, expectedResponse string
|
||||
}
|
||||
|
||||
successCases := []testCase{
|
||||
{
|
||||
"localhost",
|
||||
`{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
"https://localhost",
|
||||
`{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
"http://localhost",
|
||||
`{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
`localhost {
|
||||
reverse_proxy http://localhost:3000
|
||||
}`,
|
||||
`{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:3000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
`localhost {
|
||||
reverse_proxy https://localhost:3000
|
||||
}`,
|
||||
`{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"transport": {
|
||||
"protocol": "http",
|
||||
"tls": {}
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:3000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
`localhost {
|
||||
reverse_proxy h2c://localhost:3000
|
||||
}`,
|
||||
`{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"transport": {
|
||||
"protocol": "http",
|
||||
"versions": [
|
||||
"h2c",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:3000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
`localhost {
|
||||
reverse_proxy localhost:3000
|
||||
}`,
|
||||
`{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:3000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, successCase := range successCases {
|
||||
caddytest.AssertAdapt(t, successCase.rawConfig, "caddyfile", successCase.expectedResponse)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,8 @@ func TestBrowse(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
grace_period 1ns
|
||||
}
|
||||
http://localhost:9080 {
|
||||
file_server browse
|
||||
|
||||
@@ -11,11 +11,8 @@ func TestMap(t *testing.T) {
|
||||
// arrange
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
grace_period 1ns
|
||||
}
|
||||
|
||||
localhost:9080 {
|
||||
@@ -41,8 +38,6 @@ func TestMapRespondWithDefault(t *testing.T) {
|
||||
// arrange
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
}
|
||||
@@ -70,17 +65,7 @@ func TestMapAsJSON(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"admin": {
|
||||
"listen": "localhost:2999"
|
||||
},
|
||||
"apps": {
|
||||
"pki": {
|
||||
"certificate_authorities" : {
|
||||
"local" : {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
)
|
||||
|
||||
func TestLeafCertLifetimeLessThanIntermediate(t *testing.T) {
|
||||
caddytest.AssertLoadError(t, `
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"ca": "internal",
|
||||
"handler": "acme_server",
|
||||
"lifetime": 604800000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities": {
|
||||
"internal": {
|
||||
"install_trust": false,
|
||||
"intermediate_lifetime": 604800000000000,
|
||||
"name": "Internal CA"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json", "certificate lifetime (168h0m0s) should be less than intermediate certificate lifetime (168h0m0s)")
|
||||
}
|
||||
|
||||
func TestIntermediateLifetimeLessThanRoot(t *testing.T) {
|
||||
caddytest.AssertLoadError(t, `
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"ca": "internal",
|
||||
"handler": "acme_server",
|
||||
"lifetime": 2592000000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities": {
|
||||
"internal": {
|
||||
"install_trust": false,
|
||||
"intermediate_lifetime": 311040000000000000,
|
||||
"name": "Internal CA"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json", "intermediate certificate lifetime must be less than root certificate lifetime (86400h0m0s)")
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
)
|
||||
@@ -17,43 +16,66 @@ func TestSRVReverseProxy(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"admin": {
|
||||
"listen": "localhost:2999"
|
||||
},
|
||||
"apps": {
|
||||
"pki": {
|
||||
"certificate_authorities": {
|
||||
"local": {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"http": {
|
||||
"grace_period": 1,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":18080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"dynamic_upstreams": {
|
||||
"source": "srv",
|
||||
"name": "srv.host.service.consul"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"lookup_srv": "srv.host.service.consul"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
}
|
||||
`, "json")
|
||||
}
|
||||
|
||||
func TestSRVWithDial(t *testing.T) {
|
||||
caddytest.AssertLoadError(t, `
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "tcp/address.to.upstream:80",
|
||||
"lookup_srv": "srv.host.service.consul"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json", `upstream: specifying dial address is incompatible with lookup_srv: 0: {\"dial\": \"tcp/address.to.upstream:80\", \"lookup_srv\": \"srv.host.service.consul\"}`)
|
||||
}
|
||||
|
||||
func TestDialWithPlaceholderUnix(t *testing.T) {
|
||||
@@ -91,46 +113,35 @@ func TestDialWithPlaceholderUnix(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"admin": {
|
||||
"listen": "localhost:2999"
|
||||
},
|
||||
"apps": {
|
||||
"pki": {
|
||||
"certificate_authorities": {
|
||||
"local": {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"http": {
|
||||
"grace_period": 1,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":18080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "unix/{http.request.header.X-Caddy-Upstream-Dial}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "unix/{http.request.header.X-Caddy-Upstream-Dial}"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:18080", nil)
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:8080", nil)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
@@ -143,23 +154,12 @@ func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"admin": {
|
||||
"listen": "localhost:2999"
|
||||
},
|
||||
"apps": {
|
||||
"pki": {
|
||||
"certificate_authorities": {
|
||||
"local": {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"http": {
|
||||
"grace_period": 1,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":18080"
|
||||
":8080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
@@ -222,14 +222,14 @@ func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
`, "json")
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
req.Header.Set("X-Caddy-Upstream-Dial", "localhost:18080")
|
||||
req.Header.Set("X-Caddy-Upstream-Dial", "localhost:8080")
|
||||
tester.AssertResponse(req, 200, "Hello, World!")
|
||||
}
|
||||
|
||||
@@ -237,23 +237,12 @@ func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"admin": {
|
||||
"listen": "localhost:2999"
|
||||
},
|
||||
"apps": {
|
||||
"pki": {
|
||||
"certificate_authorities": {
|
||||
"local": {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"http": {
|
||||
"grace_period": 1,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":18080"
|
||||
":8080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
@@ -298,7 +287,7 @@ func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "tcp/{http.request.header.X-Caddy-Upstream-Dial}:18080"
|
||||
"dial": "tcp/{http.request.header.X-Caddy-Upstream-Dial}:8080"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -316,7 +305,7 @@ func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
`, "json")
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil)
|
||||
if err != nil {
|
||||
@@ -327,15 +316,49 @@ func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
|
||||
tester.AssertResponse(req, 200, "Hello, World!")
|
||||
}
|
||||
|
||||
func TestSRVWithActiveHealthcheck(t *testing.T) {
|
||||
caddytest.AssertLoadError(t, `
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"health_checks": {
|
||||
"active": {
|
||||
"path": "/ok"
|
||||
}
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"lookup_srv": "srv.host.service.consul"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json", `upstream: lookup_srv is incompatible with active health checks: 0: {\"dial\": \"\", \"lookup_srv\": \"srv.host.service.consul\"}`)
|
||||
}
|
||||
|
||||
func TestReverseProxyHealthCheck(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
grace_period 1ns
|
||||
}
|
||||
http://localhost:2020 {
|
||||
respond "Hello, World!"
|
||||
@@ -349,13 +372,12 @@ func TestReverseProxyHealthCheck(t *testing.T) {
|
||||
|
||||
health_uri /health
|
||||
health_port 2021
|
||||
health_interval 10ms
|
||||
health_timeout 100ms
|
||||
health_interval 2s
|
||||
health_timeout 5s
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
`, "caddyfile")
|
||||
|
||||
time.Sleep(100 * time.Millisecond) // TODO: for some reason this test seems particularly flaky, getting 503 when it should be 200, unless we wait
|
||||
tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!")
|
||||
}
|
||||
|
||||
@@ -396,11 +418,8 @@ func TestReverseProxyHealthCheckUnixSocket(t *testing.T) {
|
||||
|
||||
tester.InitServer(fmt.Sprintf(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
grace_period 1ns
|
||||
}
|
||||
http://localhost:9080 {
|
||||
reverse_proxy {
|
||||
@@ -454,11 +473,8 @@ func TestReverseProxyHealthCheckUnixSocketWithoutPort(t *testing.T) {
|
||||
|
||||
tester.InitServer(fmt.Sprintf(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
grace_period 1ns
|
||||
}
|
||||
http://localhost:9080 {
|
||||
reverse_proxy {
|
||||
|
||||
+237
-257
@@ -11,95 +11,91 @@ func TestDefaultSNI(t *testing.T) {
|
||||
// arrange
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`{
|
||||
"admin": {
|
||||
"listen": "localhost:2999"
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"grace_period": 1,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":9443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from a.caddy.localhost",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/version"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"127.0.0.1"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"certificate_selection": {
|
||||
"any_tag": ["cert0"]
|
||||
},
|
||||
"match": {
|
||||
"sni": [
|
||||
"127.0.0.1"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"default_sni": "*.caddy.localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"certificates": {
|
||||
"load_files": [
|
||||
{
|
||||
"certificate": "/caddy.localhost.crt",
|
||||
"key": "/caddy.localhost.key",
|
||||
"tags": [
|
||||
"cert0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities" : {
|
||||
"local" : {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":9443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from a.caddy.localhost",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/version"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"127.0.0.1"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"certificate_selection": {
|
||||
"any_tag": ["cert0"]
|
||||
},
|
||||
"match": {
|
||||
"sni": [
|
||||
"127.0.0.1"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"default_sni": "*.caddy.localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"certificates": {
|
||||
"load_files": [
|
||||
{
|
||||
"certificate": "/caddy.localhost.crt",
|
||||
"key": "/caddy.localhost.key",
|
||||
"tags": [
|
||||
"cert0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities" : {
|
||||
"local" : {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
|
||||
// act and assert
|
||||
// makes a request with no sni
|
||||
@@ -111,100 +107,96 @@ func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
|
||||
// arrange
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"admin": {
|
||||
"listen": "localhost:2999"
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"grace_period": 1,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":9443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from a",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/version"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"a.caddy.localhost",
|
||||
"127.0.0.1"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"certificate_selection": {
|
||||
"any_tag": ["cert0"]
|
||||
},
|
||||
"default_sni": "a.caddy.localhost",
|
||||
"match": {
|
||||
"sni": [
|
||||
"a.caddy.localhost",
|
||||
"127.0.0.1",
|
||||
""
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"default_sni": "a.caddy.localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"certificates": {
|
||||
"load_files": [
|
||||
{
|
||||
"certificate": "/a.caddy.localhost.crt",
|
||||
"key": "/a.caddy.localhost.key",
|
||||
"tags": [
|
||||
"cert0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities" : {
|
||||
"local" : {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":9443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from a",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/version"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"a.caddy.localhost",
|
||||
"127.0.0.1"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"certificate_selection": {
|
||||
"any_tag": ["cert0"]
|
||||
},
|
||||
"default_sni": "a.caddy.localhost",
|
||||
"match": {
|
||||
"sni": [
|
||||
"a.caddy.localhost",
|
||||
"127.0.0.1",
|
||||
""
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"default_sni": "a.caddy.localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"certificates": {
|
||||
"load_files": [
|
||||
{
|
||||
"certificate": "/a.caddy.localhost.crt",
|
||||
"key": "/a.caddy.localhost.key",
|
||||
"tags": [
|
||||
"cert0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities" : {
|
||||
"local" : {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
|
||||
// act and assert
|
||||
// makes a request with no sni
|
||||
@@ -215,72 +207,68 @@ func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
|
||||
// arrange
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"admin": {
|
||||
"listen": "localhost:2999"
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"grace_period": 1,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":9443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from a.caddy.localhost",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/version"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"certificate_selection": {
|
||||
"any_tag": ["cert0"]
|
||||
},
|
||||
"default_sni": "a.caddy.localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"certificates": {
|
||||
"load_files": [
|
||||
{
|
||||
"certificate": "/a.caddy.localhost.crt",
|
||||
"key": "/a.caddy.localhost.key",
|
||||
"tags": [
|
||||
"cert0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities" : {
|
||||
"local" : {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":9443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from a.caddy.localhost",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/version"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"certificate_selection": {
|
||||
"any_tag": ["cert0"]
|
||||
},
|
||||
"default_sni": "a.caddy.localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"certificates": {
|
||||
"load_files": [
|
||||
{
|
||||
"certificate": "/a.caddy.localhost.crt",
|
||||
"key": "/a.caddy.localhost.key",
|
||||
"tags": [
|
||||
"cert0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities" : {
|
||||
"local" : {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
|
||||
// act and assert
|
||||
// makes a request with no sni
|
||||
@@ -290,7 +278,6 @@ func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
|
||||
func TestHttpOnlyOnDomainWithSNI(t *testing.T) {
|
||||
caddytest.AssertAdapt(t, `
|
||||
{
|
||||
skip_install_trust
|
||||
default_sni a.caddy.localhost
|
||||
}
|
||||
:80 {
|
||||
@@ -326,13 +313,6 @@ func TestHttpOnlyOnDomainWithSNI(t *testing.T) {
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities": {
|
||||
"local": {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
@@ -23,14 +23,10 @@ func TestH2ToH2CStream(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"admin": {
|
||||
"listen": "localhost:2999"
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"grace_period": 1,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
@@ -176,7 +172,9 @@ func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server {
|
||||
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.WriteHeader(200)
|
||||
http.NewResponseController(w).Flush()
|
||||
if f, ok := w.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
buf := make([]byte, 4*1024)
|
||||
|
||||
@@ -207,9 +205,6 @@ func TestH2ToH1ChunkedResponse(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"admin": {
|
||||
"listen": "localhost:2999"
|
||||
},
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
@@ -221,7 +216,6 @@ func TestH2ToH1ChunkedResponse(t *testing.T) {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"grace_period": 1,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
respond "'I am {args[0]}', hears {args[1]}"
|
||||
respond "'I am {args.0}', hears {args.1}"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user