mirror of
https://github.com/caddyserver/caddy.git
synced 2026-05-26 08:42:31 -04:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 432f174623 | |||
| c6f34011fb | |||
| 71e27b844b |
+6
-16
@@ -1,7 +1,7 @@
|
|||||||
Contributing to Caddy
|
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.
|
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.
|
- **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`.
|
- **[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.
|
- **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:
|
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!
|
||||||
|
|
||||||
- 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.
|
|
||||||
|
|
||||||
|
|
||||||
#### HOW TO MAKE A PULL REQUEST TO CADDY
|
#### 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 |
|
| Version | Supported |
|
||||||
| ------- | ------------------ |
|
| ------- | ------------------ |
|
||||||
| 2.x | ✔️ |
|
| 2.x | :white_check_mark: |
|
||||||
| 1.x | :x: |
|
| 1.x | :x: |
|
||||||
| < 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.
|
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
|
## 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
|
- Specific minimal steps to reproduce the issue from scratch
|
||||||
- A working patch
|
- 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.
|
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"
|
|
||||||
+35
-23
@@ -18,22 +18,17 @@ jobs:
|
|||||||
# Default is true, cancels jobs for other platforms in the matrix if one fails
|
# Default is true, cancels jobs for other platforms in the matrix if one fails
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os:
|
os: [ ubuntu-latest, macos-latest, windows-latest ]
|
||||||
- ubuntu-latest
|
go: [ '1.18', '1.19' ]
|
||||||
- macos-latest
|
|
||||||
- windows-latest
|
|
||||||
go:
|
|
||||||
- '1.20'
|
|
||||||
# - '1.21'
|
|
||||||
|
|
||||||
include:
|
include:
|
||||||
# Set the minimum Go patch version for the given Go minor
|
# Set the minimum Go patch version for the given Go minor
|
||||||
# Usable via ${{ matrix.GO_SEMVER }}
|
# Usable via ${{ matrix.GO_SEMVER }}
|
||||||
- go: '1.20'
|
- go: '1.18'
|
||||||
GO_SEMVER: '~1.20.6'
|
GO_SEMVER: '~1.18.4'
|
||||||
|
|
||||||
# - go: '1.21'
|
- go: '1.19'
|
||||||
# GO_SEMVER: '~1.21.0'
|
GO_SEMVER: '~1.19.0'
|
||||||
|
|
||||||
# Set some variables per OS, usable via ${{ matrix.VAR }}
|
# Set some variables per OS, usable via ${{ matrix.VAR }}
|
||||||
# CADDY_BIN_PATH: the path to the compiled Caddy binary, for artifact publishing
|
# CADDY_BIN_PATH: the path to the compiled Caddy binary, for artifact publishing
|
||||||
@@ -53,15 +48,15 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.GO_SEMVER }}
|
go-version: ${{ matrix.GO_SEMVER }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
# These tools would be useful if we later decide to reinvestigate
|
# These tools would be useful if we later decide to reinvestigate
|
||||||
# publishing test/coverage reports to some tool for easier consumption
|
# publishing test/coverage reports to some tool for easier consumption
|
||||||
# - name: Install test and coverage analysis tools
|
# - name: Install test and coverage analysis tools
|
||||||
@@ -69,7 +64,7 @@ jobs:
|
|||||||
# go get github.com/axw/gocov/gocov
|
# go get github.com/axw/gocov/gocov
|
||||||
# go get github.com/AlekSi/gocov-xml
|
# go get github.com/AlekSi/gocov-xml
|
||||||
# go get -u github.com/jstemmer/go-junit-report
|
# 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
|
- name: Print Go version and environment
|
||||||
id: vars
|
id: vars
|
||||||
@@ -82,7 +77,24 @@ jobs:
|
|||||||
env
|
env
|
||||||
printf "Git version: $(git version)\n\n"
|
printf "Git version: $(git version)\n\n"
|
||||||
# Calculate the short SHA1 hash of the git commit
|
# 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
|
- name: Get dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -97,7 +109,7 @@ jobs:
|
|||||||
go build -trimpath -ldflags="-w -s" -v
|
go build -trimpath -ldflags="-w -s" -v
|
||||||
|
|
||||||
- name: Publish Build Artifact
|
- name: Publish Build Artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: caddy_${{ runner.os }}_go${{ matrix.go }}_${{ steps.vars.outputs.short_sha }}
|
name: caddy_${{ runner.os }}_go${{ matrix.go }}_${{ steps.vars.outputs.short_sha }}
|
||||||
path: ${{ matrix.CADDY_BIN_PATH }}
|
path: ${{ matrix.CADDY_BIN_PATH }}
|
||||||
@@ -111,7 +123,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
# (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out
|
# (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out
|
||||||
go test -v -coverprofile="cover-profile.out" -short -race ./...
|
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
|
# Relevant step if we reinvestigate publishing test/coverage reports
|
||||||
# - name: Prepare coverage reports
|
# - name: Prepare coverage reports
|
||||||
@@ -131,10 +143,10 @@ jobs:
|
|||||||
s390x-test:
|
s390x-test:
|
||||||
name: test (s390x on IBM Z)
|
name: test (s390x on IBM Z)
|
||||||
runs-on: ubuntu-latest
|
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
|
continue-on-error: true # August 2020: s390x VM is down due to weather and power issues
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code into the Go module directory
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: |
|
run: |
|
||||||
@@ -160,10 +172,10 @@ jobs:
|
|||||||
goreleaser-check:
|
goreleaser-check:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: goreleaser/goreleaser-action@v4
|
- uses: goreleaser/goreleaser-action@v2
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: check
|
args: check
|
||||||
|
|||||||
@@ -15,35 +15,20 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
goos:
|
goos: ['android', 'linux', 'solaris', 'illumos', 'dragonfly', 'freebsd', 'openbsd', 'plan9', 'windows', 'darwin', 'netbsd']
|
||||||
- 'android'
|
go: [ '1.19' ]
|
||||||
- 'linux'
|
|
||||||
- 'solaris'
|
|
||||||
- 'illumos'
|
|
||||||
- 'dragonfly'
|
|
||||||
- 'freebsd'
|
|
||||||
- 'openbsd'
|
|
||||||
- 'plan9'
|
|
||||||
- 'windows'
|
|
||||||
- 'darwin'
|
|
||||||
- 'netbsd'
|
|
||||||
go:
|
|
||||||
- '1.20'
|
|
||||||
|
|
||||||
include:
|
include:
|
||||||
# Set the minimum Go patch version for the given Go minor
|
# Set the minimum Go patch version for the given Go minor
|
||||||
# Usable via ${{ matrix.GO_SEMVER }}
|
# Usable via ${{ matrix.GO_SEMVER }}
|
||||||
- go: '1.20'
|
- go: '1.19'
|
||||||
GO_SEMVER: '~1.20.6'
|
GO_SEMVER: '~1.19.0'
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.GO_SEMVER }}
|
go-version: ${{ matrix.GO_SEMVER }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@@ -58,6 +43,22 @@ jobs:
|
|||||||
printf "\n\nSystem environment:\n\n"
|
printf "\n\nSystem environment:\n\n"
|
||||||
env
|
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
|
- name: Run Build
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
|||||||
@@ -10,43 +10,26 @@ on:
|
|||||||
- master
|
- master
|
||||||
- 2.*
|
- 2.*
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# From https://github.com/golangci/golangci-lint-action
|
# From https://github.com/golangci/golangci-lint-action
|
||||||
golangci:
|
golangci:
|
||||||
permissions:
|
|
||||||
contents: read # for actions/checkout to fetch code
|
|
||||||
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
|
|
||||||
name: lint
|
name: lint
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os:
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
- ubuntu-latest
|
|
||||||
- macos-latest
|
|
||||||
- windows-latest
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '~1.20.6'
|
go-version: '~1.18.4'
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
# Workaround for https://github.com/golangci/golangci-lint-action/issues/135
|
|
||||||
skip-pkg-cache: true
|
|
||||||
|
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v3
|
||||||
with:
|
with:
|
||||||
version: v1.53
|
version: v1.47
|
||||||
|
|
||||||
# Workaround for https://github.com/golangci/golangci-lint-action/issues/135
|
|
||||||
skip-pkg-cache: true
|
|
||||||
|
|
||||||
# Windows times out frequently after about 5m50s if we don't set a longer timeout.
|
# Windows times out frequently after about 5m50s if we don't set a longer timeout.
|
||||||
args: --timeout 10m
|
args: --timeout 10m
|
||||||
|
|
||||||
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||||
# only-new-issues: true
|
# only-new-issues: true
|
||||||
|
|||||||
@@ -10,16 +10,14 @@ jobs:
|
|||||||
name: Release
|
name: Release
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os:
|
os: [ ubuntu-latest ]
|
||||||
- ubuntu-latest
|
go: [ '1.19' ]
|
||||||
go:
|
|
||||||
- '1.20'
|
|
||||||
|
|
||||||
include:
|
include:
|
||||||
# Set the minimum Go patch version for the given Go minor
|
# Set the minimum Go patch version for the given Go minor
|
||||||
# Usable via ${{ matrix.GO_SEMVER }}
|
# Usable via ${{ matrix.GO_SEMVER }}
|
||||||
- go: '1.20'
|
- go: '1.19'
|
||||||
GO_SEMVER: '~1.20.6'
|
GO_SEMVER: '~1.19.0'
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
|
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
|
||||||
@@ -31,17 +29,17 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.GO_SEMVER }}
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: ${{ matrix.GO_SEMVER }}
|
|
||||||
check-latest: true
|
|
||||||
|
|
||||||
# Force fetch upstream tags -- because 65 minutes
|
# Force fetch upstream tags -- because 65 minutes
|
||||||
# tl;dr: actions/checkout@v3 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/
|
# git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/
|
||||||
@@ -63,8 +61,8 @@ jobs:
|
|||||||
go env
|
go env
|
||||||
printf "\n\nSystem environment:\n\n"
|
printf "\n\nSystem environment:\n\n"
|
||||||
env
|
env
|
||||||
echo "version_tag=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
|
echo "::set-output name=version_tag::${GITHUB_REF/refs\/tags\//}"
|
||||||
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
# Add "pip install" CLI tools to PATH
|
# Add "pip install" CLI tools to PATH
|
||||||
echo ~/.local/bin >> $GITHUB_PATH
|
echo ~/.local/bin >> $GITHUB_PATH
|
||||||
@@ -76,10 +74,10 @@ jobs:
|
|||||||
TAG_MINOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\2#"`
|
TAG_MINOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\2#"`
|
||||||
TAG_PATCH=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\3#"`
|
TAG_PATCH=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\3#"`
|
||||||
TAG_SPECIAL=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\4#"`
|
TAG_SPECIAL=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\4#"`
|
||||||
echo "tag_major=${TAG_MAJOR}" >> $GITHUB_OUTPUT
|
echo "::set-output name=tag_major::${TAG_MAJOR}"
|
||||||
echo "tag_minor=${TAG_MINOR}" >> $GITHUB_OUTPUT
|
echo "::set-output name=tag_minor::${TAG_MINOR}"
|
||||||
echo "tag_patch=${TAG_PATCH}" >> $GITHUB_OUTPUT
|
echo "::set-output name=tag_patch::${TAG_PATCH}"
|
||||||
echo "tag_special=${TAG_SPECIAL}" >> $GITHUB_OUTPUT
|
echo "::set-output name=tag_special::${TAG_SPECIAL}"
|
||||||
|
|
||||||
# Cloudsmith CLI tooling for pushing releases
|
# Cloudsmith CLI tooling for pushing releases
|
||||||
# See https://help.cloudsmith.io/docs/cli
|
# See https://help.cloudsmith.io/docs/cli
|
||||||
@@ -96,6 +94,18 @@ jobs:
|
|||||||
# tags are only accepted if signed by Matt's key
|
# tags are only accepted if signed by Matt's key
|
||||||
git verify-tag "${{ steps.vars.outputs.version_tag }}" || exit 1
|
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
|
- name: Install Cosign
|
||||||
uses: sigstore/cosign-installer@main
|
uses: sigstore/cosign-installer@main
|
||||||
- name: Cosign version
|
- name: Cosign version
|
||||||
@@ -106,7 +116,7 @@ jobs:
|
|||||||
run: syft version
|
run: syft version
|
||||||
# GoReleaser will take care of publishing those artifacts into the release
|
# GoReleaser will take care of publishing those artifacts into the release
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v4
|
uses: goreleaser/goreleaser-action@v2
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: release --rm-dist --timeout 60m
|
args: release --rm-dist --timeout 60m
|
||||||
|
|||||||
@@ -10,15 +10,14 @@ jobs:
|
|||||||
name: Release Published
|
name: Release Published
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os:
|
os: [ ubuntu-latest ]
|
||||||
- ubuntu-latest
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
# See https://github.com/peter-evans/repository-dispatch
|
# See https://github.com/peter-evans/repository-dispatch
|
||||||
- name: Trigger event on caddyserver/dist
|
- name: Trigger event on caddyserver/dist
|
||||||
uses: peter-evans/repository-dispatch@v2
|
uses: peter-evans/repository-dispatch@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
||||||
repository: caddyserver/dist
|
repository: caddyserver/dist
|
||||||
@@ -26,7 +25,7 @@ jobs:
|
|||||||
client-payload: '{"tag": "${{ github.event.release.tag_name }}"}'
|
client-payload: '{"tag": "${{ github.event.release.tag_name }}"}'
|
||||||
|
|
||||||
- name: Trigger event on caddyserver/caddy-docker
|
- name: Trigger event on caddyserver/caddy-docker
|
||||||
uses: peter-evans/repository-dispatch@v2
|
uses: peter-evans/repository-dispatch@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
||||||
repository: caddyserver/caddy-docker
|
repository: caddyserver/caddy-docker
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ Caddyfile.*
|
|||||||
# build artifacts and helpers
|
# build artifacts and helpers
|
||||||
cmd/caddy/caddy
|
cmd/caddy/caddy
|
||||||
cmd/caddy/caddy.exe
|
cmd/caddy/caddy.exe
|
||||||
cmd/caddy/tmp/*.exe
|
|
||||||
|
|
||||||
# mac specific
|
# mac specific
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
+3
-4
@@ -7,6 +7,7 @@ linters:
|
|||||||
disable-all: true
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
- bodyclose
|
- bodyclose
|
||||||
|
- deadcode
|
||||||
- errcheck
|
- errcheck
|
||||||
- gofmt
|
- gofmt
|
||||||
- goimports
|
- goimports
|
||||||
@@ -17,9 +18,11 @@ linters:
|
|||||||
- misspell
|
- misspell
|
||||||
- prealloc
|
- prealloc
|
||||||
- staticcheck
|
- staticcheck
|
||||||
|
- structcheck
|
||||||
- typecheck
|
- typecheck
|
||||||
- unconvert
|
- unconvert
|
||||||
- unused
|
- unused
|
||||||
|
- varcheck
|
||||||
# these are implicitly disabled:
|
# these are implicitly disabled:
|
||||||
# - asciicheck
|
# - asciicheck
|
||||||
# - depguard
|
# - depguard
|
||||||
@@ -93,7 +96,3 @@ issues:
|
|||||||
text: "G404" # G404: Insecure random number source (rand)
|
text: "G404" # G404: Insecure random number source (rand)
|
||||||
linters:
|
linters:
|
||||||
- gosec
|
- gosec
|
||||||
- path: modules/caddyhttp/reverseproxy/streaming.go
|
|
||||||
text: "G404" # G404: Insecure random number source (rand)
|
|
||||||
linters:
|
|
||||||
- gosec
|
|
||||||
|
|||||||
+7
-55
@@ -4,9 +4,7 @@ before:
|
|||||||
# This is so we can run goreleaser on tag without Git complaining of being dirty. The main.go in cmd/caddy directory
|
# 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
|
# 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.
|
# subsequently causes gorleaser to refuse running.
|
||||||
- rm -rf caddy-build caddy-dist vendor
|
- rm -rf caddy-build caddy-dist
|
||||||
# vendor Caddy deps
|
|
||||||
- go mod vendor
|
|
||||||
- mkdir -p caddy-build
|
- mkdir -p caddy-build
|
||||||
- cp cmd/caddy/main.go caddy-build/main.go
|
- cp cmd/caddy/main.go caddy-build/main.go
|
||||||
- /bin/sh -c 'cd ./caddy-build && go mod init caddy'
|
- /bin/sh -c 'cd ./caddy-build && go mod init caddy'
|
||||||
@@ -16,8 +14,6 @@ before:
|
|||||||
# as of Go 1.16, `go` commands no longer automatically change go.{mod,sum}. We now have to explicitly
|
# 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.
|
# 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'
|
- /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
|
- git clone --depth 1 https://github.com/caddyserver/dist caddy-dist
|
||||||
- mkdir -p caddy-dist/man
|
- mkdir -p caddy-dist/man
|
||||||
- go mod download
|
- go mod download
|
||||||
@@ -70,69 +66,24 @@ builds:
|
|||||||
- -mod=readonly
|
- -mod=readonly
|
||||||
ldflags:
|
ldflags:
|
||||||
- -s -w
|
- -s -w
|
||||||
|
|
||||||
signs:
|
signs:
|
||||||
- cmd: cosign
|
- cmd: cosign
|
||||||
signature: "${artifact}.sig"
|
signature: "${artifact}.sig"
|
||||||
certificate: '{{ trimsuffix (trimsuffix .Env.artifact ".zip") ".tar.gz" }}.pem'
|
certificate: '{{ trimsuffix (trimsuffix .Env.artifact ".zip") ".tar.gz" }}.pem'
|
||||||
args: ["sign-blob", "--yes", "--output-signature=${signature}", "--output-certificate", "${certificate}", "${artifact}"]
|
args: ["sign-blob", "--output-signature=${signature}", "--output-certificate", "${certificate}", "${artifact}"]
|
||||||
artifacts: all
|
artifacts: all
|
||||||
|
|
||||||
sboms:
|
sboms:
|
||||||
- artifacts: binary
|
- artifacts: binary
|
||||||
documents:
|
documents:
|
||||||
- >-
|
- '{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{if .Arm}}v{{ .Arm }}{{end}}.sbom'
|
||||||
{{ .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
|
|
||||||
cmd: syft
|
cmd: syft
|
||||||
args: ["$artifact", "--file", "${document}", "--output", "cyclonedx-json"]
|
args: ["$artifact", "--file", "${document}", "--output", "cyclonedx-json"]
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- id: default
|
- format_overrides:
|
||||||
format_overrides:
|
|
||||||
- goos: windows
|
- goos: windows
|
||||||
format: zip
|
format: zip
|
||||||
name_template: >-
|
replacements:
|
||||||
{{ .ProjectName }}_
|
darwin: mac
|
||||||
{{- .Version }}_
|
|
||||||
{{- if eq .Os "darwin" }}mac{{ else }}{{ .Os }}{{ end }}_
|
|
||||||
{{- .Arch }}
|
|
||||||
{{- with .Arm }}v{{ . }}{{ end }}
|
|
||||||
{{- with .Mips }}_{{ . }}{{ end }}
|
|
||||||
{{- if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}
|
|
||||||
|
|
||||||
# packge 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
|
|
||||||
|
|
||||||
|
|
||||||
checksum:
|
checksum:
|
||||||
algorithm: sha512
|
algorithm: sha512
|
||||||
|
|
||||||
@@ -177,6 +128,7 @@ nfpms:
|
|||||||
preremove: ./caddy-dist/scripts/preremove.sh
|
preremove: ./caddy-dist/scripts/preremove.sh
|
||||||
postremove: ./caddy-dist/scripts/postremove.sh
|
postremove: ./caddy-dist/scripts/postremove.sh
|
||||||
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
github:
|
github:
|
||||||
owner: caddyserver
|
owner: caddyserver
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://caddyserver.com">
|
<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>
|
||||||
<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>
|
|
||||||
<br>
|
<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>
|
</p>
|
||||||
<hr>
|
<hr>
|
||||||
<h3 align="center">Every site on HTTPS</h3>
|
<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">Caddy is an extensible server platform that uses TLS by default.</p>
|
||||||
<p align="center">
|
<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>
|
<a href="https://pkg.go.dev/github.com/caddyserver/caddy/v2"><img src="https://img.shields.io/badge/godoc-reference-%23007d9c.svg"></a>
|
||||||
<br>
|
<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>
|
<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">
|
<p align="center">
|
||||||
<b>Powered by</b>
|
<b>Powered by</b>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://github.com/caddyserver/certmagic">
|
<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>
|
||||||
<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>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
@@ -70,7 +58,7 @@
|
|||||||
- **Stays up when other servers go down** due to TLS/OCSP/certificate-related issues
|
- **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
|
- **Production-ready** after serving trillions of requests and managing millions of TLS certificates
|
||||||
- **Scales to hundreds of thousands of sites** as proven in production
|
- **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
|
- **Highly extensible** [modular architecture](https://caddyserver.com/docs/architecture) lets Caddy do anything without bloat
|
||||||
- **Runs anywhere** with **no external dependencies** (not even libc)
|
- **Runs anywhere** with **no external dependencies** (not even libc)
|
||||||
- Written in Go, a language with higher **memory safety guarantees** than other servers
|
- 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:
|
Requirements:
|
||||||
|
|
||||||
- [Go 1.20 or newer](https://golang.org/dl/)
|
- [Go 1.18 or newer](https://golang.org/dl/)
|
||||||
|
|
||||||
### For development
|
### 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._
|
_**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
|
```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.
|
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"
|
"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
|
// AdminConfig configures Caddy's API endpoint, which is used
|
||||||
// to manage Caddy while it is running.
|
// to manage Caddy while it is running.
|
||||||
type AdminConfig struct {
|
type AdminConfig struct {
|
||||||
@@ -68,14 +57,7 @@ type AdminConfig struct {
|
|||||||
|
|
||||||
// The address to which the admin endpoint's listener should
|
// The address to which the admin endpoint's listener should
|
||||||
// bind itself. Can be any single network address that can be
|
// bind itself. Can be any single network address that can be
|
||||||
// parsed by Caddy. Accepts placeholders.
|
// parsed by Caddy. Accepts placeholders. Default: localhost:2019
|
||||||
// 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.
|
|
||||||
Listen string `json:"listen,omitempty"`
|
Listen string `json:"listen,omitempty"`
|
||||||
|
|
||||||
// If true, CORS headers will be emitted, and requests to the
|
// If true, CORS headers will be emitted, and requests to the
|
||||||
@@ -318,32 +300,7 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
|
|||||||
// messages. If the requested URI does not include an Internet host
|
// messages. If the requested URI does not include an Internet host
|
||||||
// name for the service being requested, then the Host header field MUST
|
// name for the service being requested, then the Host header field MUST
|
||||||
// be given with an empty value."
|
// 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[""] = struct{}{}
|
||||||
uniqueOrigins["127.0.0.1"] = struct{}{}
|
|
||||||
uniqueOrigins["::1"] = struct{}{}
|
|
||||||
} else {
|
} else {
|
||||||
uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
|
uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
|
||||||
uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
|
uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
|
||||||
@@ -615,13 +572,12 @@ func replaceRemoteAdminServer(ctx Context, cfg *Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ident *IdentityConfig) certmagicConfig(logger *zap.Logger, makeCache bool) *certmagic.Config {
|
func (ident *IdentityConfig) certmagicConfig(logger *zap.Logger, makeCache bool) *certmagic.Config {
|
||||||
var cmCfg *certmagic.Config
|
|
||||||
if ident == nil {
|
if ident == nil {
|
||||||
// user might not have configured identity; that's OK, we can still make a
|
// 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
|
// certmagic config, although it'll be mostly useless for remote management
|
||||||
ident = new(IdentityConfig)
|
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)
|
Storage: DefaultStorage, // do not act as part of a cluster (this is for the server's local identity)
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
Issuers: ident.issuers,
|
Issuers: ident.issuers,
|
||||||
@@ -631,11 +587,9 @@ func (ident *IdentityConfig) certmagicConfig(logger *zap.Logger, makeCache bool)
|
|||||||
GetConfigForCert: func(certmagic.Certificate) (*certmagic.Config, error) {
|
GetConfigForCert: func(certmagic.Certificate) (*certmagic.Config, error) {
|
||||||
return cmCfg, nil
|
return cmCfg, nil
|
||||||
},
|
},
|
||||||
Logger: logger.Named("cache"),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
cmCfg = certmagic.New(identityCertCache, template)
|
return certmagic.New(identityCertCache, *cmCfg)
|
||||||
return cmCfg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IdentityCredentials returns this instance's configured, managed identity credentials
|
// IdentityCredentials returns this instance's configured, managed identity credentials
|
||||||
@@ -1041,9 +995,9 @@ func handleConfigID(w http.ResponseWriter, r *http.Request) error {
|
|||||||
id := parts[2]
|
id := parts[2]
|
||||||
|
|
||||||
// map the ID to the expanded path
|
// map the ID to the expanded path
|
||||||
rawCfgMu.RLock()
|
currentCtxMu.RLock()
|
||||||
expanded, ok := rawCfgIndex[id]
|
expanded, ok := rawCfgIndex[id]
|
||||||
rawCfgMu.RUnlock()
|
defer currentCtxMu.RUnlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
return APIError{
|
return APIError{
|
||||||
HTTPStatus: http.StatusNotFound,
|
HTTPStatus: http.StatusNotFound,
|
||||||
|
|||||||
@@ -156,8 +156,8 @@ func changeConfig(method, path string, input []byte, ifMatchHeader string, force
|
|||||||
return fmt.Errorf("method not allowed")
|
return fmt.Errorf("method not allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
rawCfgMu.Lock()
|
currentCtxMu.Lock()
|
||||||
defer rawCfgMu.Unlock()
|
defer currentCtxMu.Unlock()
|
||||||
|
|
||||||
if ifMatchHeader != "" {
|
if ifMatchHeader != "" {
|
||||||
// expect the first and last character to be quotes
|
// expect the first and last character to be quotes
|
||||||
@@ -257,8 +257,8 @@ func changeConfig(method, path string, input []byte, ifMatchHeader string, force
|
|||||||
// readConfig traverses the current config to path
|
// readConfig traverses the current config to path
|
||||||
// and writes its JSON encoding to out.
|
// and writes its JSON encoding to out.
|
||||||
func readConfig(path string, out io.Writer) error {
|
func readConfig(path string, out io.Writer) error {
|
||||||
rawCfgMu.RLock()
|
currentCtxMu.RLock()
|
||||||
defer rawCfgMu.RUnlock()
|
defer currentCtxMu.RUnlock()
|
||||||
return unsyncedConfigAccess(http.MethodGet, path, nil, out)
|
return unsyncedConfigAccess(http.MethodGet, path, nil, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,7 +305,7 @@ func indexConfigObjects(ptr any, configPath string, index map[string]string) err
|
|||||||
// it as the new config, replacing any other current config.
|
// it as the new config, replacing any other current config.
|
||||||
// It does NOT update the raw config state, as this is a
|
// It does NOT update the raw config state, as this is a
|
||||||
// lower-level function; most callers will want to use Load
|
// 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,
|
// allowPersist is false, it will not be persisted to disk,
|
||||||
// even if it is configured to.
|
// even if it is configured to.
|
||||||
func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
||||||
@@ -314,7 +314,7 @@ func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
|||||||
strippedCfgJSON := RemoveMetaFields(cfgJSON)
|
strippedCfgJSON := RemoveMetaFields(cfgJSON)
|
||||||
|
|
||||||
var newCfg *Config
|
var newCfg *Config
|
||||||
err := StrictUnmarshalJSON(strippedCfgJSON, &newCfg)
|
err := strictUnmarshalJSON(strippedCfgJSON, &newCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -340,10 +340,8 @@ func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// swap old context (including its config) with the new one
|
// swap old context (including its config) with the new one
|
||||||
currentCtxMu.Lock()
|
|
||||||
oldCtx := currentCtx
|
oldCtx := currentCtx
|
||||||
currentCtx = ctx
|
currentCtx = ctx
|
||||||
currentCtxMu.Unlock()
|
|
||||||
|
|
||||||
// Stop, Cleanup each old app
|
// Stop, Cleanup each old app
|
||||||
unsyncedStop(oldCtx)
|
unsyncedStop(oldCtx)
|
||||||
@@ -629,35 +627,22 @@ type ConfigLoader interface {
|
|||||||
// stop the others. Stop should only be called
|
// stop the others. Stop should only be called
|
||||||
// if not replacing with a new config.
|
// if not replacing with a new config.
|
||||||
func Stop() error {
|
func Stop() error {
|
||||||
currentCtxMu.RLock()
|
|
||||||
ctx := currentCtx
|
|
||||||
currentCtxMu.RUnlock()
|
|
||||||
|
|
||||||
rawCfgMu.Lock()
|
|
||||||
unsyncedStop(ctx)
|
|
||||||
|
|
||||||
currentCtxMu.Lock()
|
currentCtxMu.Lock()
|
||||||
|
defer currentCtxMu.Unlock()
|
||||||
|
unsyncedStop(currentCtx)
|
||||||
currentCtx = Context{}
|
currentCtx = Context{}
|
||||||
currentCtxMu.Unlock()
|
|
||||||
|
|
||||||
rawCfgJSON = nil
|
rawCfgJSON = nil
|
||||||
rawCfgIndex = nil
|
rawCfgIndex = nil
|
||||||
rawCfg[rawConfigKey] = nil
|
rawCfg[rawConfigKey] = nil
|
||||||
rawCfgMu.Unlock()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// unsyncedStop stops ctx from running, but has
|
// unsyncedStop stops cfg from running, but has
|
||||||
// no locking around ctx. It is a no-op if ctx has a
|
// no locking around cfg. It is a no-op if cfg is
|
||||||
// nil cfg. If any app returns an error when stopping,
|
// nil. If any app returns an error when stopping,
|
||||||
// it is logged and the function continues stopping
|
// it is logged and the function continues stopping
|
||||||
// the next app. This function assumes all apps in
|
// the next app. This function assumes all apps in
|
||||||
// ctx were successfully started first.
|
// cfg 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.
|
|
||||||
func unsyncedStop(ctx Context) {
|
func unsyncedStop(ctx Context) {
|
||||||
if ctx.cfg == nil {
|
if ctx.cfg == nil {
|
||||||
return
|
return
|
||||||
@@ -984,12 +969,14 @@ type CtxKey string
|
|||||||
|
|
||||||
// This group of variables pertains to the current configuration.
|
// This group of variables pertains to the current configuration.
|
||||||
var (
|
var (
|
||||||
|
// currentCtxMu protects everything in this var block.
|
||||||
|
currentCtxMu sync.RWMutex
|
||||||
|
|
||||||
// currentCtx is the root context for the currently-running
|
// currentCtx is the root context for the currently-running
|
||||||
// configuration, which can be accessed through this value.
|
// configuration, which can be accessed through this value.
|
||||||
// If the Config contained in this value is not nil, then
|
// If the Config contained in this value is not nil, then
|
||||||
// a config is currently active/running.
|
// a config is currently active/running.
|
||||||
currentCtx Context
|
currentCtx Context
|
||||||
currentCtxMu sync.RWMutex
|
|
||||||
|
|
||||||
// rawCfg is the current, generic-decoded configuration;
|
// rawCfg is the current, generic-decoded configuration;
|
||||||
// we initialize it as a map with one field ("config")
|
// we initialize it as a map with one field ("config")
|
||||||
@@ -1007,10 +994,6 @@ var (
|
|||||||
// rawCfgIndex is the map of user-assigned ID to expanded
|
// rawCfgIndex is the map of user-assigned ID to expanded
|
||||||
// path, for converting /id/ paths to /config/ paths.
|
// path, for converting /id/ paths to /config/ paths.
|
||||||
rawCfgIndex map[string]string
|
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
|
// 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
|
// 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)
|
// 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)
|
warnings = append(warnings, warning)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,10 +63,10 @@ func (a Adapter) Adapt(body []byte, options map[string]any) ([]byte, []caddyconf
|
|||||||
return result, warnings, err
|
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.
|
// is any different from the input; empty warning and false otherwise.
|
||||||
// TODO: also perform this check on imported files
|
// 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
|
// replace windows-style newlines to normalize comparison
|
||||||
normalizedBody := bytes.Replace(body, []byte("\r\n"), []byte("\n"), -1)
|
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{
|
return caddyconfig.Warning{
|
||||||
File: filename,
|
File: filename,
|
||||||
Line: line,
|
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
|
}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,12 +101,12 @@ func (d *Dispenser) nextOnSameLine() bool {
|
|||||||
d.cursor++
|
d.cursor++
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if d.cursor >= len(d.tokens)-1 {
|
if d.cursor >= len(d.tokens) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
curr := d.tokens[d.cursor]
|
if d.cursor < len(d.tokens)-1 &&
|
||||||
next := d.tokens[d.cursor+1]
|
d.tokens[d.cursor].File == d.tokens[d.cursor+1].File &&
|
||||||
if !isNextOnNewLine(curr, next) {
|
d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) == d.tokens[d.cursor+1].Line {
|
||||||
d.cursor++
|
d.cursor++
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -122,12 +122,12 @@ func (d *Dispenser) NextLine() bool {
|
|||||||
d.cursor++
|
d.cursor++
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if d.cursor >= len(d.tokens)-1 {
|
if d.cursor >= len(d.tokens) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
curr := d.tokens[d.cursor]
|
if d.cursor < len(d.tokens)-1 &&
|
||||||
next := d.tokens[d.cursor+1]
|
(d.tokens[d.cursor].File != d.tokens[d.cursor+1].File ||
|
||||||
if isNextOnNewLine(curr, next) {
|
d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) < d.tokens[d.cursor+1].Line) {
|
||||||
d.cursor++
|
d.cursor++
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -203,17 +203,14 @@ func (d *Dispenser) Val() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValRaw gets the raw text of the current token (including quotes).
|
// 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.
|
// If there is no token loaded, it returns empty string.
|
||||||
func (d *Dispenser) ValRaw() string {
|
func (d *Dispenser) ValRaw() string {
|
||||||
if d.cursor < 0 || d.cursor >= len(d.tokens) {
|
if d.cursor < 0 || d.cursor >= len(d.tokens) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
quote := d.tokens[d.cursor].wasQuoted
|
quote := d.tokens[d.cursor].wasQuoted
|
||||||
if quote > 0 && quote != '<' {
|
if quote > 0 {
|
||||||
// string literal
|
return string(quote) + d.tokens[d.cursor].Text + string(quote) // string literal
|
||||||
return string(quote) + d.tokens[d.cursor].Text + string(quote)
|
|
||||||
}
|
}
|
||||||
return d.tokens[d.cursor].Text
|
return d.tokens[d.cursor].Text
|
||||||
}
|
}
|
||||||
@@ -399,7 +396,7 @@ func (d *Dispenser) ArgErr() error {
|
|||||||
// SyntaxErr creates a generic syntax error which explains what was
|
// SyntaxErr creates a generic syntax error which explains what was
|
||||||
// found and what was expected.
|
// found and what was expected.
|
||||||
func (d *Dispenser) SyntaxErr(expected string) error {
|
func (d *Dispenser) SyntaxErr(expected string) error {
|
||||||
msg := fmt.Sprintf("%s:%d - Syntax error: Unexpected token '%s', expecting '%s', import chain: ['%s']", d.File(), d.Line(), d.Val(), expected, 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)
|
return errors.New(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,7 +418,7 @@ func (d *Dispenser) Errf(format string, args ...any) error {
|
|||||||
|
|
||||||
// WrapErr takes an existing error and adds the Caddyfile file and line number.
|
// WrapErr takes an existing error and adds the Caddyfile file and line number.
|
||||||
func (d *Dispenser) WrapErr(err error) error {
|
func (d *Dispenser) WrapErr(err error) error {
|
||||||
return fmt.Errorf("%s:%d - Error during parsing: %w, import chain: ['%s']", d.File(), d.Line(), err, strings.Join(d.Token().imports, "','"))
|
return fmt.Errorf("%s:%d - Error during parsing: %w", d.File(), d.Line(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes the current token and returns the updated slice
|
// Delete deletes the current token and returns the updated slice
|
||||||
@@ -441,14 +438,14 @@ func (d *Dispenser) Delete() []Token {
|
|||||||
return d.tokens
|
return d.tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteN is the same as Delete, but can delete many tokens at once.
|
// numLineBreaks counts how many line breaks are in the token
|
||||||
// If there aren't N tokens available to delete, none are deleted.
|
// value given by the token index tknIdx. It returns 0 if the
|
||||||
func (d *Dispenser) DeleteN(amount int) []Token {
|
// token does not exist or there are no line breaks.
|
||||||
if amount > 0 && d.cursor >= (amount-1) && d.cursor <= len(d.tokens)-1 {
|
func (d *Dispenser) numLineBreaks(tknIdx int) int {
|
||||||
d.tokens = append(d.tokens[:d.cursor-(amount-1)], d.tokens[d.cursor+1:]...)
|
if tknIdx < 0 || tknIdx >= len(d.tokens) {
|
||||||
d.cursor -= amount
|
return 0
|
||||||
}
|
}
|
||||||
return d.tokens
|
return strings.Count(d.tokens[tknIdx].Text, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// isNewLine determines whether the current token is on a different
|
// isNewLine determines whether the current token is on a different
|
||||||
@@ -464,7 +461,25 @@ func (d *Dispenser) isNewLine() bool {
|
|||||||
|
|
||||||
prev := d.tokens[d.cursor-1]
|
prev := d.tokens[d.cursor-1]
|
||||||
curr := d.tokens[d.cursor]
|
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
|
// isNextOnNewLine determines whether the current token is on a different
|
||||||
@@ -480,5 +495,23 @@ func (d *Dispenser) isNextOnNewLine() bool {
|
|||||||
|
|
||||||
curr := d.tokens[d.cursor]
|
curr := d.tokens[d.cursor]
|
||||||
next := d.tokens[d.cursor+1]
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,142 +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"
|
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
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 {
|
|
||||||
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\[(.+)]`)
|
|
||||||
)
|
|
||||||
+32
-200
@@ -17,10 +17,7 @@ package caddyfile
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,41 +35,15 @@ type (
|
|||||||
|
|
||||||
// Token represents a single parsable unit.
|
// Token represents a single parsable unit.
|
||||||
Token struct {
|
Token struct {
|
||||||
File string
|
File string
|
||||||
imports []string
|
Line int
|
||||||
Line int
|
Text string
|
||||||
Text string
|
wasQuoted rune // enclosing quote character, if any
|
||||||
wasQuoted rune // enclosing quote character, if any
|
inSnippet bool
|
||||||
heredocMarker string
|
snippetName string
|
||||||
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.
|
// load prepares the lexer to scan an input for tokens.
|
||||||
// It discards any leading byte order mark.
|
// It discards any leading byte order mark.
|
||||||
func (l *lexer) load(input io.Reader) error {
|
func (l *lexer) load(input io.Reader) error {
|
||||||
@@ -104,93 +75,28 @@ func (l *lexer) load(input io.Reader) error {
|
|||||||
// may be escaped. The rest of the line is skipped
|
// may be escaped. The rest of the line is skipped
|
||||||
// if a "#" character is read in. Returns true if
|
// if a "#" character is read in. Returns true if
|
||||||
// a token was loaded; false otherwise.
|
// a token was loaded; false otherwise.
|
||||||
func (l *lexer) next() (bool, error) {
|
func (l *lexer) next() bool {
|
||||||
var val []rune
|
var val []rune
|
||||||
var comment, quoted, btQuoted, inHeredoc, heredocEscaped, escaped bool
|
var comment, quoted, btQuoted, escaped bool
|
||||||
var heredocMarker string
|
|
||||||
|
|
||||||
makeToken := func(quoted rune) bool {
|
makeToken := func(quoted rune) bool {
|
||||||
l.token.Text = string(val)
|
l.token.Text = string(val)
|
||||||
l.token.wasQuoted = quoted
|
l.token.wasQuoted = quoted
|
||||||
l.token.heredocMarker = heredocMarker
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
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()
|
ch, _, err := l.reader.ReadRune()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if len(val) > 0 {
|
if len(val) > 0 {
|
||||||
if inHeredoc {
|
return makeToken(0)
|
||||||
return false, fmt.Errorf("incomplete heredoc <<%s on line #%d, expected ending marker %s", heredocMarker, l.line+l.skippedLines, heredocMarker)
|
|
||||||
}
|
|
||||||
|
|
||||||
return makeToken(0), nil
|
|
||||||
}
|
}
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
return false, err
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// detect whether we have the start of a heredoc
|
|
||||||
if !inHeredoc && !heredocEscaped && len(val) > 1 && string(val[:2]) == "<<" {
|
|
||||||
if ch == '<' {
|
|
||||||
return false, fmt.Errorf("too many '<' for heredoc on line #%d; only use two, for example <<END", l.line)
|
|
||||||
}
|
|
||||||
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' {
|
|
||||||
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 == '\\' {
|
if !escaped && !btQuoted && ch == '\\' {
|
||||||
escaped = true
|
escaped = true
|
||||||
continue
|
continue
|
||||||
@@ -205,29 +111,26 @@ func (l *lexer) next() (bool, error) {
|
|||||||
}
|
}
|
||||||
escaped = false
|
escaped = false
|
||||||
} else {
|
} else {
|
||||||
if (quoted && ch == '"') || (btQuoted && ch == '`') {
|
if quoted && ch == '"' {
|
||||||
return makeToken(ch), nil
|
return makeToken('"')
|
||||||
|
}
|
||||||
|
if btQuoted && ch == '`' {
|
||||||
|
return makeToken('`')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// allow quoted text to wrap continue on multiple lines
|
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
l.line += 1 + l.skippedLines
|
l.line += 1 + l.skippedLines
|
||||||
l.skippedLines = 0
|
l.skippedLines = 0
|
||||||
}
|
}
|
||||||
// collect this character as part of the quoted token
|
|
||||||
val = append(val, ch)
|
val = append(val, ch)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if unicode.IsSpace(ch) {
|
if unicode.IsSpace(ch) {
|
||||||
// ignore CR altogether, we only actually care about LF (\n)
|
|
||||||
if ch == '\r' {
|
if ch == '\r' {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// end of the line
|
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
// newlines can be escaped to chain arguments
|
|
||||||
// onto multiple lines; else, increment the line count
|
|
||||||
if escaped {
|
if escaped {
|
||||||
l.skippedLines++
|
l.skippedLines++
|
||||||
escaped = false
|
escaped = false
|
||||||
@@ -235,18 +138,14 @@ func (l *lexer) next() (bool, error) {
|
|||||||
l.line += 1 + l.skippedLines
|
l.line += 1 + l.skippedLines
|
||||||
l.skippedLines = 0
|
l.skippedLines = 0
|
||||||
}
|
}
|
||||||
// comments (#) are single-line only
|
|
||||||
comment = false
|
comment = false
|
||||||
}
|
}
|
||||||
// any kind of space means we're at the end of this token
|
|
||||||
if len(val) > 0 {
|
if len(val) > 0 {
|
||||||
return makeToken(0), nil
|
return makeToken(0)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// comments must be at the start of a token,
|
|
||||||
// in other words, preceded by space or newline
|
|
||||||
if ch == '#' && len(val) == 0 {
|
if ch == '#' && len(val) == 0 {
|
||||||
comment = true
|
comment = true
|
||||||
}
|
}
|
||||||
@@ -267,12 +166,7 @@ func (l *lexer) next() (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if escaped {
|
if escaped {
|
||||||
// allow escaping the first < to skip the heredoc syntax
|
val = append(val, '\\')
|
||||||
if ch == '<' {
|
|
||||||
heredocEscaped = true
|
|
||||||
} else {
|
|
||||||
val = append(val, '\\')
|
|
||||||
}
|
|
||||||
escaped = false
|
escaped = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,86 +174,24 @@ func (l *lexer) next() (bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// finalizeHeredoc takes the runes read as the heredoc text and the marker,
|
// Tokenize takes bytes as input and lexes it into
|
||||||
// and processes the text to strip leading whitespace, returning the final
|
// a list of tokens that can be parsed as a Caddyfile.
|
||||||
// value without the leading whitespace.
|
// Also takes a filename to fill the token's File as
|
||||||
func (l *lexer) finalizeHeredoc(val []rune, marker string) ([]rune, error) {
|
// the source of the tokens, which is important to
|
||||||
stringVal := string(val)
|
// determine relative paths for `import` directives.
|
||||||
|
func Tokenize(input []byte, filename string) ([]Token, error) {
|
||||||
// find the last newline of the heredoc, which is where the contents end
|
l := lexer{}
|
||||||
lastNewline := strings.LastIndex(stringVal, "\n")
|
if err := l.load(bytes.NewReader(input)); err != nil {
|
||||||
|
return nil, err
|
||||||
// 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", "")
|
|
||||||
}
|
}
|
||||||
|
var tokens []Token
|
||||||
// Remove the trailing newline from the loop
|
for l.next() {
|
||||||
if len(out) > 0 && out[len(out)-1] == '\n' {
|
l.token.File = filename
|
||||||
out = out[:len(out)-1]
|
tokens = append(tokens, l.token)
|
||||||
}
|
}
|
||||||
|
return tokens, nil
|
||||||
// return the final value
|
|
||||||
return []rune(out), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t Token) Quoted() bool {
|
func (t Token) Quoted() bool {
|
||||||
return t.wasQuoted > 0
|
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"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type lexerTestCase struct {
|
||||||
|
input []byte
|
||||||
|
expected []Token
|
||||||
|
}
|
||||||
|
|
||||||
func TestLexer(t *testing.T) {
|
func TestLexer(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []lexerTestCase{
|
||||||
input []byte
|
|
||||||
expected []Token
|
|
||||||
expectErr bool
|
|
||||||
errorMessage string
|
|
||||||
}{
|
|
||||||
{
|
{
|
||||||
input: []byte(`host:123`),
|
input: []byte(`host:123`),
|
||||||
expected: []Token{
|
expected: []Token{
|
||||||
@@ -249,178 +249,12 @@ func TestLexer(t *testing.T) {
|
|||||||
{Line: 1, Text: `quotes`},
|
{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(`heredoc <EOF
|
|
||||||
content
|
|
||||||
EOF same-line-arg
|
|
||||||
`),
|
|
||||||
expected: []Token{
|
|
||||||
{Line: 1, Text: `heredoc`},
|
|
||||||
{Line: 1, Text: `<EOF`},
|
|
||||||
{Line: 2, Text: `content`},
|
|
||||||
{Line: 3, Text: `EOF`},
|
|
||||||
{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(`heredoc <<HERE SAME LINE
|
|
||||||
content
|
|
||||||
HERE same-line-arg
|
|
||||||
`),
|
|
||||||
expectErr: true,
|
|
||||||
errorMessage: "heredoc marker on line #1 must contain only alpha-numeric characters, dashes and underscores; got 'HERE SAME LINE'",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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 {
|
for i, testCase := range testCases {
|
||||||
actual, err := Tokenize(testCase.input, "")
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("%v", err)
|
t.Errorf("%v", err)
|
||||||
}
|
}
|
||||||
lexerCompare(t, i, testCase.expected, actual)
|
lexerCompare(t, i, testCase.expected, actual)
|
||||||
}
|
}
|
||||||
@@ -428,17 +262,17 @@ EOF same-line-arg
|
|||||||
|
|
||||||
func lexerCompare(t *testing.T, n int, expected, actual []Token) {
|
func lexerCompare(t *testing.T, n int, expected, actual []Token) {
|
||||||
if len(expected) != len(actual) {
|
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++ {
|
for i := 0; i < len(actual) && i < len(expected); i++ {
|
||||||
if actual[i].Line != expected[i].Line {
|
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)
|
n, i, expected[i].Text, expected[i].Line, actual[i].Line)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if actual[i].Text != expected[i].Text {
|
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)
|
n, i, expected[i].Text, actual[i].Text)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-139
@@ -20,6 +20,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
@@ -60,12 +61,20 @@ func Parse(filename string, input []byte) ([]ServerBlock, error) {
|
|||||||
// It returns all the tokens from the input, unstructured
|
// It returns all the tokens from the input, unstructured
|
||||||
// and in order. It may mutate input as it expands env vars.
|
// and in order. It may mutate input as it expands env vars.
|
||||||
func allTokens(filename string, input []byte) ([]Token, error) {
|
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.
|
// replaceEnvVars replaces all occurrences of environment variables.
|
||||||
// It mutates the underlying array and returns the updated slice.
|
// It mutates the underlying array and returns the updated slice.
|
||||||
func replaceEnvVars(input []byte) []byte {
|
func replaceEnvVars(input []byte) ([]byte, error) {
|
||||||
var offset int
|
var offset int
|
||||||
for {
|
for {
|
||||||
begin := bytes.Index(input[offset:], spanOpen)
|
begin := bytes.Index(input[offset:], spanOpen)
|
||||||
@@ -106,7 +115,7 @@ func replaceEnvVars(input []byte) []byte {
|
|||||||
// continue at the end of the replacement
|
// continue at the end of the replacement
|
||||||
offset = begin + len(envVarBytes)
|
offset = begin + len(envVarBytes)
|
||||||
}
|
}
|
||||||
return input
|
return input, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type parser struct {
|
type parser struct {
|
||||||
@@ -148,6 +157,7 @@ func (p *parser) begin() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := p.addresses()
|
err := p.addresses()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -158,25 +168,6 @@ func (p *parser) begin() error {
|
|||||||
return nil
|
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 ok, name := p.isSnippet(); ok {
|
||||||
if p.definedSnippets == nil {
|
if p.definedSnippets == nil {
|
||||||
p.definedSnippets = map[string][]Token{}
|
p.definedSnippets = map[string][]Token{}
|
||||||
@@ -185,15 +176,16 @@ func (p *parser) begin() error {
|
|||||||
return p.Errf("redeclaration of previously declared snippet %s", name)
|
return p.Errf("redeclaration of previously declared snippet %s", name)
|
||||||
}
|
}
|
||||||
// consume all tokens til matched close brace
|
// consume all tokens til matched close brace
|
||||||
tokens, err := p.blockTokens(false)
|
tokens, err := p.snippetTokens()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Just as we need to track which file the token comes from, we need to
|
// 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
|
// keep track of which snippets do the tokens come from. This is helpful
|
||||||
// in tracking import cycles across files/snippets by namespacing them.
|
// in tracking import cycles across files/snippets by namespacing them. Without
|
||||||
// Without this, we end up with false-positives in cycle-detection.
|
// this we end up with false-positives in cycle-detection.
|
||||||
for k, v := range tokens {
|
for k, v := range tokens {
|
||||||
|
v.inSnippet = true
|
||||||
v.snippetName = name
|
v.snippetName = name
|
||||||
tokens[k] = v
|
tokens[k] = v
|
||||||
}
|
}
|
||||||
@@ -214,7 +206,7 @@ func (p *parser) addresses() error {
|
|||||||
|
|
||||||
// special case: import directive replaces tokens during parse-time
|
// special case: import directive replaces tokens during parse-time
|
||||||
if tkn == "import" && p.isNewLine() {
|
if tkn == "import" && p.isNewLine() {
|
||||||
err := p.doImport(0)
|
err := p.doImport()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -314,7 +306,7 @@ func (p *parser) directives() error {
|
|||||||
|
|
||||||
// special case: import directive replaces tokens during parse-time
|
// special case: import directive replaces tokens during parse-time
|
||||||
if p.Val() == "import" {
|
if p.Val() == "import" {
|
||||||
err := p.doImport(1)
|
err := p.doImport()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -340,7 +332,7 @@ func (p *parser) directives() error {
|
|||||||
// is on the token before where the import directive was. In
|
// is on the token before where the import directive was. In
|
||||||
// other words, call Next() to access the first token that was
|
// other words, call Next() to access the first token that was
|
||||||
// imported.
|
// imported.
|
||||||
func (p *parser) doImport(nesting int) error {
|
func (p *parser) doImport() error {
|
||||||
// syntax checks
|
// syntax checks
|
||||||
if !p.NextArg() {
|
if !p.NextArg() {
|
||||||
return p.ArgErr()
|
return p.ArgErr()
|
||||||
@@ -353,8 +345,11 @@ func (p *parser) doImport(nesting int) error {
|
|||||||
// grab remaining args as placeholder replacements
|
// grab remaining args as placeholder replacements
|
||||||
args := p.RemainingArgs()
|
args := p.RemainingArgs()
|
||||||
|
|
||||||
// set up a replacer for non-variadic args replacement
|
// add args to the replacer
|
||||||
repl := makeArgsReplacer(args)
|
repl := caddy.NewEmptyReplacer()
|
||||||
|
for index, arg := range args {
|
||||||
|
repl.Set("args."+strconv.Itoa(index), arg)
|
||||||
|
}
|
||||||
|
|
||||||
// splice out the import directive and its arguments
|
// splice out the import directive and its arguments
|
||||||
// (2 tokens, plus the length of args)
|
// (2 tokens, plus the length of args)
|
||||||
@@ -402,20 +397,6 @@ func (p *parser) doImport(nesting int) error {
|
|||||||
} else {
|
} else {
|
||||||
return p.Errf("File to import not found: %s", importPattern)
|
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
|
// collect all the imported tokens
|
||||||
@@ -430,7 +411,7 @@ func (p *parser) doImport(nesting int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nodeName := p.File()
|
nodeName := p.File()
|
||||||
if p.Token().snippetName != "" {
|
if p.Token().inSnippet {
|
||||||
nodeName += fmt.Sprintf(":%s", p.Token().snippetName)
|
nodeName += fmt.Sprintf(":%s", p.Token().snippetName)
|
||||||
}
|
}
|
||||||
p.importGraph.addNode(nodeName)
|
p.importGraph.addNode(nodeName)
|
||||||
@@ -441,69 +422,13 @@ func (p *parser) doImport(nesting int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// copy the tokens so we don't overwrite p.definedSnippets
|
// copy the tokens so we don't overwrite p.definedSnippets
|
||||||
tokensCopy := make([]Token, 0, len(importedTokens))
|
tokensCopy := make([]Token, len(importedTokens))
|
||||||
|
copy(tokensCopy, importedTokens)
|
||||||
var (
|
|
||||||
maybeSnippet bool
|
|
||||||
maybeSnippetId bool
|
|
||||||
index int
|
|
||||||
)
|
|
||||||
|
|
||||||
// run the argument replacer on the tokens
|
// run the argument replacer on the tokens
|
||||||
// golang for range slice return a copy of value
|
for index, token := range tokensCopy {
|
||||||
// similarly, append also copy value
|
token.Text = repl.ReplaceKnown(token.Text, "")
|
||||||
for i, token := range importedTokens {
|
tokensCopy[index] = token
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// splice the imported tokens in the place of the import statement
|
// splice the imported tokens in the place of the import statement
|
||||||
@@ -534,12 +459,6 @@ func (p *parser) doSingleImport(importFile string) ([]Token, error) {
|
|||||||
return nil, p.Errf("Could not read imported file %s: %v", importFile, err)
|
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)
|
importedTokens, err := allTokens(importFile, input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, p.Errf("Could not read tokens while importing %s: %v", importFile, err)
|
return nil, p.Errf("Could not read tokens while importing %s: %v", importFile, err)
|
||||||
@@ -578,9 +497,6 @@ func (p *parser) directive() error {
|
|||||||
if !p.isNextOnNewLine() && p.Token().wasQuoted == 0 {
|
if !p.isNextOnNewLine() && p.Token().wasQuoted == 0 {
|
||||||
return p.Err("Unexpected next token after '{' on same line")
|
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() == "{}" {
|
} else if p.Val() == "{}" {
|
||||||
if p.isNextOnNewLine() && p.Token().wasQuoted == 0 {
|
if p.isNextOnNewLine() && p.Token().wasQuoted == 0 {
|
||||||
return p.Err("Unexpected '{}' at end of line")
|
return p.Err("Unexpected '{}' at end of line")
|
||||||
@@ -593,7 +509,7 @@ func (p *parser) directive() error {
|
|||||||
} else if p.Val() == "}" && p.nesting == 0 {
|
} else if p.Val() == "}" && p.nesting == 0 {
|
||||||
return p.Err("Unexpected '}' because no matching opening brace")
|
return p.Err("Unexpected '}' because no matching opening brace")
|
||||||
} else if p.Val() == "import" && p.isNewLine() {
|
} else if p.Val() == "import" && p.isNewLine() {
|
||||||
if err := p.doImport(1); err != nil {
|
if err := p.doImport(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.cursor-- // cursor is advanced when we continue, so roll back one more
|
p.cursor-- // cursor is advanced when we continue, so roll back one more
|
||||||
@@ -634,15 +550,6 @@ func (p *parser) closeCurlyBrace() error {
|
|||||||
return nil
|
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) {
|
func (p *parser) isSnippet() (bool, string) {
|
||||||
keys := p.block.Keys
|
keys := p.block.Keys
|
||||||
// A snippet block is a single key with parens. Nothing else qualifies.
|
// 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.
|
// read and store everything in a block for later replay.
|
||||||
func (p *parser) blockTokens(retainCurlies bool) ([]Token, error) {
|
func (p *parser) snippetTokens() ([]Token, error) {
|
||||||
// block must have curlies.
|
// snippet must have curlies.
|
||||||
err := p.openCurlyBrace()
|
err := p.openCurlyBrace()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
nesting := 1 // count our own nesting
|
nesting := 1 // count our own nesting in snippets
|
||||||
tokens := []Token{}
|
tokens := []Token{}
|
||||||
if retainCurlies {
|
|
||||||
tokens = append(tokens, p.Token())
|
|
||||||
}
|
|
||||||
for p.Next() {
|
for p.Next() {
|
||||||
if p.Val() == "}" {
|
if p.Val() == "}" {
|
||||||
nesting--
|
nesting--
|
||||||
if nesting == 0 {
|
if nesting == 0 {
|
||||||
if retainCurlies {
|
|
||||||
tokens = append(tokens, p.Token())
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -690,10 +591,9 @@ func (p *parser) blockTokens(retainCurlies bool) ([]Token, error) {
|
|||||||
// head of the server block with tokens, which are
|
// head of the server block with tokens, which are
|
||||||
// grouped by segments.
|
// grouped by segments.
|
||||||
type ServerBlock struct {
|
type ServerBlock struct {
|
||||||
HasBraces bool
|
HasBraces bool
|
||||||
Keys []string
|
Keys []string
|
||||||
Segments []Segment
|
Segments []Segment
|
||||||
IsNamedRoute bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DispenseDirective returns a dispenser that contains
|
// DispenseDirective returns a dispenser that contains
|
||||||
|
|||||||
@@ -21,88 +21,6 @@ import (
|
|||||||
"testing"
|
"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) {
|
func TestAllTokens(t *testing.T) {
|
||||||
input := []byte("a b c\nd e")
|
input := []byte("a b c\nd e")
|
||||||
expected := []string{"a", "b", "c", "d", "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{}},
|
{`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{}},
|
||||||
|
|
||||||
{``, false, []string{}, []int{}},
|
{``, false, []string{}, []int{}},
|
||||||
@@ -293,14 +194,6 @@ func TestParseOneAndImport(t *testing.T) {
|
|||||||
// Unexpected next token after '{' on same line
|
// Unexpected next token after '{' on same line
|
||||||
{`localhost
|
{`localhost
|
||||||
dir1 { a b }`, true, []string{"localhost"}, []int{}},
|
dir1 { a b }`, true, []string{"localhost"}, []int{}},
|
||||||
|
|
||||||
// Unexpected '{' on a new line
|
|
||||||
{`localhost
|
|
||||||
dir1
|
|
||||||
{
|
|
||||||
a b
|
|
||||||
}`, true, []string{"localhost"}, []int{}},
|
|
||||||
|
|
||||||
// Workaround with quotes
|
// Workaround with quotes
|
||||||
{`localhost
|
{`localhost
|
||||||
dir1 "{" a b "}"`, false, []string{"localhost"}, []int{5}},
|
dir1 "{" a b "}"`, false, []string{"localhost"}, []int{5}},
|
||||||
@@ -711,7 +604,10 @@ func TestEnvironmentReplacement(t *testing.T) {
|
|||||||
expect: "}{$",
|
expect: "}{$",
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
actual := replaceEnvVars([]byte(test.input))
|
actual, err := replaceEnvVars([]byte(test.input))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
if !bytes.Equal(actual, []byte(test.expect)) {
|
if !bytes.Equal(actual, []byte(test.expect)) {
|
||||||
t.Errorf("Test %d: Expected: '%s' but got '%s'", i, test.expect, actual)
|
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,7 +24,6 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
@@ -48,7 +47,6 @@ func init() {
|
|||||||
RegisterHandlerDirective("route", parseRoute)
|
RegisterHandlerDirective("route", parseRoute)
|
||||||
RegisterHandlerDirective("handle", parseHandle)
|
RegisterHandlerDirective("handle", parseHandle)
|
||||||
RegisterDirective("handle_errors", parseHandleErrors)
|
RegisterDirective("handle_errors", parseHandleErrors)
|
||||||
RegisterHandlerDirective("invoke", parseInvoke)
|
|
||||||
RegisterDirective("log", parseLog)
|
RegisterDirective("log", parseLog)
|
||||||
RegisterHandlerDirective("skip_log", parseSkipLog)
|
RegisterHandlerDirective("skip_log", parseSkipLog)
|
||||||
}
|
}
|
||||||
@@ -77,22 +75,16 @@ func parseBind(h Helper) ([]ConfigValue, error) {
|
|||||||
// trusted_leaf_cert <base64_der>
|
// trusted_leaf_cert <base64_der>
|
||||||
// trusted_leaf_cert_file <filename>
|
// trusted_leaf_cert_file <filename>
|
||||||
// }
|
// }
|
||||||
// alpn <values...>
|
// alpn <values...>
|
||||||
// load <paths...>
|
// load <paths...>
|
||||||
// ca <acme_ca_endpoint>
|
// ca <acme_ca_endpoint>
|
||||||
// ca_root <pem_file>
|
// ca_root <pem_file>
|
||||||
// key_type [ed25519|p256|p384|rsa2048|rsa4096]
|
// dns <provider_name> [...]
|
||||||
// dns <provider_name> [...]
|
|
||||||
// propagation_delay <duration>
|
|
||||||
// propagation_timeout <duration>
|
|
||||||
// resolvers <dns_servers...>
|
|
||||||
// dns_ttl <duration>
|
|
||||||
// dns_challenge_override_domain <domain>
|
|
||||||
// on_demand
|
// on_demand
|
||||||
// eab <key_id> <mac_key>
|
// eab <key_id> <mac_key>
|
||||||
// issuer <module_name> [...]
|
// issuer <module_name> [...]
|
||||||
// get_certificate <module_name> [...]
|
// get_certificate <module_name> [...]
|
||||||
// insecure_secrets_log <log_file>
|
// insecure_secrets_log <log_file>
|
||||||
// }
|
// }
|
||||||
func parseTLS(h Helper) ([]ConfigValue, error) {
|
func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||||
cp := new(caddytls.ConnectionPolicy)
|
cp := new(caddytls.ConnectionPolicy)
|
||||||
@@ -371,75 +363,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||||||
}
|
}
|
||||||
acmeIssuer.Challenges.DNS.Resolvers = args
|
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":
|
case "dns_challenge_override_domain":
|
||||||
arg := h.RemainingArgs()
|
arg := h.RemainingArgs()
|
||||||
if len(arg) != 1 {
|
if len(arg) != 1 {
|
||||||
@@ -732,20 +655,29 @@ func parseError(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
|||||||
|
|
||||||
// parseRoute parses the route directive.
|
// parseRoute parses the route directive.
|
||||||
func parseRoute(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
func parseRoute(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||||
|
sr := new(caddyhttp.Subroute)
|
||||||
|
|
||||||
allResults, err := parseSegmentAsConfig(h)
|
allResults, err := parseSegmentAsConfig(h)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, result := range allResults {
|
for _, result := range allResults {
|
||||||
switch result.Value.(type) {
|
switch handler := result.Value.(type) {
|
||||||
case caddyhttp.Route, caddyhttp.Subroute:
|
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:
|
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 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) {
|
func parseHandle(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||||
@@ -765,31 +697,9 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
|
|||||||
}, nil
|
}, 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:
|
// parseLog parses the log directive. Syntax:
|
||||||
//
|
//
|
||||||
// log <logger_name> {
|
// log {
|
||||||
// hostnames <hostnames...>
|
|
||||||
// output <writer_module> ...
|
// output <writer_module> ...
|
||||||
// format <encoder_module> ...
|
// format <encoder_module> ...
|
||||||
// level <level>
|
// level <level>
|
||||||
@@ -810,13 +720,11 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
|||||||
var configValues []ConfigValue
|
var configValues []ConfigValue
|
||||||
for h.Next() {
|
for h.Next() {
|
||||||
// Logic below expects that a name is always present when a
|
// Logic below expects that a name is always present when a
|
||||||
// global option is being parsed; or an optional override
|
// global option is being parsed.
|
||||||
// is supported for access logs.
|
var globalLogName string
|
||||||
var logName string
|
|
||||||
|
|
||||||
if parseAsGlobalOption {
|
if parseAsGlobalOption {
|
||||||
if h.NextArg() {
|
if h.NextArg() {
|
||||||
logName = h.Val()
|
globalLogName = h.Val()
|
||||||
|
|
||||||
// Only a single argument is supported.
|
// Only a single argument is supported.
|
||||||
if h.NextArg() {
|
if h.NextArg() {
|
||||||
@@ -827,47 +735,26 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
|||||||
// reference the default logger. See the
|
// reference the default logger. See the
|
||||||
// setupNewDefault function in the logging
|
// setupNewDefault function in the logging
|
||||||
// package for where this is configured.
|
// package for where this is configured.
|
||||||
logName = caddy.DefaultLoggerName
|
globalLogName = caddy.DefaultLoggerName
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify this name is unused.
|
// Verify this name is unused.
|
||||||
_, used := globalLogNames[logName]
|
_, used := globalLogNames[globalLogName]
|
||||||
if used {
|
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 {
|
} else {
|
||||||
// An optional override of the logger name can be provided;
|
// No arguments are supported for the server block log directive
|
||||||
// otherwise a default will be used, like "log0", "log1", etc.
|
|
||||||
if h.NextArg() {
|
if h.NextArg() {
|
||||||
logName = h.Val()
|
return nil, h.ArgErr()
|
||||||
|
|
||||||
// Only a single argument is supported.
|
|
||||||
if h.NextArg() {
|
|
||||||
return nil, h.ArgErr()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cl := new(caddy.CustomLog)
|
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) {
|
for h.NextBlock(0) {
|
||||||
switch h.Val() {
|
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":
|
case "output":
|
||||||
if !h.NextArg() {
|
if !h.NextArg() {
|
||||||
return nil, h.ArgErr()
|
return nil, h.ArgErr()
|
||||||
@@ -926,16 +813,18 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "include":
|
case "include":
|
||||||
|
// This configuration is only allowed in the global options
|
||||||
if !parseAsGlobalOption {
|
if !parseAsGlobalOption {
|
||||||
return nil, h.Err("include is not allowed in the log directive")
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
for h.NextArg() {
|
for h.NextArg() {
|
||||||
cl.Include = append(cl.Include, h.Val())
|
cl.Include = append(cl.Include, h.Val())
|
||||||
}
|
}
|
||||||
|
|
||||||
case "exclude":
|
case "exclude":
|
||||||
|
// This configuration is only allowed in the global options
|
||||||
if !parseAsGlobalOption {
|
if !parseAsGlobalOption {
|
||||||
return nil, h.Err("exclude is not allowed in the log directive")
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
for h.NextArg() {
|
for h.NextArg() {
|
||||||
cl.Exclude = append(cl.Exclude, h.Val())
|
cl.Exclude = append(cl.Exclude, h.Val())
|
||||||
@@ -947,34 +836,24 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
|||||||
}
|
}
|
||||||
|
|
||||||
var val namedCustomLog
|
var val namedCustomLog
|
||||||
val.hostnames = customHostnames
|
|
||||||
|
|
||||||
isEmptyConfig := reflect.DeepEqual(cl, new(caddy.CustomLog))
|
|
||||||
|
|
||||||
// Skip handling of empty logging configs
|
// Skip handling of empty logging configs
|
||||||
|
if !reflect.DeepEqual(cl, new(caddy.CustomLog)) {
|
||||||
if parseAsGlobalOption {
|
if parseAsGlobalOption {
|
||||||
// Use indicated name for global log options
|
// Use indicated name for global log options
|
||||||
val.name = logName
|
val.name = globalLogName
|
||||||
} else {
|
val.log = cl
|
||||||
if logName != "" {
|
} else {
|
||||||
val.name = logName
|
|
||||||
} else if !isEmptyConfig {
|
|
||||||
// Construct a log name for server log streams
|
// Construct a log name for server log streams
|
||||||
logCounter, ok := h.State["logCounter"].(int)
|
logCounter, ok := h.State["logCounter"].(int)
|
||||||
if !ok {
|
if !ok {
|
||||||
logCounter = 0
|
logCounter = 0
|
||||||
}
|
}
|
||||||
val.name = fmt.Sprintf("log%d", logCounter)
|
val.name = fmt.Sprintf("log%d", logCounter)
|
||||||
|
cl.Include = []string{"http.log.access." + val.name}
|
||||||
|
val.log = cl
|
||||||
logCounter++
|
logCounter++
|
||||||
h.State["logCounter"] = 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{
|
configValues = append(configValues, ConfigValue{
|
||||||
Class: "custom_log",
|
Class: "custom_log",
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package httpcaddyfile
|
package httpcaddyfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
@@ -52,13 +51,12 @@ func TestLogDirectiveSyntax(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `:8080 {
|
input: `:8080 {
|
||||||
log name-override {
|
log invalid {
|
||||||
output file foo.log
|
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: true,
|
||||||
expectError: false,
|
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ var directiveOrder = []string{
|
|||||||
"templates",
|
"templates",
|
||||||
|
|
||||||
// special routing & dispatching directives
|
// special routing & dispatching directives
|
||||||
"invoke",
|
|
||||||
"handle",
|
"handle",
|
||||||
"handle_path",
|
"handle_path",
|
||||||
"route",
|
"route",
|
||||||
@@ -173,7 +172,6 @@ func (h Helper) Caddyfiles() []string {
|
|||||||
for file := range files {
|
for file := range files {
|
||||||
filesSlice = append(filesSlice, file)
|
filesSlice = append(filesSlice, file)
|
||||||
}
|
}
|
||||||
sort.Strings(filesSlice)
|
|
||||||
return filesSlice
|
return filesSlice
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +289,7 @@ func ParseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildSubroute(allResults, h.groupCounter, true)
|
return buildSubroute(allResults, h.groupCounter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseSegmentAsConfig parses the segment such that its subdirectives
|
// parseSegmentAsConfig parses the segment such that its subdirectives
|
||||||
@@ -429,16 +427,26 @@ func sortRoutes(routes []ConfigValue) {
|
|||||||
jPathLen = len(jPM[0])
|
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
|
// we can only confidently compare path lengths if both
|
||||||
// directives have a single path to match (issue #5037)
|
// directives have a single path to match (issue #5037)
|
||||||
if iPathLen > 0 && jPathLen > 0 {
|
if iPathLen > 0 && jPathLen > 0 {
|
||||||
// if both paths are the same except for a trailing wildcard,
|
// sort least-specific (shortest) path first
|
||||||
// sort by the shorter path first (which is more specific)
|
return iPathLen < jPathLen
|
||||||
if strings.TrimSuffix(iPM[0], "*") == strings.TrimSuffix(jPM[0], "*") {
|
}
|
||||||
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
|
// sort most-specific (longest) path first
|
||||||
return iPathLen > jPathLen
|
return iPathLen > jPathLen
|
||||||
}
|
}
|
||||||
@@ -447,18 +455,7 @@ func sortRoutes(routes []ConfigValue) {
|
|||||||
// sort whichever one has a matcher first; if both have
|
// sort whichever one has a matcher first; if both have
|
||||||
// a matcher, sort equally (stable sort preserves order)
|
// a matcher, sort equally (stable sort preserves order)
|
||||||
return len(iRoute.MatcherSetsRaw) > 0 && len(jRoute.MatcherSetsRaw) == 0
|
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
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import (
|
|||||||
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -53,10 +52,8 @@ type ServerType struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup makes a config from the tokens.
|
// Setup makes a config from the tokens.
|
||||||
func (st ServerType) Setup(
|
func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
||||||
inputServerBlocks []caddyfile.ServerBlock,
|
options map[string]any) (*caddy.Config, []caddyconfig.Warning, error) {
|
||||||
options map[string]any,
|
|
||||||
) (*caddy.Config, []caddyconfig.Warning, error) {
|
|
||||||
var warnings []caddyconfig.Warning
|
var warnings []caddyconfig.Warning
|
||||||
gc := counter{new(int)}
|
gc := counter{new(int)}
|
||||||
state := make(map[string]any)
|
state := make(map[string]any)
|
||||||
@@ -82,11 +79,6 @@ func (st ServerType) Setup(
|
|||||||
return nil, warnings, err
|
return nil, warnings, err
|
||||||
}
|
}
|
||||||
|
|
||||||
originalServerBlocks, err = st.extractNamedRoutes(originalServerBlocks, options, &warnings)
|
|
||||||
if err != nil {
|
|
||||||
return nil, warnings, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace shorthand placeholders (which are convenient
|
// replace shorthand placeholders (which are convenient
|
||||||
// when writing a Caddyfile) with their actual placeholder
|
// when writing a Caddyfile) with their actual placeholder
|
||||||
// identifiers or variable names
|
// identifiers or variable names
|
||||||
@@ -180,18 +172,6 @@ func (st ServerType) Setup(
|
|||||||
result.directive = dir
|
result.directive = dir
|
||||||
sb.pile[result.Class] = append(sb.pile[result.Class], result)
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +222,7 @@ func (st ServerType) Setup(
|
|||||||
if ncl.name == caddy.DefaultLoggerName {
|
if ncl.name == caddy.DefaultLoggerName {
|
||||||
hasDefaultLog = true
|
hasDefaultLog = true
|
||||||
}
|
}
|
||||||
if _, ok := options["debug"]; ok && ncl.log != nil && ncl.log.Level == "" {
|
if _, ok := options["debug"]; ok && ncl.log.Level == "" {
|
||||||
ncl.log.Level = zap.DebugLevel.CapitalString()
|
ncl.log.Level = zap.DebugLevel.CapitalString()
|
||||||
}
|
}
|
||||||
customLogs = append(customLogs, ncl)
|
customLogs = append(customLogs, ncl)
|
||||||
@@ -261,9 +241,7 @@ func (st ServerType) Setup(
|
|||||||
if _, ok := options["debug"]; ok {
|
if _, ok := options["debug"]; ok {
|
||||||
customLogs = append(customLogs, namedCustomLog{
|
customLogs = append(customLogs, namedCustomLog{
|
||||||
name: caddy.DefaultLoggerName,
|
name: caddy.DefaultLoggerName,
|
||||||
log: &caddy.CustomLog{
|
log: &caddy.CustomLog{Level: zap.DebugLevel.CapitalString()},
|
||||||
BaseLog: caddy.BaseLog{Level: zap.DebugLevel.CapitalString()},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,38 +286,13 @@ func (st ServerType) Setup(
|
|||||||
if adminConfig, ok := options["admin"].(*caddy.AdminConfig); ok && adminConfig != nil {
|
if adminConfig, ok := options["admin"].(*caddy.AdminConfig); ok && adminConfig != nil {
|
||||||
cfg.Admin = adminConfig
|
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 len(customLogs) > 0 {
|
||||||
if cfg.Logging == nil {
|
if cfg.Logging == nil {
|
||||||
cfg.Logging = &caddy.Logging{
|
cfg.Logging = &caddy.Logging{
|
||||||
Logs: make(map[string]*caddy.CustomLog),
|
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 {
|
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 != "" {
|
if ncl.name != "" {
|
||||||
cfg.Logging.Logs[ncl.name] = ncl.log
|
cfg.Logging.Logs[ncl.name] = ncl.log
|
||||||
}
|
}
|
||||||
@@ -353,16 +306,8 @@ func (st ServerType) Setup(
|
|||||||
cfg.Logging.Logs[caddy.DefaultLoggerName] = defaultLog
|
cfg.Logging.Logs[caddy.DefaultLoggerName] = defaultLog
|
||||||
}
|
}
|
||||||
defaultLog.Exclude = append(defaultLog.Exclude, ncl.log.Include...)
|
defaultLog.Exclude = append(defaultLog.Exclude, ncl.log.Include...)
|
||||||
|
|
||||||
// avoid duplicates by sorting + compacting
|
|
||||||
slices.Sort[string](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
|
return cfg, warnings, nil
|
||||||
@@ -445,77 +390,6 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options
|
|||||||
return serverBlocks[1:], nil
|
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,
|
|
||||||
) ([]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
|
|
||||||
}
|
|
||||||
|
|
||||||
// zip up all the segments since ParseSegmentAsSubroute
|
|
||||||
// was designed to take a directive+
|
|
||||||
wholeSegment := caddyfile.Segment{}
|
|
||||||
for _, segment := range sb.block.Segments {
|
|
||||||
wholeSegment = append(wholeSegment, segment...)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
// serversFromPairings creates the servers for each pairing of addresses
|
||||||
// to server blocks. Each pairing is essentially a server definition.
|
// to server blocks. Each pairing is essentially a server definition.
|
||||||
func (st *ServerType) serversFromPairings(
|
func (st *ServerType) serversFromPairings(
|
||||||
@@ -526,7 +400,6 @@ func (st *ServerType) serversFromPairings(
|
|||||||
) (map[string]*caddyhttp.Server, error) {
|
) (map[string]*caddyhttp.Server, error) {
|
||||||
servers := make(map[string]*caddyhttp.Server)
|
servers := make(map[string]*caddyhttp.Server)
|
||||||
defaultSNI := tryString(options["default_sni"], warnings)
|
defaultSNI := tryString(options["default_sni"], warnings)
|
||||||
fallbackSNI := tryString(options["fallback_sni"], warnings)
|
|
||||||
|
|
||||||
httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
|
httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
|
||||||
if hp, ok := options["http_port"].(int); ok {
|
if hp, ok := options["http_port"].(int); ok {
|
||||||
@@ -655,24 +528,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
|
// create a subroute for each site in the server block
|
||||||
for _, sblock := range p.serverBlocks {
|
for _, sblock := range p.serverBlocks {
|
||||||
matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock)
|
matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock)
|
||||||
@@ -702,11 +557,6 @@ func (st *ServerType) serversFromPairings(
|
|||||||
cp.DefaultSNI = defaultSNI
|
cp.DefaultSNI = defaultSNI
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if h == fallbackSNI {
|
|
||||||
hosts = append(hosts, "")
|
|
||||||
cp.FallbackSNI = fallbackSNI
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(hosts) > 0 {
|
if len(hosts) > 0 {
|
||||||
@@ -715,7 +565,6 @@ func (st *ServerType) serversFromPairings(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cp.DefaultSNI = defaultSNI
|
cp.DefaultSNI = defaultSNI
|
||||||
cp.FallbackSNI = fallbackSNI
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// only append this policy if it actually changes something
|
// only append this policy if it actually changes something
|
||||||
@@ -769,7 +618,7 @@ func (st *ServerType) serversFromPairings(
|
|||||||
|
|
||||||
// set up each handler directive, making sure to honor directive order
|
// set up each handler directive, making sure to honor directive order
|
||||||
dirRoutes := sblock.pile["route"]
|
dirRoutes := sblock.pile["route"]
|
||||||
siteSubroute, err := buildSubroute(dirRoutes, groupCounter, true)
|
siteSubroute, err := buildSubroute(dirRoutes, groupCounter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -793,20 +642,12 @@ func (st *ServerType) serversFromPairings(
|
|||||||
sblockLogHosts := sblock.hostsFromKeys(true)
|
sblockLogHosts := sblock.hostsFromKeys(true)
|
||||||
for _, cval := range sblock.pile["custom_log"] {
|
for _, cval := range sblock.pile["custom_log"] {
|
||||||
ncl := cval.Value.(namedCustomLog)
|
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
|
// all requests for hosts not able to be listed should use
|
||||||
// this log because it's a catch-all-hosts server block
|
// this log because it's a catch-all-hosts server block
|
||||||
srv.Logs.DefaultLoggerName = ncl.name
|
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 {
|
} else {
|
||||||
// otherwise, map each host to the logger name
|
// map each host to the user's desired logger name
|
||||||
for _, h := range sblockLogHosts {
|
for _, h := range sblockLogHosts {
|
||||||
if srv.Logs.LoggerNames == nil {
|
if srv.Logs.LoggerNames == nil {
|
||||||
srv.Logs.LoggerNames = make(map[string]string)
|
srv.Logs.LoggerNames = make(map[string]string)
|
||||||
@@ -849,8 +690,8 @@ func (st *ServerType) serversFromPairings(
|
|||||||
// policy missing for any HTTPS-enabled hosts, if so, add it... maybe?
|
// policy missing for any HTTPS-enabled hosts, if so, add it... maybe?
|
||||||
if addressQualifiesForTLS &&
|
if addressQualifiesForTLS &&
|
||||||
!hasCatchAllTLSConnPolicy &&
|
!hasCatchAllTLSConnPolicy &&
|
||||||
(len(srv.TLSConnPolicies) > 0 || !autoHTTPSWillAddConnPolicy || defaultSNI != "" || fallbackSNI != "") {
|
(len(srv.TLSConnPolicies) > 0 || !autoHTTPSWillAddConnPolicy || defaultSNI != "") {
|
||||||
srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{DefaultSNI: defaultSNI, FallbackSNI: fallbackSNI})
|
srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{DefaultSNI: defaultSNI})
|
||||||
}
|
}
|
||||||
|
|
||||||
// tidy things up a bit
|
// tidy things up a bit
|
||||||
@@ -865,7 +706,7 @@ func (st *ServerType) serversFromPairings(
|
|||||||
|
|
||||||
err := applyServerOptions(servers, options, warnings)
|
err := applyServerOptions(servers, options, warnings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("applying global server options: %v", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return servers, nil
|
return servers, nil
|
||||||
@@ -1118,17 +959,15 @@ func appendSubrouteToRouteList(routeList caddyhttp.RouteList,
|
|||||||
|
|
||||||
// buildSubroute turns the config values, which are expected to be routes
|
// buildSubroute turns the config values, which are expected to be routes
|
||||||
// into a clean and orderly subroute that has all the routes within it.
|
// into a clean and orderly subroute that has all the routes within it.
|
||||||
func buildSubroute(routes []ConfigValue, groupCounter counter, needsSorting bool) (*caddyhttp.Subroute, error) {
|
func buildSubroute(routes []ConfigValue, groupCounter counter) (*caddyhttp.Subroute, error) {
|
||||||
if needsSorting {
|
for _, val := range routes {
|
||||||
for _, val := range routes {
|
if !directiveIsOrdered(val.directive) {
|
||||||
if !directiveIsOrdered(val.directive) {
|
return nil, fmt.Errorf("directive '%s' is not an ordered HTTP handler, so it cannot be used here", 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sortRoutes(routes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortRoutes(routes)
|
||||||
|
|
||||||
subroute := new(caddyhttp.Subroute)
|
subroute := new(caddyhttp.Subroute)
|
||||||
|
|
||||||
// some directives are mutually exclusive (only first matching
|
// some directives are mutually exclusive (only first matching
|
||||||
@@ -1476,7 +1315,6 @@ func placeholderShorthands() []string {
|
|||||||
"{tls_client_certificate_pem}", "{http.request.tls.client.certificate_pem}",
|
"{tls_client_certificate_pem}", "{http.request.tls.client.certificate_pem}",
|
||||||
"{tls_client_certificate_der_base64}", "{http.request.tls.client.certificate_der_base64}",
|
"{tls_client_certificate_der_base64}", "{http.request.tls.client.certificate_der_base64}",
|
||||||
"{upstream_hostport}", "{http.reverse_proxy.upstream.hostport}",
|
"{upstream_hostport}", "{http.reverse_proxy.upstream.hostport}",
|
||||||
"{client_ip}", "{http.vars.client_ip}",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1595,9 +1433,8 @@ func (c counter) nextGroup() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type namedCustomLog struct {
|
type namedCustomLog struct {
|
||||||
name string
|
name string
|
||||||
hostnames []string
|
log *caddy.CustomLog
|
||||||
log *caddy.CustomLog
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sbAddrAssociation is a mapping from a list of
|
// sbAddrAssociation is a mapping from a list of
|
||||||
@@ -1609,7 +1446,6 @@ type sbAddrAssociation struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const matcherPrefix = "@"
|
const matcherPrefix = "@"
|
||||||
const namedRouteKey = "named_route"
|
|
||||||
|
|
||||||
// Interface guard
|
// Interface guard
|
||||||
var _ caddyfile.ServerType = (*ServerType)(nil)
|
var _ caddyfile.ServerType = (*ServerType)(nil)
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ func init() {
|
|||||||
RegisterGlobalOption("grace_period", parseOptDuration)
|
RegisterGlobalOption("grace_period", parseOptDuration)
|
||||||
RegisterGlobalOption("shutdown_delay", parseOptDuration)
|
RegisterGlobalOption("shutdown_delay", parseOptDuration)
|
||||||
RegisterGlobalOption("default_sni", parseOptSingleString)
|
RegisterGlobalOption("default_sni", parseOptSingleString)
|
||||||
RegisterGlobalOption("fallback_sni", parseOptSingleString)
|
|
||||||
RegisterGlobalOption("order", parseOptOrder)
|
RegisterGlobalOption("order", parseOptOrder)
|
||||||
RegisterGlobalOption("storage", parseOptStorage)
|
RegisterGlobalOption("storage", parseOptStorage)
|
||||||
RegisterGlobalOption("storage_clean_interval", parseOptDuration)
|
RegisterGlobalOption("storage_clean_interval", parseOptDuration)
|
||||||
@@ -55,7 +54,6 @@ func init() {
|
|||||||
RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions)
|
RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions)
|
||||||
RegisterGlobalOption("log", parseLogOptions)
|
RegisterGlobalOption("log", parseLogOptions)
|
||||||
RegisterGlobalOption("preferred_chains", parseOptPreferredChains)
|
RegisterGlobalOption("preferred_chains", parseOptPreferredChains)
|
||||||
RegisterGlobalOption("persist_config", parseOptPersistConfig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil }
|
func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil }
|
||||||
@@ -388,21 +386,6 @@ func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
return ond, nil
|
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) {
|
func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
d.Next() // consume parameter name
|
d.Next() // consume parameter name
|
||||||
if !d.Next() {
|
if !d.Next() {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
package httpcaddyfile
|
package httpcaddyfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/caddyserver/caddy/v2"
|
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||||||
@@ -27,24 +26,23 @@ func init() {
|
|||||||
|
|
||||||
// parsePKIApp parses the global log option. Syntax:
|
// parsePKIApp parses the global log option. Syntax:
|
||||||
//
|
//
|
||||||
// pki {
|
// pki {
|
||||||
// ca [<id>] {
|
// ca [<id>] {
|
||||||
// name <name>
|
// name <name>
|
||||||
// root_cn <name>
|
// root_cn <name>
|
||||||
// intermediate_cn <name>
|
// intermediate_cn <name>
|
||||||
// intermediate_lifetime <duration>
|
// root {
|
||||||
// root {
|
// cert <path>
|
||||||
// cert <path>
|
// key <path>
|
||||||
// key <path>
|
// format <format>
|
||||||
// format <format>
|
// }
|
||||||
// }
|
// intermediate {
|
||||||
// intermediate {
|
// cert <path>
|
||||||
// cert <path>
|
// key <path>
|
||||||
// key <path>
|
// format <format>
|
||||||
// format <format>
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
|
||||||
//
|
//
|
||||||
// When the CA ID is unspecified, 'local' is assumed.
|
// When the CA ID is unspecified, 'local' is assumed.
|
||||||
func parsePKIApp(d *caddyfile.Dispenser, existingVal any) (any, error) {
|
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()
|
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":
|
case "root":
|
||||||
if pkiCa.Root == nil {
|
if pkiCa.Root == nil {
|
||||||
pkiCa.Root = new(caddypki.KeyPair)
|
pkiCa.Root = new(caddypki.KeyPair)
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ type serverOptions struct {
|
|||||||
ListenerAddress string
|
ListenerAddress string
|
||||||
|
|
||||||
// These will all map 1:1 to the caddyhttp.Server struct
|
// These will all map 1:1 to the caddyhttp.Server struct
|
||||||
Name string
|
|
||||||
ListenerWrappersRaw []json.RawMessage
|
ListenerWrappersRaw []json.RawMessage
|
||||||
ReadTimeout caddy.Duration
|
ReadTimeout caddy.Duration
|
||||||
ReadHeaderTimeout caddy.Duration
|
ReadHeaderTimeout caddy.Duration
|
||||||
@@ -41,11 +40,8 @@ type serverOptions struct {
|
|||||||
IdleTimeout caddy.Duration
|
IdleTimeout caddy.Duration
|
||||||
KeepAliveInterval caddy.Duration
|
KeepAliveInterval caddy.Duration
|
||||||
MaxHeaderBytes int
|
MaxHeaderBytes int
|
||||||
EnableFullDuplex bool
|
|
||||||
Protocols []string
|
Protocols []string
|
||||||
StrictSNIHost *bool
|
StrictSNIHost *bool
|
||||||
TrustedProxiesRaw json.RawMessage
|
|
||||||
ClientIPHeaders []string
|
|
||||||
ShouldLogCredentials bool
|
ShouldLogCredentials bool
|
||||||
Metrics *caddyhttp.Metrics
|
Metrics *caddyhttp.Metrics
|
||||||
}
|
}
|
||||||
@@ -61,15 +57,6 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
|||||||
}
|
}
|
||||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||||
switch d.Val() {
|
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":
|
case "listener_wrappers":
|
||||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||||
modID := "caddy.listeners." + d.Val()
|
modID := "caddy.listeners." + d.Val()
|
||||||
@@ -158,12 +145,6 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
|||||||
}
|
}
|
||||||
serverOpts.MaxHeaderBytes = int(size)
|
serverOpts.MaxHeaderBytes = int(size)
|
||||||
|
|
||||||
case "enable_full_duplex":
|
|
||||||
if d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
serverOpts.EnableFullDuplex = true
|
|
||||||
|
|
||||||
case "log_credentials":
|
case "log_credentials":
|
||||||
if d.NextArg() {
|
if d.NextArg() {
|
||||||
return nil, d.ArgErr()
|
return nil, d.ArgErr()
|
||||||
@@ -195,39 +176,6 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
|||||||
}
|
}
|
||||||
serverOpts.StrictSNIHost = &boolVal
|
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":
|
case "metrics":
|
||||||
if d.NextArg() {
|
if d.NextArg() {
|
||||||
return nil, d.ArgErr()
|
return nil, d.ArgErr()
|
||||||
@@ -290,22 +238,7 @@ func applyServerOptions(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for duplicate names, which would clobber the config
|
for _, server := range servers {
|
||||||
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 {
|
|
||||||
// find the options that apply to this server
|
// find the options that apply to this server
|
||||||
opts := func() *serverOptions {
|
opts := func() *serverOptions {
|
||||||
for _, entry := range serverOpts {
|
for _, entry := range serverOpts {
|
||||||
@@ -334,11 +267,8 @@ func applyServerOptions(
|
|||||||
server.IdleTimeout = opts.IdleTimeout
|
server.IdleTimeout = opts.IdleTimeout
|
||||||
server.KeepAliveInterval = opts.KeepAliveInterval
|
server.KeepAliveInterval = opts.KeepAliveInterval
|
||||||
server.MaxHeaderBytes = opts.MaxHeaderBytes
|
server.MaxHeaderBytes = opts.MaxHeaderBytes
|
||||||
server.EnableFullDuplex = opts.EnableFullDuplex
|
|
||||||
server.Protocols = opts.Protocols
|
server.Protocols = opts.Protocols
|
||||||
server.StrictSNIHost = opts.StrictSNIHost
|
server.StrictSNIHost = opts.StrictSNIHost
|
||||||
server.TrustedProxiesRaw = opts.TrustedProxiesRaw
|
|
||||||
server.ClientIPHeaders = opts.ClientIPHeaders
|
|
||||||
server.Metrics = opts.Metrics
|
server.Metrics = opts.Metrics
|
||||||
if opts.ShouldLogCredentials {
|
if opts.ShouldLogCredentials {
|
||||||
if server.Logs == nil {
|
if server.Logs == nil {
|
||||||
@@ -346,16 +276,6 @@ func applyServerOptions(
|
|||||||
}
|
}
|
||||||
server.Logs.ShouldLogCredentials = opts.ShouldLogCredentials
|
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
|
return nil
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -206,8 +206,8 @@ func (st ServerType) buildTLSApp(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// associate our new automation policy with this server block's hosts
|
// associate our new automation policy with this server block's hosts
|
||||||
ap.SubjectsRaw = sblock.hostsFromKeysNotHTTP(httpPort)
|
ap.Subjects = sblock.hostsFromKeysNotHTTP(httpPort)
|
||||||
sort.Strings(ap.SubjectsRaw) // solely for deterministic test results
|
sort.Strings(ap.Subjects) // solely for deterministic test results
|
||||||
|
|
||||||
// if a combination of public and internal names were given
|
// if a combination of public and internal names were given
|
||||||
// for this same server block and no issuer was specified, we
|
// for this same server block and no issuer was specified, we
|
||||||
@@ -217,11 +217,7 @@ func (st ServerType) buildTLSApp(
|
|||||||
var ap2 *caddytls.AutomationPolicy
|
var ap2 *caddytls.AutomationPolicy
|
||||||
if len(ap.Issuers) == 0 {
|
if len(ap.Issuers) == 0 {
|
||||||
var internal, external []string
|
var internal, external []string
|
||||||
for _, s := range ap.SubjectsRaw {
|
for _, s := range ap.Subjects {
|
||||||
// do not create Issuers for Tailscale domains; they will be given a Manager instead
|
|
||||||
if strings.HasSuffix(strings.ToLower(s), ".ts.net") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !certmagic.SubjectQualifiesForCert(s) {
|
if !certmagic.SubjectQualifiesForCert(s) {
|
||||||
return nil, warnings, fmt.Errorf("subject does not qualify for certificate: '%s'", s)
|
return nil, warnings, fmt.Errorf("subject does not qualify for certificate: '%s'", s)
|
||||||
}
|
}
|
||||||
@@ -239,10 +235,10 @@ func (st ServerType) buildTLSApp(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(external) > 0 && len(internal) > 0 {
|
if len(external) > 0 && len(internal) > 0 {
|
||||||
ap.SubjectsRaw = external
|
ap.Subjects = external
|
||||||
apCopy := *ap
|
apCopy := *ap
|
||||||
ap2 = &apCopy
|
ap2 = &apCopy
|
||||||
ap2.SubjectsRaw = internal
|
ap2.Subjects = internal
|
||||||
ap2.IssuersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)}
|
ap2.IssuersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,14 +339,14 @@ func (st ServerType) buildTLSApp(
|
|||||||
for h := range httpsHostsSharedWithHostlessKey {
|
for h := range httpsHostsSharedWithHostlessKey {
|
||||||
al = append(al, h)
|
al = append(al, h)
|
||||||
if !certmagic.SubjectQualifiesForPublicCert(h) {
|
if !certmagic.SubjectQualifiesForPublicCert(h) {
|
||||||
internalAP.SubjectsRaw = append(internalAP.SubjectsRaw, h)
|
internalAP.Subjects = append(internalAP.Subjects, h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(al) > 0 {
|
if len(al) > 0 {
|
||||||
tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings)
|
tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings)
|
||||||
}
|
}
|
||||||
if len(internalAP.SubjectsRaw) > 0 {
|
if len(internalAP.Subjects) > 0 {
|
||||||
if tlsApp.Automation == nil {
|
if tlsApp.Automation == nil {
|
||||||
tlsApp.Automation = new(caddytls.AutomationConfig)
|
tlsApp.Automation = new(caddytls.AutomationConfig)
|
||||||
}
|
}
|
||||||
@@ -416,7 +412,7 @@ func (st ServerType) buildTLSApp(
|
|||||||
// for convenience)
|
// for convenience)
|
||||||
automationHostSet := make(map[string]struct{})
|
automationHostSet := make(map[string]struct{})
|
||||||
for _, ap := range tlsApp.Automation.Policies {
|
for _, ap := range tlsApp.Automation.Policies {
|
||||||
for _, s := range ap.SubjectsRaw {
|
for _, s := range ap.Subjects {
|
||||||
if _, ok := automationHostSet[s]; ok {
|
if _, ok := automationHostSet[s]; ok {
|
||||||
return nil, warnings, fmt.Errorf("hostname appears in more than one automation policy, making certificate management ambiguous: %s", s)
|
return nil, warnings, fmt.Errorf("hostname appears in more than one automation policy, making certificate management ambiguous: %s", s)
|
||||||
}
|
}
|
||||||
@@ -537,7 +533,7 @@ func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls
|
|||||||
if automationPolicyIsSubset(aps[j], aps[i]) {
|
if automationPolicyIsSubset(aps[j], aps[i]) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return len(aps[i].SubjectsRaw) > len(aps[j].SubjectsRaw)
|
return len(aps[i].Subjects) > len(aps[j].Subjects)
|
||||||
})
|
})
|
||||||
|
|
||||||
emptyAPCount := 0
|
emptyAPCount := 0
|
||||||
@@ -545,7 +541,7 @@ func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls
|
|||||||
// compute the number of empty policies (disregarding subjects) - see #4128
|
// compute the number of empty policies (disregarding subjects) - see #4128
|
||||||
emptyAP := new(caddytls.AutomationPolicy)
|
emptyAP := new(caddytls.AutomationPolicy)
|
||||||
for i := 0; i < len(aps); i++ {
|
for i := 0; i < len(aps); i++ {
|
||||||
emptyAP.SubjectsRaw = aps[i].SubjectsRaw
|
emptyAP.Subjects = aps[i].Subjects
|
||||||
if reflect.DeepEqual(aps[i], emptyAP) {
|
if reflect.DeepEqual(aps[i], emptyAP) {
|
||||||
emptyAPCount++
|
emptyAPCount++
|
||||||
if !automationPolicyHasAllPublicNames(aps[i]) {
|
if !automationPolicyHasAllPublicNames(aps[i]) {
|
||||||
@@ -587,7 +583,7 @@ outer:
|
|||||||
aps[i].KeyType == aps[j].KeyType &&
|
aps[i].KeyType == aps[j].KeyType &&
|
||||||
aps[i].OnDemand == aps[j].OnDemand &&
|
aps[i].OnDemand == aps[j].OnDemand &&
|
||||||
aps[i].RenewalWindowRatio == aps[j].RenewalWindowRatio {
|
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
|
// later policy (at j) has no subjects ("catch-all"), so we can
|
||||||
// remove the identical-but-more-specific policy that comes first
|
// remove the identical-but-more-specific policy that comes first
|
||||||
// AS LONG AS it is not shadowed by another policy before it; e.g.
|
// AS LONG AS it is not shadowed by another policy before it; e.g.
|
||||||
@@ -602,9 +598,9 @@ outer:
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// avoid repeated subjects
|
// avoid repeated subjects
|
||||||
for _, subj := range aps[j].SubjectsRaw {
|
for _, subj := range aps[j].Subjects {
|
||||||
if !sliceContains(aps[i].SubjectsRaw, subj) {
|
if !sliceContains(aps[i].Subjects, subj) {
|
||||||
aps[i].SubjectsRaw = append(aps[i].SubjectsRaw, subj)
|
aps[i].Subjects = append(aps[i].Subjects, subj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
aps = append(aps[:j], aps[j+1:]...)
|
aps = append(aps[:j], aps[j+1:]...)
|
||||||
@@ -620,15 +616,15 @@ outer:
|
|||||||
// automationPolicyIsSubset returns true if a's subjects are a subset
|
// automationPolicyIsSubset returns true if a's subjects are a subset
|
||||||
// of b's subjects.
|
// of b's subjects.
|
||||||
func automationPolicyIsSubset(a, b *caddytls.AutomationPolicy) bool {
|
func automationPolicyIsSubset(a, b *caddytls.AutomationPolicy) bool {
|
||||||
if len(b.SubjectsRaw) == 0 {
|
if len(b.Subjects) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if len(a.SubjectsRaw) == 0 {
|
if len(a.Subjects) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, aSubj := range a.SubjectsRaw {
|
for _, aSubj := range a.Subjects {
|
||||||
var inSuperset bool
|
var inSuperset bool
|
||||||
for _, bSubj := range b.SubjectsRaw {
|
for _, bSubj := range b.Subjects {
|
||||||
if certmagic.MatchWildcard(aSubj, bSubj) {
|
if certmagic.MatchWildcard(aSubj, bSubj) {
|
||||||
inSuperset = true
|
inSuperset = true
|
||||||
break
|
break
|
||||||
@@ -666,7 +662,7 @@ func subjectQualifiesForPublicCert(ap *caddytls.AutomationPolicy, subj string) b
|
|||||||
}
|
}
|
||||||
|
|
||||||
func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
|
func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
|
||||||
for _, subj := range ap.SubjectsRaw {
|
for _, subj := range ap.Subjects {
|
||||||
if !subjectQualifiesForPublicCert(ap, subj) {
|
if !subjectQualifiesForPublicCert(ap, subj) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ func TestAutomationPolicyIsSubset(t *testing.T) {
|
|||||||
expect: false,
|
expect: false,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
apA := &caddytls.AutomationPolicy{SubjectsRaw: test.a}
|
apA := &caddytls.AutomationPolicy{Subjects: test.a}
|
||||||
apB := &caddytls.AutomationPolicy{SubjectsRaw: test.b}
|
apB := &caddytls.AutomationPolicy{Subjects: test.b}
|
||||||
if actual := automationPolicyIsSubset(apA, apB); actual != test.expect {
|
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)
|
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{})
|
caddy.RegisterModule(HTTPLoader{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPLoader can load Caddy configs over HTTP(S).
|
// HTTPLoader can load Caddy configs over HTTP(S). It can adapt the config
|
||||||
//
|
// based on the Content-Type header of the HTTP response.
|
||||||
// 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.
|
|
||||||
type HTTPLoader struct {
|
type HTTPLoader struct {
|
||||||
// The method for the request. Default: GET
|
// The method for the request. Default: GET
|
||||||
Method string `json:"method,omitempty"`
|
Method string `json:"method,omitempty"`
|
||||||
@@ -51,11 +45,6 @@ type HTTPLoader struct {
|
|||||||
// Maximum time allowed for a complete connection and request.
|
// Maximum time allowed for a complete connection and request.
|
||||||
Timeout caddy.Duration `json:"timeout,omitempty"`
|
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 {
|
TLS *struct {
|
||||||
// Present this instance's managed remote identity credentials to the server.
|
// Present this instance's managed remote identity credentials to the server.
|
||||||
UseServerIdentity bool `json:"use_server_identity,omitempty"`
|
UseServerIdentity bool `json:"use_server_identity,omitempty"`
|
||||||
@@ -119,12 +108,7 @@ func (hl HTTPLoader) LoadConfig(ctx caddy.Context) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// adapt the config based on either manually-configured adapter or server's response header
|
result, warnings, err := adaptByContentType(resp.Header.Get("Content-Type"), body)
|
||||||
ct := resp.Header.Get("Content-Type")
|
|
||||||
if hl.Adapter != "" {
|
|
||||||
ct = "text/" + hl.Adapter
|
|
||||||
}
|
|
||||||
result, warnings, err := adaptByContentType(ct, body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -140,7 +124,6 @@ func attemptHttpCall(client *http.Client, request *http.Request) (*http.Response
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("problem calling http loader url: %v", err)
|
return nil, fmt.Errorf("problem calling http loader url: %v", err)
|
||||||
} else if resp.StatusCode < 200 || resp.StatusCode > 499 {
|
} 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 nil, fmt.Errorf("bad response status code from http loader url: %v", resp.StatusCode)
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
@@ -151,16 +134,16 @@ func doHttpCallWithRetries(ctx caddy.Context, client *http.Client, request *http
|
|||||||
var err error
|
var err error
|
||||||
const maxAttempts = 10
|
const maxAttempts = 10
|
||||||
|
|
||||||
|
// attempt up to 10 times
|
||||||
for i := 0; i < maxAttempts; i++ {
|
for i := 0; i < maxAttempts; i++ {
|
||||||
resp, err = attemptHttpCall(client, request)
|
resp, err = attemptHttpCall(client, request)
|
||||||
if err != nil && i < maxAttempts-1 {
|
if err != nil && i < maxAttempts-1 {
|
||||||
|
// wait 500ms before reattempting, or until context is done
|
||||||
select {
|
select {
|
||||||
case <-time.After(time.Millisecond * 500):
|
case <-time.After(time.Millisecond * 500):
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return resp, ctx.Err()
|
return resp, ctx.Err()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-22
@@ -114,7 +114,7 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := validateTestPrerequisites(tc.t)
|
err := validateTestPrerequisites()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err)
|
tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err)
|
||||||
return nil
|
return nil
|
||||||
@@ -218,20 +218,15 @@ func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error
|
|||||||
if reflect.DeepEqual(expected, fetchConfig(client)) {
|
if reflect.DeepEqual(expected, fetchConfig(client)) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(10 * time.Millisecond)
|
||||||
}
|
}
|
||||||
tc.t.Errorf("POSTed configuration isn't active")
|
tc.t.Errorf("POSTed configuration isn't active")
|
||||||
return errors.New("EnsureConfigRunning: 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
|
// validateTestPrerequisites ensures the certificates are available in the
|
||||||
// designated path and Caddy sub-process is running.
|
// designated path and Caddy sub-process is running.
|
||||||
func validateTestPrerequisites(t *testing.T) error {
|
func validateTestPrerequisites() error {
|
||||||
|
|
||||||
// check certificates are found
|
// check certificates are found
|
||||||
for _, certName := range Default.Certifcates {
|
for _, certName := range Default.Certifcates {
|
||||||
@@ -241,27 +236,15 @@ func validateTestPrerequisites(t *testing.T) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isCaddyAdminRunning() != nil {
|
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
|
// start inprocess caddy server
|
||||||
os.Args = []string{"caddy", "run", "--config", f.Name(), "--adapter", "caddyfile"}
|
os.Args = []string{"caddy", "run", "--config", "./test.init.config", "--adapter", "caddyfile"}
|
||||||
go func() {
|
go func() {
|
||||||
caddycmd.Main()
|
caddycmd.Main()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// wait for caddy to start serving the initial config
|
// wait for caddy to start serving the initial config
|
||||||
for retries := 10; retries > 0 && isCaddyAdminRunning() != nil; retries-- {
|
for retries := 10; retries > 0 && isCaddyAdminRunning() != nil; retries-- {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(10 * time.Millisecond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,13 +11,9 @@
|
|||||||
idle 30s
|
idle 30s
|
||||||
}
|
}
|
||||||
max_header_size 100MB
|
max_header_size 100MB
|
||||||
enable_full_duplex
|
|
||||||
log_credentials
|
log_credentials
|
||||||
protocols h1 h2 h2c h3
|
protocols h1 h2 h2c h3
|
||||||
strict_sni_host
|
strict_sni_host
|
||||||
trusted_proxies static private_ranges
|
|
||||||
client_ip_headers Custom-Real-Client-IP X-Forwarded-For
|
|
||||||
client_ip_headers A-Third-One
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +42,6 @@ foo.com {
|
|||||||
"write_timeout": 30000000000,
|
"write_timeout": 30000000000,
|
||||||
"idle_timeout": 30000000000,
|
"idle_timeout": 30000000000,
|
||||||
"max_header_bytes": 100000000,
|
"max_header_bytes": 100000000,
|
||||||
"enable_full_duplex": true,
|
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"match": [
|
"match": [
|
||||||
@@ -60,22 +55,6 @@ foo.com {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"strict_sni_host": true,
|
"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": {
|
"logs": {
|
||||||
"should_log_credentials": true
|
"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 "Foo"
|
||||||
+Link "Bar"
|
+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) {
|
(logging) {
|
||||||
log {
|
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,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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -100,16 +100,16 @@ vars {
|
|||||||
],
|
],
|
||||||
"source": "{http.request.host}"
|
"source": "{http.request.host}"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"foo": "bar",
|
||||||
|
"handler": "vars"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"abc": true,
|
"abc": true,
|
||||||
"def": 1,
|
"def": 1,
|
||||||
"ghi": 2.3,
|
"ghi": 2.3,
|
||||||
"handler": "vars",
|
"handler": "vars",
|
||||||
"jkl": "mn op"
|
"jkl": "mn op"
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo": "bar",
|
|
||||||
"handler": "vars"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,9 +43,6 @@
|
|||||||
|
|
||||||
@matcher11 remote_ip private_ranges
|
@matcher11 remote_ip private_ranges
|
||||||
respond @matcher11 "remote_ip matcher with 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"
|
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ route {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "group0",
|
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "rewrite",
|
"handler": "rewrite",
|
||||||
@@ -130,4 +129,4 @@ route {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,6 @@
|
|||||||
resolvers 8.8.8.8 8.8.4.4
|
resolvers 8.8.8.8 8.8.4.4
|
||||||
dial_timeout 2s
|
dial_timeout 2s
|
||||||
dial_fallback_delay 300ms
|
dial_fallback_delay 300ms
|
||||||
versions ipv6
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,10 +66,7 @@
|
|||||||
"8.8.4.4"
|
"8.8.4.4"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"source": "a",
|
"source": "a"
|
||||||
"versions": {
|
|
||||||
"ipv6": true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"handler": "reverse_proxy"
|
"handler": "reverse_proxy"
|
||||||
}
|
}
|
||||||
@@ -117,4 +113,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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 {
|
https://example.com {
|
||||||
reverse_proxy /path https://localhost:54321 {
|
reverse_proxy /path https://localhost:54321 {
|
||||||
header_up Host {upstream_hostport}
|
header_up Host {upstream_hostport}
|
||||||
@@ -6,7 +7,7 @@ https://example.com {
|
|||||||
method GET
|
method GET
|
||||||
rewrite /rewritten?uri={uri}
|
rewrite /rewritten?uri={uri}
|
||||||
|
|
||||||
request_buffers 4KB
|
buffer_requests
|
||||||
|
|
||||||
transport http {
|
transport http {
|
||||||
read_buffer 10MB
|
read_buffer 10MB
|
||||||
@@ -23,12 +24,13 @@ https://example.com {
|
|||||||
max_conns_per_host 5
|
max_conns_per_host 5
|
||||||
keepalive_idle_conns_per_host 2
|
keepalive_idle_conns_per_host 2
|
||||||
keepalive_interval 30s
|
keepalive_interval 30s
|
||||||
|
|
||||||
tls_renegotiation freely
|
tls_renegotiation freely
|
||||||
tls_except_ports 8181 8182
|
tls_except_ports 8181 8182
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
----------
|
----------
|
||||||
{
|
{
|
||||||
"apps": {
|
"apps": {
|
||||||
@@ -54,6 +56,7 @@ https://example.com {
|
|||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
|
"buffer_requests": true,
|
||||||
"handler": "reverse_proxy",
|
"handler": "reverse_proxy",
|
||||||
"headers": {
|
"headers": {
|
||||||
"request": {
|
"request": {
|
||||||
@@ -67,7 +70,6 @@ https://example.com {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"request_buffers": 4000,
|
|
||||||
"rewrite": {
|
"rewrite": {
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"uri": "/rewritten?uri={http.request.uri}"
|
"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 {
|
*.example.com {
|
||||||
@foo host foo.example.com
|
@foo host foo.example.com
|
||||||
handle @foo {
|
handle @foo {
|
||||||
handle_path /strip {
|
handle_path /strip* {
|
||||||
respond "this should be first"
|
respond "this should be first"
|
||||||
}
|
}
|
||||||
handle_path /strip* {
|
|
||||||
respond "this should be second"
|
|
||||||
}
|
|
||||||
handle {
|
handle {
|
||||||
respond "this should be last"
|
respond "this should be second"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handle {
|
handle {
|
||||||
@@ -38,13 +35,13 @@
|
|||||||
"handler": "subroute",
|
"handler": "subroute",
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"group": "group6",
|
"group": "group5",
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"handler": "subroute",
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"group": "group3",
|
"group": "group2",
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"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": [
|
"match": [
|
||||||
{
|
{
|
||||||
"path": [
|
"path": [
|
||||||
@@ -110,7 +74,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "group3",
|
"group": "group2",
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"handler": "subroute",
|
||||||
@@ -118,7 +82,7 @@
|
|||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"body": "this should be last",
|
"body": "this should be second",
|
||||||
"handler": "static_response"
|
"handler": "static_response"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -139,7 +103,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "group6",
|
"group": "group5",
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"handler": "subroute",
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
:80
|
:80
|
||||||
|
|
||||||
vars /foobar foo last
|
vars /foobar foo last
|
||||||
vars /foo foo middle-last
|
vars /foo foo middle
|
||||||
vars /foo* foo middle-first
|
|
||||||
vars * foo first
|
vars * foo first
|
||||||
----------
|
----------
|
||||||
{
|
{
|
||||||
@@ -22,21 +21,6 @@ vars * foo first
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"path": [
|
|
||||||
"/foo*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"foo": "middle-first",
|
|
||||||
"handler": "vars"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"match": [
|
"match": [
|
||||||
{
|
{
|
||||||
@@ -47,7 +31,7 @@ vars * foo first
|
|||||||
],
|
],
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"foo": "middle-last",
|
"foo": "middle",
|
||||||
"handler": "vars"
|
"handler": "vars"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)")
|
|
||||||
}
|
|
||||||
@@ -22,38 +22,80 @@ func TestSRVReverseProxy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"apps": {
|
"apps": {
|
||||||
"pki": {
|
"pki": {
|
||||||
"certificate_authorities": {
|
"certificate_authorities" : {
|
||||||
"local": {
|
"local" : {
|
||||||
"install_trust": false
|
"install_trust": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"http": {
|
"http": {
|
||||||
"grace_period": 1,
|
"grace_period": 1,
|
||||||
"servers": {
|
"servers": {
|
||||||
"srv0": {
|
"srv0": {
|
||||||
"listen": [
|
"listen": [
|
||||||
":18080"
|
":8080"
|
||||||
],
|
],
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "reverse_proxy",
|
"handler": "reverse_proxy",
|
||||||
"dynamic_upstreams": {
|
"upstreams": [
|
||||||
"source": "srv",
|
{
|
||||||
"name": "srv.host.service.consul"
|
"lookup_srv": "srv.host.service.consul"
|
||||||
}
|
}
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, "json")
|
`, "json")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSRVWithDial(t *testing.T) {
|
||||||
|
caddytest.AssertLoadError(t, `
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"pki": {
|
||||||
|
"certificate_authorities" : {
|
||||||
|
"local" : {
|
||||||
|
"install_trust": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"grace_period": 1,
|
||||||
|
"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) {
|
func TestDialWithPlaceholderUnix(t *testing.T) {
|
||||||
@@ -96,41 +138,41 @@ func TestDialWithPlaceholderUnix(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"apps": {
|
"apps": {
|
||||||
"pki": {
|
"pki": {
|
||||||
"certificate_authorities": {
|
"certificate_authorities" : {
|
||||||
"local": {
|
"local" : {
|
||||||
"install_trust": false
|
"install_trust": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"http": {
|
"http": {
|
||||||
"grace_period": 1,
|
"grace_period": 1,
|
||||||
"servers": {
|
"servers": {
|
||||||
"srv0": {
|
"srv0": {
|
||||||
"listen": [
|
"listen": [
|
||||||
":18080"
|
":8080"
|
||||||
],
|
],
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "reverse_proxy",
|
"handler": "reverse_proxy",
|
||||||
"upstreams": [
|
"upstreams": [
|
||||||
{
|
{
|
||||||
"dial": "unix/{http.request.header.X-Caddy-Upstream-Dial}"
|
"dial": "unix/{http.request.header.X-Caddy-Upstream-Dial}"
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, "json")
|
`, "json")
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:18080", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://localhost:8080", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
return
|
return
|
||||||
@@ -148,18 +190,18 @@ func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"apps": {
|
"apps": {
|
||||||
"pki": {
|
"pki": {
|
||||||
"certificate_authorities": {
|
"certificate_authorities" : {
|
||||||
"local": {
|
"local" : {
|
||||||
"install_trust": false
|
"install_trust": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"http": {
|
"http": {
|
||||||
"grace_period": 1,
|
"grace_period": 1,
|
||||||
"servers": {
|
"servers": {
|
||||||
"srv0": {
|
"srv0": {
|
||||||
"listen": [
|
"listen": [
|
||||||
":18080"
|
":8080"
|
||||||
],
|
],
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
@@ -222,14 +264,14 @@ func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, "json")
|
`, "json")
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
return
|
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!")
|
tester.AssertResponse(req, 200, "Hello, World!")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,18 +284,18 @@ func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"apps": {
|
"apps": {
|
||||||
"pki": {
|
"pki": {
|
||||||
"certificate_authorities": {
|
"certificate_authorities" : {
|
||||||
"local": {
|
"local" : {
|
||||||
"install_trust": false
|
"install_trust": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"http": {
|
"http": {
|
||||||
"grace_period": 1,
|
"grace_period": 1,
|
||||||
"servers": {
|
"servers": {
|
||||||
"srv0": {
|
"srv0": {
|
||||||
"listen": [
|
"listen": [
|
||||||
":18080"
|
":8080"
|
||||||
],
|
],
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
@@ -298,7 +340,7 @@ func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
|
|||||||
"handler": "reverse_proxy",
|
"handler": "reverse_proxy",
|
||||||
"upstreams": [
|
"upstreams": [
|
||||||
{
|
{
|
||||||
"dial": "tcp/{http.request.header.X-Caddy-Upstream-Dial}:18080"
|
"dial": "tcp/{http.request.header.X-Caddy-Upstream-Dial}:8080"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -316,7 +358,7 @@ func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, "json")
|
`, "json")
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil)
|
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -327,6 +369,51 @@ func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
|
|||||||
tester.AssertResponse(req, 200, "Hello, World!")
|
tester.AssertResponse(req, 200, "Hello, World!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSRVWithActiveHealthcheck(t *testing.T) {
|
||||||
|
caddytest.AssertLoadError(t, `
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"pki": {
|
||||||
|
"certificate_authorities" : {
|
||||||
|
"local" : {
|
||||||
|
"install_trust": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"grace_period": 1,
|
||||||
|
"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) {
|
func TestReverseProxyHealthCheck(t *testing.T) {
|
||||||
tester := caddytest.NewTester(t)
|
tester := caddytest.NewTester(t)
|
||||||
tester.InitServer(`
|
tester.InitServer(`
|
||||||
@@ -353,7 +440,7 @@ func TestReverseProxyHealthCheck(t *testing.T) {
|
|||||||
health_timeout 100ms
|
health_timeout 100ms
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, "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
|
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!")
|
tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!")
|
||||||
|
|||||||
@@ -176,7 +176,9 @@ func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server {
|
|||||||
|
|
||||||
w.Header().Set("Cache-Control", "no-store")
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
http.NewResponseController(w).Flush()
|
if f, ok := w.(http.Flusher); ok {
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
buf := make([]byte, 4*1024)
|
buf := make([]byte, 4*1024)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
admin localhost:2999
|
||||||
|
}
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
respond "'I am {args[0]}', hears {args[1]}"
|
respond "'I am {args.0}', hears {args.1}"
|
||||||
+4
-4
@@ -19,10 +19,10 @@
|
|||||||
// There is no need to modify the Caddy source code to customize your
|
// There is no need to modify the Caddy source code to customize your
|
||||||
// builds. You can easily build a custom Caddy with these simple steps:
|
// builds. You can easily build a custom Caddy with these simple steps:
|
||||||
//
|
//
|
||||||
// 1. Copy this file (main.go) into a new folder
|
// 1. Copy this file (main.go) into a new folder
|
||||||
// 2. Edit the imports below to include the modules you want plugged in
|
// 2. Edit the imports below to include the modules you want plugged in
|
||||||
// 3. Run `go mod init caddy`
|
// 3. Run `go mod init caddy`
|
||||||
// 4. Run `go install` or `go build` - you now have a custom binary!
|
// 4. Run `go install` or `go build` - you now have a custom binary!
|
||||||
//
|
//
|
||||||
// Or you can use xcaddy which does it all for you as a command:
|
// Or you can use xcaddy which does it all for you as a command:
|
||||||
// https://github.com/caddyserver/xcaddy
|
// https://github.com/caddyserver/xcaddy
|
||||||
|
|||||||
+8
-17
@@ -101,29 +101,20 @@ const fullDocsFooter = `Full documentation is available at:
|
|||||||
https://caddyserver.com/docs/command-line`
|
https://caddyserver.com/docs/command-line`
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n")
|
rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func caddyCmdToCobra(caddyCmd Command) *cobra.Command {
|
func caddyCmdToCoral(caddyCmd Command) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: caddyCmd.Name,
|
Use: caddyCmd.Name,
|
||||||
Short: caddyCmd.Short,
|
Short: caddyCmd.Short,
|
||||||
Long: caddyCmd.Long,
|
Long: caddyCmd.Long,
|
||||||
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
|
fls := cmd.Flags()
|
||||||
|
_, err := caddyCmd.Func(Flags{fls})
|
||||||
|
return err
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if caddyCmd.CobraFunc != nil {
|
cmd.Flags().AddGoFlagSet(caddyCmd.Flags)
|
||||||
caddyCmd.CobraFunc(cmd)
|
|
||||||
} else {
|
|
||||||
cmd.RunE = WrapCommandFuncForCobra(caddyCmd.Func)
|
|
||||||
cmd.Flags().AddGoFlagSet(caddyCmd.Flags)
|
|
||||||
}
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// WrapCommandFuncForCobra wraps a Caddy CommandFunc for use
|
|
||||||
// in a cobra command's RunE field.
|
|
||||||
func WrapCommandFuncForCobra(f CommandFunc) func(cmd *cobra.Command, _ []string) error {
|
|
||||||
return func(cmd *cobra.Command, _ []string) error {
|
|
||||||
_, err := f(Flags{cmd.Flags()})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+27
-50
@@ -208,16 +208,6 @@ func cmdRun(fl Flags) (int, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create pidfile now, in case loading config takes a while (issue #5477)
|
|
||||||
if runCmdPidfileFlag != "" {
|
|
||||||
err := caddy.PIDFile(runCmdPidfileFlag)
|
|
||||||
if err != nil {
|
|
||||||
caddy.Log().Error("unable to write PID file",
|
|
||||||
zap.String("pidfile", runCmdPidfileFlag),
|
|
||||||
zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// run the initial config
|
// run the initial config
|
||||||
err = caddy.Load(config, true)
|
err = caddy.Load(config, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -252,6 +242,16 @@ func cmdRun(fl Flags) (int, error) {
|
|||||||
go watchConfigFile(configFile, runCmdConfigAdapterFlag)
|
go watchConfigFile(configFile, runCmdConfigAdapterFlag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create pidfile
|
||||||
|
if runCmdPidfileFlag != "" {
|
||||||
|
err := caddy.PIDFile(runCmdPidfileFlag)
|
||||||
|
if err != nil {
|
||||||
|
caddy.Log().Error("unable to write PID file",
|
||||||
|
zap.String("pidfile", runCmdPidfileFlag),
|
||||||
|
zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// warn if the environment does not provide enough information about the disk
|
// warn if the environment does not provide enough information about the disk
|
||||||
hasXDG := os.Getenv("XDG_DATA_HOME") != "" &&
|
hasXDG := os.Getenv("XDG_DATA_HOME") != "" &&
|
||||||
os.Getenv("XDG_CONFIG_HOME") != "" &&
|
os.Getenv("XDG_CONFIG_HOME") != "" &&
|
||||||
@@ -490,7 +490,7 @@ func cmdAdaptConfig(fl Flags) (int, error) {
|
|||||||
// validate output if requested
|
// validate output if requested
|
||||||
if adaptCmdValidateFlag {
|
if adaptCmdValidateFlag {
|
||||||
var cfg *caddy.Config
|
var cfg *caddy.Config
|
||||||
err = caddy.StrictUnmarshalJSON(adaptedConfig, &cfg)
|
err = json.Unmarshal(adaptedConfig, &cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("decoding config: %v", err)
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("decoding config: %v", err)
|
||||||
}
|
}
|
||||||
@@ -506,15 +506,6 @@ func cmdAdaptConfig(fl Flags) (int, error) {
|
|||||||
func cmdValidateConfig(fl Flags) (int, error) {
|
func cmdValidateConfig(fl Flags) (int, error) {
|
||||||
validateCmdConfigFlag := fl.String("config")
|
validateCmdConfigFlag := fl.String("config")
|
||||||
validateCmdAdapterFlag := fl.String("adapter")
|
validateCmdAdapterFlag := fl.String("adapter")
|
||||||
runCmdLoadEnvfileFlag := fl.String("envfile")
|
|
||||||
|
|
||||||
// load all additional envs as soon as possible
|
|
||||||
if runCmdLoadEnvfileFlag != "" {
|
|
||||||
if err := loadEnvFromFile(runCmdLoadEnvfileFlag); err != nil {
|
|
||||||
return caddy.ExitCodeFailedStartup,
|
|
||||||
fmt.Errorf("loading additional environment variables: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input, _, err := LoadConfig(validateCmdConfigFlag, validateCmdAdapterFlag)
|
input, _, err := LoadConfig(validateCmdConfigFlag, validateCmdAdapterFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -523,7 +514,7 @@ func cmdValidateConfig(fl Flags) (int, error) {
|
|||||||
input = caddy.RemoveMetaFields(input)
|
input = caddy.RemoveMetaFields(input)
|
||||||
|
|
||||||
var cfg *caddy.Config
|
var cfg *caddy.Config
|
||||||
err = caddy.StrictUnmarshalJSON(input, &cfg)
|
err = json.Unmarshal(input, &cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("decoding config: %v", err)
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("decoding config: %v", err)
|
||||||
}
|
}
|
||||||
@@ -567,10 +558,7 @@ func cmdFmt(fl Flags) (int, error) {
|
|||||||
if err := os.WriteFile(formatCmdConfigFile, output, 0600); err != nil {
|
if err := os.WriteFile(formatCmdConfigFile, output, 0600); err != nil {
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("overwriting formatted file: %v", err)
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("overwriting formatted file: %v", err)
|
||||||
}
|
}
|
||||||
return caddy.ExitCodeSuccess, nil
|
} else if fl.Bool("diff") {
|
||||||
}
|
|
||||||
|
|
||||||
if fl.Bool("diff") {
|
|
||||||
diff := difflib.Diff(
|
diff := difflib.Diff(
|
||||||
strings.Split(string(input), "\n"),
|
strings.Split(string(input), "\n"),
|
||||||
strings.Split(string(output), "\n"))
|
strings.Split(string(output), "\n"))
|
||||||
@@ -588,13 +576,6 @@ func cmdFmt(fl Flags) (int, error) {
|
|||||||
fmt.Print(string(output))
|
fmt.Print(string(output))
|
||||||
}
|
}
|
||||||
|
|
||||||
if warning, diff := caddyfile.FormattingDifference(formatCmdConfigFile, input); diff {
|
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf(`%s:%d: Caddyfile input is not formatted; Tip: use '--overwrite' to update your Caddyfile in-place instead of previewing it. Consult '--help' for more options`,
|
|
||||||
warning.File,
|
|
||||||
warning.Line,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return caddy.ExitCodeSuccess, nil
|
return caddy.ExitCodeSuccess, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,7 +591,7 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io
|
|||||||
}
|
}
|
||||||
origin := "http://" + parsedAddr.JoinHostPort(0)
|
origin := "http://" + parsedAddr.JoinHostPort(0)
|
||||||
if parsedAddr.IsUnixNetwork() {
|
if parsedAddr.IsUnixNetwork() {
|
||||||
origin = "http://127.0.0.1" // bogus host is a hack so that http.NewRequest() is happy
|
origin = "http://unixsocket" // hack so that http.NewRequest() is happy
|
||||||
}
|
}
|
||||||
|
|
||||||
// form the request
|
// form the request
|
||||||
@@ -619,24 +600,20 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io
|
|||||||
return nil, fmt.Errorf("making request: %v", err)
|
return nil, fmt.Errorf("making request: %v", err)
|
||||||
}
|
}
|
||||||
if parsedAddr.IsUnixNetwork() {
|
if parsedAddr.IsUnixNetwork() {
|
||||||
// We used to conform to RFC 2616 Section 14.26 which requires
|
// When listening on a unix socket, the admin endpoint doesn't
|
||||||
// an empty host header when there is no host, as is the case
|
// accept any Host header because there is no host:port for
|
||||||
// with unix sockets. However, Go required a Host value so we
|
// a unix socket's address. The server's host check is fairly
|
||||||
// used a hack of a space character as the host (it would see
|
// strict for security reasons, so we don't allow just any
|
||||||
// the Host was non-empty, then trim the space later). As of
|
// Host header. For unix sockets, the Host header must be
|
||||||
// Go 1.20.6 (July 2023), this hack no longer works. See:
|
// empty. Unfortunately, Go makes it impossible to make HTTP
|
||||||
// https://github.com/golang/go/issues/60374
|
// requests with an empty Host header... except with this one
|
||||||
// See also the discussion here:
|
// weird trick. (Hopefully they don't fix it. It's already
|
||||||
// https://github.com/golang/go/issues/61431
|
// hard enough to use HTTP over unix sockets.)
|
||||||
//
|
//
|
||||||
// After that, we now require a Host value of either 127.0.0.1
|
// An equivalent curl command would be something like:
|
||||||
// or ::1 if one is set. Above I choose to use 127.0.0.1. Even
|
// $ curl --unix-socket caddy.sock http:/:$REQUEST_URI
|
||||||
// though the value should be completely irrelevant (it could be
|
req.URL.Host = " "
|
||||||
// "srldkjfsd"), if for some reason the Host *is* used, at least
|
req.Host = ""
|
||||||
// we can have some reasonable assurance it will stay on the local
|
|
||||||
// machine and that browsers, if they ever allow access to unix
|
|
||||||
// sockets, can still enforce CORS, ensuring it is still coming
|
|
||||||
// from the local machine.
|
|
||||||
} else {
|
} else {
|
||||||
req.Header.Set("Origin", origin)
|
req.Header.Set("Origin", origin)
|
||||||
}
|
}
|
||||||
|
|||||||
+135
-182
@@ -34,6 +34,12 @@ type Command struct {
|
|||||||
// Required.
|
// Required.
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
|
// Func is a function that executes a subcommand using
|
||||||
|
// the parsed flags. It returns an exit code and any
|
||||||
|
// associated error.
|
||||||
|
// Required.
|
||||||
|
Func CommandFunc
|
||||||
|
|
||||||
// Usage is a brief message describing the syntax of
|
// Usage is a brief message describing the syntax of
|
||||||
// the subcommand's flags and args. Use [] to indicate
|
// the subcommand's flags and args. Use [] to indicate
|
||||||
// optional parameters and <> to enclose literal values
|
// optional parameters and <> to enclose literal values
|
||||||
@@ -54,21 +60,7 @@ type Command struct {
|
|||||||
Long string
|
Long string
|
||||||
|
|
||||||
// Flags is the flagset for command.
|
// Flags is the flagset for command.
|
||||||
// This is ignored if CobraFunc is set.
|
|
||||||
Flags *flag.FlagSet
|
Flags *flag.FlagSet
|
||||||
|
|
||||||
// Func is a function that executes a subcommand using
|
|
||||||
// the parsed flags. It returns an exit code and any
|
|
||||||
// associated error.
|
|
||||||
// Required if CobraFunc is not set.
|
|
||||||
Func CommandFunc
|
|
||||||
|
|
||||||
// CobraFunc allows further configuration of the command
|
|
||||||
// via cobra's APIs. If this is set, then Func and Flags
|
|
||||||
// are ignored, with the assumption that they are set in
|
|
||||||
// this function. A caddycmd.WrapCommandFuncForCobra helper
|
|
||||||
// exists to simplify porting CommandFunc to Cobra's RunE.
|
|
||||||
CobraFunc func(*cobra.Command)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommandFunc is a command's function. It runs the
|
// CommandFunc is a command's function. It runs the
|
||||||
@@ -87,6 +79,7 @@ var commands = make(map[string]Command)
|
|||||||
func init() {
|
func init() {
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "start",
|
Name: "start",
|
||||||
|
Func: cmdStart,
|
||||||
Usage: "[--config <path> [--adapter <name>]] [--envfile <path>] [--watch] [--pidfile <file>]",
|
Usage: "[--config <path> [--adapter <name>]] [--envfile <path>] [--watch] [--pidfile <file>]",
|
||||||
Short: "Starts the Caddy process in the background and then returns",
|
Short: "Starts the Caddy process in the background and then returns",
|
||||||
Long: `
|
Long: `
|
||||||
@@ -98,21 +91,22 @@ the KEY=VALUE format will be loaded into the Caddy process.
|
|||||||
|
|
||||||
On Windows, the spawned child process will remain attached to the terminal, so
|
On Windows, the spawned child process will remain attached to the terminal, so
|
||||||
closing the window will forcefully stop Caddy; to avoid forgetting this, try
|
closing the window will forcefully stop Caddy; to avoid forgetting this, try
|
||||||
using 'caddy run' instead to keep it in the foreground.
|
using 'caddy run' instead to keep it in the foreground.`,
|
||||||
`,
|
Flags: func() *flag.FlagSet {
|
||||||
CobraFunc: func(cmd *cobra.Command) {
|
fs := flag.NewFlagSet("start", flag.ExitOnError)
|
||||||
cmd.Flags().StringP("config", "c", "", "Configuration file")
|
fs.String("config", "", "Configuration file")
|
||||||
cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply")
|
fs.String("envfile", "", "Environment file to load")
|
||||||
cmd.Flags().StringP("envfile", "", "", "Environment file to load")
|
fs.String("adapter", "", "Name of config adapter to apply")
|
||||||
cmd.Flags().BoolP("watch", "w", false, "Reload changed config file automatically")
|
fs.String("pidfile", "", "Path of file to which to write process ID")
|
||||||
cmd.Flags().StringP("pidfile", "", "", "Path of file to which to write process ID")
|
fs.Bool("watch", false, "Reload changed config file automatically")
|
||||||
cmd.RunE = WrapCommandFuncForCobra(cmdStart)
|
return fs
|
||||||
},
|
}(),
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "run",
|
Name: "run",
|
||||||
Usage: "[--config <path> [--adapter <name>]] [--envfile <path>] [--environ] [--resume] [--watch] [--pidfile <file>]",
|
Func: cmdRun,
|
||||||
|
Usage: "[--config <path> [--adapter <name>]] [--envfile <path>] [--environ] [--resume] [--watch] [--pidfile <fil>]",
|
||||||
Short: `Starts the Caddy process and blocks indefinitely`,
|
Short: `Starts the Caddy process and blocks indefinitely`,
|
||||||
Long: `
|
Long: `
|
||||||
Starts the Caddy process, optionally bootstrapped with an initial config file,
|
Starts the Caddy process, optionally bootstrapped with an initial config file,
|
||||||
@@ -144,42 +138,44 @@ save file. It is not an error if --resume is used and no autosave file exists.
|
|||||||
|
|
||||||
If --watch is specified, the config file will be loaded automatically after
|
If --watch is specified, the config file will be loaded automatically after
|
||||||
changes. ⚠️ This can make unintentional config changes easier; only use this
|
changes. ⚠️ This can make unintentional config changes easier; only use this
|
||||||
option in a local development environment.
|
option in a local development environment.`,
|
||||||
`,
|
Flags: func() *flag.FlagSet {
|
||||||
CobraFunc: func(cmd *cobra.Command) {
|
fs := flag.NewFlagSet("run", flag.ExitOnError)
|
||||||
cmd.Flags().StringP("config", "c", "", "Configuration file")
|
fs.String("config", "", "Configuration file")
|
||||||
cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply")
|
fs.String("adapter", "", "Name of config adapter to apply")
|
||||||
cmd.Flags().StringP("envfile", "", "", "Environment file to load")
|
fs.String("envfile", "", "Environment file to load")
|
||||||
cmd.Flags().BoolP("environ", "e", false, "Print environment")
|
fs.Bool("environ", false, "Print environment")
|
||||||
cmd.Flags().BoolP("resume", "r", false, "Use saved config, if any (and prefer over --config file)")
|
fs.Bool("resume", false, "Use saved config, if any (and prefer over --config file)")
|
||||||
cmd.Flags().BoolP("watch", "w", false, "Watch config file for changes and reload it automatically")
|
fs.Bool("watch", false, "Watch config file for changes and reload it automatically")
|
||||||
cmd.Flags().StringP("pidfile", "", "", "Path of file to which to write process ID")
|
fs.String("pidfile", "", "Path of file to which to write process ID")
|
||||||
cmd.Flags().StringP("pingback", "", "", "Echo confirmation bytes to this address on success")
|
fs.String("pingback", "", "Echo confirmation bytes to this address on success")
|
||||||
cmd.RunE = WrapCommandFuncForCobra(cmdRun)
|
return fs
|
||||||
},
|
}(),
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "stop",
|
Name: "stop",
|
||||||
Usage: "[--config <path> [--adapter <name>]] [--address <interface>]",
|
Func: cmdStop,
|
||||||
|
Usage: "[--address <interface>] [--config <path> [--adapter <name>]]",
|
||||||
Short: "Gracefully stops a started Caddy process",
|
Short: "Gracefully stops a started Caddy process",
|
||||||
Long: `
|
Long: `
|
||||||
Stops the background Caddy process as gracefully as possible.
|
Stops the background Caddy process as gracefully as possible.
|
||||||
|
|
||||||
It requires that the admin API is enabled and accessible, since it will
|
It requires that the admin API is enabled and accessible, since it will
|
||||||
use the API's /stop endpoint. The address of this request can be customized
|
use the API's /stop endpoint. The address of this request can be customized
|
||||||
using the --address flag, or from the given --config, if not the default.
|
using the --address flag, or from the given --config, if not the default.`,
|
||||||
`,
|
Flags: func() *flag.FlagSet {
|
||||||
CobraFunc: func(cmd *cobra.Command) {
|
fs := flag.NewFlagSet("stop", flag.ExitOnError)
|
||||||
cmd.Flags().StringP("config", "c", "", "Configuration file to use to parse the admin address, if --address is not used")
|
fs.String("address", "", "The address to use to reach the admin API endpoint, if not the default")
|
||||||
cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply (when --config is used)")
|
fs.String("config", "", "Configuration file to use to parse the admin address, if --address is not used")
|
||||||
cmd.Flags().StringP("address", "", "", "The address to use to reach the admin API endpoint, if not the default")
|
fs.String("adapter", "", "Name of config adapter to apply (when --config is used)")
|
||||||
cmd.RunE = WrapCommandFuncForCobra(cmdStop)
|
return fs
|
||||||
},
|
}(),
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "reload",
|
Name: "reload",
|
||||||
|
Func: cmdReload,
|
||||||
Usage: "--config <path> [--adapter <name>] [--address <interface>]",
|
Usage: "--config <path> [--adapter <name>] [--address <interface>]",
|
||||||
Short: "Changes the config of the running Caddy instance",
|
Short: "Changes the config of the running Caddy instance",
|
||||||
Long: `
|
Long: `
|
||||||
@@ -189,19 +185,20 @@ workflows revolving around config files.
|
|||||||
|
|
||||||
Since the admin endpoint is configurable, the endpoint configuration is loaded
|
Since the admin endpoint is configurable, the endpoint configuration is loaded
|
||||||
from the --address flag if specified; otherwise it is loaded from the given
|
from the --address flag if specified; otherwise it is loaded from the given
|
||||||
config file; otherwise the default is assumed.
|
config file; otherwise the default is assumed.`,
|
||||||
`,
|
Flags: func() *flag.FlagSet {
|
||||||
CobraFunc: func(cmd *cobra.Command) {
|
fs := flag.NewFlagSet("reload", flag.ExitOnError)
|
||||||
cmd.Flags().StringP("config", "c", "", "Configuration file (required)")
|
fs.String("config", "", "Configuration file (required)")
|
||||||
cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply")
|
fs.String("adapter", "", "Name of config adapter to apply")
|
||||||
cmd.Flags().StringP("address", "", "", "Address of the administration listener, if different from config")
|
fs.String("address", "", "Address of the administration listener, if different from config")
|
||||||
cmd.Flags().BoolP("force", "f", false, "Force config reload, even if it is the same")
|
fs.Bool("force", false, "Force config reload, even if it is the same")
|
||||||
cmd.RunE = WrapCommandFuncForCobra(cmdReload)
|
return fs
|
||||||
},
|
}(),
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "version",
|
Name: "version",
|
||||||
|
Func: cmdVersion,
|
||||||
Short: "Prints the version",
|
Short: "Prints the version",
|
||||||
Long: `
|
Long: `
|
||||||
Prints the version of this Caddy binary.
|
Prints the version of this Caddy binary.
|
||||||
@@ -216,29 +213,31 @@ detailed version information is printed as given by Go modules.
|
|||||||
For more details about the full version string, see the Go module
|
For more details about the full version string, see the Go module
|
||||||
documentation: https://go.dev/doc/modules/version-numbers
|
documentation: https://go.dev/doc/modules/version-numbers
|
||||||
`,
|
`,
|
||||||
Func: cmdVersion,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "list-modules",
|
Name: "list-modules",
|
||||||
Usage: "[--packages] [--versions] [--skip-standard]",
|
Func: cmdListModules,
|
||||||
|
Usage: "[--packages] [--versions]",
|
||||||
Short: "Lists the installed Caddy modules",
|
Short: "Lists the installed Caddy modules",
|
||||||
CobraFunc: func(cmd *cobra.Command) {
|
Flags: func() *flag.FlagSet {
|
||||||
cmd.Flags().BoolP("packages", "", false, "Print package paths")
|
fs := flag.NewFlagSet("list-modules", flag.ExitOnError)
|
||||||
cmd.Flags().BoolP("versions", "", false, "Print version information")
|
fs.Bool("packages", false, "Print package paths")
|
||||||
cmd.Flags().BoolP("skip-standard", "s", false, "Skip printing standard modules")
|
fs.Bool("versions", false, "Print version information")
|
||||||
cmd.RunE = WrapCommandFuncForCobra(cmdListModules)
|
fs.Bool("skip-standard", false, "Skip printing standard modules")
|
||||||
},
|
return fs
|
||||||
|
}(),
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "build-info",
|
Name: "build-info",
|
||||||
Short: "Prints information about this build",
|
|
||||||
Func: cmdBuildInfo,
|
Func: cmdBuildInfo,
|
||||||
|
Short: "Prints information about this build",
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "environ",
|
Name: "environ",
|
||||||
|
Func: cmdEnviron,
|
||||||
Short: "Prints the environment",
|
Short: "Prints the environment",
|
||||||
Long: `
|
Long: `
|
||||||
Prints the environment as seen by this Caddy process.
|
Prints the environment as seen by this Caddy process.
|
||||||
@@ -258,11 +257,11 @@ by adding the "--environ" flag.
|
|||||||
|
|
||||||
Environments may contain sensitive data.
|
Environments may contain sensitive data.
|
||||||
`,
|
`,
|
||||||
Func: cmdEnviron,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "adapt",
|
Name: "adapt",
|
||||||
|
Func: cmdAdaptConfig,
|
||||||
Usage: "--config <path> [--adapter <name>] [--pretty] [--validate]",
|
Usage: "--config <path> [--adapter <name>] [--pretty] [--validate]",
|
||||||
Short: "Adapts a configuration to Caddy's native JSON",
|
Short: "Adapts a configuration to Caddy's native JSON",
|
||||||
Long: `
|
Long: `
|
||||||
@@ -274,90 +273,38 @@ for human readability.
|
|||||||
|
|
||||||
If --validate is used, the adapted config will be checked for validity.
|
If --validate is used, the adapted config will be checked for validity.
|
||||||
If the config is invalid, an error will be printed to stderr and a non-
|
If the config is invalid, an error will be printed to stderr and a non-
|
||||||
zero exit status will be returned.
|
zero exit status will be returned.`,
|
||||||
`,
|
Flags: func() *flag.FlagSet {
|
||||||
CobraFunc: func(cmd *cobra.Command) {
|
fs := flag.NewFlagSet("adapt", flag.ExitOnError)
|
||||||
cmd.Flags().StringP("config", "c", "", "Configuration file to adapt (required)")
|
fs.String("config", "", "Configuration file to adapt (required)")
|
||||||
cmd.Flags().StringP("adapter", "a", "caddyfile", "Name of config adapter")
|
fs.String("adapter", "caddyfile", "Name of config adapter")
|
||||||
cmd.Flags().BoolP("pretty", "p", false, "Format the output for human readability")
|
fs.Bool("pretty", false, "Format the output for human readability")
|
||||||
cmd.Flags().BoolP("validate", "", false, "Validate the output")
|
fs.Bool("validate", false, "Validate the output")
|
||||||
cmd.RunE = WrapCommandFuncForCobra(cmdAdaptConfig)
|
return fs
|
||||||
},
|
}(),
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "validate",
|
Name: "validate",
|
||||||
Usage: "--config <path> [--adapter <name>] [--envfile <path>]",
|
Func: cmdValidateConfig,
|
||||||
|
Usage: "--config <path> [--adapter <name>]",
|
||||||
Short: "Tests whether a configuration file is valid",
|
Short: "Tests whether a configuration file is valid",
|
||||||
Long: `
|
Long: `
|
||||||
Loads and provisions the provided config, but does not start running it.
|
Loads and provisions the provided config, but does not start running it.
|
||||||
This reveals any errors with the configuration through the loading and
|
This reveals any errors with the configuration through the loading and
|
||||||
provisioning stages.
|
provisioning stages.`,
|
||||||
|
Flags: func() *flag.FlagSet {
|
||||||
If --envfile is specified, an environment file with environment variables in
|
fs := flag.NewFlagSet("validate", flag.ExitOnError)
|
||||||
the KEY=VALUE format will be loaded into the Caddy process.
|
fs.String("config", "", "Input configuration file")
|
||||||
`,
|
fs.String("adapter", "", "Name of config adapter")
|
||||||
CobraFunc: func(cmd *cobra.Command) {
|
return fs
|
||||||
cmd.Flags().StringP("config", "c", "", "Input configuration file")
|
}(),
|
||||||
cmd.Flags().StringP("adapter", "a", "", "Name of config adapter")
|
|
||||||
cmd.Flags().StringP("envfile", "", "", "Environment file to load")
|
|
||||||
cmd.RunE = WrapCommandFuncForCobra(cmdValidateConfig)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
RegisterCommand(Command{
|
|
||||||
Name: "storage",
|
|
||||||
Short: "Commands for working with Caddy's storage (EXPERIMENTAL)",
|
|
||||||
Long: `
|
|
||||||
Allows exporting and importing Caddy's storage contents. The two commands can be
|
|
||||||
combined in a pipeline to transfer directly from one storage to another:
|
|
||||||
|
|
||||||
$ caddy storage export --config Caddyfile.old --output - |
|
|
||||||
> caddy storage import --config Caddyfile.new --input -
|
|
||||||
|
|
||||||
The - argument refers to stdout and stdin, respectively.
|
|
||||||
|
|
||||||
NOTE: When importing to or exporting from file_system storage (the default), the command
|
|
||||||
should be run as the user that owns the associated root path.
|
|
||||||
|
|
||||||
EXPERIMENTAL: May be changed or removed.
|
|
||||||
`,
|
|
||||||
CobraFunc: func(cmd *cobra.Command) {
|
|
||||||
exportCmd := &cobra.Command{
|
|
||||||
Use: "export --config <path> --output <path>",
|
|
||||||
Short: "Exports storage assets as a tarball",
|
|
||||||
Long: `
|
|
||||||
The contents of the configured storage module (TLS certificates, etc)
|
|
||||||
are exported via a tarball.
|
|
||||||
|
|
||||||
--output is required, - can be given for stdout.
|
|
||||||
`,
|
|
||||||
RunE: WrapCommandFuncForCobra(cmdExportStorage),
|
|
||||||
}
|
|
||||||
exportCmd.Flags().StringP("config", "c", "", "Input configuration file (required)")
|
|
||||||
exportCmd.Flags().StringP("output", "o", "", "Output path")
|
|
||||||
cmd.AddCommand(exportCmd)
|
|
||||||
|
|
||||||
importCmd := &cobra.Command{
|
|
||||||
Use: "import --config <path> --input <path>",
|
|
||||||
Short: "Imports storage assets from a tarball.",
|
|
||||||
Long: `
|
|
||||||
Imports storage assets to the configured storage module. The import file must be
|
|
||||||
a tar archive.
|
|
||||||
|
|
||||||
--input is required, - can be given for stdin.
|
|
||||||
`,
|
|
||||||
RunE: WrapCommandFuncForCobra(cmdImportStorage),
|
|
||||||
}
|
|
||||||
importCmd.Flags().StringP("config", "c", "", "Configuration file to load (required)")
|
|
||||||
importCmd.Flags().StringP("input", "i", "", "Tar of assets to load (required)")
|
|
||||||
cmd.AddCommand(importCmd)
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "fmt",
|
Name: "fmt",
|
||||||
Usage: "[--overwrite] [--diff] [<path>]",
|
Func: cmdFmt,
|
||||||
|
Usage: "[--overwrite] [<path>]",
|
||||||
Short: "Formats a Caddyfile",
|
Short: "Formats a Caddyfile",
|
||||||
Long: `
|
Long: `
|
||||||
Formats the Caddyfile by adding proper indentation and spaces to improve
|
Formats the Caddyfile by adding proper indentation and spaces to improve
|
||||||
@@ -373,30 +320,32 @@ is not a valid patch format.
|
|||||||
|
|
||||||
If you wish you use stdin instead of a regular file, use - as the path.
|
If you wish you use stdin instead of a regular file, use - as the path.
|
||||||
When reading from stdin, the --overwrite flag has no effect: the result
|
When reading from stdin, the --overwrite flag has no effect: the result
|
||||||
is always printed to stdout.
|
is always printed to stdout.`,
|
||||||
`,
|
Flags: func() *flag.FlagSet {
|
||||||
CobraFunc: func(cmd *cobra.Command) {
|
fs := flag.NewFlagSet("fmt", flag.ExitOnError)
|
||||||
cmd.Flags().BoolP("overwrite", "w", false, "Overwrite the input file with the results")
|
fs.Bool("overwrite", false, "Overwrite the input file with the results")
|
||||||
cmd.Flags().BoolP("diff", "d", false, "Print the differences between the input file and the formatted output")
|
fs.Bool("diff", false, "Print the differences between the input file and the formatted output")
|
||||||
cmd.RunE = WrapCommandFuncForCobra(cmdFmt)
|
return fs
|
||||||
},
|
}(),
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "upgrade",
|
Name: "upgrade",
|
||||||
|
Func: cmdUpgrade,
|
||||||
Short: "Upgrade Caddy (EXPERIMENTAL)",
|
Short: "Upgrade Caddy (EXPERIMENTAL)",
|
||||||
Long: `
|
Long: `
|
||||||
Downloads an updated Caddy binary with the same modules/plugins at the
|
Downloads an updated Caddy binary with the same modules/plugins at the
|
||||||
latest versions. EXPERIMENTAL: May be changed or removed.
|
latest versions. EXPERIMENTAL: May be changed or removed.`,
|
||||||
`,
|
Flags: func() *flag.FlagSet {
|
||||||
CobraFunc: func(cmd *cobra.Command) {
|
fs := flag.NewFlagSet("upgrade", flag.ExitOnError)
|
||||||
cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it")
|
fs.Bool("keep-backup", false, "Keep the backed up binary, instead of deleting it")
|
||||||
cmd.RunE = WrapCommandFuncForCobra(cmdUpgrade)
|
return fs
|
||||||
},
|
}(),
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "add-package",
|
Name: "add-package",
|
||||||
|
Func: cmdAddPackage,
|
||||||
Usage: "<packages...>",
|
Usage: "<packages...>",
|
||||||
Short: "Adds Caddy packages (EXPERIMENTAL)",
|
Short: "Adds Caddy packages (EXPERIMENTAL)",
|
||||||
Long: `
|
Long: `
|
||||||
@@ -404,10 +353,11 @@ Downloads an updated Caddy binary with the specified packages (module/plugin)
|
|||||||
added. Retains existing packages. Returns an error if the any of packages are
|
added. Retains existing packages. Returns an error if the any of packages are
|
||||||
already included. EXPERIMENTAL: May be changed or removed.
|
already included. EXPERIMENTAL: May be changed or removed.
|
||||||
`,
|
`,
|
||||||
CobraFunc: func(cmd *cobra.Command) {
|
Flags: func() *flag.FlagSet {
|
||||||
cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it")
|
fs := flag.NewFlagSet("add-package", flag.ExitOnError)
|
||||||
cmd.RunE = WrapCommandFuncForCobra(cmdAddPackage)
|
fs.Bool("keep-backup", false, "Keep the backed up binary, instead of deleting it")
|
||||||
},
|
return fs
|
||||||
|
}(),
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
@@ -420,14 +370,31 @@ Downloads an updated Caddy binaries without the specified packages (module/plugi
|
|||||||
Returns an error if any of the packages are not included.
|
Returns an error if any of the packages are not included.
|
||||||
EXPERIMENTAL: May be changed or removed.
|
EXPERIMENTAL: May be changed or removed.
|
||||||
`,
|
`,
|
||||||
CobraFunc: func(cmd *cobra.Command) {
|
Flags: func() *flag.FlagSet {
|
||||||
cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it")
|
fs := flag.NewFlagSet("remove-package", flag.ExitOnError)
|
||||||
cmd.RunE = WrapCommandFuncForCobra(cmdRemovePackage)
|
fs.Bool("keep-backup", false, "Keep the backed up binary, instead of deleting it")
|
||||||
},
|
return fs
|
||||||
|
}(),
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "manpage",
|
Name: "manpage",
|
||||||
|
Func: func(fl Flags) (int, error) {
|
||||||
|
dir := strings.TrimSpace(fl.String("directory"))
|
||||||
|
if dir == "" {
|
||||||
|
return caddy.ExitCodeFailedQuit, fmt.Errorf("designated output directory and specified section are required")
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
|
return caddy.ExitCodeFailedQuit, err
|
||||||
|
}
|
||||||
|
if err := doc.GenManTree(rootCmd, &doc.GenManHeader{
|
||||||
|
Title: "Caddy",
|
||||||
|
Section: "8", // https://en.wikipedia.org/wiki/Man_page#Manual_sections
|
||||||
|
}, dir); err != nil {
|
||||||
|
return caddy.ExitCodeFailedQuit, err
|
||||||
|
}
|
||||||
|
return caddy.ExitCodeSuccess, nil
|
||||||
|
},
|
||||||
Usage: "--directory <path>",
|
Usage: "--directory <path>",
|
||||||
Short: "Generates the manual pages for Caddy commands",
|
Short: "Generates the manual pages for Caddy commands",
|
||||||
Long: `
|
Long: `
|
||||||
@@ -437,25 +404,11 @@ tagged into section 8 (System Administration).
|
|||||||
The manual page files are generated into the directory specified by the
|
The manual page files are generated into the directory specified by the
|
||||||
argument of --directory. If the directory does not exist, it will be created.
|
argument of --directory. If the directory does not exist, it will be created.
|
||||||
`,
|
`,
|
||||||
CobraFunc: func(cmd *cobra.Command) {
|
Flags: func() *flag.FlagSet {
|
||||||
cmd.Flags().StringP("directory", "o", "", "The output directory where the manpages are generated")
|
fs := flag.NewFlagSet("manpage", flag.ExitOnError)
|
||||||
cmd.RunE = WrapCommandFuncForCobra(func(fl Flags) (int, error) {
|
fs.String("directory", "", "The output directory where the manpages are generated")
|
||||||
dir := strings.TrimSpace(fl.String("directory"))
|
return fs
|
||||||
if dir == "" {
|
}(),
|
||||||
return caddy.ExitCodeFailedQuit, fmt.Errorf("designated output directory and specified section are required")
|
|
||||||
}
|
|
||||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
||||||
return caddy.ExitCodeFailedQuit, err
|
|
||||||
}
|
|
||||||
if err := doc.GenManTree(rootCmd, &doc.GenManHeader{
|
|
||||||
Title: "Caddy",
|
|
||||||
Section: "8", // https://en.wikipedia.org/wiki/Man_page#Manual_sections
|
|
||||||
}, dir); err != nil {
|
|
||||||
return caddy.ExitCodeFailedQuit, err
|
|
||||||
}
|
|
||||||
return caddy.ExitCodeSuccess, nil
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// source: https://github.com/spf13/cobra/blob/main/shell_completions.md
|
// source: https://github.com/spf13/cobra/blob/main/shell_completions.md
|
||||||
@@ -503,7 +456,7 @@ argument of --directory. If the directory does not exist, it will be created.
|
|||||||
`, rootCmd.Root().Name()),
|
`, rootCmd.Root().Name()),
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
Args: cobra.ExactValidArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "bash":
|
case "bash":
|
||||||
@@ -539,7 +492,7 @@ func RegisterCommand(cmd Command) {
|
|||||||
if cmd.Name == "" {
|
if cmd.Name == "" {
|
||||||
panic("command name is required")
|
panic("command name is required")
|
||||||
}
|
}
|
||||||
if cmd.Func == nil && cmd.CobraFunc == nil {
|
if cmd.Func == nil {
|
||||||
panic("command function missing")
|
panic("command function missing")
|
||||||
}
|
}
|
||||||
if cmd.Short == "" {
|
if cmd.Short == "" {
|
||||||
@@ -551,7 +504,7 @@ func RegisterCommand(cmd Command) {
|
|||||||
if !commandNameRegex.MatchString(cmd.Name) {
|
if !commandNameRegex.MatchString(cmd.Name) {
|
||||||
panic("invalid command name")
|
panic("invalid command name")
|
||||||
}
|
}
|
||||||
rootCmd.AddCommand(caddyCmdToCobra(cmd))
|
rootCmd.AddCommand(caddyCmdToCoral(cmd))
|
||||||
}
|
}
|
||||||
|
|
||||||
var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`)
|
var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`)
|
||||||
|
|||||||
+53
-38
@@ -89,10 +89,6 @@ func handlePingbackConn(conn net.Conn, expect []byte) error {
|
|||||||
// and returns the resulting JSON config bytes along with
|
// and returns the resulting JSON config bytes along with
|
||||||
// the name of the loaded config file (if any).
|
// the name of the loaded config file (if any).
|
||||||
func LoadConfig(configFile, adapterName string) ([]byte, string, error) {
|
func LoadConfig(configFile, adapterName string) ([]byte, string, error) {
|
||||||
return loadConfigWithLogger(caddy.Log(), configFile, adapterName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([]byte, string, error) {
|
|
||||||
// specifying an adapter without a config file is ambiguous
|
// specifying an adapter without a config file is ambiguous
|
||||||
if adapterName != "" && configFile == "" {
|
if adapterName != "" && configFile == "" {
|
||||||
return nil, "", fmt.Errorf("cannot adapt config without config file (use --config)")
|
return nil, "", fmt.Errorf("cannot adapt config without config file (use --config)")
|
||||||
@@ -111,11 +107,9 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("reading config file: %v", err)
|
return nil, "", fmt.Errorf("reading config file: %v", err)
|
||||||
}
|
}
|
||||||
if logger != nil {
|
caddy.Log().Info("using provided configuration",
|
||||||
logger.Info("using provided configuration",
|
zap.String("config_file", configFile),
|
||||||
zap.String("config_file", configFile),
|
zap.String("config_adapter", adapterName))
|
||||||
zap.String("config_adapter", adapterName))
|
|
||||||
}
|
|
||||||
} else if adapterName == "" {
|
} else if adapterName == "" {
|
||||||
// as a special case when no config file or adapter
|
// as a special case when no config file or adapter
|
||||||
// is specified, see if the Caddyfile adapter is
|
// is specified, see if the Caddyfile adapter is
|
||||||
@@ -132,9 +126,7 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
|
|||||||
} else {
|
} else {
|
||||||
// success reading default Caddyfile
|
// success reading default Caddyfile
|
||||||
configFile = "Caddyfile"
|
configFile = "Caddyfile"
|
||||||
if logger != nil {
|
caddy.Log().Info("using adjacent Caddyfile")
|
||||||
logger.Info("using adjacent Caddyfile")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,9 +161,7 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
|
|||||||
if warn.Directive != "" {
|
if warn.Directive != "" {
|
||||||
msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message)
|
msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message)
|
||||||
}
|
}
|
||||||
if logger != nil {
|
caddy.Log().Warn(msg, zap.String("adapter", adapterName), zap.String("file", warn.File), zap.Int("line", warn.Line))
|
||||||
logger.Warn(msg, zap.String("adapter", adapterName), zap.String("file", warn.File), zap.Int("line", warn.Line))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
config = adaptedConfig
|
config = adaptedConfig
|
||||||
}
|
}
|
||||||
@@ -184,8 +174,6 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
|
|||||||
// blocks indefinitely; it only quits if the poller has errors for
|
// blocks indefinitely; it only quits if the poller has errors for
|
||||||
// long enough time. The filename passed in must be the actual
|
// long enough time. The filename passed in must be the actual
|
||||||
// config file used, not one to be discovered.
|
// config file used, not one to be discovered.
|
||||||
// Each second the config files is loaded and parsed into an object
|
|
||||||
// and is compared to the last config object that was loaded
|
|
||||||
func watchConfigFile(filename, adapterName string) {
|
func watchConfigFile(filename, adapterName string) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
@@ -201,36 +189,64 @@ func watchConfigFile(filename, adapterName string) {
|
|||||||
With(zap.String("config_file", filename))
|
With(zap.String("config_file", filename))
|
||||||
}
|
}
|
||||||
|
|
||||||
// get current config
|
// get the initial timestamp on the config file
|
||||||
lastCfg, _, err := loadConfigWithLogger(nil, filename, adapterName)
|
info, err := os.Stat(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger().Error("unable to load latest config", zap.Error(err))
|
logger().Error("cannot watch config file", zap.Error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
lastModified := info.ModTime()
|
||||||
|
|
||||||
logger().Info("watching config file for changes")
|
logger().Info("watching config file for changes")
|
||||||
|
|
||||||
|
// if the file disappears or something, we can
|
||||||
|
// stop polling if the error lasts long enough
|
||||||
|
var lastErr time.Time
|
||||||
|
finalError := func(err error) bool {
|
||||||
|
if lastErr.IsZero() {
|
||||||
|
lastErr = time.Now()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if time.Since(lastErr) > 30*time.Second {
|
||||||
|
logger().Error("giving up watching config file; too many errors",
|
||||||
|
zap.Error(err))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// begin poller
|
// begin poller
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
for range time.Tick(1 * time.Second) {
|
for range time.Tick(1 * time.Second) {
|
||||||
// get current config
|
// get the file info
|
||||||
newCfg, _, err := loadConfigWithLogger(nil, filename, adapterName)
|
info, err := os.Stat(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger().Error("unable to load latest config", zap.Error(err))
|
if finalError(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if it hasn't changed, nothing to do
|
|
||||||
if bytes.Equal(lastCfg, newCfg) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
lastErr = time.Time{} // no error, so clear any memory of one
|
||||||
|
|
||||||
|
// if it hasn't changed, nothing to do
|
||||||
|
if !info.ModTime().After(lastModified) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
logger().Info("config file changed; reloading")
|
logger().Info("config file changed; reloading")
|
||||||
|
|
||||||
// remember the current config
|
// remember this timestamp
|
||||||
lastCfg = newCfg
|
lastModified = info.ModTime()
|
||||||
|
|
||||||
|
// load the contents of the file
|
||||||
|
config, _, err := LoadConfig(filename, adapterName)
|
||||||
|
if err != nil {
|
||||||
|
logger().Error("unable to load latest config", zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// apply the updated config
|
// apply the updated config
|
||||||
err = caddy.Load(lastCfg, false)
|
err = caddy.Load(config, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger().Error("applying latest config", zap.Error(err))
|
logger().Error("applying latest config", zap.Error(err))
|
||||||
continue
|
continue
|
||||||
@@ -358,19 +374,18 @@ func parseEnvFile(envInput io.Reader) (map[string]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// quoted value: support newlines
|
// quoted value: support newlines
|
||||||
if strings.HasPrefix(val, `"`) || strings.HasPrefix(val, "'") {
|
if strings.HasPrefix(val, `"`) {
|
||||||
quote := string(val[0])
|
for !(strings.HasSuffix(line, `"`) && !strings.HasSuffix(line, `\"`)) {
|
||||||
for !(strings.HasSuffix(line, quote) && !strings.HasSuffix(line, `\`+quote)) {
|
val = strings.ReplaceAll(val, `\"`, `"`)
|
||||||
val = strings.ReplaceAll(val, `\`+quote, quote)
|
|
||||||
if !scanner.Scan() {
|
if !scanner.Scan() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
lineNumber++
|
lineNumber++
|
||||||
line = strings.ReplaceAll(scanner.Text(), `\`+quote, quote)
|
line = strings.ReplaceAll(scanner.Text(), `\"`, `"`)
|
||||||
val += "\n" + line
|
val += "\n" + line
|
||||||
}
|
}
|
||||||
val = strings.TrimPrefix(val, quote)
|
val = strings.TrimPrefix(val, `"`)
|
||||||
val = strings.TrimSuffix(val, quote)
|
val = strings.TrimSuffix(val, `"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
envMap[key] = val
|
envMap[key] = val
|
||||||
|
|||||||
@@ -1,220 +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 caddycmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
|
||||||
"github.com/caddyserver/certmagic"
|
|
||||||
)
|
|
||||||
|
|
||||||
type storVal struct {
|
|
||||||
StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// determineStorage returns the top-level storage module from the given config.
|
|
||||||
// It may return nil even if no error.
|
|
||||||
func determineStorage(configFile string, configAdapter string) (*storVal, error) {
|
|
||||||
cfg, _, err := LoadConfig(configFile, configAdapter)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// storage defaults to FileStorage if not explicitly
|
|
||||||
// defined in the config, so the config can be valid
|
|
||||||
// json but unmarshaling will fail.
|
|
||||||
if !json.Valid(cfg) {
|
|
||||||
return nil, &json.SyntaxError{}
|
|
||||||
}
|
|
||||||
var tmpStruct storVal
|
|
||||||
err = json.Unmarshal(cfg, &tmpStruct)
|
|
||||||
if err != nil {
|
|
||||||
// default case, ignore the error
|
|
||||||
var jsonError *json.SyntaxError
|
|
||||||
if errors.As(err, &jsonError) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tmpStruct, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cmdImportStorage(fl Flags) (int, error) {
|
|
||||||
importStorageCmdConfigFlag := fl.String("config")
|
|
||||||
importStorageCmdImportFile := fl.String("input")
|
|
||||||
|
|
||||||
if importStorageCmdConfigFlag == "" {
|
|
||||||
return caddy.ExitCodeFailedStartup, errors.New("--config is required")
|
|
||||||
}
|
|
||||||
if importStorageCmdImportFile == "" {
|
|
||||||
return caddy.ExitCodeFailedStartup, errors.New("--input is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract storage from config if possible
|
|
||||||
storageCfg, err := determineStorage(importStorageCmdConfigFlag, "")
|
|
||||||
if err != nil {
|
|
||||||
return caddy.ExitCodeFailedStartup, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// load specified storage or fallback to default
|
|
||||||
var stor certmagic.Storage
|
|
||||||
ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
|
|
||||||
defer cancel()
|
|
||||||
if storageCfg != nil && storageCfg.StorageRaw != nil {
|
|
||||||
val, err := ctx.LoadModule(storageCfg, "StorageRaw")
|
|
||||||
if err != nil {
|
|
||||||
return caddy.ExitCodeFailedStartup, err
|
|
||||||
}
|
|
||||||
stor, err = val.(caddy.StorageConverter).CertMagicStorage()
|
|
||||||
if err != nil {
|
|
||||||
return caddy.ExitCodeFailedStartup, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stor = caddy.DefaultStorage
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup input
|
|
||||||
var f *os.File
|
|
||||||
if importStorageCmdImportFile == "-" {
|
|
||||||
f = os.Stdin
|
|
||||||
} else {
|
|
||||||
f, err = os.Open(importStorageCmdImportFile)
|
|
||||||
if err != nil {
|
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("opening input file: %v", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// store each archive element
|
|
||||||
tr := tar.NewReader(f)
|
|
||||||
for {
|
|
||||||
hdr, err := tr.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return caddy.ExitCodeFailedQuit, fmt.Errorf("reading archive: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := io.ReadAll(tr)
|
|
||||||
if err != nil {
|
|
||||||
return caddy.ExitCodeFailedQuit, fmt.Errorf("reading archive: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = stor.Store(ctx, hdr.Name, b)
|
|
||||||
if err != nil {
|
|
||||||
return caddy.ExitCodeFailedQuit, fmt.Errorf("reading archive: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Successfully imported storage")
|
|
||||||
return caddy.ExitCodeSuccess, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cmdExportStorage(fl Flags) (int, error) {
|
|
||||||
exportStorageCmdConfigFlag := fl.String("config")
|
|
||||||
exportStorageCmdOutputFlag := fl.String("output")
|
|
||||||
|
|
||||||
if exportStorageCmdConfigFlag == "" {
|
|
||||||
return caddy.ExitCodeFailedStartup, errors.New("--config is required")
|
|
||||||
}
|
|
||||||
if exportStorageCmdOutputFlag == "" {
|
|
||||||
return caddy.ExitCodeFailedStartup, errors.New("--output is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract storage from config if possible
|
|
||||||
storageCfg, err := determineStorage(exportStorageCmdConfigFlag, "")
|
|
||||||
if err != nil {
|
|
||||||
return caddy.ExitCodeFailedStartup, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// load specified storage or fallback to default
|
|
||||||
var stor certmagic.Storage
|
|
||||||
ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
|
|
||||||
defer cancel()
|
|
||||||
if storageCfg != nil && storageCfg.StorageRaw != nil {
|
|
||||||
val, err := ctx.LoadModule(storageCfg, "StorageRaw")
|
|
||||||
if err != nil {
|
|
||||||
return caddy.ExitCodeFailedStartup, err
|
|
||||||
}
|
|
||||||
stor, err = val.(caddy.StorageConverter).CertMagicStorage()
|
|
||||||
if err != nil {
|
|
||||||
return caddy.ExitCodeFailedStartup, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stor = caddy.DefaultStorage
|
|
||||||
}
|
|
||||||
|
|
||||||
// enumerate all keys
|
|
||||||
keys, err := stor.List(ctx, "", true)
|
|
||||||
if err != nil {
|
|
||||||
return caddy.ExitCodeFailedStartup, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup output
|
|
||||||
var f *os.File
|
|
||||||
if exportStorageCmdOutputFlag == "-" {
|
|
||||||
f = os.Stdout
|
|
||||||
} else {
|
|
||||||
f, err = os.Create(exportStorageCmdOutputFlag)
|
|
||||||
if err != nil {
|
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("opening output file: %v", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// `IsTerminal: true` keys hold the values we
|
|
||||||
// care about, write them out
|
|
||||||
tw := tar.NewWriter(f)
|
|
||||||
for _, k := range keys {
|
|
||||||
info, err := stor.Stat(ctx, k)
|
|
||||||
if err != nil {
|
|
||||||
return caddy.ExitCodeFailedQuit, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.IsTerminal {
|
|
||||||
v, err := stor.Load(ctx, k)
|
|
||||||
if err != nil {
|
|
||||||
return caddy.ExitCodeFailedQuit, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hdr := &tar.Header{
|
|
||||||
Name: k,
|
|
||||||
Mode: 0600,
|
|
||||||
Size: int64(len(v)),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = tw.WriteHeader(hdr); err != nil {
|
|
||||||
return caddy.ExitCodeFailedQuit, fmt.Errorf("writing archive: %v", err)
|
|
||||||
}
|
|
||||||
if _, err = tw.Write(v); err != nil {
|
|
||||||
return caddy.ExitCodeFailedQuit, fmt.Errorf("writing archive: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = tw.Close(); err != nil {
|
|
||||||
return caddy.ExitCodeFailedQuit, fmt.Errorf("writing archive: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return caddy.ExitCodeSuccess, nil
|
|
||||||
}
|
|
||||||
+9
-25
@@ -326,7 +326,7 @@ func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error
|
|||||||
|
|
||||||
// fill in its config only if there is a config to fill in
|
// fill in its config only if there is a config to fill in
|
||||||
if len(rawMsg) > 0 {
|
if len(rawMsg) > 0 {
|
||||||
err := StrictUnmarshalJSON(rawMsg, &val)
|
err := strictUnmarshalJSON(rawMsg, &val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("decoding module config: %s: %v", modInfo, err)
|
return nil, fmt.Errorf("decoding module config: %s: %v", modInfo, err)
|
||||||
}
|
}
|
||||||
@@ -410,11 +410,6 @@ func (ctx Context) loadModuleInline(moduleNameKey, moduleScope string, raw json.
|
|||||||
// called during the Provision/Validate phase to reference a
|
// called during the Provision/Validate phase to reference a
|
||||||
// module's own host app (since the parent app module is still
|
// module's own host app (since the parent app module is still
|
||||||
// in the process of being provisioned, it is not yet ready).
|
// in the process of being provisioned, it is not yet ready).
|
||||||
//
|
|
||||||
// We return any type instead of the App type because it is NOT
|
|
||||||
// intended for the caller of this method to be the one to start
|
|
||||||
// or stop App modules. The caller is expected to assert to the
|
|
||||||
// concrete type.
|
|
||||||
func (ctx Context) App(name string) (any, error) {
|
func (ctx Context) App(name string) (any, error) {
|
||||||
if app, ok := ctx.cfg.apps[name]; ok {
|
if app, ok := ctx.cfg.apps[name]; ok {
|
||||||
return app, nil
|
return app, nil
|
||||||
@@ -431,23 +426,15 @@ func (ctx Context) App(name string) (any, error) {
|
|||||||
return modVal, nil
|
return modVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppIfConfigured returns an app by its name if it has been
|
// AppIsConfigured returns whether an app named name has been
|
||||||
// configured. Can be called instead of App() to avoid
|
// configured. Can be called before calling App() to avoid
|
||||||
// instantiating an empty app when that's not desirable. If
|
// instantiating an empty app when that's not desirable.
|
||||||
// the app has not been loaded, nil is returned.
|
func (ctx Context) AppIsConfigured(name string) bool {
|
||||||
//
|
if _, ok := ctx.cfg.apps[name]; ok {
|
||||||
// We return any type instead of the App type because it is not
|
return true
|
||||||
// intended for the caller of this method to be the one to start
|
|
||||||
// or stop App modules. The caller is expected to assert to the
|
|
||||||
// concrete type.
|
|
||||||
func (ctx Context) AppIfConfigured(name string) any {
|
|
||||||
if ctx.cfg == nil {
|
|
||||||
// this can happen if the currently-active context
|
|
||||||
// is being accessed, but no config has successfully
|
|
||||||
// been loaded yet
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return ctx.cfg.apps[name]
|
appRaw := ctx.cfg.AppsRaw[name]
|
||||||
|
return appRaw != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Storage returns the configured Caddy storage implementation.
|
// Storage returns the configured Caddy storage implementation.
|
||||||
@@ -488,9 +475,6 @@ func (ctx Context) Logger(module ...Module) *zap.Logger {
|
|||||||
if len(module) > 0 {
|
if len(module) > 0 {
|
||||||
mod = module[0]
|
mod = module[0]
|
||||||
}
|
}
|
||||||
if mod == nil {
|
|
||||||
return Log()
|
|
||||||
}
|
|
||||||
return ctx.cfg.Logging.Logger(mod)
|
return ctx.cfg.Logging.Logger(mod)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,152 +1,138 @@
|
|||||||
module github.com/caddyserver/caddy/v2
|
module github.com/caddyserver/caddy/v2
|
||||||
|
|
||||||
go 1.20
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.3.2
|
github.com/BurntSushi/toml v1.2.0
|
||||||
github.com/Masterminds/sprig/v3 v3.2.3
|
github.com/Masterminds/sprig/v3 v3.2.2
|
||||||
github.com/alecthomas/chroma/v2 v2.7.0
|
github.com/alecthomas/chroma v0.10.0
|
||||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
|
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
|
||||||
github.com/caddyserver/certmagic v0.19.1
|
github.com/caddyserver/certmagic v0.17.2
|
||||||
github.com/dustin/go-humanize v1.0.1
|
github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac
|
||||||
github.com/go-chi/chi v4.1.2+incompatible
|
github.com/go-chi/chi v4.1.2+incompatible
|
||||||
github.com/google/cel-go v0.15.1
|
github.com/google/cel-go v0.12.5
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/klauspost/compress v1.16.7
|
github.com/klauspost/compress v1.15.11
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5
|
github.com/klauspost/cpuid/v2 v2.1.1
|
||||||
github.com/mastercactapus/proxyprotocol v0.0.4
|
github.com/lucas-clemente/quic-go v0.29.2
|
||||||
github.com/mholt/acmez v1.2.0
|
github.com/mholt/acmez v1.0.4
|
||||||
github.com/prometheus/client_golang v1.14.0
|
github.com/prometheus/client_golang v1.12.2
|
||||||
github.com/quic-go/quic-go v0.37.1
|
github.com/smallstep/certificates v0.22.1
|
||||||
github.com/smallstep/certificates v0.24.3-rc.5
|
github.com/smallstep/cli v0.22.0
|
||||||
github.com/smallstep/nosql v0.6.0
|
github.com/smallstep/nosql v0.4.0
|
||||||
github.com/smallstep/truststore v0.12.1
|
github.com/smallstep/truststore v0.12.0
|
||||||
github.com/spf13/cobra v1.7.0
|
github.com/spf13/cobra v1.5.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2
|
||||||
github.com/tailscale/tscert v0.0.0-20230509043813-4e9cb4f2b4ad
|
github.com/yuin/goldmark v1.5.2
|
||||||
github.com/yuin/goldmark v1.5.5
|
github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.34.0
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0
|
go.opentelemetry.io/otel v1.9.0
|
||||||
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.0
|
||||||
go.opentelemetry.io/otel v1.16.0
|
go.opentelemetry.io/otel/sdk v1.4.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0
|
go.uber.org/zap v1.23.0
|
||||||
go.opentelemetry.io/otel/sdk v1.16.0
|
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
|
||||||
go.uber.org/zap v1.25.0
|
golang.org/x/net v0.0.0-20220812165438-1d4ff48094d1
|
||||||
golang.org/x/crypto v0.11.0
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||||
golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0
|
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad
|
||||||
golang.org/x/net v0.12.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
golang.org/x/sync v0.3.0
|
|
||||||
golang.org/x/term v0.10.0
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130
|
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
|
||||||
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
|
||||||
github.com/golang/glog v1.1.0 // indirect
|
|
||||||
github.com/golang/mock v1.6.0 // indirect
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
github.com/google/certificate-transparency-go v1.1.4 // indirect
|
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
|
||||||
github.com/google/go-tpm v0.3.3 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
github.com/google/go-tspi v0.3.0 // indirect
|
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
|
|
||||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
|
||||||
github.com/quic-go/qtls-go1-20 v0.3.0 // indirect
|
|
||||||
github.com/smallstep/go-attestation v0.4.4-0.20230509120429-e17291421738 // indirect
|
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
|
||||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
|
||||||
go.opentelemetry.io/contrib/propagators/aws v1.17.0 // indirect
|
|
||||||
go.opentelemetry.io/contrib/propagators/b3 v1.17.0 // indirect
|
|
||||||
go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 // indirect
|
|
||||||
go.opentelemetry.io/contrib/propagators/ot v1.17.0 // indirect
|
|
||||||
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.0.0 // indirect
|
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
|
||||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
|
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
|
||||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||||
|
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220418222510-f25a4f6275ed // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
|
||||||
github.com/cespare/xxhash v1.1.0 // indirect
|
github.com/cespare/xxhash v1.1.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
github.com/chzyer/readline v1.5.1 // indirect
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
github.com/dgraph-io/badger v1.6.2 // indirect
|
github.com/dgraph-io/badger v1.6.2 // indirect
|
||||||
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
|
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
|
||||||
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd // indirect
|
||||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
||||||
github.com/dlclark/regexp2 v1.7.0 // indirect
|
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||||
github.com/go-kit/kit v0.10.0 // indirect
|
github.com/go-kit/kit v0.10.0 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.5.1 // indirect
|
github.com/go-logfmt/logfmt v0.5.0 // indirect
|
||||||
github.com/go-logr/logr v1.2.4 // indirect
|
github.com/go-logr/logr v1.2.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/huandu/xstrings v1.3.3 // indirect
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||||
|
github.com/huandu/xstrings v1.3.2 // indirect
|
||||||
github.com/imdario/mergo v0.3.12 // indirect
|
github.com/imdario/mergo v0.3.12 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||||
github.com/jackc/pgconn v1.14.0 // indirect
|
github.com/jackc/pgconn v1.10.1 // indirect
|
||||||
github.com/jackc/pgio v1.0.0 // indirect
|
github.com/jackc/pgio v1.0.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgproto3/v2 v2.3.2 // indirect
|
github.com/jackc/pgproto3/v2 v2.2.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||||
github.com/jackc/pgtype v1.14.0 // indirect
|
github.com/jackc/pgtype v1.9.0 // indirect
|
||||||
github.com/jackc/pgx/v4 v4.18.0 // indirect
|
github.com/jackc/pgx/v4 v4.14.0 // indirect
|
||||||
github.com/libdns/libdns v0.2.1 // indirect
|
github.com/libdns/libdns v0.2.1 // indirect
|
||||||
github.com/manifoldco/promptui v0.9.0 // indirect
|
github.com/manifoldco/promptui v0.9.0 // indirect
|
||||||
|
github.com/marten-seemann/qpack v0.2.1 // indirect
|
||||||
|
github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
|
||||||
|
github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
github.com/micromdm/scep/v2 v2.1.0 // indirect
|
github.com/micromdm/scep/v2 v2.1.0 // indirect
|
||||||
github.com/miekg/dns v1.1.55 // indirect
|
github.com/miekg/dns v1.1.50 // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
|
github.com/nxadm/tail v1.4.8 // indirect
|
||||||
|
github.com/onsi/ginkgo v1.16.4 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/prometheus/client_model v0.3.0 // indirect
|
github.com/prometheus/client_model v0.2.0 // indirect
|
||||||
github.com/prometheus/common v0.37.0 // indirect
|
github.com/prometheus/common v0.32.1 // indirect
|
||||||
github.com/prometheus/procfs v0.8.0 // indirect
|
github.com/prometheus/procfs v0.7.3 // indirect
|
||||||
github.com/rs/xid v1.5.0 // indirect
|
github.com/rs/xid v1.2.1 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/shopspring/decimal v1.2.0 // indirect
|
github.com/shopspring/decimal v1.2.0 // indirect
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
github.com/slackhq/nebula v1.6.1 // indirect
|
github.com/slackhq/nebula v1.5.2 // indirect
|
||||||
github.com/spf13/cast v1.4.1 // indirect
|
github.com/spf13/cast v1.4.1 // indirect
|
||||||
github.com/stoewer/go-strcase v1.2.0 // indirect
|
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||||
github.com/urfave/cli v1.22.14 // indirect
|
github.com/urfave/cli v1.22.5 // indirect
|
||||||
go.etcd.io/bbolt v1.3.7 // indirect
|
go.etcd.io/bbolt v1.3.6 // indirect
|
||||||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect
|
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.16.0 // indirect
|
go.opentelemetry.io/otel/metric v0.31.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.16.0
|
go.opentelemetry.io/otel/trace v1.9.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
|
||||||
go.step.sm/cli-utils v0.8.0 // indirect
|
go.step.sm/cli-utils v0.7.4 // indirect
|
||||||
go.step.sm/crypto v0.33.0
|
go.step.sm/crypto v0.18.0 // indirect
|
||||||
go.step.sm/linkedca v0.20.0 // indirect
|
go.step.sm/linkedca v0.18.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
golang.org/x/mod v0.11.0 // indirect
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
golang.org/x/sys v0.10.0
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||||
golang.org/x/text v0.11.0 // indirect
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10
|
||||||
golang.org/x/tools v0.10.0 // indirect
|
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b // indirect
|
||||||
google.golang.org/grpc v1.56.2 // indirect
|
golang.org/x/tools v0.1.10 // indirect
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
|
||||||
|
google.golang.org/grpc v1.47.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.28.0 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||||
howett.net/plist v1.0.0 // indirect
|
howett.net/plist v1.0.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,7 +12,10 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
//go:build !unix
|
// TODO: Go 1.19 introduced the "unix" build tag. We have to support Go 1.18 until Go 1.20 is released.
|
||||||
|
// When Go 1.19 is our minimum, change this build tag to simply "!unix".
|
||||||
|
// (see similar change needed in listen_unix.go)
|
||||||
|
//go:build !(aix || android || darwin || dragonfly || freebsd || hurd || illumos || ios || linux || netbsd || openbsd || solaris)
|
||||||
|
|
||||||
package caddy
|
package caddy
|
||||||
|
|
||||||
|
|||||||
+8
-63
@@ -12,10 +12,10 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Even though the filename ends in _unix.go, we still have to specify the
|
// TODO: Go 1.19 introduced the "unix" build tag. We have to support Go 1.18 until Go 1.20 is released.
|
||||||
// build constraint here, because the filename convention only works for
|
// When Go 1.19 is our minimum, remove this build tag, since "_unix" in the filename will do this.
|
||||||
// literal GOOS values, and "unix" is a shortcut unique to build tags.
|
// (see also change needed in listen.go)
|
||||||
//go:build unix
|
//go:build aix || android || darwin || dragonfly || freebsd || hurd || illumos || ios || linux || netbsd || openbsd || solaris
|
||||||
|
|
||||||
package caddy
|
package caddy
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ import (
|
|||||||
// reuseUnixSocket copies and reuses the unix domain socket (UDS) if we already
|
// reuseUnixSocket copies and reuses the unix domain socket (UDS) if we already
|
||||||
// have it open; if not, unlink it so we can have it. No-op if not a unix network.
|
// have it open; if not, unlink it so we can have it. No-op if not a unix network.
|
||||||
func reuseUnixSocket(network, addr string) (any, error) {
|
func reuseUnixSocket(network, addr string) (any, error) {
|
||||||
if !IsUnixNetwork(network) {
|
if !isUnixNetwork(network) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,37 +98,16 @@ func listenTCPOrUnix(ctx context.Context, lnKey string, network, address string,
|
|||||||
}
|
}
|
||||||
return reusePort(network, address, c)
|
return reusePort(network, address, c)
|
||||||
}
|
}
|
||||||
|
return config.Listen(ctx, network, address)
|
||||||
// even though SO_REUSEPORT lets us bind the socket multiple times,
|
|
||||||
// we still put it in the listenerPool so we can count how many
|
|
||||||
// configs are using this socket; necessary to ensure we can know
|
|
||||||
// whether to enforce shutdown delays, for example (see #5393).
|
|
||||||
ln, err := config.Listen(ctx, network, address)
|
|
||||||
if err == nil {
|
|
||||||
listenerPool.LoadOrStore(lnKey, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if new listener is a unix socket, make sure we can reuse it later
|
|
||||||
// (we do our own "unlink on close" -- not required, but more tidy)
|
|
||||||
one := int32(1)
|
|
||||||
if unix, ok := ln.(*net.UnixListener); ok {
|
|
||||||
unix.SetUnlinkOnClose(false)
|
|
||||||
ln = &unixListener{unix, lnKey, &one}
|
|
||||||
unixSockets[lnKey] = ln.(*unixListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
// lightly wrap the listener so that when it is closed,
|
|
||||||
// we can decrement the usage pool counter
|
|
||||||
return deleteListener{ln, lnKey}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// reusePort sets SO_REUSEPORT. Ineffective for unix sockets.
|
// reusePort sets SO_REUSEPORT. Ineffective for unix sockets.
|
||||||
func reusePort(network, address string, conn syscall.RawConn) error {
|
func reusePort(network, address string, conn syscall.RawConn) error {
|
||||||
if IsUnixNetwork(network) {
|
if isUnixNetwork(network) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return conn.Control(func(descriptor uintptr) {
|
return conn.Control(func(descriptor uintptr) {
|
||||||
if err := unix.SetsockoptInt(int(descriptor), unix.SOL_SOCKET, unixSOREUSEPORT, 1); err != nil {
|
if err := unix.SetsockoptInt(int(descriptor), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {
|
||||||
Log().Error("setting SO_REUSEPORT",
|
Log().Error("setting SO_REUSEPORT",
|
||||||
zap.String("network", network),
|
zap.String("network", network),
|
||||||
zap.String("address", address),
|
zap.String("address", address),
|
||||||
@@ -137,37 +116,3 @@ func reusePort(network, address string, conn syscall.RawConn) error {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type unixListener struct {
|
|
||||||
*net.UnixListener
|
|
||||||
mapKey string
|
|
||||||
count *int32 // accessed atomically
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uln *unixListener) Close() error {
|
|
||||||
newCount := atomic.AddInt32(uln.count, -1)
|
|
||||||
if newCount == 0 {
|
|
||||||
defer func() {
|
|
||||||
addr := uln.Addr().String()
|
|
||||||
unixSocketsMu.Lock()
|
|
||||||
delete(unixSockets, uln.mapKey)
|
|
||||||
unixSocketsMu.Unlock()
|
|
||||||
_ = syscall.Unlink(addr)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
return uln.UnixListener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteListener is a type that simply deletes itself
|
|
||||||
// from the listenerPool when it closes. It is used
|
|
||||||
// solely for the purpose of reference counting (i.e.
|
|
||||||
// counting how many configs are using a given socket).
|
|
||||||
type deleteListener struct {
|
|
||||||
net.Listener
|
|
||||||
lnKey string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dl deleteListener) Close() error {
|
|
||||||
_, _ = listenerPool.Delete(dl.lnKey)
|
|
||||||
return dl.Listener.Close()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
//go:build unix && !freebsd
|
|
||||||
|
|
||||||
package caddy
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
const unixSOREUSEPORT = unix.SO_REUSEPORT
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
//go:build freebsd
|
|
||||||
|
|
||||||
package caddy
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
const unixSOREUSEPORT = unix.SO_REUSEPORT_LB
|
|
||||||
+74
-192
@@ -20,7 +20,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -31,8 +30,8 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/lucas-clemente/quic-go"
|
||||||
"github.com/quic-go/quic-go/http3"
|
"github.com/lucas-clemente/quic-go/http3"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -149,32 +148,11 @@ func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net
|
|||||||
func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
|
func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
|
||||||
var ln any
|
var ln any
|
||||||
var err error
|
var err error
|
||||||
var address string
|
|
||||||
var unixFileMode fs.FileMode
|
|
||||||
var isAbtractUnixSocket bool
|
|
||||||
|
|
||||||
// split unix socket addr early so lnKey
|
address := na.JoinHostPort(portOffset)
|
||||||
// is independent of permissions bits
|
|
||||||
if na.IsUnixNetwork() {
|
|
||||||
var err error
|
|
||||||
address, unixFileMode, err = splitUnixSocketPermissionsBits(na.Host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
isAbtractUnixSocket = strings.HasPrefix(address, "@")
|
|
||||||
} else {
|
|
||||||
address = na.JoinHostPort(portOffset)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if this is a unix socket, see if we already have it open,
|
// if this is a unix socket, see if we already have it open
|
||||||
// force socket permissions on it and return early
|
|
||||||
if socket, err := reuseUnixSocket(na.Network, address); socket != nil || err != nil {
|
if socket, err := reuseUnixSocket(na.Network, address); socket != nil || err != nil {
|
||||||
if !isAbtractUnixSocket {
|
|
||||||
if err := os.Chmod(address, unixFileMode); err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return socket, err
|
return socket, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,8 +174,7 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
spc := sharedPc.(*sharedPacketConn)
|
ln = &fakeClosePacketConn{sharedPacketConn: sharedPc.(*sharedPacketConn)}
|
||||||
ln = &fakeClosePacketConn{spc: spc, UDPConn: spc.PacketConn.(*net.UDPConn)}
|
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(na.Network, "ip") {
|
if strings.HasPrefix(na.Network, "ip") {
|
||||||
ln, err = config.ListenPacket(ctx, na.Network, address)
|
ln, err = config.ListenPacket(ctx, na.Network, address)
|
||||||
@@ -209,19 +186,17 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net
|
|||||||
return nil, fmt.Errorf("unsupported network type: %s", na.Network)
|
return nil, fmt.Errorf("unsupported network type: %s", na.Network)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Not 100% sure this is necessary, but we do this for net.UnixListener in listen_unix.go, so...
|
// if new listener is a unix socket, make sure we can reuse it later
|
||||||
if unix, ok := ln.(*net.UnixConn); ok {
|
// (we do our own "unlink on close" -- not required, but more tidy)
|
||||||
one := int32(1)
|
one := int32(1)
|
||||||
|
switch unix := ln.(type) {
|
||||||
|
case *net.UnixListener:
|
||||||
|
unix.SetUnlinkOnClose(false)
|
||||||
|
ln = &unixListener{unix, lnKey, &one}
|
||||||
|
unixSockets[lnKey] = ln.(*unixListener)
|
||||||
|
case *net.UnixConn:
|
||||||
ln = &unixConn{unix, address, lnKey, &one}
|
ln = &unixConn{unix, address, lnKey, &one}
|
||||||
unixSockets[lnKey] = unix
|
unixSockets[lnKey] = ln.(*unixConn)
|
||||||
}
|
|
||||||
|
|
||||||
if IsUnixNetwork(na.Network) {
|
|
||||||
if !isAbtractUnixSocket {
|
|
||||||
if err := os.Chmod(address, unixFileMode); err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ln, nil
|
return ln, nil
|
||||||
@@ -230,7 +205,7 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net
|
|||||||
// IsUnixNetwork returns true if na.Network is
|
// IsUnixNetwork returns true if na.Network is
|
||||||
// unix, unixgram, or unixpacket.
|
// unix, unixgram, or unixpacket.
|
||||||
func (na NetworkAddress) IsUnixNetwork() bool {
|
func (na NetworkAddress) IsUnixNetwork() bool {
|
||||||
return IsUnixNetwork(na.Network)
|
return isUnixNetwork(na.Network)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JoinHostPort is like net.JoinHostPort, but where the port
|
// JoinHostPort is like net.JoinHostPort, but where the port
|
||||||
@@ -314,43 +289,8 @@ func (na NetworkAddress) String() string {
|
|||||||
return JoinNetworkAddress(na.Network, na.Host, na.port())
|
return JoinNetworkAddress(na.Network, na.Host, na.port())
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUnixNetwork returns true if the netw is a unix network.
|
func isUnixNetwork(netw string) bool {
|
||||||
func IsUnixNetwork(netw string) bool {
|
return netw == "unix" || netw == "unixgram" || netw == "unixpacket"
|
||||||
return strings.HasPrefix(netw, "unix")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Takes a unix socket address in the unusual "path|bits" format
|
|
||||||
// (e.g. /run/caddy.sock|0222) and tries to split it into
|
|
||||||
// socket path (host) and permissions bits (port). Colons (":")
|
|
||||||
// can't be used as separator, as socket paths on Windows may
|
|
||||||
// include a drive letter (e.g. `unix/c:\absolute\path.sock`).
|
|
||||||
// Permission bits will default to 0200 if none are specified.
|
|
||||||
// Throws an error, if the first carrying bit does not
|
|
||||||
// include write perms (e.g. `0422` or `022`).
|
|
||||||
// Symbolic permission representation (e.g. `u=w,g=w,o=w`)
|
|
||||||
// is not supported and will throw an error for now!
|
|
||||||
func splitUnixSocketPermissionsBits(addr string) (path string, fileMode fs.FileMode, err error) {
|
|
||||||
addrSplit := strings.SplitN(addr, "|", 2)
|
|
||||||
|
|
||||||
if len(addrSplit) == 2 {
|
|
||||||
// parse octal permission bit string as uint32
|
|
||||||
fileModeUInt64, err := strconv.ParseUint(addrSplit[1], 8, 32)
|
|
||||||
if err != nil {
|
|
||||||
return "", 0, fmt.Errorf("could not parse octal permission bits in %s: %v", addr, err)
|
|
||||||
}
|
|
||||||
fileMode = fs.FileMode(fileModeUInt64)
|
|
||||||
|
|
||||||
// FileMode.String() returns a string like `-rwxr-xr--` for `u=rwx,g=rx,o=r` (`0754`)
|
|
||||||
if string(fileMode.String()[2]) != "w" {
|
|
||||||
return "", 0, fmt.Errorf("owner of the socket requires '-w-' (write, octal: '2') permissions at least; got '%s' in %s", fileMode.String()[1:4], addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return addrSplit[0], fileMode, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// default to 0200 (symbolic: `u=w,g=,o=`)
|
|
||||||
// if no permission bits are specified
|
|
||||||
return addr, 0200, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseNetworkAddress parses addr into its individual
|
// ParseNetworkAddress parses addr into its individual
|
||||||
@@ -362,32 +302,22 @@ func splitUnixSocketPermissionsBits(addr string) (path string, fileMode fs.FileM
|
|||||||
// Network addresses are distinct from URLs and do not
|
// Network addresses are distinct from URLs and do not
|
||||||
// use URL syntax.
|
// use URL syntax.
|
||||||
func ParseNetworkAddress(addr string) (NetworkAddress, error) {
|
func ParseNetworkAddress(addr string) (NetworkAddress, error) {
|
||||||
return ParseNetworkAddressWithDefaults(addr, "tcp", 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseNetworkAddressWithDefaults is like ParseNetworkAddress but allows
|
|
||||||
// the default network and port to be specified.
|
|
||||||
func ParseNetworkAddressWithDefaults(addr, defaultNetwork string, defaultPort uint) (NetworkAddress, error) {
|
|
||||||
var host, port string
|
var host, port string
|
||||||
network, host, port, err := SplitNetworkAddress(addr)
|
network, host, port, err := SplitNetworkAddress(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NetworkAddress{}, err
|
return NetworkAddress{}, err
|
||||||
}
|
}
|
||||||
if network == "" {
|
if network == "" {
|
||||||
network = defaultNetwork
|
network = "tcp"
|
||||||
}
|
}
|
||||||
if IsUnixNetwork(network) {
|
if isUnixNetwork(network) {
|
||||||
_, _, err := splitUnixSocketPermissionsBits(host)
|
|
||||||
return NetworkAddress{
|
return NetworkAddress{
|
||||||
Network: network,
|
Network: network,
|
||||||
Host: host,
|
Host: host,
|
||||||
}, err
|
}, nil
|
||||||
}
|
}
|
||||||
var start, end uint64
|
var start, end uint64
|
||||||
if port == "" {
|
if port != "" {
|
||||||
start = uint64(defaultPort)
|
|
||||||
end = uint64(defaultPort)
|
|
||||||
} else {
|
|
||||||
before, after, found := strings.Cut(port, "-")
|
before, after, found := strings.Cut(port, "-")
|
||||||
if !found {
|
if !found {
|
||||||
after = before
|
after = before
|
||||||
@@ -423,7 +353,7 @@ func SplitNetworkAddress(a string) (network, host, port string, err error) {
|
|||||||
network = strings.ToLower(strings.TrimSpace(beforeSlash))
|
network = strings.ToLower(strings.TrimSpace(beforeSlash))
|
||||||
a = afterSlash
|
a = afterSlash
|
||||||
}
|
}
|
||||||
if IsUnixNetwork(network) {
|
if isUnixNetwork(network) {
|
||||||
host = a
|
host = a
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -454,7 +384,7 @@ func JoinNetworkAddress(network, host, port string) string {
|
|||||||
if network != "" {
|
if network != "" {
|
||||||
a = network + "/"
|
a = network + "/"
|
||||||
}
|
}
|
||||||
if (host != "" && port == "") || IsUnixNetwork(network) {
|
if (host != "" && port == "") || isUnixNetwork(network) {
|
||||||
a += host
|
a += host
|
||||||
} else if port != "" {
|
} else if port != "" {
|
||||||
a += net.JoinHostPort(host, port)
|
a += net.JoinHostPort(host, port)
|
||||||
@@ -505,16 +435,11 @@ func ListenPacket(network, addr string) (net.PacketConn, error) {
|
|||||||
// NOTE: This API is EXPERIMENTAL and may be changed or removed.
|
// NOTE: This API is EXPERIMENTAL and may be changed or removed.
|
||||||
//
|
//
|
||||||
// TODO: See if we can find a more elegant solution closer to the new NetworkAddress.Listen API.
|
// TODO: See if we can find a more elegant solution closer to the new NetworkAddress.Listen API.
|
||||||
func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) (http3.QUICEarlyListener, error) {
|
func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) (quic.EarlyListener, error) {
|
||||||
lnKey := listenerKey("quic+"+ln.LocalAddr().Network(), ln.LocalAddr().String())
|
lnKey := listenerKey("quic+"+ln.LocalAddr().Network(), ln.LocalAddr().String())
|
||||||
|
|
||||||
sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
||||||
sqtc := newSharedQUICTLSConfig(tlsConf)
|
earlyLn, err := quic.ListenEarly(ln, http3.ConfigureTLSConfig(tlsConf), &quic.Config{
|
||||||
// http3.ConfigureTLSConfig only uses this field and tls App sets this field as well
|
|
||||||
//nolint:gosec
|
|
||||||
quicTlsConfig := &tls.Config{GetConfigForClient: sqtc.getConfigForClient}
|
|
||||||
earlyLn, err := quic.ListenEarly(ln, http3.ConfigureTLSConfig(quicTlsConfig), &quic.Config{
|
|
||||||
Allow0RTT: true,
|
|
||||||
RequireAddressValidation: func(clientAddr net.Addr) bool {
|
RequireAddressValidation: func(clientAddr net.Addr) bool {
|
||||||
var highLoad bool
|
var highLoad bool
|
||||||
if activeRequests != nil {
|
if activeRequests != nil {
|
||||||
@@ -526,29 +451,26 @@ func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &sharedQuicListener{EarlyListener: earlyLn, sqtc: sqtc, key: lnKey}, nil
|
return &sharedQuicListener{EarlyListener: earlyLn, key: lnKey}, nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sql := sharedEarlyListener.(*sharedQuicListener)
|
|
||||||
// add current tls.Config to sqtc, so GetConfigForClient will always return the latest tls.Config in case of context cancellation
|
|
||||||
ctx, cancel := sql.sqtc.addTLSConfig(tlsConf)
|
|
||||||
|
|
||||||
// TODO: to serve QUIC over a unix socket, currently we need to hold onto
|
// TODO: to serve QUIC over a unix socket, currently we need to hold onto
|
||||||
// the underlying net.PacketConn (which we wrap as unixConn to keep count
|
// the underlying net.PacketConn (which we wrap as unixConn to keep count
|
||||||
// of closes) because closing the quic.EarlyListener doesn't actually close
|
// of closes) because closing the quic.EarlyListener doesn't actually close
|
||||||
// the underlying PacketConn, but we need to for unix sockets since we dup
|
// the underlying PacketConn, but we need to for unix sockets since we dup
|
||||||
// the file descriptor and thus need to close the original; track issue:
|
// the file descriptor and thus need to close the original; track issue:
|
||||||
// https://github.com/quic-go/quic-go/issues/3560#issuecomment-1258959608
|
// https://github.com/lucas-clemente/quic-go/issues/3560#issuecomment-1258959608
|
||||||
var unix *unixConn
|
var unix *unixConn
|
||||||
if uc, ok := ln.(*unixConn); ok {
|
if uc, ok := ln.(*unixConn); ok {
|
||||||
unix = uc
|
unix = uc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
return &fakeCloseQuicListener{
|
return &fakeCloseQuicListener{
|
||||||
sharedQuicListener: sql,
|
sharedQuicListener: sharedEarlyListener.(*sharedQuicListener),
|
||||||
uc: unix,
|
uc: unix,
|
||||||
context: ctx,
|
context: ctx,
|
||||||
contextCancel: cancel,
|
contextCancel: cancel,
|
||||||
@@ -561,77 +483,10 @@ func ListenerUsage(network, addr string) int {
|
|||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
// contextAndCancelFunc groups context and its cancelFunc
|
|
||||||
type contextAndCancelFunc struct {
|
|
||||||
context.Context
|
|
||||||
context.CancelFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// sharedQUICTLSConfig manages GetConfigForClient
|
|
||||||
// see issue: https://github.com/caddyserver/caddy/pull/4849
|
|
||||||
type sharedQUICTLSConfig struct {
|
|
||||||
rmu sync.RWMutex
|
|
||||||
tlsConfs map[*tls.Config]contextAndCancelFunc
|
|
||||||
activeTlsConf *tls.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// newSharedQUICTLSConfig creates a new sharedQUICTLSConfig
|
|
||||||
func newSharedQUICTLSConfig(tlsConfig *tls.Config) *sharedQUICTLSConfig {
|
|
||||||
sqtc := &sharedQUICTLSConfig{
|
|
||||||
tlsConfs: make(map[*tls.Config]contextAndCancelFunc),
|
|
||||||
activeTlsConf: tlsConfig,
|
|
||||||
}
|
|
||||||
sqtc.addTLSConfig(tlsConfig)
|
|
||||||
return sqtc
|
|
||||||
}
|
|
||||||
|
|
||||||
// getConfigForClient is used as tls.Config's GetConfigForClient field
|
|
||||||
func (sqtc *sharedQUICTLSConfig) getConfigForClient(ch *tls.ClientHelloInfo) (*tls.Config, error) {
|
|
||||||
sqtc.rmu.RLock()
|
|
||||||
defer sqtc.rmu.RUnlock()
|
|
||||||
return sqtc.activeTlsConf.GetConfigForClient(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// addTLSConfig adds tls.Config to the map if not present and returns the corresponding context and its cancelFunc
|
|
||||||
// so that when cancelled, the active tls.Config will change
|
|
||||||
func (sqtc *sharedQUICTLSConfig) addTLSConfig(tlsConfig *tls.Config) (context.Context, context.CancelFunc) {
|
|
||||||
sqtc.rmu.Lock()
|
|
||||||
defer sqtc.rmu.Unlock()
|
|
||||||
|
|
||||||
if cacc, ok := sqtc.tlsConfs[tlsConfig]; ok {
|
|
||||||
return cacc.Context, cacc.CancelFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
wrappedCancel := func() {
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
sqtc.rmu.Lock()
|
|
||||||
defer sqtc.rmu.Unlock()
|
|
||||||
|
|
||||||
delete(sqtc.tlsConfs, tlsConfig)
|
|
||||||
if sqtc.activeTlsConf == tlsConfig {
|
|
||||||
// select another tls.Config, if there is none,
|
|
||||||
// related sharedQuicListener will be destroyed anyway
|
|
||||||
for tc := range sqtc.tlsConfs {
|
|
||||||
sqtc.activeTlsConf = tc
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sqtc.tlsConfs[tlsConfig] = contextAndCancelFunc{ctx, wrappedCancel}
|
|
||||||
// there should be at most 2 tls.Configs
|
|
||||||
if len(sqtc.tlsConfs) > 2 {
|
|
||||||
Log().Warn("quic listener tls configs are more than 2", zap.Int("number of configs", len(sqtc.tlsConfs)))
|
|
||||||
}
|
|
||||||
return ctx, wrappedCancel
|
|
||||||
}
|
|
||||||
|
|
||||||
// sharedQuicListener is like sharedListener, but for quic.EarlyListeners.
|
// sharedQuicListener is like sharedListener, but for quic.EarlyListeners.
|
||||||
type sharedQuicListener struct {
|
type sharedQuicListener struct {
|
||||||
*quic.EarlyListener
|
quic.EarlyListener
|
||||||
sqtc *sharedQUICTLSConfig
|
key string
|
||||||
key string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destruct closes the underlying QUIC listener.
|
// Destruct closes the underlying QUIC listener.
|
||||||
@@ -669,30 +524,37 @@ func fakeClosedErr(l interface{ Addr() net.Addr }) error {
|
|||||||
// socket is actually left open.
|
// socket is actually left open.
|
||||||
var errFakeClosed = fmt.Errorf("listener 'closed' 😉")
|
var errFakeClosed = fmt.Errorf("listener 'closed' 😉")
|
||||||
|
|
||||||
// fakeClosePacketConn is like fakeCloseListener, but for PacketConns,
|
// fakeClosePacketConn is like fakeCloseListener, but for PacketConns.
|
||||||
// or more specifically, *net.UDPConn
|
|
||||||
type fakeClosePacketConn struct {
|
type fakeClosePacketConn struct {
|
||||||
closed int32 // accessed atomically; belongs to this struct only
|
closed int32 // accessed atomically; belongs to this struct only
|
||||||
spc *sharedPacketConn // its key is used in Close
|
*sharedPacketConn // embedded, so we also become a net.PacketConn
|
||||||
*net.UDPConn // embedded, so we also become a net.PacketConn and enable several other optimizations done by quic-go
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// interface guard for extra optimizations
|
|
||||||
// needed by QUIC implementation: https://github.com/caddyserver/caddy/issues/3998, https://github.com/caddyserver/caddy/issues/5605
|
|
||||||
var _ quic.OOBCapablePacketConn = (*fakeClosePacketConn)(nil)
|
|
||||||
|
|
||||||
// https://pkg.go.dev/golang.org/x/net/ipv4#NewPacketConn is used by quic-go and requires a net.PacketConn type assertable to a net.Conn,
|
|
||||||
// but doesn't actually use these methods, the only methods needed are `ReadMsgUDP` and `SyscallConn`.
|
|
||||||
var _ net.Conn = (*fakeClosePacketConn)(nil)
|
|
||||||
|
|
||||||
// Close won't close the underlying socket unless there is no more reference, then listenerPool will close it.
|
|
||||||
func (fcpc *fakeClosePacketConn) Close() error {
|
func (fcpc *fakeClosePacketConn) Close() error {
|
||||||
if atomic.CompareAndSwapInt32(&fcpc.closed, 0, 1) {
|
if atomic.CompareAndSwapInt32(&fcpc.closed, 0, 1) {
|
||||||
_, _ = listenerPool.Delete(fcpc.spc.key)
|
_, _ = listenerPool.Delete(fcpc.sharedPacketConn.key)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Supports QUIC implementation: https://github.com/caddyserver/caddy/issues/3998
|
||||||
|
func (fcpc fakeClosePacketConn) SetReadBuffer(bytes int) error {
|
||||||
|
if conn, ok := fcpc.PacketConn.(interface{ SetReadBuffer(int) error }); ok {
|
||||||
|
return conn.SetReadBuffer(bytes)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("SetReadBuffer() not implemented for %T", fcpc.PacketConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supports QUIC implementation: https://github.com/caddyserver/caddy/issues/3998
|
||||||
|
func (fcpc fakeClosePacketConn) SyscallConn() (syscall.RawConn, error) {
|
||||||
|
if conn, ok := fcpc.PacketConn.(interface {
|
||||||
|
SyscallConn() (syscall.RawConn, error)
|
||||||
|
}); ok {
|
||||||
|
return conn.SyscallConn()
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("SyscallConn() not implemented for %T", fcpc.PacketConn)
|
||||||
|
}
|
||||||
|
|
||||||
type fakeCloseQuicListener struct {
|
type fakeCloseQuicListener struct {
|
||||||
closed int32 // accessed atomically; belongs to this struct only
|
closed int32 // accessed atomically; belongs to this struct only
|
||||||
*sharedQuicListener // embedded, so we also become a quic.EarlyListener
|
*sharedQuicListener // embedded, so we also become a quic.EarlyListener
|
||||||
@@ -753,6 +615,26 @@ func RegisterNetwork(network string, getListener ListenerFunc) {
|
|||||||
networkTypes[network] = getListener
|
networkTypes[network] = getListener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type unixListener struct {
|
||||||
|
*net.UnixListener
|
||||||
|
mapKey string
|
||||||
|
count *int32 // accessed atomically
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uln *unixListener) Close() error {
|
||||||
|
newCount := atomic.AddInt32(uln.count, -1)
|
||||||
|
if newCount == 0 {
|
||||||
|
defer func() {
|
||||||
|
addr := uln.Addr().String()
|
||||||
|
unixSocketsMu.Lock()
|
||||||
|
delete(unixSockets, uln.mapKey)
|
||||||
|
unixSocketsMu.Unlock()
|
||||||
|
_ = syscall.Unlink(addr)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return uln.UnixListener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
type unixConn struct {
|
type unixConn struct {
|
||||||
*net.UnixConn
|
*net.UnixConn
|
||||||
filename string
|
filename string
|
||||||
|
|||||||
+21
-258
@@ -175,57 +175,47 @@ func TestJoinNetworkAddress(t *testing.T) {
|
|||||||
|
|
||||||
func TestParseNetworkAddress(t *testing.T) {
|
func TestParseNetworkAddress(t *testing.T) {
|
||||||
for i, tc := range []struct {
|
for i, tc := range []struct {
|
||||||
input string
|
input string
|
||||||
defaultNetwork string
|
expectAddr NetworkAddress
|
||||||
defaultPort uint
|
expectErr bool
|
||||||
expectAddr NetworkAddress
|
|
||||||
expectErr bool
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
input: "",
|
input: "",
|
||||||
expectErr: true,
|
expectErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: ":",
|
input: ":",
|
||||||
defaultNetwork: "udp",
|
|
||||||
expectAddr: NetworkAddress{
|
expectAddr: NetworkAddress{
|
||||||
Network: "udp",
|
Network: "tcp",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "[::]",
|
input: "[::]",
|
||||||
defaultNetwork: "udp",
|
|
||||||
defaultPort: 53,
|
|
||||||
expectAddr: NetworkAddress{
|
expectAddr: NetworkAddress{
|
||||||
Network: "udp",
|
Network: "tcp",
|
||||||
Host: "::",
|
Host: "::",
|
||||||
StartPort: 53,
|
|
||||||
EndPort: 53,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: ":1234",
|
input: ":1234",
|
||||||
defaultNetwork: "udp",
|
|
||||||
expectAddr: NetworkAddress{
|
expectAddr: NetworkAddress{
|
||||||
Network: "udp",
|
Network: "tcp",
|
||||||
Host: "",
|
Host: "",
|
||||||
StartPort: 1234,
|
StartPort: 1234,
|
||||||
EndPort: 1234,
|
EndPort: 1234,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "udp/:1234",
|
input: "tcp/:1234",
|
||||||
defaultNetwork: "udp",
|
|
||||||
expectAddr: NetworkAddress{
|
expectAddr: NetworkAddress{
|
||||||
Network: "udp",
|
Network: "tcp",
|
||||||
Host: "",
|
Host: "",
|
||||||
StartPort: 1234,
|
StartPort: 1234,
|
||||||
EndPort: 1234,
|
EndPort: 1234,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "tcp6/:1234",
|
input: "tcp6/:1234",
|
||||||
defaultNetwork: "tcp",
|
|
||||||
expectAddr: NetworkAddress{
|
expectAddr: NetworkAddress{
|
||||||
Network: "tcp6",
|
Network: "tcp6",
|
||||||
Host: "",
|
Host: "",
|
||||||
@@ -234,8 +224,7 @@ func TestParseNetworkAddress(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "tcp4/localhost:1234",
|
input: "tcp4/localhost:1234",
|
||||||
defaultNetwork: "tcp",
|
|
||||||
expectAddr: NetworkAddress{
|
expectAddr: NetworkAddress{
|
||||||
Network: "tcp4",
|
Network: "tcp4",
|
||||||
Host: "localhost",
|
Host: "localhost",
|
||||||
@@ -244,16 +233,14 @@ func TestParseNetworkAddress(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "unix//foo/bar",
|
input: "unix//foo/bar",
|
||||||
defaultNetwork: "tcp",
|
|
||||||
expectAddr: NetworkAddress{
|
expectAddr: NetworkAddress{
|
||||||
Network: "unix",
|
Network: "unix",
|
||||||
Host: "/foo/bar",
|
Host: "/foo/bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "localhost:1234-1234",
|
input: "localhost:1234-1234",
|
||||||
defaultNetwork: "tcp",
|
|
||||||
expectAddr: NetworkAddress{
|
expectAddr: NetworkAddress{
|
||||||
Network: "tcp",
|
Network: "tcp",
|
||||||
Host: "localhost",
|
Host: "localhost",
|
||||||
@@ -262,139 +249,11 @@ func TestParseNetworkAddress(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "localhost:2-1",
|
input: "localhost:2-1",
|
||||||
defaultNetwork: "tcp",
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "localhost:0",
|
|
||||||
defaultNetwork: "tcp",
|
|
||||||
expectAddr: NetworkAddress{
|
|
||||||
Network: "tcp",
|
|
||||||
Host: "localhost",
|
|
||||||
StartPort: 0,
|
|
||||||
EndPort: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "localhost:1-999999999999",
|
|
||||||
defaultNetwork: "tcp",
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
actualAddr, err := ParseNetworkAddressWithDefaults(tc.input, tc.defaultNetwork, tc.defaultPort)
|
|
||||||
if tc.expectErr && err == nil {
|
|
||||||
t.Errorf("Test %d: Expected error but got: %v", i, err)
|
|
||||||
}
|
|
||||||
if !tc.expectErr && err != nil {
|
|
||||||
t.Errorf("Test %d: Expected no error but got: %v", i, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if actualAddr.Network != tc.expectAddr.Network {
|
|
||||||
t.Errorf("Test %d: Expected network '%v' but got '%v'", i, tc.expectAddr, actualAddr)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(tc.expectAddr, actualAddr) {
|
|
||||||
t.Errorf("Test %d: Expected addresses %v but got %v", i, tc.expectAddr, actualAddr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseNetworkAddressWithDefaults(t *testing.T) {
|
|
||||||
for i, tc := range []struct {
|
|
||||||
input string
|
|
||||||
defaultNetwork string
|
|
||||||
defaultPort uint
|
|
||||||
expectAddr NetworkAddress
|
|
||||||
expectErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: "",
|
|
||||||
expectErr: true,
|
expectErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: ":",
|
input: "localhost:0",
|
||||||
defaultNetwork: "udp",
|
|
||||||
expectAddr: NetworkAddress{
|
|
||||||
Network: "udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "[::]",
|
|
||||||
defaultNetwork: "udp",
|
|
||||||
defaultPort: 53,
|
|
||||||
expectAddr: NetworkAddress{
|
|
||||||
Network: "udp",
|
|
||||||
Host: "::",
|
|
||||||
StartPort: 53,
|
|
||||||
EndPort: 53,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: ":1234",
|
|
||||||
defaultNetwork: "udp",
|
|
||||||
expectAddr: NetworkAddress{
|
|
||||||
Network: "udp",
|
|
||||||
Host: "",
|
|
||||||
StartPort: 1234,
|
|
||||||
EndPort: 1234,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "udp/:1234",
|
|
||||||
defaultNetwork: "udp",
|
|
||||||
expectAddr: NetworkAddress{
|
|
||||||
Network: "udp",
|
|
||||||
Host: "",
|
|
||||||
StartPort: 1234,
|
|
||||||
EndPort: 1234,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "tcp6/:1234",
|
|
||||||
defaultNetwork: "tcp",
|
|
||||||
expectAddr: NetworkAddress{
|
|
||||||
Network: "tcp6",
|
|
||||||
Host: "",
|
|
||||||
StartPort: 1234,
|
|
||||||
EndPort: 1234,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "tcp4/localhost:1234",
|
|
||||||
defaultNetwork: "tcp",
|
|
||||||
expectAddr: NetworkAddress{
|
|
||||||
Network: "tcp4",
|
|
||||||
Host: "localhost",
|
|
||||||
StartPort: 1234,
|
|
||||||
EndPort: 1234,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "unix//foo/bar",
|
|
||||||
defaultNetwork: "tcp",
|
|
||||||
expectAddr: NetworkAddress{
|
|
||||||
Network: "unix",
|
|
||||||
Host: "/foo/bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "localhost:1234-1234",
|
|
||||||
defaultNetwork: "tcp",
|
|
||||||
expectAddr: NetworkAddress{
|
|
||||||
Network: "tcp",
|
|
||||||
Host: "localhost",
|
|
||||||
StartPort: 1234,
|
|
||||||
EndPort: 1234,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "localhost:2-1",
|
|
||||||
defaultNetwork: "tcp",
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "localhost:0",
|
|
||||||
defaultNetwork: "tcp",
|
|
||||||
expectAddr: NetworkAddress{
|
expectAddr: NetworkAddress{
|
||||||
Network: "tcp",
|
Network: "tcp",
|
||||||
Host: "localhost",
|
Host: "localhost",
|
||||||
@@ -403,12 +262,11 @@ func TestParseNetworkAddressWithDefaults(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "localhost:1-999999999999",
|
input: "localhost:1-999999999999",
|
||||||
defaultNetwork: "tcp",
|
expectErr: true,
|
||||||
expectErr: true,
|
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
actualAddr, err := ParseNetworkAddressWithDefaults(tc.input, tc.defaultNetwork, tc.defaultPort)
|
actualAddr, err := ParseNetworkAddress(tc.input)
|
||||||
if tc.expectErr && err == nil {
|
if tc.expectErr && err == nil {
|
||||||
t.Errorf("Test %d: Expected error but got: %v", i, err)
|
t.Errorf("Test %d: Expected error but got: %v", i, err)
|
||||||
}
|
}
|
||||||
@@ -555,98 +413,3 @@ func TestExpand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSplitUnixSocketPermissionsBits(t *testing.T) {
|
|
||||||
for i, tc := range []struct {
|
|
||||||
input string
|
|
||||||
expectNetwork string
|
|
||||||
expectPath string
|
|
||||||
expectFileMode string
|
|
||||||
expectErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: "./foo.socket",
|
|
||||||
expectPath: "./foo.socket",
|
|
||||||
expectFileMode: "--w-------",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `.\relative\path.socket`,
|
|
||||||
expectPath: `.\relative\path.socket`,
|
|
||||||
expectFileMode: "--w-------",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// literal colon in resulting address
|
|
||||||
// and defaulting to 0200 bits
|
|
||||||
input: "./foo.socket:0666",
|
|
||||||
expectPath: "./foo.socket:0666",
|
|
||||||
expectFileMode: "--w-------",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "./foo.socket|0220",
|
|
||||||
expectPath: "./foo.socket",
|
|
||||||
expectFileMode: "--w--w----",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "/var/run/foo|222",
|
|
||||||
expectPath: "/var/run/foo",
|
|
||||||
expectFileMode: "--w--w--w-",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "./foo.socket|0660",
|
|
||||||
expectPath: "./foo.socket",
|
|
||||||
expectFileMode: "-rw-rw----",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "./foo.socket|0666",
|
|
||||||
expectPath: "./foo.socket",
|
|
||||||
expectFileMode: "-rw-rw-rw-",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "/var/run/foo|666",
|
|
||||||
expectPath: "/var/run/foo",
|
|
||||||
expectFileMode: "-rw-rw-rw-",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `c:\absolute\path.socket|220`,
|
|
||||||
expectPath: `c:\absolute\path.socket`,
|
|
||||||
expectFileMode: "--w--w----",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// symbolic permission representation is not supported for now
|
|
||||||
input: "./foo.socket|u=rw,g=rw,o=rw",
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// octal (base-8) permission representation has to be between
|
|
||||||
// `0` for no read, no write, no exec (`---`) and
|
|
||||||
// `7` for read (4), write (2), exec (1) (`rwx` => `4+2+1 = 7`)
|
|
||||||
input: "./foo.socket|888",
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// too many colons in address
|
|
||||||
input: "./foo.socket|123456|0660",
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// owner is missing write perms
|
|
||||||
input: "./foo.socket|0522",
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
actualPath, actualFileMode, err := splitUnixSocketPermissionsBits(tc.input)
|
|
||||||
if tc.expectErr && err == nil {
|
|
||||||
t.Errorf("Test %d: Expected error but got: %v", i, err)
|
|
||||||
}
|
|
||||||
if !tc.expectErr && err != nil {
|
|
||||||
t.Errorf("Test %d: Expected no error but got: %v", i, err)
|
|
||||||
}
|
|
||||||
if actualPath != tc.expectPath {
|
|
||||||
t.Errorf("Test %d: Expected path '%s' but got '%s'", i, tc.expectPath, actualPath)
|
|
||||||
}
|
|
||||||
// fileMode.Perm().String() parses 0 to "----------"
|
|
||||||
if !tc.expectErr && actualFileMode.Perm().String() != tc.expectFileMode {
|
|
||||||
t.Errorf("Test %d: Expected perms '%s' but got '%s'", i, tc.expectFileMode, actualFileMode.Perm().String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+125
-115
@@ -62,7 +62,7 @@ type Logging struct {
|
|||||||
// in dependencies that are not designed specifically for use
|
// in dependencies that are not designed specifically for use
|
||||||
// in Caddy. Because it is global and unstructured, the sink
|
// in Caddy. Because it is global and unstructured, the sink
|
||||||
// lacks most advanced features and customizations.
|
// lacks most advanced features and customizations.
|
||||||
Sink *SinkLog `json:"sink,omitempty"`
|
Sink *StandardLibLog `json:"sink,omitempty"`
|
||||||
|
|
||||||
// Logs are your logs, keyed by an arbitrary name of your
|
// Logs are your logs, keyed by an arbitrary name of your
|
||||||
// choosing. The default log can be customized by defining
|
// choosing. The default log can be customized by defining
|
||||||
@@ -259,11 +259,55 @@ func (wdest writerDestructor) Destruct() error {
|
|||||||
return wdest.Close()
|
return wdest.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// BaseLog contains the common logging parameters for logging.
|
// StandardLibLog configures the default Go standard library
|
||||||
type BaseLog struct {
|
// global logger in the log package. This is necessary because
|
||||||
|
// module dependencies which are not built specifically for
|
||||||
|
// Caddy will use the standard logger. This is also known as
|
||||||
|
// the "sink" logger.
|
||||||
|
type StandardLibLog struct {
|
||||||
// The module that writes out log entries for the sink.
|
// The module that writes out log entries for the sink.
|
||||||
WriterRaw json.RawMessage `json:"writer,omitempty" caddy:"namespace=caddy.logging.writers inline_key=output"`
|
WriterRaw json.RawMessage `json:"writer,omitempty" caddy:"namespace=caddy.logging.writers inline_key=output"`
|
||||||
|
|
||||||
|
writer io.WriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sll *StandardLibLog) provision(ctx Context, logging *Logging) error {
|
||||||
|
if sll.WriterRaw != nil {
|
||||||
|
mod, err := ctx.LoadModule(sll, "WriterRaw")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("loading sink log writer module: %v", err)
|
||||||
|
}
|
||||||
|
wo := mod.(WriterOpener)
|
||||||
|
|
||||||
|
var isNew bool
|
||||||
|
sll.writer, isNew, err = logging.openWriter(wo)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("opening sink log writer %#v: %v", mod, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isNew {
|
||||||
|
log.Printf("[INFO] Redirecting sink to: %s", wo)
|
||||||
|
log.SetOutput(sll.writer)
|
||||||
|
log.Printf("[INFO] Redirected sink to here (%s)", wo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomLog represents a custom logger configuration.
|
||||||
|
//
|
||||||
|
// By default, a log will emit all log entries. Some entries
|
||||||
|
// will be skipped if sampling is enabled. Further, the Include
|
||||||
|
// and Exclude parameters define which loggers (by name) are
|
||||||
|
// allowed or rejected from emitting in this log. If both Include
|
||||||
|
// and Exclude are populated, their values must be mutually
|
||||||
|
// exclusive, and longer namespaces have priority. If neither
|
||||||
|
// are populated, all logs are emitted.
|
||||||
|
type CustomLog struct {
|
||||||
|
// The writer defines where log entries are emitted.
|
||||||
|
WriterRaw json.RawMessage `json:"writer,omitempty" caddy:"namespace=caddy.logging.writers inline_key=output"`
|
||||||
|
|
||||||
// The encoder is how the log entries are formatted or encoded.
|
// The encoder is how the log entries are formatted or encoded.
|
||||||
EncoderRaw json.RawMessage `json:"encoder,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"`
|
EncoderRaw json.RawMessage `json:"encoder,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"`
|
||||||
|
|
||||||
@@ -277,6 +321,16 @@ type BaseLog struct {
|
|||||||
// servers.
|
// servers.
|
||||||
Sampling *LogSampling `json:"sampling,omitempty"`
|
Sampling *LogSampling `json:"sampling,omitempty"`
|
||||||
|
|
||||||
|
// Include defines the names of loggers to emit in this
|
||||||
|
// log. For example, to include only logs emitted by the
|
||||||
|
// admin API, you would include "admin.api".
|
||||||
|
Include []string `json:"include,omitempty"`
|
||||||
|
|
||||||
|
// Exclude defines the names of loggers that should be
|
||||||
|
// skipped by this log. For example, to exclude only
|
||||||
|
// HTTP access logs, you would exclude "http.log.access".
|
||||||
|
Exclude []string `json:"exclude,omitempty"`
|
||||||
|
|
||||||
writerOpener WriterOpener
|
writerOpener WriterOpener
|
||||||
writer io.WriteCloser
|
writer io.WriteCloser
|
||||||
encoder zapcore.Encoder
|
encoder zapcore.Encoder
|
||||||
@@ -284,23 +338,8 @@ type BaseLog struct {
|
|||||||
core zapcore.Core
|
core zapcore.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *BaseLog) provisionCommon(ctx Context, logging *Logging) error {
|
func (cl *CustomLog) provision(ctx Context, logging *Logging) error {
|
||||||
if cl.WriterRaw != nil {
|
// Replace placeholder for log level
|
||||||
mod, err := ctx.LoadModule(cl, "WriterRaw")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("loading log writer module: %v", err)
|
|
||||||
}
|
|
||||||
cl.writerOpener = mod.(WriterOpener)
|
|
||||||
}
|
|
||||||
if cl.writerOpener == nil {
|
|
||||||
cl.writerOpener = StderrWriter{}
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
cl.writer, _, err = logging.openWriter(cl.writerOpener)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("opening log writer using %#v: %v", cl.writerOpener, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
repl := NewReplacer()
|
repl := NewReplacer()
|
||||||
level, err := repl.ReplaceOrErr(cl.Level, true, true)
|
level, err := repl.ReplaceOrErr(cl.Level, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -326,101 +365,6 @@ func (cl *BaseLog) provisionCommon(ctx Context, logging *Logging) error {
|
|||||||
return fmt.Errorf("unrecognized log level: %s", cl.Level)
|
return fmt.Errorf("unrecognized log level: %s", cl.Level)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cl.EncoderRaw != nil {
|
|
||||||
mod, err := ctx.LoadModule(cl, "EncoderRaw")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("loading log encoder module: %v", err)
|
|
||||||
}
|
|
||||||
cl.encoder = mod.(zapcore.Encoder)
|
|
||||||
}
|
|
||||||
if cl.encoder == nil {
|
|
||||||
// only allow colorized output if this log is going to stdout or stderr
|
|
||||||
var colorize bool
|
|
||||||
switch cl.writerOpener.(type) {
|
|
||||||
case StdoutWriter, StderrWriter,
|
|
||||||
*StdoutWriter, *StderrWriter:
|
|
||||||
colorize = true
|
|
||||||
}
|
|
||||||
cl.encoder = newDefaultProductionLogEncoder(colorize)
|
|
||||||
}
|
|
||||||
cl.buildCore()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *BaseLog) buildCore() {
|
|
||||||
// logs which only discard their output don't need
|
|
||||||
// to perform encoding or any other processing steps
|
|
||||||
// at all, so just shorcut to a nop core instead
|
|
||||||
if _, ok := cl.writerOpener.(*DiscardWriter); ok {
|
|
||||||
cl.core = zapcore.NewNopCore()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c := zapcore.NewCore(
|
|
||||||
cl.encoder,
|
|
||||||
zapcore.AddSync(cl.writer),
|
|
||||||
cl.levelEnabler,
|
|
||||||
)
|
|
||||||
if cl.Sampling != nil {
|
|
||||||
if cl.Sampling.Interval == 0 {
|
|
||||||
cl.Sampling.Interval = 1 * time.Second
|
|
||||||
}
|
|
||||||
if cl.Sampling.First == 0 {
|
|
||||||
cl.Sampling.First = 100
|
|
||||||
}
|
|
||||||
if cl.Sampling.Thereafter == 0 {
|
|
||||||
cl.Sampling.Thereafter = 100
|
|
||||||
}
|
|
||||||
c = zapcore.NewSamplerWithOptions(c, cl.Sampling.Interval,
|
|
||||||
cl.Sampling.First, cl.Sampling.Thereafter)
|
|
||||||
}
|
|
||||||
cl.core = c
|
|
||||||
}
|
|
||||||
|
|
||||||
// SinkLog configures the default Go standard library
|
|
||||||
// global logger in the log package. This is necessary because
|
|
||||||
// module dependencies which are not built specifically for
|
|
||||||
// Caddy will use the standard logger. This is also known as
|
|
||||||
// the "sink" logger.
|
|
||||||
type SinkLog struct {
|
|
||||||
BaseLog
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sll *SinkLog) provision(ctx Context, logging *Logging) error {
|
|
||||||
if err := sll.provisionCommon(ctx, logging); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ctx.cleanupFuncs = append(ctx.cleanupFuncs, zap.RedirectStdLog(zap.New(sll.core)))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CustomLog represents a custom logger configuration.
|
|
||||||
//
|
|
||||||
// By default, a log will emit all log entries. Some entries
|
|
||||||
// will be skipped if sampling is enabled. Further, the Include
|
|
||||||
// and Exclude parameters define which loggers (by name) are
|
|
||||||
// allowed or rejected from emitting in this log. If both Include
|
|
||||||
// and Exclude are populated, their values must be mutually
|
|
||||||
// exclusive, and longer namespaces have priority. If neither
|
|
||||||
// are populated, all logs are emitted.
|
|
||||||
type CustomLog struct {
|
|
||||||
BaseLog
|
|
||||||
|
|
||||||
// Include defines the names of loggers to emit in this
|
|
||||||
// log. For example, to include only logs emitted by the
|
|
||||||
// admin API, you would include "admin.api".
|
|
||||||
Include []string `json:"include,omitempty"`
|
|
||||||
|
|
||||||
// Exclude defines the names of loggers that should be
|
|
||||||
// skipped by this log. For example, to exclude only
|
|
||||||
// HTTP access logs, you would exclude "http.log.access".
|
|
||||||
Exclude []string `json:"exclude,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *CustomLog) provision(ctx Context, logging *Logging) error {
|
|
||||||
if err := cl.provisionCommon(ctx, logging); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If both Include and Exclude lists are populated, then each item must
|
// If both Include and Exclude lists are populated, then each item must
|
||||||
// be a superspace or subspace of an item in the other list, because
|
// be a superspace or subspace of an item in the other list, because
|
||||||
// populating both lists means that any given item is either a rule
|
// populating both lists means that any given item is either a rule
|
||||||
@@ -450,9 +394,75 @@ func (cl *CustomLog) provision(ctx Context, logging *Logging) error {
|
|||||||
return fmt.Errorf("when both include and exclude are populated, each element must be a superspace or subspace of one in the other list; check '%s' in include", allow)
|
return fmt.Errorf("when both include and exclude are populated, each element must be a superspace or subspace of one in the other list; check '%s' in include", allow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cl.WriterRaw != nil {
|
||||||
|
mod, err := ctx.LoadModule(cl, "WriterRaw")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("loading log writer module: %v", err)
|
||||||
|
}
|
||||||
|
cl.writerOpener = mod.(WriterOpener)
|
||||||
|
}
|
||||||
|
if cl.writerOpener == nil {
|
||||||
|
cl.writerOpener = StderrWriter{}
|
||||||
|
}
|
||||||
|
|
||||||
|
cl.writer, _, err = logging.openWriter(cl.writerOpener)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("opening log writer using %#v: %v", cl.writerOpener, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cl.EncoderRaw != nil {
|
||||||
|
mod, err := ctx.LoadModule(cl, "EncoderRaw")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("loading log encoder module: %v", err)
|
||||||
|
}
|
||||||
|
cl.encoder = mod.(zapcore.Encoder)
|
||||||
|
}
|
||||||
|
if cl.encoder == nil {
|
||||||
|
// only allow colorized output if this log is going to stdout or stderr
|
||||||
|
var colorize bool
|
||||||
|
switch cl.writerOpener.(type) {
|
||||||
|
case StdoutWriter, StderrWriter,
|
||||||
|
*StdoutWriter, *StderrWriter:
|
||||||
|
colorize = true
|
||||||
|
}
|
||||||
|
cl.encoder = newDefaultProductionLogEncoder(colorize)
|
||||||
|
}
|
||||||
|
|
||||||
|
cl.buildCore()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cl *CustomLog) buildCore() {
|
||||||
|
// logs which only discard their output don't need
|
||||||
|
// to perform encoding or any other processing steps
|
||||||
|
// at all, so just shorcut to a nop core instead
|
||||||
|
if _, ok := cl.writerOpener.(*DiscardWriter); ok {
|
||||||
|
cl.core = zapcore.NewNopCore()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c := zapcore.NewCore(
|
||||||
|
cl.encoder,
|
||||||
|
zapcore.AddSync(cl.writer),
|
||||||
|
cl.levelEnabler,
|
||||||
|
)
|
||||||
|
if cl.Sampling != nil {
|
||||||
|
if cl.Sampling.Interval == 0 {
|
||||||
|
cl.Sampling.Interval = 1 * time.Second
|
||||||
|
}
|
||||||
|
if cl.Sampling.First == 0 {
|
||||||
|
cl.Sampling.First = 100
|
||||||
|
}
|
||||||
|
if cl.Sampling.Thereafter == 0 {
|
||||||
|
cl.Sampling.Thereafter = 100
|
||||||
|
}
|
||||||
|
c = zapcore.NewSamplerWithOptions(c, cl.Sampling.Interval,
|
||||||
|
cl.Sampling.First, cl.Sampling.Thereafter)
|
||||||
|
}
|
||||||
|
cl.core = c
|
||||||
|
}
|
||||||
|
|
||||||
func (cl *CustomLog) matchesModule(moduleID string) bool {
|
func (cl *CustomLog) matchesModule(moduleID string) bool {
|
||||||
return cl.loggerAllowed(moduleID, true)
|
return cl.loggerAllowed(moduleID, true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,9 +66,3 @@ func (d *delegator) WriteHeader(code int) {
|
|||||||
d.status = code
|
d.status = code
|
||||||
d.ResponseWriter.WriteHeader(code)
|
d.ResponseWriter.WriteHeader(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap returns the underlying ResponseWriter, necessary for
|
|
||||||
// http.ResponseController to work correctly.
|
|
||||||
func (d *delegator) Unwrap() http.ResponseWriter {
|
|
||||||
return d.ResponseWriter
|
|
||||||
}
|
|
||||||
|
|||||||
+2
-2
@@ -333,11 +333,11 @@ func ParseStructTag(tag string) (map[string]string, error) {
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrictUnmarshalJSON is like json.Unmarshal but returns an error
|
// strictUnmarshalJSON is like json.Unmarshal but returns an error
|
||||||
// if any of the fields are unrecognized. Useful when decoding
|
// if any of the fields are unrecognized. Useful when decoding
|
||||||
// module configurations, where you want to be more sure they're
|
// module configurations, where you want to be more sure they're
|
||||||
// correct.
|
// correct.
|
||||||
func StrictUnmarshalJSON(data []byte, v any) error {
|
func strictUnmarshalJSON(data []byte, v any) error {
|
||||||
dec := json.NewDecoder(bytes.NewReader(data))
|
dec := json.NewDecoder(bytes.NewReader(data))
|
||||||
dec.DisallowUnknownFields()
|
dec.DisallowUnknownFields()
|
||||||
return dec.Decode(v)
|
return dec.Decode(v)
|
||||||
|
|||||||
+21
-92
@@ -20,9 +20,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -56,17 +54,16 @@ func init() {
|
|||||||
// `{http.request.duration_ms}` | Same as 'duration', but in milliseconds.
|
// `{http.request.duration_ms}` | Same as 'duration', but in milliseconds.
|
||||||
// `{http.request.uuid}` | The request unique identifier
|
// `{http.request.uuid}` | The request unique identifier
|
||||||
// `{http.request.header.*}` | Specific request header field
|
// `{http.request.header.*}` | Specific request header field
|
||||||
// `{http.request.host}` | The host part of the request's Host header
|
|
||||||
// `{http.request.host.labels.*}` | Request host labels (0-based from right); e.g. for foo.example.com: 0=com, 1=example, 2=foo
|
// `{http.request.host.labels.*}` | Request host labels (0-based from right); e.g. for foo.example.com: 0=com, 1=example, 2=foo
|
||||||
|
// `{http.request.host}` | The host part of the request's Host header
|
||||||
// `{http.request.hostport}` | The host and port from the request's Host header
|
// `{http.request.hostport}` | The host and port from the request's Host header
|
||||||
// `{http.request.method}` | The request method
|
// `{http.request.method}` | The request method
|
||||||
// `{http.request.orig_method}` | The request's original method
|
// `{http.request.orig_method}` | The request's original method
|
||||||
// `{http.request.orig_uri}` | The request's original URI
|
|
||||||
// `{http.request.orig_uri.path}` | The request's original path
|
|
||||||
// `{http.request.orig_uri.path.*}` | Parts of the original path, split by `/` (0-based from left)
|
|
||||||
// `{http.request.orig_uri.path.dir}` | The request's original directory
|
// `{http.request.orig_uri.path.dir}` | The request's original directory
|
||||||
// `{http.request.orig_uri.path.file}` | The request's original filename
|
// `{http.request.orig_uri.path.file}` | The request's original filename
|
||||||
|
// `{http.request.orig_uri.path}` | The request's original path
|
||||||
// `{http.request.orig_uri.query}` | The request's original query string (without `?`)
|
// `{http.request.orig_uri.query}` | The request's original query string (without `?`)
|
||||||
|
// `{http.request.orig_uri}` | The request's original URI
|
||||||
// `{http.request.port}` | The port part of the request's Host header
|
// `{http.request.port}` | The port part of the request's Host header
|
||||||
// `{http.request.proto}` | The protocol of the request
|
// `{http.request.proto}` | The protocol of the request
|
||||||
// `{http.request.remote.host}` | The host (IP) part of the remote client's address
|
// `{http.request.remote.host}` | The host (IP) part of the remote client's address
|
||||||
@@ -91,13 +88,13 @@ func init() {
|
|||||||
// `{http.request.tls.client.san.emails.*}` | SAN email addresses (index optional)
|
// `{http.request.tls.client.san.emails.*}` | SAN email addresses (index optional)
|
||||||
// `{http.request.tls.client.san.ips.*}` | SAN IP addresses (index optional)
|
// `{http.request.tls.client.san.ips.*}` | SAN IP addresses (index optional)
|
||||||
// `{http.request.tls.client.san.uris.*}` | SAN URIs (index optional)
|
// `{http.request.tls.client.san.uris.*}` | SAN URIs (index optional)
|
||||||
// `{http.request.uri}` | The full request URI
|
|
||||||
// `{http.request.uri.path}` | The path component of the request URI
|
|
||||||
// `{http.request.uri.path.*}` | Parts of the path, split by `/` (0-based from left)
|
// `{http.request.uri.path.*}` | Parts of the path, split by `/` (0-based from left)
|
||||||
// `{http.request.uri.path.dir}` | The directory, excluding leaf filename
|
// `{http.request.uri.path.dir}` | The directory, excluding leaf filename
|
||||||
// `{http.request.uri.path.file}` | The filename of the path, excluding directory
|
// `{http.request.uri.path.file}` | The filename of the path, excluding directory
|
||||||
// `{http.request.uri.query}` | The query string (without `?`)
|
// `{http.request.uri.path}` | The path component of the request URI
|
||||||
// `{http.request.uri.query.*}` | Individual query string value
|
// `{http.request.uri.query.*}` | Individual query string value
|
||||||
|
// `{http.request.uri.query}` | The query string (without `?`)
|
||||||
|
// `{http.request.uri}` | The full request URI
|
||||||
// `{http.response.header.*}` | Specific response header field
|
// `{http.response.header.*}` | Specific response header field
|
||||||
// `{http.vars.*}` | Custom variables in the HTTP handler chain
|
// `{http.vars.*}` | Custom variables in the HTTP handler chain
|
||||||
// `{http.shutting_down}` | True if the HTTP app is shutting down
|
// `{http.shutting_down}` | True if the HTTP app is shutting down
|
||||||
@@ -225,20 +222,6 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||||||
srv.StrictSNIHost = &trueBool
|
srv.StrictSNIHost = &trueBool
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up the trusted proxies source
|
|
||||||
for srv.TrustedProxiesRaw != nil {
|
|
||||||
val, err := ctx.LoadModule(srv, "TrustedProxiesRaw")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("loading trusted proxies modules: %v", err)
|
|
||||||
}
|
|
||||||
srv.trustedProxies = val.(IPRangeSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the default client IP header to read from
|
|
||||||
if srv.ClientIPHeaders == nil {
|
|
||||||
srv.ClientIPHeaders = []string{"X-Forwarded-For"}
|
|
||||||
}
|
|
||||||
|
|
||||||
// process each listener address
|
// process each listener address
|
||||||
for i := range srv.Listen {
|
for i := range srv.Listen {
|
||||||
lnOut, err := repl.ReplaceOrErr(srv.Listen[i], true, true)
|
lnOut, err := repl.ReplaceOrErr(srv.Listen[i], true, true)
|
||||||
@@ -295,19 +278,11 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||||||
if srv.Errors != nil {
|
if srv.Errors != nil {
|
||||||
err := srv.Errors.Routes.Provision(ctx)
|
err := srv.Errors.Routes.Provision(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("server %s: setting up error handling routes: %v", srvName, err)
|
return fmt.Errorf("server %s: setting up server error handling routes: %v", srvName, err)
|
||||||
}
|
}
|
||||||
srv.errorHandlerChain = srv.Errors.Routes.Compile(errorEmptyHandler)
|
srv.errorHandlerChain = srv.Errors.Routes.Compile(errorEmptyHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// provision the named routes (they get compiled at runtime)
|
|
||||||
for name, route := range srv.NamedRoutes {
|
|
||||||
err := route.Provision(ctx, srv.Metrics)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("server %s: setting up named route '%s' handlers: %v", name, srvName, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare the TLS connection policies
|
// prepare the TLS connection policies
|
||||||
err = srv.TLSConnPolicies.Provision(ctx)
|
err = srv.TLSConnPolicies.Provision(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -327,15 +302,9 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||||||
|
|
||||||
// Validate ensures the app's configuration is valid.
|
// Validate ensures the app's configuration is valid.
|
||||||
func (app *App) Validate() error {
|
func (app *App) Validate() error {
|
||||||
isGo120 := strings.Contains(runtime.Version(), "go1.20")
|
|
||||||
|
|
||||||
// each server must use distinct listener addresses
|
// each server must use distinct listener addresses
|
||||||
lnAddrs := make(map[string]string)
|
lnAddrs := make(map[string]string)
|
||||||
for srvName, srv := range app.Servers {
|
for srvName, srv := range app.Servers {
|
||||||
if isGo120 && srv.EnableFullDuplex {
|
|
||||||
app.logger.Warn("enable_full_duplex is not supported in Go 1.20, use a build made with Go 1.21 or later", zap.String("server", srvName))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range srv.Listen {
|
for _, addr := range srv.Listen {
|
||||||
listenAddr, err := caddy.ParseNetworkAddress(addr)
|
listenAddr, err := caddy.ParseNetworkAddress(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -373,14 +342,6 @@ func (app *App) Start() error {
|
|||||||
MaxHeaderBytes: srv.MaxHeaderBytes,
|
MaxHeaderBytes: srv.MaxHeaderBytes,
|
||||||
Handler: srv,
|
Handler: srv,
|
||||||
ErrorLog: serverLogger,
|
ErrorLog: serverLogger,
|
||||||
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
|
|
||||||
return context.WithValue(ctx, ConnCtxKey, c)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
h2server := &http2.Server{
|
|
||||||
NewWriteScheduler: func() http2.WriteScheduler {
|
|
||||||
return http2.NewPriorityWriteScheduler(nil)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// disable HTTP/2, which we enabled by default during provisioning
|
// disable HTTP/2, which we enabled by default during provisioning
|
||||||
@@ -402,9 +363,6 @@ func (app *App) Start() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
//nolint:errcheck
|
|
||||||
http2.ConfigureServer(srv.server, h2server)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this TLS config is used by the std lib to choose the actual TLS config for connections
|
// this TLS config is used by the std lib to choose the actual TLS config for connections
|
||||||
@@ -414,6 +372,9 @@ func (app *App) Start() error {
|
|||||||
|
|
||||||
// enable H2C if configured
|
// enable H2C if configured
|
||||||
if srv.protocol("h2c") {
|
if srv.protocol("h2c") {
|
||||||
|
h2server := &http2.Server{
|
||||||
|
IdleTimeout: time.Duration(srv.IdleTimeout),
|
||||||
|
}
|
||||||
srv.server.Handler = h2c.NewHandler(srv, h2server)
|
srv.server.Handler = h2c.NewHandler(srv, h2server)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,17 +441,6 @@ func (app *App) Start() error {
|
|||||||
ln = srv.listenerWrappers[i].WrapListener(ln)
|
ln = srv.listenerWrappers[i].WrapListener(ln)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle http2 if use tls listener wrapper
|
|
||||||
if useTLS {
|
|
||||||
http2lnWrapper := &http2Listener{
|
|
||||||
Listener: ln,
|
|
||||||
server: srv.server,
|
|
||||||
h2server: h2server,
|
|
||||||
}
|
|
||||||
srv.h2listeners = append(srv.h2listeners, http2lnWrapper)
|
|
||||||
ln = http2lnWrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
// if binding to port 0, the OS chooses a port for us;
|
// if binding to port 0, the OS chooses a port for us;
|
||||||
// but the user won't know the port unless we print it
|
// but the user won't know the port unless we print it
|
||||||
if !listenAddr.IsUnixNetwork() && listenAddr.StartPort == 0 && listenAddr.EndPort == 0 {
|
if !listenAddr.IsUnixNetwork() && listenAddr.StartPort == 0 && listenAddr.EndPort == 0 {
|
||||||
@@ -557,7 +507,7 @@ func (app *App) Stop() error {
|
|||||||
|
|
||||||
// honor scheduled/delayed shutdown time
|
// honor scheduled/delayed shutdown time
|
||||||
if delay {
|
if delay {
|
||||||
app.logger.Info("shutdown scheduled",
|
app.logger.Debug("shutdown scheduled",
|
||||||
zap.Duration("delay_duration", time.Duration(app.ShutdownDelay)),
|
zap.Duration("delay_duration", time.Duration(app.ShutdownDelay)),
|
||||||
zap.Time("time", scheduledTime))
|
zap.Time("time", scheduledTime))
|
||||||
time.Sleep(time.Duration(app.ShutdownDelay))
|
time.Sleep(time.Duration(app.ShutdownDelay))
|
||||||
@@ -568,9 +518,9 @@ func (app *App) Stop() error {
|
|||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(app.GracePeriod))
|
ctx, cancel = context.WithTimeout(ctx, time.Duration(app.GracePeriod))
|
||||||
defer cancel()
|
defer cancel()
|
||||||
app.logger.Info("servers shutting down; grace period initiated", zap.Duration("duration", time.Duration(app.GracePeriod)))
|
app.logger.Debug("servers shutting down; grace period initiated", zap.Duration("duration", time.Duration(app.GracePeriod)))
|
||||||
} else {
|
} else {
|
||||||
app.logger.Info("servers shutting down with eternal grace period")
|
app.logger.Debug("servers shutting down with eternal grace period")
|
||||||
}
|
}
|
||||||
|
|
||||||
// goroutines aren't guaranteed to be scheduled right away,
|
// goroutines aren't guaranteed to be scheduled right away,
|
||||||
@@ -602,24 +552,9 @@ func (app *App) Stop() error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// First close h3server then close listeners unlike stdlib for several reasons:
|
|
||||||
// 1, udp has only a single socket, once closed, no more data can be read and
|
|
||||||
// written. In contrast, closing tcp listeners won't affect established connections.
|
|
||||||
// This have something to do with graceful shutdown when upstream implements it.
|
|
||||||
// 2, h3server will only close listeners it's registered (quic listeners). Closing
|
|
||||||
// listener first and these listeners maybe unregistered thus won't be closed. caddy
|
|
||||||
// distinguishes quic-listener and underlying datagram sockets.
|
|
||||||
|
|
||||||
// TODO: CloseGracefully, once implemented upstream (see https://github.com/quic-go/quic-go/issues/2103)
|
|
||||||
if err := server.h3server.Close(); err != nil {
|
|
||||||
app.logger.Error("HTTP/3 server shutdown",
|
|
||||||
zap.Error(err),
|
|
||||||
zap.Strings("addresses", server.Listen))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: we have to manually close our listeners because quic-go won't
|
// TODO: we have to manually close our listeners because quic-go won't
|
||||||
// close listeners it didn't create along with the server itself...
|
// close listeners it didn't create along with the server itself...
|
||||||
// see https://github.com/quic-go/quic-go/issues/3560
|
// see https://github.com/lucas-clemente/quic-go/issues/3560
|
||||||
for _, el := range server.h3listeners {
|
for _, el := range server.h3listeners {
|
||||||
if err := el.Close(); err != nil {
|
if err := el.Close(); err != nil {
|
||||||
app.logger.Error("HTTP/3 listener close",
|
app.logger.Error("HTTP/3 listener close",
|
||||||
@@ -627,26 +562,20 @@ func (app *App) Stop() error {
|
|||||||
zap.String("address", el.LocalAddr().String()))
|
zap.String("address", el.LocalAddr().String()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
stopH2Listener := func(server *Server) {
|
|
||||||
defer finishedShutdown.Done()
|
|
||||||
startedShutdown.Done()
|
|
||||||
|
|
||||||
for i, s := range server.h2listeners {
|
// TODO: CloseGracefully, once implemented upstream (see https://github.com/lucas-clemente/quic-go/issues/2103)
|
||||||
if err := s.Shutdown(ctx); err != nil {
|
if err := server.h3server.Close(); err != nil {
|
||||||
app.logger.Error("http2 listener shutdown",
|
app.logger.Error("HTTP/3 server shutdown",
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Int("index", i))
|
zap.Strings("addresses", server.Listen))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, server := range app.Servers {
|
for _, server := range app.Servers {
|
||||||
startedShutdown.Add(3)
|
startedShutdown.Add(2)
|
||||||
finishedShutdown.Add(3)
|
finishedShutdown.Add(2)
|
||||||
go stopServer(server)
|
go stopServer(server)
|
||||||
go stopH3Server(server)
|
go stopH3Server(server)
|
||||||
go stopH2Listener(server)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// block until all the goroutines have been run by the scheduler;
|
// block until all the goroutines have been run by the scheduler;
|
||||||
|
|||||||
@@ -83,8 +83,6 @@ func (ahc AutoHTTPSConfig) Skipped(name string, skipSlice []string) bool {
|
|||||||
// even servers to the app, which still need to be set up with the
|
// even servers to the app, which still need to be set up with the
|
||||||
// rest of them during provisioning.
|
// rest of them during provisioning.
|
||||||
func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) error {
|
func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) error {
|
||||||
logger := app.logger.Named("auto_https")
|
|
||||||
|
|
||||||
// this map acts as a set to store the domain names
|
// this map acts as a set to store the domain names
|
||||||
// for which we will manage certificates automatically
|
// for which we will manage certificates automatically
|
||||||
uniqueDomainsForCerts := make(map[string]struct{})
|
uniqueDomainsForCerts := make(map[string]struct{})
|
||||||
@@ -116,13 +114,13 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||||||
srv.AutoHTTPS = new(AutoHTTPSConfig)
|
srv.AutoHTTPS = new(AutoHTTPSConfig)
|
||||||
}
|
}
|
||||||
if srv.AutoHTTPS.Disabled {
|
if srv.AutoHTTPS.Disabled {
|
||||||
logger.Warn("automatic HTTPS is completely disabled for server", zap.String("server_name", srvName))
|
app.logger.Warn("automatic HTTPS is completely disabled for server", zap.String("server_name", srvName))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip if all listeners use the HTTP port
|
// skip if all listeners use the HTTP port
|
||||||
if !srv.listenersUseAnyPortOtherThan(app.httpPort()) {
|
if !srv.listenersUseAnyPortOtherThan(app.httpPort()) {
|
||||||
logger.Warn("server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server",
|
app.logger.Warn("server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server",
|
||||||
zap.String("server_name", srvName),
|
zap.String("server_name", srvName),
|
||||||
zap.Int("http_port", app.httpPort()),
|
zap.Int("http_port", app.httpPort()),
|
||||||
)
|
)
|
||||||
@@ -136,7 +134,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||||||
// needing to specify one empty policy to enable it
|
// needing to specify one empty policy to enable it
|
||||||
if srv.TLSConnPolicies == nil &&
|
if srv.TLSConnPolicies == nil &&
|
||||||
!srv.listenersUseAnyPortOtherThan(app.httpsPort()) {
|
!srv.listenersUseAnyPortOtherThan(app.httpsPort()) {
|
||||||
logger.Info("server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS",
|
app.logger.Info("server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS",
|
||||||
zap.String("server_name", srvName),
|
zap.String("server_name", srvName),
|
||||||
zap.Int("https_port", app.httpsPort()),
|
zap.Int("https_port", app.httpsPort()),
|
||||||
)
|
)
|
||||||
@@ -188,16 +186,22 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||||||
// a deduplicated list of names for which to obtain certs
|
// a deduplicated list of names for which to obtain certs
|
||||||
// (only if cert management not disabled for this server)
|
// (only if cert management not disabled for this server)
|
||||||
if srv.AutoHTTPS.DisableCerts {
|
if srv.AutoHTTPS.DisableCerts {
|
||||||
logger.Warn("skipping automated certificate management for server because it is disabled", zap.String("server_name", srvName))
|
app.logger.Warn("skipping automated certificate management for server because it is disabled", zap.String("server_name", srvName))
|
||||||
} else {
|
} else {
|
||||||
for d := range serverDomainSet {
|
for d := range serverDomainSet {
|
||||||
|
// the implicit Tailscale manager module will get its own certs at run-time
|
||||||
|
if isTailscaleDomain(d) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if certmagic.SubjectQualifiesForCert(d) &&
|
if certmagic.SubjectQualifiesForCert(d) &&
|
||||||
!srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.SkipCerts) {
|
!srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.SkipCerts) {
|
||||||
// if a certificate for this name is already loaded,
|
// if a certificate for this name is already loaded,
|
||||||
// don't obtain another one for it, unless we are
|
// don't obtain another one for it, unless we are
|
||||||
// supposed to ignore loaded certificates
|
// supposed to ignore loaded certificates
|
||||||
if !srv.AutoHTTPS.IgnoreLoadedCerts && app.tlsApp.HasCertificateForSubject(d) {
|
if !srv.AutoHTTPS.IgnoreLoadedCerts &&
|
||||||
logger.Info("skipping automatic certificate management because one or more matching certificates are already loaded",
|
len(app.tlsApp.AllMatchingCertificates(d)) > 0 {
|
||||||
|
app.logger.Info("skipping automatic certificate management because one or more matching certificates are already loaded",
|
||||||
zap.String("domain", d),
|
zap.String("domain", d),
|
||||||
zap.String("server_name", srvName),
|
zap.String("server_name", srvName),
|
||||||
)
|
)
|
||||||
@@ -208,7 +212,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||||||
// can handle that, but as a courtesy, warn the user
|
// can handle that, but as a courtesy, warn the user
|
||||||
if strings.Contains(d, "*") &&
|
if strings.Contains(d, "*") &&
|
||||||
strings.Count(strings.Trim(d, "."), ".") == 1 {
|
strings.Count(strings.Trim(d, "."), ".") == 1 {
|
||||||
logger.Warn("most clients do not trust second-level wildcard certificates (*.tld)",
|
app.logger.Warn("most clients do not trust second-level wildcard certificates (*.tld)",
|
||||||
zap.String("domain", d))
|
zap.String("domain", d))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,11 +228,11 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||||||
|
|
||||||
// nothing left to do if auto redirects are disabled
|
// nothing left to do if auto redirects are disabled
|
||||||
if srv.AutoHTTPS.DisableRedir {
|
if srv.AutoHTTPS.DisableRedir {
|
||||||
logger.Warn("automatic HTTP->HTTPS redirects are disabled", zap.String("server_name", srvName))
|
app.logger.Warn("automatic HTTP->HTTPS redirects are disabled", zap.String("server_name", srvName))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("enabling automatic HTTP->HTTPS redirects", zap.String("server_name", srvName))
|
app.logger.Info("enabling automatic HTTP->HTTPS redirects", zap.String("server_name", srvName))
|
||||||
|
|
||||||
// create HTTP->HTTPS redirects
|
// create HTTP->HTTPS redirects
|
||||||
for _, listenAddr := range srv.Listen {
|
for _, listenAddr := range srv.Listen {
|
||||||
@@ -268,15 +272,12 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||||||
// we now have a list of all the unique names for which we need certs;
|
// we now have a list of all the unique names for which we need certs;
|
||||||
// turn the set into a slice so that phase 2 can use it
|
// turn the set into a slice so that phase 2 can use it
|
||||||
app.allCertDomains = make([]string, 0, len(uniqueDomainsForCerts))
|
app.allCertDomains = make([]string, 0, len(uniqueDomainsForCerts))
|
||||||
var internal, tailscale []string
|
var internal []string
|
||||||
uniqueDomainsLoop:
|
uniqueDomainsLoop:
|
||||||
for d := range uniqueDomainsForCerts {
|
for d := range uniqueDomainsForCerts {
|
||||||
if !isTailscaleDomain(d) {
|
// whether or not there is already an automation policy for this
|
||||||
// whether or not there is already an automation policy for this
|
// name, we should add it to the list to manage a cert for it
|
||||||
// name, we should add it to the list to manage a cert for it,
|
app.allCertDomains = append(app.allCertDomains, d)
|
||||||
// unless it's a Tailscale domain, because we don't manage those
|
|
||||||
app.allCertDomains = append(app.allCertDomains, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// some names we've found might already have automation policies
|
// some names we've found might already have automation policies
|
||||||
// explicitly specified for them; we should exclude those from
|
// explicitly specified for them; we should exclude those from
|
||||||
@@ -284,7 +285,7 @@ uniqueDomainsLoop:
|
|||||||
// one automation policy would be confusing and an error
|
// one automation policy would be confusing and an error
|
||||||
if app.tlsApp.Automation != nil {
|
if app.tlsApp.Automation != nil {
|
||||||
for _, ap := range app.tlsApp.Automation.Policies {
|
for _, ap := range app.tlsApp.Automation.Policies {
|
||||||
for _, apHost := range ap.Subjects() {
|
for _, apHost := range ap.Subjects {
|
||||||
if apHost == d {
|
if apHost == d {
|
||||||
continue uniqueDomainsLoop
|
continue uniqueDomainsLoop
|
||||||
}
|
}
|
||||||
@@ -294,15 +295,13 @@ uniqueDomainsLoop:
|
|||||||
|
|
||||||
// if no automation policy exists for the name yet, we
|
// if no automation policy exists for the name yet, we
|
||||||
// will associate it with an implicit one
|
// will associate it with an implicit one
|
||||||
if isTailscaleDomain(d) {
|
if !certmagic.SubjectQualifiesForPublicCert(d) {
|
||||||
tailscale = append(tailscale, d)
|
|
||||||
} else if !certmagic.SubjectQualifiesForPublicCert(d) {
|
|
||||||
internal = append(internal, d)
|
internal = append(internal, d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure there is an automation policy to handle these certs
|
// ensure there is an automation policy to handle these certs
|
||||||
err := app.createAutomationPolicies(ctx, internal, tailscale)
|
err := app.createAutomationPolicies(ctx, internal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -425,10 +424,6 @@ redirServersLoop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug("adjusted config",
|
|
||||||
zap.Reflect("tls", app.tlsApp),
|
|
||||||
zap.Reflect("http", app))
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,7 +466,7 @@ func (app *App) makeRedirRoute(redirToPort uint, matcherSet MatcherSet) Route {
|
|||||||
// automation policy exists, it will be shallow-copied and used as the
|
// automation policy exists, it will be shallow-copied and used as the
|
||||||
// base for the new ones (this is important for preserving behavior the
|
// base for the new ones (this is important for preserving behavior the
|
||||||
// user intends to be "defaults").
|
// user intends to be "defaults").
|
||||||
func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames, tailscaleNames []string) error {
|
func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames []string) error {
|
||||||
// before we begin, loop through the existing automation policies
|
// before we begin, loop through the existing automation policies
|
||||||
// and, for any ACMEIssuers we find, make sure they're filled in
|
// and, for any ACMEIssuers we find, make sure they're filled in
|
||||||
// with default values that might be specified in our HTTP app; also
|
// with default values that might be specified in our HTTP app; also
|
||||||
@@ -485,22 +480,6 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames, tails
|
|||||||
app.tlsApp.Automation = new(caddytls.AutomationConfig)
|
app.tlsApp.Automation = new(caddytls.AutomationConfig)
|
||||||
}
|
}
|
||||||
for _, ap := range app.tlsApp.Automation.Policies {
|
for _, ap := range app.tlsApp.Automation.Policies {
|
||||||
// on-demand policies can have the tailscale manager added implicitly
|
|
||||||
// if there's no explicit manager configured -- for convenience
|
|
||||||
if ap.OnDemand && len(ap.Managers) == 0 {
|
|
||||||
var ts caddytls.Tailscale
|
|
||||||
if err := ts.Provision(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ap.Managers = []certmagic.Manager{ts}
|
|
||||||
|
|
||||||
// must reprovision the automation policy so that the underlying
|
|
||||||
// CertMagic config knows about the updated Managers
|
|
||||||
if err := ap.Provision(app.tlsApp); err != nil {
|
|
||||||
return fmt.Errorf("re-provisioning automation policy: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up default issuer -- honestly, this is only
|
// set up default issuer -- honestly, this is only
|
||||||
// really necessary because the HTTP app is opinionated
|
// really necessary because the HTTP app is opinionated
|
||||||
// and has settings which could be inferred as new
|
// and has settings which could be inferred as new
|
||||||
@@ -522,8 +501,24 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames, tails
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if no external managers were configured, enable
|
||||||
|
// implicit Tailscale support for convenience
|
||||||
|
if ap.Managers == nil {
|
||||||
|
ts, err := implicitTailscale(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ap.Managers = []certmagic.Manager{ts}
|
||||||
|
|
||||||
|
// must reprovision the automation policy so that the underlying
|
||||||
|
// CertMagic config knows about the updated Managers
|
||||||
|
if err := ap.Provision(app.tlsApp); err != nil {
|
||||||
|
return fmt.Errorf("re-provisioning automation policy: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// while we're here, is this the catch-all/base policy?
|
// while we're here, is this the catch-all/base policy?
|
||||||
if !foundBasePolicy && len(ap.SubjectsRaw) == 0 {
|
if !foundBasePolicy && len(ap.Subjects) == 0 {
|
||||||
basePolicy = ap
|
basePolicy = ap
|
||||||
foundBasePolicy = true
|
foundBasePolicy = true
|
||||||
}
|
}
|
||||||
@@ -534,6 +529,15 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames, tails
|
|||||||
basePolicy = new(caddytls.AutomationPolicy)
|
basePolicy = new(caddytls.AutomationPolicy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if basePolicy.Managers == nil {
|
||||||
|
// add implicit Tailscale integration, for harmless convenience
|
||||||
|
ts, err := implicitTailscale(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
basePolicy.Managers = []certmagic.Manager{ts}
|
||||||
|
}
|
||||||
|
|
||||||
// if the basePolicy has an existing ACMEIssuer (particularly to
|
// if the basePolicy has an existing ACMEIssuer (particularly to
|
||||||
// include any type that embeds/wraps an ACMEIssuer), let's use it
|
// include any type that embeds/wraps an ACMEIssuer), let's use it
|
||||||
// (I guess we just use the first one?), otherwise we'll make one
|
// (I guess we just use the first one?), otherwise we'll make one
|
||||||
@@ -630,7 +634,7 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames, tails
|
|||||||
// rather they just want to change the CA for the set
|
// rather they just want to change the CA for the set
|
||||||
// of names that would normally use the production API;
|
// of names that would normally use the production API;
|
||||||
// anyway, that gets into the weeds a bit...
|
// anyway, that gets into the weeds a bit...
|
||||||
newPolicy.SubjectsRaw = internalNames
|
newPolicy.Subjects = internalNames
|
||||||
newPolicy.Issuers = []certmagic.Issuer{internalIssuer}
|
newPolicy.Issuers = []certmagic.Issuer{internalIssuer}
|
||||||
err := app.tlsApp.AddAutomationPolicy(newPolicy)
|
err := app.tlsApp.AddAutomationPolicy(newPolicy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -638,27 +642,6 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames, tails
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// tailscale names go in their own automation policies because
|
|
||||||
// they require on-demand TLS to be enabled, which we obviously
|
|
||||||
// can't enable for everything
|
|
||||||
if len(tailscaleNames) > 0 {
|
|
||||||
policyCopy := *basePolicy
|
|
||||||
newPolicy := &policyCopy
|
|
||||||
|
|
||||||
var ts caddytls.Tailscale
|
|
||||||
if err := ts.Provision(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
newPolicy.SubjectsRaw = tailscaleNames
|
|
||||||
newPolicy.Issuers = nil
|
|
||||||
newPolicy.Managers = append(newPolicy.Managers, ts)
|
|
||||||
err := app.tlsApp.AddAutomationPolicy(newPolicy)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// we just changed a lot of stuff, so double-check that it's all good
|
// we just changed a lot of stuff, so double-check that it's all good
|
||||||
err := app.tlsApp.Validate()
|
err := app.tlsApp.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -737,6 +720,13 @@ func (app *App) automaticHTTPSPhase2() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// implicitTailscale returns a new and provisioned Tailscale module configured to be optional.
|
||||||
|
func implicitTailscale(ctx caddy.Context) (caddytls.Tailscale, error) {
|
||||||
|
ts := caddytls.Tailscale{Optional: true}
|
||||||
|
err := ts.Provision(ctx)
|
||||||
|
return ts, err
|
||||||
|
}
|
||||||
|
|
||||||
func isTailscaleDomain(name string) bool {
|
func isTailscaleDomain(name string) bool {
|
||||||
return strings.HasSuffix(strings.ToLower(name), ".ts.net")
|
return strings.HasSuffix(strings.ToLower(name), ".ts.net")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,13 +23,15 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"golang.org/x/sync/singleflight"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(HTTPBasicAuth{})
|
caddy.RegisterModule(HTTPBasicAuth{})
|
||||||
|
|
||||||
|
weakrand.Seed(time.Now().UnixNano())
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPBasicAuth facilitates HTTP basic authentication.
|
// HTTPBasicAuth facilitates HTTP basic authentication.
|
||||||
@@ -140,7 +142,6 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
|
|||||||
if hba.HashCache != nil {
|
if hba.HashCache != nil {
|
||||||
hba.HashCache.cache = make(map[string]bool)
|
hba.HashCache.cache = make(map[string]bool)
|
||||||
hba.HashCache.mu = new(sync.RWMutex)
|
hba.HashCache.mu = new(sync.RWMutex)
|
||||||
hba.HashCache.g = new(singleflight.Group)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -189,18 +190,12 @@ func (hba HTTPBasicAuth) correctPassword(account Account, plaintextPassword []by
|
|||||||
if ok {
|
if ok {
|
||||||
return same, nil
|
return same, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// slow track: do the expensive op, then add it to the cache
|
// slow track: do the expensive op, then add it to the cache
|
||||||
// but perform it in a singleflight group so that multiple
|
same, err := compare()
|
||||||
// parallel requests using the same password don't cause a
|
|
||||||
// thundering herd problem by all performing the same hashing
|
|
||||||
// operation before the first one finishes and caches it.
|
|
||||||
v, err, _ := hba.HashCache.g.Do(cacheKey, func() (any, error) {
|
|
||||||
return compare()
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
same = v.(bool)
|
|
||||||
hba.HashCache.mu.Lock()
|
hba.HashCache.mu.Lock()
|
||||||
if len(hba.HashCache.cache) >= 1000 {
|
if len(hba.HashCache.cache) >= 1000 {
|
||||||
hba.HashCache.makeRoom() // keep cache size under control
|
hba.HashCache.makeRoom() // keep cache size under control
|
||||||
@@ -228,7 +223,6 @@ func (hba HTTPBasicAuth) promptForCredentials(w http.ResponseWriter, err error)
|
|||||||
// compute on every HTTP request.
|
// compute on every HTTP request.
|
||||||
type Cache struct {
|
type Cache struct {
|
||||||
mu *sync.RWMutex
|
mu *sync.RWMutex
|
||||||
g *singleflight.Group
|
|
||||||
|
|
||||||
// map of concatenated hashed password + plaintext password + salt, to result
|
// map of concatenated hashed password + plaintext password + salt, to result
|
||||||
cache map[string]bool
|
cache map[string]bool
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ func init() {
|
|||||||
|
|
||||||
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
||||||
//
|
//
|
||||||
// basicauth [<matcher>] [<hash_algorithm> [<realm>]] {
|
// basicauth [<matcher>] [<hash_algorithm> [<realm>]] {
|
||||||
// <username> <hashed_password_base64> [<salt_base64>]
|
// <username> <hashed_password_base64> [<salt_base64>]
|
||||||
// ...
|
// ...
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// If no hash algorithm is supplied, bcrypt will be assumed.
|
// If no hash algorithm is supplied, bcrypt will be assumed.
|
||||||
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||||
|
|||||||
@@ -18,19 +18,20 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddycmd.RegisterCommand(caddycmd.Command{
|
caddycmd.RegisterCommand(caddycmd.Command{
|
||||||
Name: "hash-password",
|
Name: "hash-password",
|
||||||
|
Func: cmdHashPassword,
|
||||||
Usage: "[--algorithm <name>] [--salt <string>] [--plaintext <password>]",
|
Usage: "[--algorithm <name>] [--salt <string>] [--plaintext <password>]",
|
||||||
Short: "Hashes a password and writes base64",
|
Short: "Hashes a password and writes base64",
|
||||||
Long: `
|
Long: `
|
||||||
@@ -49,12 +50,13 @@ be provided (scrypt).
|
|||||||
|
|
||||||
Note that scrypt is deprecated. Please use 'bcrypt' instead.
|
Note that scrypt is deprecated. Please use 'bcrypt' instead.
|
||||||
`,
|
`,
|
||||||
CobraFunc: func(cmd *cobra.Command) {
|
Flags: func() *flag.FlagSet {
|
||||||
cmd.Flags().StringP("plaintext", "p", "", "The plaintext password")
|
fs := flag.NewFlagSet("hash-password", flag.ExitOnError)
|
||||||
cmd.Flags().StringP("salt", "s", "", "The password salt")
|
fs.String("algorithm", "bcrypt", "Name of the hash algorithm")
|
||||||
cmd.Flags().StringP("algorithm", "a", "bcrypt", "Name of the hash algorithm")
|
fs.String("plaintext", "", "The plaintext password")
|
||||||
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdHashPassword)
|
fs.String("salt", "", "The password salt")
|
||||||
},
|
return fs
|
||||||
|
}(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ func SanitizedPathJoin(root, reqPath string) string {
|
|||||||
root = "."
|
root = "."
|
||||||
}
|
}
|
||||||
|
|
||||||
path := filepath.Join(root, path.Clean("/"+reqPath))
|
path := filepath.Join(root, filepath.Clean("/"+reqPath))
|
||||||
|
|
||||||
// filepath.Join also cleans the path, and cleaning strips
|
// filepath.Join also cleans the path, and cleaning strips
|
||||||
// the trailing slash, so we need to re-add it afterwards.
|
// the trailing slash, so we need to re-add it afterwards.
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ func TestSanitizedPathJoin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
actual := SanitizedPathJoin(tc.inputRoot, u.Path)
|
actual := SanitizedPathJoin(tc.inputRoot, u.Path)
|
||||||
if actual != tc.expect {
|
if actual != tc.expect {
|
||||||
t.Errorf("Test %d: SanitizedPathJoin('%s', '%s') => '%s' (expected '%s')",
|
t.Errorf("Test %d: SanitizedPathJoin('%s', '%s') => %s (expected '%s')",
|
||||||
i, tc.inputRoot, tc.inputPath, actual, tc.expect)
|
i, tc.inputRoot, tc.inputPath, actual, tc.expect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error {
|
|||||||
|
|
||||||
// create the CEL environment
|
// create the CEL environment
|
||||||
env, err := cel.NewEnv(
|
env, err := cel.NewEnv(
|
||||||
cel.Function(placeholderFuncName, cel.SingletonBinaryBinding(m.caddyPlaceholderFunc), cel.Overload(
|
cel.Function(placeholderFuncName, cel.SingletonBinaryImpl(m.caddyPlaceholderFunc), cel.Overload(
|
||||||
placeholderFuncName+"_httpRequest_string",
|
placeholderFuncName+"_httpRequest_string",
|
||||||
[]*cel.Type{httpRequestObjectType, cel.StringType},
|
[]*cel.Type{httpRequestObjectType, cel.StringType},
|
||||||
cel.AnyType,
|
cel.AnyType,
|
||||||
@@ -191,17 +191,15 @@ func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
|
|||||||
celReq, ok := lhs.(celHTTPRequest)
|
celReq, ok := lhs.(celHTTPRequest)
|
||||||
if !ok {
|
if !ok {
|
||||||
return types.NewErr(
|
return types.NewErr(
|
||||||
"invalid request of type '%v' to %s(request, placeholderVarName)",
|
"invalid request of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)",
|
||||||
lhs.Type(),
|
lhs.Type(),
|
||||||
placeholderFuncName,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
phStr, ok := rhs.(types.String)
|
phStr, ok := rhs.(types.String)
|
||||||
if !ok {
|
if !ok {
|
||||||
return types.NewErr(
|
return types.NewErr(
|
||||||
"invalid placeholder variable name of type '%v' to %s(request, placeholderVarName)",
|
"invalid placeholder variable name of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)",
|
||||||
rhs.Type(),
|
rhs.Type(),
|
||||||
placeholderFuncName,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,10 +253,7 @@ type celPkixName struct{ *pkix.Name }
|
|||||||
func (pn celPkixName) ConvertToNative(typeDesc reflect.Type) (any, error) {
|
func (pn celPkixName) ConvertToNative(typeDesc reflect.Type) (any, error) {
|
||||||
return pn.Name, nil
|
return pn.Name, nil
|
||||||
}
|
}
|
||||||
func (pn celPkixName) ConvertToType(typeVal ref.Type) ref.Val {
|
func (celPkixName) ConvertToType(typeVal ref.Type) ref.Val {
|
||||||
if typeVal.TypeName() == "string" {
|
|
||||||
return types.String(pn.Name.String())
|
|
||||||
}
|
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
func (pn celPkixName) Equal(other ref.Val) ref.Val {
|
func (pn celPkixName) Equal(other ref.Val) ref.Val {
|
||||||
@@ -350,7 +345,7 @@ func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fa
|
|||||||
cel.Macros(macro),
|
cel.Macros(macro),
|
||||||
cel.Function(funcName,
|
cel.Function(funcName,
|
||||||
cel.Overload(funcName, append([]*cel.Type{requestType}, matcherDataTypes...), cel.BoolType),
|
cel.Overload(funcName, append([]*cel.Type{requestType}, matcherDataTypes...), cel.BoolType),
|
||||||
cel.SingletonBinaryBinding(CELMatcherRuntimeFunction(funcName, fac))),
|
cel.SingletonBinaryImpl(CELMatcherRuntimeFunction(funcName, fac))),
|
||||||
}
|
}
|
||||||
programOptions := []cel.ProgramOption{
|
programOptions := []cel.ProgramOption{
|
||||||
cel.CustomDecorator(CELMatcherDecorator(funcName, fac)),
|
cel.CustomDecorator(CELMatcherDecorator(funcName, fac)),
|
||||||
@@ -496,7 +491,7 @@ func celMatcherStringMacroExpander(funcName string) parser.MacroExpander {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// celMatcherJSONMacroExpander validates that the macro is called a single
|
// celMatcherStringMacroExpander validates that the macro is called a single
|
||||||
// map literal argument.
|
// map literal argument.
|
||||||
//
|
//
|
||||||
// The following function call is returned: <funcName>(request, arg)
|
// The following function call is returned: <funcName>(request, arg)
|
||||||
|
|||||||
@@ -1,25 +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.
|
|
||||||
|
|
||||||
//go:build !go1.21
|
|
||||||
|
|
||||||
package caddyhttp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func enableFullDuplex(w http.ResponseWriter) {
|
|
||||||
// Do nothing, Go 1.20 and earlier do not support full duplex
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user