mirror of
https://github.com/caddyserver/caddy.git
synced 2026-05-25 16:22:36 -04:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5968ebd0f4 | |||
| a5f4fae145 |
@@ -1,5 +0,0 @@
|
|||||||
[*]
|
|
||||||
end_of_line = lf
|
|
||||||
|
|
||||||
[caddytest/integration/caddyfile_adapt/*.txt]
|
|
||||||
indent_style = tab
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
*.go text eol=lf
|
|
||||||
+1
-1
@@ -48,7 +48,7 @@ We consider publicly-registered domain names to be public information. This nece
|
|||||||
|
|
||||||
It will speed things up if you suggest a working patch, such as a code diff, and explain why and how it works. Reports that are not actionable, do not contain enough information, are too pushy/demanding, or are not able to convince us that it is a viable and practical attack on the web server itself may be deferred to a later time or possibly ignored, depending on available resources. Priority will be given to credible, responsible reports that are constructive, specific, and actionable. (We get a lot of invalid reports.) Thank you for understanding.
|
It will speed things up if you suggest a working patch, such as a code diff, and explain why and how it works. Reports that are not actionable, do not contain enough information, are too pushy/demanding, or are not able to convince us that it is a viable and practical attack on the web server itself may be deferred to a later time or possibly ignored, depending on available resources. Priority will be given to credible, responsible reports that are constructive, specific, and actionable. (We get a lot of invalid reports.) Thank you for understanding.
|
||||||
|
|
||||||
When you are ready, please email Matt Holt (the author) directly: matt at dyanim dot com.
|
When you are ready, please email Matt Holt (the author) directly: matt [at] lightcodelabs [dot com].
|
||||||
|
|
||||||
Please don't encrypt the email body. It only makes the process more complicated.
|
Please don't encrypt the email body. It only makes the process more complicated.
|
||||||
|
|
||||||
|
|||||||
@@ -19,20 +19,12 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ ubuntu-latest, macos-latest, windows-latest ]
|
os: [ ubuntu-latest, macos-latest, windows-latest ]
|
||||||
go: [ '1.18', '1.19' ]
|
go: [ '1.16', '1.17' ]
|
||||||
|
|
||||||
include:
|
|
||||||
# Set the minimum Go patch version for the given Go minor
|
|
||||||
# Usable via ${{ matrix.GO_SEMVER }}
|
|
||||||
- go: '1.18'
|
|
||||||
GO_SEMVER: '~1.18.4'
|
|
||||||
|
|
||||||
- go: '1.19'
|
|
||||||
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
|
||||||
# SUCCESS: the typical value for $? per OS (Windows/pwsh returns 'True')
|
# SUCCESS: the typical value for $? per OS (Windows/pwsh returns 'True')
|
||||||
|
include:
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
CADDY_BIN_PATH: ./cmd/caddy/caddy
|
CADDY_BIN_PATH: ./cmd/caddy/caddy
|
||||||
SUCCESS: 0
|
SUCCESS: 0
|
||||||
@@ -49,13 +41,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.GO_SEMVER }}
|
go-version: ${{ matrix.go }}
|
||||||
check-latest: true
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
# 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
|
||||||
@@ -78,20 +69,12 @@ jobs:
|
|||||||
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 "::set-output name=short_sha::$(git rev-parse --short HEAD)"
|
echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
|
||||||
|
echo "::set-output name=go_cache::$(go env GOCACHE)"
|
||||||
|
|
||||||
- name: Cache the build cache
|
- name: Cache the build cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
# In order:
|
path: ${{ steps.vars.outputs.go_cache }}
|
||||||
# * 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') }}
|
key: ${{ runner.os }}-${{ matrix.go }}-go-ci-${{ hashFiles('**/go.sum') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-${{ matrix.go }}-go-ci
|
${{ runner.os }}-${{ matrix.go }}-go-ci
|
||||||
@@ -147,7 +130,7 @@ jobs:
|
|||||||
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 into the Go module directory
|
- name: Checkout code into the Go module directory
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/.ssh && echo -e "${SSH_KEY//_/\\n}" > ~/.ssh/id_ecdsa && chmod og-rwx ~/.ssh/id_ecdsa
|
mkdir -p ~/.ssh && echo -e "${SSH_KEY//_/\\n}" > ~/.ssh/id_ecdsa && chmod og-rwx ~/.ssh/id_ecdsa
|
||||||
@@ -172,7 +155,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- uses: goreleaser/goreleaser-action@v2
|
- uses: goreleaser/goreleaser-action@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -16,22 +16,14 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
goos: ['android', 'linux', 'solaris', 'illumos', 'dragonfly', 'freebsd', 'openbsd', 'plan9', 'windows', 'darwin', 'netbsd']
|
goos: ['android', 'linux', 'solaris', 'illumos', 'dragonfly', 'freebsd', 'openbsd', 'plan9', 'windows', 'darwin', 'netbsd']
|
||||||
go: [ '1.19' ]
|
go: [ '1.17' ]
|
||||||
|
|
||||||
include:
|
|
||||||
# Set the minimum Go patch version for the given Go minor
|
|
||||||
# Usable via ${{ matrix.GO_SEMVER }}
|
|
||||||
- go: '1.19'
|
|
||||||
GO_SEMVER: '~1.19.0'
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.GO_SEMVER }}
|
go-version: ${{ matrix.go }}
|
||||||
check-latest: true
|
|
||||||
|
|
||||||
- name: Print Go version and environment
|
- name: Print Go version and environment
|
||||||
id: vars
|
id: vars
|
||||||
@@ -42,22 +34,18 @@ jobs:
|
|||||||
go env
|
go env
|
||||||
printf "\n\nSystem environment:\n\n"
|
printf "\n\nSystem environment:\n\n"
|
||||||
env
|
env
|
||||||
|
echo "::set-output name=go_cache::$(go env GOCACHE)"
|
||||||
|
|
||||||
- name: Cache the build cache
|
- name: Cache the build cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
# In order:
|
path: ${{ steps.vars.outputs.go_cache }}
|
||||||
# * Module download cache
|
|
||||||
# * Build cache (Linux)
|
|
||||||
path: |
|
|
||||||
~/go/pkg/mod
|
|
||||||
~/.cache/go-build
|
|
||||||
key: cross-build-go${{ matrix.go }}-${{ matrix.goos }}-${{ hashFiles('**/go.sum') }}
|
key: cross-build-go${{ matrix.go }}-${{ matrix.goos }}-${{ hashFiles('**/go.sum') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
cross-build-go${{ matrix.go }}-${{ matrix.goos }}
|
cross-build-go${{ matrix.go }}-${{ matrix.goos }}
|
||||||
|
|
||||||
- name: Checkout code into the Go module directory
|
- name: Checkout code into the Go module directory
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Run Build
|
- name: Run Build
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -14,22 +14,12 @@ jobs:
|
|||||||
# From https://github.com/golangci/golangci-lint-action
|
# From https://github.com/golangci/golangci-lint-action
|
||||||
golangci:
|
golangci:
|
||||||
name: lint
|
name: lint
|
||||||
strategy:
|
runs-on: ubuntu-latest
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: '~1.18.4'
|
|
||||||
check-latest: true
|
|
||||||
|
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v2
|
||||||
with:
|
with:
|
||||||
version: v1.47
|
version: v1.31
|
||||||
# Windows times out frequently after about 5m50s if we don't set a longer timeout.
|
|
||||||
args: --timeout 10m
|
|
||||||
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||||
# only-new-issues: true
|
# only-new-issues: true
|
||||||
|
|||||||
@@ -11,37 +11,22 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ ubuntu-latest ]
|
os: [ ubuntu-latest ]
|
||||||
go: [ '1.19' ]
|
go: [ '1.17' ]
|
||||||
|
|
||||||
include:
|
|
||||||
# Set the minimum Go patch version for the given Go minor
|
|
||||||
# Usable via ${{ matrix.GO_SEMVER }}
|
|
||||||
- go: '1.19'
|
|
||||||
GO_SEMVER: '~1.19.0'
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
|
|
||||||
# https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
# https://docs.github.com/en/rest/overview/permissions-required-for-github-apps#permission-on-contents
|
|
||||||
# "Releases" is part of `contents`, so it needs the `write`
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.GO_SEMVER }}
|
go-version: ${{ matrix.go }}
|
||||||
check-latest: true
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
# 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@v2 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/
|
||||||
# which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran:
|
# which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran:
|
||||||
# git fetch --prune --unshallow
|
# git fetch --prune --unshallow
|
||||||
@@ -63,6 +48,7 @@ jobs:
|
|||||||
env
|
env
|
||||||
echo "::set-output name=version_tag::${GITHUB_REF/refs\/tags\//}"
|
echo "::set-output name=version_tag::${GITHUB_REF/refs\/tags\//}"
|
||||||
echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
|
echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
|
||||||
|
echo "::set-output name=go_cache::$(go env GOCACHE)"
|
||||||
|
|
||||||
# Add "pip install" CLI tools to PATH
|
# Add "pip install" CLI tools to PATH
|
||||||
echo ~/.local/bin >> $GITHUB_PATH
|
echo ~/.local/bin >> $GITHUB_PATH
|
||||||
@@ -97,23 +83,11 @@ jobs:
|
|||||||
- name: Cache the build cache
|
- name: Cache the build cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
# In order:
|
path: ${{ steps.vars.outputs.go_cache }}
|
||||||
# * Module download cache
|
|
||||||
# * Build cache (Linux)
|
|
||||||
path: |
|
|
||||||
~/go/pkg/mod
|
|
||||||
~/.cache/go-build
|
|
||||||
key: ${{ runner.os }}-go${{ matrix.go }}-release-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go${{ matrix.go }}-release-${{ hashFiles('**/go.sum') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-go${{ matrix.go }}-release
|
${{ runner.os }}-go${{ matrix.go }}-release
|
||||||
- name: Install Cosign
|
|
||||||
uses: sigstore/cosign-installer@main
|
|
||||||
- name: Cosign version
|
|
||||||
run: cosign version
|
|
||||||
- name: Install Syft
|
|
||||||
uses: anchore/sbom-action/download-syft@main
|
|
||||||
- name: 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@v2
|
uses: goreleaser/goreleaser-action@v2
|
||||||
@@ -123,10 +97,9 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
TAG: ${{ steps.vars.outputs.version_tag }}
|
TAG: ${{ steps.vars.outputs.version_tag }}
|
||||||
COSIGN_EXPERIMENTAL: 1
|
|
||||||
|
|
||||||
# Only publish on non-special tags (e.g. non-beta)
|
# Only publish on non-special tags (e.g. non-beta)
|
||||||
# We will continue to push to Gemfury for the foreseeable future, although
|
# We will continue to push to Gemfury for the forseeable future, although
|
||||||
# Cloudsmith is probably better, to not break things for existing users of Gemfury.
|
# Cloudsmith is probably better, to not break things for existing users of Gemfury.
|
||||||
# See https://gemfury.com/caddy/deb:caddy
|
# See https://gemfury.com/caddy/deb:caddy
|
||||||
- name: Publish .deb to Gemfury
|
- name: Publish .deb to Gemfury
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
_gitignore/
|
_gitignore/
|
||||||
*.log
|
*.log
|
||||||
Caddyfile
|
Caddyfile
|
||||||
Caddyfile.*
|
|
||||||
!caddyfile/
|
!caddyfile/
|
||||||
|
|
||||||
# artifacts from pprof tooling
|
# artifacts from pprof tooling
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
linters-settings:
|
linters-settings:
|
||||||
errcheck:
|
errcheck:
|
||||||
ignore: fmt:.*,go.uber.org/zap/zapcore:^Add.*
|
ignore: fmt:.*,io/ioutil:^Read.*,go.uber.org/zap/zapcore:^Add.*
|
||||||
ignoretests: true
|
ignoretests: true
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
|
|||||||
+9
-28
@@ -6,7 +6,8 @@ before:
|
|||||||
# subsequently causes gorleaser to refuse running.
|
# subsequently causes gorleaser to refuse running.
|
||||||
- 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'
|
- cp ./go.mod caddy-build/go.mod
|
||||||
|
- sed -i.bkp 's|github.com/caddyserver/caddy/v2|caddy|g' ./caddy-build/go.mod
|
||||||
# GoReleaser doesn't seem to offer {{.Tag}} at this stage, so we have to embed it into the env
|
# GoReleaser doesn't seem to offer {{.Tag}} at this stage, so we have to embed it into the env
|
||||||
# so we run: TAG=$(git describe --abbrev=0) goreleaser release --rm-dist --skip-publish --skip-validate
|
# so we run: TAG=$(git describe --abbrev=0) goreleaser release --rm-dist --skip-publish --skip-validate
|
||||||
- go mod edit -require=github.com/caddyserver/caddy/v2@{{.Env.TAG}} ./caddy-build/go.mod
|
- go mod edit -require=github.com/caddyserver/caddy/v2@{{.Env.TAG}} ./caddy-build/go.mod
|
||||||
@@ -14,11 +15,7 @@ before:
|
|||||||
# 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'
|
||||||
- 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
|
|
||||||
- go mod download
|
- go mod download
|
||||||
- go run cmd/caddy/main.go manpage --directory ./caddy-dist/man
|
|
||||||
- gzip -r ./caddy-dist/man/
|
|
||||||
- /bin/sh -c 'go run cmd/caddy/main.go completion bash > ./caddy-dist/scripts/bash-completion'
|
|
||||||
|
|
||||||
builds:
|
builds:
|
||||||
- env:
|
- env:
|
||||||
@@ -39,9 +36,9 @@ builds:
|
|||||||
- s390x
|
- s390x
|
||||||
- ppc64le
|
- ppc64le
|
||||||
goarm:
|
goarm:
|
||||||
- "5"
|
- 5
|
||||||
- "6"
|
- 6
|
||||||
- "7"
|
- 7
|
||||||
ignore:
|
ignore:
|
||||||
- goos: darwin
|
- goos: darwin
|
||||||
goarch: arm
|
goarch: arm
|
||||||
@@ -59,25 +56,12 @@ builds:
|
|||||||
goarch: s390x
|
goarch: s390x
|
||||||
- goos: freebsd
|
- goos: freebsd
|
||||||
goarch: arm
|
goarch: arm
|
||||||
goarm: "5"
|
goarm: 5
|
||||||
flags:
|
flags:
|
||||||
- -trimpath
|
- -trimpath
|
||||||
- -mod=readonly
|
|
||||||
ldflags:
|
ldflags:
|
||||||
- -s -w
|
- -s -w
|
||||||
signs:
|
|
||||||
- cmd: cosign
|
|
||||||
signature: "${artifact}.sig"
|
|
||||||
certificate: '{{ trimsuffix .Env.artifact ".tar.gz" }}.pem'
|
|
||||||
args: ["sign-blob", "--output-signature=${signature}", "--output-certificate", "${certificate}", "${artifact}"]
|
|
||||||
artifacts: all
|
|
||||||
sboms:
|
|
||||||
- artifacts: binary
|
|
||||||
# defaults to
|
|
||||||
# documents:
|
|
||||||
# - "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}.sbom"
|
|
||||||
cmd: syft
|
|
||||||
args: ["$artifact", "--file", "${document}", "--output", "cyclonedx-json"]
|
|
||||||
archives:
|
archives:
|
||||||
- format_overrides:
|
- format_overrides:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
@@ -91,7 +75,7 @@ nfpms:
|
|||||||
- id: default
|
- id: default
|
||||||
package_name: caddy
|
package_name: caddy
|
||||||
|
|
||||||
vendor: Dyanim
|
vendor: Light Code Labs
|
||||||
homepage: https://caddyserver.com
|
homepage: https://caddyserver.com
|
||||||
maintainer: Matthew Holt <mholt@users.noreply.github.com>
|
maintainer: Matthew Holt <mholt@users.noreply.github.com>
|
||||||
description: |
|
description: |
|
||||||
@@ -113,16 +97,13 @@ nfpms:
|
|||||||
- src: ./caddy-dist/welcome/index.html
|
- src: ./caddy-dist/welcome/index.html
|
||||||
dst: /usr/share/caddy/index.html
|
dst: /usr/share/caddy/index.html
|
||||||
|
|
||||||
- src: ./caddy-dist/scripts/bash-completion
|
- src: ./caddy-dist/scripts/completions/bash-completion
|
||||||
dst: /etc/bash_completion.d/caddy
|
dst: /etc/bash_completion.d/caddy
|
||||||
|
|
||||||
- src: ./caddy-dist/config/Caddyfile
|
- src: ./caddy-dist/config/Caddyfile
|
||||||
dst: /etc/caddy/Caddyfile
|
dst: /etc/caddy/Caddyfile
|
||||||
type: config
|
type: config
|
||||||
|
|
||||||
- src: ./caddy-dist/man/*
|
|
||||||
dst: /usr/share/man/man8/
|
|
||||||
|
|
||||||
scripts:
|
scripts:
|
||||||
postinstall: ./caddy-dist/scripts/postinstall.sh
|
postinstall: ./caddy-dist/scripts/postinstall.sh
|
||||||
preremove: ./caddy-dist/scripts/preremove.sh
|
preremove: ./caddy-dist/scripts/preremove.sh
|
||||||
|
|||||||
@@ -57,25 +57,25 @@
|
|||||||
- Multi-issuer fallback
|
- Multi-issuer fallback
|
||||||
- **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 tens of thousands of sites** ... and probably more
|
||||||
- **HTTP/1.1, HTTP/2, and HTTP/3** supported all by default
|
- **HTTP/1.1, HTTP/2, and experimental HTTP/3** support
|
||||||
- **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
|
||||||
- Actually **fun to use**
|
- Actually **fun to use**
|
||||||
- So much more to [discover](https://caddyserver.com/v2)
|
- So, so much more to [discover](https://caddyserver.com/v2)
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
The simplest, cross-platform way to get started is to download Caddy from [GitHub Releases](https://github.com/caddyserver/caddy/releases) and place the executable file in your PATH.
|
The simplest, cross-platform way is to download from [GitHub Releases](https://github.com/caddyserver/caddy/releases) and place the executable file in your PATH.
|
||||||
|
|
||||||
See [our online documentation](https://caddyserver.com/docs/install) for other install instructions.
|
For other install options, see https://caddyserver.com/docs/install.
|
||||||
|
|
||||||
## Build from source
|
## Build from source
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
|
|
||||||
- [Go 1.18 or newer](https://golang.org/dl/)
|
- [Go 1.16 or newer](https://golang.org/dl/)
|
||||||
|
|
||||||
### For development
|
### For development
|
||||||
|
|
||||||
@@ -164,9 +164,9 @@ The docs are also open source. You can contribute to them here: https://github.c
|
|||||||
|
|
||||||
## Getting help
|
## Getting help
|
||||||
|
|
||||||
- We advise companies using Caddy to secure a support contract through [Ardan Labs](https://www.ardanlabs.com/my/contact-us?dd=caddy) before help is needed.
|
- We **strongly recommend** that all professionals or companies using Caddy get a support contract through [Ardan Labs](https://www.ardanlabs.com/my/contact-us?dd=caddy) before help is needed.
|
||||||
|
|
||||||
- A [sponsorship](https://github.com/sponsors/mholt) goes a long way! We can offer private help to sponsors. If Caddy is benefitting your company, please consider a sponsorship. This not only helps fund full-time work to ensure the longevity of the project, it provides your company the resources, support, and discounts you need; along with being a great look for your company to your customers and potential customers!
|
- A [sponsorship](https://github.com/sponsors/mholt) goes a long way! If Caddy is benefitting your company, please consider a sponsorship! This not only helps fund full-time work to ensure the longevity of the project, it's also a great look for your company to your customers and potential customers!
|
||||||
|
|
||||||
- Individuals can exchange help for free on our community forum at https://caddy.community. Remember that people give help out of their spare time and good will. The best way to get help is to give it first!
|
- Individuals can exchange help for free on our community forum at https://caddy.community. Remember that people give help out of their spare time and good will. The best way to get help is to give it first!
|
||||||
|
|
||||||
@@ -176,8 +176,6 @@ Please use our [issue tracker](https://github.com/caddyserver/caddy/issues) only
|
|||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
Matthew Holt began developing Caddy in 2014 while studying computer science at Brigham Young University. (The name "Caddy" was chosen because this software helps with the tedious, mundane tasks of serving the Web, and is also a single place for multiple things to be organized together.) It soon became the first web server to use HTTPS automatically and by default, and now has hundreds of contributors and has served trillions of HTTPS requests.
|
|
||||||
|
|
||||||
**The name "Caddy" is trademarked.** The name of the software is "Caddy", not "Caddy Server" or "CaddyServer". Please call it "Caddy" or, if you wish to clarify, "the Caddy web server". Caddy is a registered trademark of Stack Holdings GmbH.
|
**The name "Caddy" is trademarked.** The name of the software is "Caddy", not "Caddy Server" or "CaddyServer". Please call it "Caddy" or, if you wish to clarify, "the Caddy web server". Caddy is a registered trademark of Stack Holdings GmbH.
|
||||||
|
|
||||||
- _Project on Twitter: [@caddyserver](https://twitter.com/caddyserver)_
|
- _Project on Twitter: [@caddyserver](https://twitter.com/caddyserver)_
|
||||||
|
|||||||
@@ -25,9 +25,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"expvar"
|
"expvar"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
|
||||||
"hash/fnv"
|
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/pprof"
|
"net/http/pprof"
|
||||||
@@ -40,10 +39,10 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2/notify"
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AdminConfig configures Caddy's API endpoint, which is used
|
// AdminConfig configures Caddy's API endpoint, which is used
|
||||||
@@ -93,10 +92,6 @@ type AdminConfig struct {
|
|||||||
//
|
//
|
||||||
// EXPERIMENTAL: This feature is subject to change.
|
// EXPERIMENTAL: This feature is subject to change.
|
||||||
Remote *RemoteAdmin `json:"remote,omitempty"`
|
Remote *RemoteAdmin `json:"remote,omitempty"`
|
||||||
|
|
||||||
// Holds onto the routers so that we can later provision them
|
|
||||||
// if they require provisioning.
|
|
||||||
routers []AdminRouter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigSettings configures the management of configuration.
|
// ConfigSettings configures the management of configuration.
|
||||||
@@ -106,26 +101,20 @@ type ConfigSettings struct {
|
|||||||
// are not persisted; only configs that are pushed to Caddy get persisted.
|
// are not persisted; only configs that are pushed to Caddy get persisted.
|
||||||
Persist *bool `json:"persist,omitempty"`
|
Persist *bool `json:"persist,omitempty"`
|
||||||
|
|
||||||
// Loads a new configuration. This is helpful if your configs are
|
// Loads a configuration to use. This is helpful if your configs are
|
||||||
// managed elsewhere and you want Caddy to pull its config dynamically
|
// managed elsewhere, and you want Caddy to pull its config dynamically
|
||||||
// when it starts. The pulled config completely replaces the current
|
// when it starts. The pulled config completely replaces the current
|
||||||
// one, just like any other config load. It is an error if a pulled
|
// one, just like any other config load. It is an error if a pulled
|
||||||
// config is configured to pull another config without a load_delay,
|
// config is configured to pull another config.
|
||||||
// as this creates a tight loop.
|
|
||||||
//
|
//
|
||||||
// EXPERIMENTAL: Subject to change.
|
// EXPERIMENTAL: Subject to change.
|
||||||
LoadRaw json.RawMessage `json:"load,omitempty" caddy:"namespace=caddy.config_loaders inline_key=module"`
|
LoadRaw json.RawMessage `json:"load,omitempty" caddy:"namespace=caddy.config_loaders inline_key=module"`
|
||||||
|
|
||||||
// The duration after which to load config. If set, config will be pulled
|
// The interval to pull config. With a non-zero value, will pull config
|
||||||
// from the config loader after this duration. A delay is required if a
|
// from config loader (eg. a http loader) with given interval.
|
||||||
// dynamically-loaded config is configured to load yet another config. To
|
|
||||||
// load configs on a regular interval, ensure this value is set the same
|
|
||||||
// on all loaded configs; it can also be variable if needed, and to stop
|
|
||||||
// the loop, simply remove dynamic config loading from the next-loaded
|
|
||||||
// config.
|
|
||||||
//
|
//
|
||||||
// EXPERIMENTAL: Subject to change.
|
// EXPERIMENTAL: Subject to change.
|
||||||
LoadDelay Duration `json:"load_delay,omitempty"`
|
LoadInterval Duration `json:"load_interval,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IdentityConfig configures management of this server's identity. An identity
|
// IdentityConfig configures management of this server's identity. An identity
|
||||||
@@ -195,7 +184,7 @@ type AdminPermissions struct {
|
|||||||
|
|
||||||
// newAdminHandler reads admin's config and returns an http.Handler suitable
|
// newAdminHandler reads admin's config and returns an http.Handler suitable
|
||||||
// for use in an admin endpoint server, which will be listening on listenAddr.
|
// for use in an admin endpoint server, which will be listening on listenAddr.
|
||||||
func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool) adminHandler {
|
func (admin AdminConfig) newAdminHandler(addr NetworkAddress, remote bool) adminHandler {
|
||||||
muxWrap := adminHandler{mux: http.NewServeMux()}
|
muxWrap := adminHandler{mux: http.NewServeMux()}
|
||||||
|
|
||||||
// secure the local or remote endpoint respectively
|
// secure the local or remote endpoint respectively
|
||||||
@@ -204,7 +193,6 @@ func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool) admi
|
|||||||
} else {
|
} else {
|
||||||
muxWrap.enforceHost = !addr.isWildcardInterface()
|
muxWrap.enforceHost = !addr.isWildcardInterface()
|
||||||
muxWrap.allowedOrigins = admin.allowedOrigins(addr)
|
muxWrap.allowedOrigins = admin.allowedOrigins(addr)
|
||||||
muxWrap.enforceOrigin = admin.EnforceOrigin
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addRouteWithMetrics := func(pattern string, handlerLabel string, h http.Handler) {
|
addRouteWithMetrics := func(pattern string, handlerLabel string, h http.Handler) {
|
||||||
@@ -255,39 +243,17 @@ func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool) admi
|
|||||||
for _, route := range router.Routes() {
|
for _, route := range router.Routes() {
|
||||||
addRoute(route.Pattern, handlerLabel, route.Handler)
|
addRoute(route.Pattern, handlerLabel, route.Handler)
|
||||||
}
|
}
|
||||||
admin.routers = append(admin.routers, router)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return muxWrap
|
return muxWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
// provisionAdminRouters provisions all the router modules
|
|
||||||
// in the admin.api namespace that need provisioning.
|
|
||||||
func (admin *AdminConfig) provisionAdminRouters(ctx Context) error {
|
|
||||||
for _, router := range admin.routers {
|
|
||||||
provisioner, ok := router.(Provisioner)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err := provisioner.Provision(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We no longer need the routers once provisioned, allow for GC
|
|
||||||
admin.routers = nil
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// allowedOrigins returns a list of origins that are allowed.
|
// allowedOrigins returns a list of origins that are allowed.
|
||||||
// If admin.Origins is nil (null), the provided listen address
|
// If admin.Origins is nil (null), the provided listen address
|
||||||
// will be used as the default origin. If admin.Origins is
|
// will be used as the default origin. If admin.Origins is
|
||||||
// empty, no origins will be allowed, effectively bricking the
|
// empty, no origins will be allowed, effectively bricking the
|
||||||
// endpoint for non-unix-socket endpoints, but whatever.
|
// endpoint for non-unix-socket endpoints, but whatever.
|
||||||
func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
|
func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []string {
|
||||||
uniqueOrigins := make(map[string]struct{})
|
uniqueOrigins := make(map[string]struct{})
|
||||||
for _, o := range admin.Origins {
|
for _, o := range admin.Origins {
|
||||||
uniqueOrigins[o] = struct{}{}
|
uniqueOrigins[o] = struct{}{}
|
||||||
@@ -311,23 +277,8 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
|
|||||||
uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
|
uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allowed := make([]*url.URL, 0, len(uniqueOrigins))
|
allowed := make([]string, 0, len(uniqueOrigins))
|
||||||
for originStr := range uniqueOrigins {
|
for origin := range uniqueOrigins {
|
||||||
var origin *url.URL
|
|
||||||
if strings.Contains(originStr, "://") {
|
|
||||||
var err error
|
|
||||||
origin, err = url.Parse(originStr)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
origin.Path = ""
|
|
||||||
origin.RawPath = ""
|
|
||||||
origin.Fragment = ""
|
|
||||||
origin.RawFragment = ""
|
|
||||||
origin.RawQuery = ""
|
|
||||||
} else {
|
|
||||||
origin = &url.URL{Host: originStr}
|
|
||||||
}
|
|
||||||
allowed = append(allowed, origin)
|
allowed = append(allowed, origin)
|
||||||
}
|
}
|
||||||
return allowed
|
return allowed
|
||||||
@@ -339,19 +290,17 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
|
|||||||
// that there is always an admin server (unless it is explicitly
|
// that there is always an admin server (unless it is explicitly
|
||||||
// configured to be disabled).
|
// configured to be disabled).
|
||||||
func replaceLocalAdminServer(cfg *Config) error {
|
func replaceLocalAdminServer(cfg *Config) error {
|
||||||
// always* be sure to close down the old admin endpoint
|
// always be sure to close down the old admin endpoint
|
||||||
// as gracefully as possible, even if the new one is
|
// as gracefully as possible, even if the new one is
|
||||||
// disabled -- careful to use reference to the current
|
// disabled -- careful to use reference to the current
|
||||||
// (old) admin endpoint since it will be different
|
// (old) admin endpoint since it will be different
|
||||||
// when the function returns
|
// when the function returns
|
||||||
// (* except if the new one fails to start)
|
|
||||||
oldAdminServer := localAdminServer
|
oldAdminServer := localAdminServer
|
||||||
var err error
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// do the shutdown asynchronously so that any
|
// do the shutdown asynchronously so that any
|
||||||
// current API request gets a response; this
|
// current API request gets a response; this
|
||||||
// goroutine may last a few seconds
|
// goroutine may last a few seconds
|
||||||
if oldAdminServer != nil && err == nil {
|
if oldAdminServer != nil {
|
||||||
go func(oldAdminServer *http.Server) {
|
go func(oldAdminServer *http.Server) {
|
||||||
err := stopAdminServer(oldAdminServer)
|
err := stopAdminServer(oldAdminServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -361,26 +310,25 @@ func replaceLocalAdminServer(cfg *Config) error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// set a default if admin wasn't otherwise configured
|
// always get a valid admin config
|
||||||
if cfg.Admin == nil {
|
adminConfig := DefaultAdminConfig
|
||||||
cfg.Admin = &AdminConfig{
|
if cfg != nil && cfg.Admin != nil {
|
||||||
Listen: DefaultAdminListen,
|
adminConfig = cfg.Admin
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if new admin endpoint is to be disabled, we're done
|
// if new admin endpoint is to be disabled, we're done
|
||||||
if cfg.Admin.Disabled {
|
if adminConfig.Disabled {
|
||||||
Log().Named("admin").Warn("admin endpoint disabled")
|
Log().Named("admin").Warn("admin endpoint disabled")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract a singular listener address
|
// extract a singular listener address
|
||||||
addr, err := parseAdminListenAddr(cfg.Admin.Listen, DefaultAdminListen)
|
addr, err := parseAdminListenAddr(adminConfig.Listen, DefaultAdminListen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
handler := cfg.Admin.newAdminHandler(addr, false)
|
handler := adminConfig.newAdminHandler(addr, false)
|
||||||
|
|
||||||
ln, err := Listen(addr.Network, addr.JoinHostPort(0))
|
ln, err := Listen(addr.Network, addr.JoinHostPort(0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -410,8 +358,8 @@ func replaceLocalAdminServer(cfg *Config) error {
|
|||||||
|
|
||||||
adminLogger.Info("admin endpoint started",
|
adminLogger.Info("admin endpoint started",
|
||||||
zap.String("address", addr.String()),
|
zap.String("address", addr.String()),
|
||||||
zap.Bool("enforce_origin", cfg.Admin.EnforceOrigin),
|
zap.Bool("enforce_origin", adminConfig.EnforceOrigin),
|
||||||
zap.Array("origins", loggableURLArray(handler.allowedOrigins)))
|
zap.Strings("origins", handler.allowedOrigins))
|
||||||
|
|
||||||
if !handler.enforceHost {
|
if !handler.enforceHost {
|
||||||
adminLogger.Warn("admin endpoint on open interface; host checking disabled",
|
adminLogger.Warn("admin endpoint on open interface; host checking disabled",
|
||||||
@@ -442,7 +390,7 @@ func manageIdentity(ctx Context, cfg *Config) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading identity issuer modules: %s", err)
|
return fmt.Errorf("loading identity issuer modules: %s", err)
|
||||||
}
|
}
|
||||||
for _, issVal := range val.([]any) {
|
for _, issVal := range val.([]interface{}) {
|
||||||
cfg.Admin.Identity.issuers = append(cfg.Admin.Identity.issuers, issVal.(certmagic.Issuer))
|
cfg.Admin.Identity.issuers = append(cfg.Admin.Identity.issuers, issVal.(certmagic.Issuer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -519,9 +467,6 @@ func replaceRemoteAdminServer(ctx Context, cfg *Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create TLS config that will enforce mutual authentication
|
// create TLS config that will enforce mutual authentication
|
||||||
if identityCertCache == nil {
|
|
||||||
return fmt.Errorf("cannot enable remote admin without a certificate cache; configure identity management to initialize a certificate cache")
|
|
||||||
}
|
|
||||||
cmCfg := cfg.Admin.Identity.certmagicConfig(remoteLogger, false)
|
cmCfg := cfg.Admin.Identity.certmagicConfig(remoteLogger, false)
|
||||||
tlsConfig := cmCfg.TLSConfig()
|
tlsConfig := cmCfg.TLSConfig()
|
||||||
tlsConfig.NextProtos = nil // this server does not solve ACME challenges
|
tlsConfig.NextProtos = nil // this server does not solve ACME challenges
|
||||||
@@ -703,10 +648,10 @@ type AdminRoute struct {
|
|||||||
type adminHandler struct {
|
type adminHandler struct {
|
||||||
mux *http.ServeMux
|
mux *http.ServeMux
|
||||||
|
|
||||||
// security for local/plaintext endpoint
|
// security for local/plaintext) endpoint, on by default
|
||||||
enforceOrigin bool
|
enforceOrigin bool
|
||||||
enforceHost bool
|
enforceHost bool
|
||||||
allowedOrigins []*url.URL
|
allowedOrigins []string
|
||||||
|
|
||||||
// security for remote/encrypted endpoint
|
// security for remote/encrypted endpoint
|
||||||
remoteControl *RemoteAdmin
|
remoteControl *RemoteAdmin
|
||||||
@@ -715,17 +660,11 @@ type adminHandler struct {
|
|||||||
// ServeHTTP is the external entry point for API requests.
|
// ServeHTTP is the external entry point for API requests.
|
||||||
// It will only be called once per request.
|
// It will only be called once per request.
|
||||||
func (h adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
ip, port, err := net.SplitHostPort(r.RemoteAddr)
|
|
||||||
if err != nil {
|
|
||||||
ip = r.RemoteAddr
|
|
||||||
port = ""
|
|
||||||
}
|
|
||||||
log := Log().Named("admin.api").With(
|
log := Log().Named("admin.api").With(
|
||||||
zap.String("method", r.Method),
|
zap.String("method", r.Method),
|
||||||
zap.String("host", r.Host),
|
zap.String("host", r.Host),
|
||||||
zap.String("uri", r.RequestURI),
|
zap.String("uri", r.RequestURI),
|
||||||
zap.String("remote_ip", ip),
|
zap.String("remote_addr", r.RemoteAddr),
|
||||||
zap.String("remote_port", port),
|
|
||||||
zap.Reflect("headers", r.Header),
|
zap.Reflect("headers", r.Header),
|
||||||
)
|
)
|
||||||
if r.TLS != nil {
|
if r.TLS != nil {
|
||||||
@@ -832,8 +771,8 @@ func (h adminHandler) handleError(w http.ResponseWriter, r *http.Request, err er
|
|||||||
// rebinding attacks.
|
// rebinding attacks.
|
||||||
func (h adminHandler) checkHost(r *http.Request) error {
|
func (h adminHandler) checkHost(r *http.Request) error {
|
||||||
var allowed bool
|
var allowed bool
|
||||||
for _, allowedOrigin := range h.allowedOrigins {
|
for _, allowedHost := range h.allowedOrigins {
|
||||||
if r.Host == allowedOrigin.Host {
|
if r.Host == allowedHost {
|
||||||
allowed = true
|
allowed = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -852,81 +791,59 @@ func (h adminHandler) checkHost(r *http.Request) error {
|
|||||||
// sites from issuing requests to our listener. It
|
// sites from issuing requests to our listener. It
|
||||||
// returns the origin that was obtained from r.
|
// returns the origin that was obtained from r.
|
||||||
func (h adminHandler) checkOrigin(r *http.Request) (string, error) {
|
func (h adminHandler) checkOrigin(r *http.Request) (string, error) {
|
||||||
originStr, origin := h.getOrigin(r)
|
origin := h.getOriginHost(r)
|
||||||
if origin == nil {
|
if origin == "" {
|
||||||
return "", APIError{
|
return origin, APIError{
|
||||||
HTTPStatus: http.StatusForbidden,
|
HTTPStatus: http.StatusForbidden,
|
||||||
Err: fmt.Errorf("required Origin header is missing or invalid"),
|
Err: fmt.Errorf("missing required Origin header"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !h.originAllowed(origin) {
|
if !h.originAllowed(origin) {
|
||||||
return "", APIError{
|
return origin, APIError{
|
||||||
HTTPStatus: http.StatusForbidden,
|
HTTPStatus: http.StatusForbidden,
|
||||||
Err: fmt.Errorf("client is not allowed to access from origin '%s'", originStr),
|
Err: fmt.Errorf("client is not allowed to access from origin %s", origin),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return origin.String(), nil
|
return origin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h adminHandler) getOrigin(r *http.Request) (string, *url.URL) {
|
func (h adminHandler) getOriginHost(r *http.Request) string {
|
||||||
origin := r.Header.Get("Origin")
|
origin := r.Header.Get("Origin")
|
||||||
if origin == "" {
|
if origin == "" {
|
||||||
origin = r.Header.Get("Referer")
|
origin = r.Header.Get("Referer")
|
||||||
}
|
}
|
||||||
originURL, err := url.Parse(origin)
|
originURL, err := url.Parse(origin)
|
||||||
if err != nil {
|
if err == nil && originURL.Host != "" {
|
||||||
return origin, nil
|
origin = originURL.Host
|
||||||
}
|
}
|
||||||
originURL.Path = ""
|
return origin
|
||||||
originURL.RawPath = ""
|
|
||||||
originURL.Fragment = ""
|
|
||||||
originURL.RawFragment = ""
|
|
||||||
originURL.RawQuery = ""
|
|
||||||
return origin, originURL
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h adminHandler) originAllowed(origin *url.URL) bool {
|
func (h adminHandler) originAllowed(origin string) bool {
|
||||||
for _, allowedOrigin := range h.allowedOrigins {
|
for _, allowedOrigin := range h.allowedOrigins {
|
||||||
if allowedOrigin.Scheme != "" && origin.Scheme != allowedOrigin.Scheme {
|
originCopy := origin
|
||||||
continue
|
if !strings.Contains(allowedOrigin, "://") {
|
||||||
|
// no scheme specified, so allow both
|
||||||
|
originCopy = strings.TrimPrefix(originCopy, "http://")
|
||||||
|
originCopy = strings.TrimPrefix(originCopy, "https://")
|
||||||
}
|
}
|
||||||
if origin.Host == allowedOrigin.Host {
|
if originCopy == allowedOrigin {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// etagHasher returns a the hasher we used on the config to both
|
|
||||||
// produce and verify ETags.
|
|
||||||
func etagHasher() hash.Hash32 { return fnv.New32a() }
|
|
||||||
|
|
||||||
// makeEtag returns an Etag header value (including quotes) for
|
|
||||||
// the given config path and hash of contents at that path.
|
|
||||||
func makeEtag(path string, hash hash.Hash) string {
|
|
||||||
return fmt.Sprintf(`"%s %x"`, path, hash.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleConfig(w http.ResponseWriter, r *http.Request) error {
|
func handleConfig(w http.ResponseWriter, r *http.Request) error {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
// Set the ETag as a trailer header.
|
|
||||||
// The alternative is to write the config to a buffer, and
|
|
||||||
// then hash that.
|
|
||||||
w.Header().Set("Trailer", "ETag")
|
|
||||||
|
|
||||||
hash := etagHasher()
|
err := readConfig(r.URL.Path, w)
|
||||||
configWriter := io.MultiWriter(w, hash)
|
|
||||||
err := readConfig(r.URL.Path, configWriter)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return APIError{HTTPStatus: http.StatusBadRequest, Err: err}
|
return APIError{HTTPStatus: http.StatusBadRequest, Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we could consider setting up a sync.Pool for the summed
|
|
||||||
// hashes to reduce GC pressure.
|
|
||||||
w.Header().Set("Etag", makeEtag(r.URL.Path, hash))
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case http.MethodPost,
|
case http.MethodPost,
|
||||||
@@ -960,8 +877,8 @@ func handleConfig(w http.ResponseWriter, r *http.Request) error {
|
|||||||
|
|
||||||
forceReload := r.Header.Get("Cache-Control") == "must-revalidate"
|
forceReload := r.Header.Get("Cache-Control") == "must-revalidate"
|
||||||
|
|
||||||
err := changeConfig(r.Method, r.URL.Path, body, r.Header.Get("If-Match"), forceReload)
|
err := changeConfig(r.Method, r.URL.Path, body, forceReload)
|
||||||
if err != nil && !errors.Is(err, errSameConfig) {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -980,28 +897,19 @@ func handleConfigID(w http.ResponseWriter, r *http.Request) error {
|
|||||||
|
|
||||||
parts := strings.Split(idPath, "/")
|
parts := strings.Split(idPath, "/")
|
||||||
if len(parts) < 3 || parts[2] == "" {
|
if len(parts) < 3 || parts[2] == "" {
|
||||||
return APIError{
|
return fmt.Errorf("request path is missing object ID")
|
||||||
HTTPStatus: http.StatusBadRequest,
|
|
||||||
Err: fmt.Errorf("request path is missing object ID"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if parts[0] != "" || parts[1] != "id" {
|
if parts[0] != "" || parts[1] != "id" {
|
||||||
return APIError{
|
return fmt.Errorf("malformed object path")
|
||||||
HTTPStatus: http.StatusBadRequest,
|
|
||||||
Err: fmt.Errorf("malformed object path"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
id := parts[2]
|
id := parts[2]
|
||||||
|
|
||||||
// map the ID to the expanded path
|
// map the ID to the expanded path
|
||||||
currentCtxMu.RLock()
|
currentCfgMu.RLock()
|
||||||
expanded, ok := rawCfgIndex[id]
|
expanded, ok := rawCfgIndex[id]
|
||||||
defer currentCtxMu.RUnlock()
|
defer currentCfgMu.RUnlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
return APIError{
|
return fmt.Errorf("unknown object ID '%s'", id)
|
||||||
HTTPStatus: http.StatusNotFound,
|
|
||||||
Err: fmt.Errorf("unknown object ID '%s'", id),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// piece the full URL path back together
|
// piece the full URL path back together
|
||||||
@@ -1019,7 +927,11 @@ func handleStop(w http.ResponseWriter, r *http.Request) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exitProcess(context.Background(), Log().Named("admin.api"))
|
if err := notify.NotifyStopping(); err != nil {
|
||||||
|
Log().Error("unable to notify stopping to service manager", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
exitProcess(Log().Named("admin.api"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1027,11 +939,11 @@ func handleStop(w http.ResponseWriter, r *http.Request) error {
|
|||||||
// the operation at path according to method, using body and out as
|
// the operation at path according to method, using body and out as
|
||||||
// needed. This is a low-level, unsynchronized function; most callers
|
// needed. This is a low-level, unsynchronized function; most callers
|
||||||
// will want to use changeConfig or readConfig instead. This requires a
|
// will want to use changeConfig or readConfig instead. This requires a
|
||||||
// read or write lock on currentCtxMu, depending on method (GET needs
|
// read or write lock on currentCfgMu, depending on method (GET needs
|
||||||
// only a read lock; all others need a write lock).
|
// only a read lock; all others need a write lock).
|
||||||
func unsyncedConfigAccess(method, path string, body []byte, out io.Writer) error {
|
func unsyncedConfigAccess(method, path string, body []byte, out io.Writer) error {
|
||||||
var err error
|
var err error
|
||||||
var val any
|
var val interface{}
|
||||||
|
|
||||||
// if there is a request body, decode it into the
|
// if there is a request body, decode it into the
|
||||||
// variable that will be set in the config according
|
// variable that will be set in the config according
|
||||||
@@ -1068,16 +980,16 @@ func unsyncedConfigAccess(method, path string, body []byte, out io.Writer) error
|
|||||||
parts = parts[:len(parts)-1]
|
parts = parts[:len(parts)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
var ptr any = rawCfg
|
var ptr interface{} = rawCfg
|
||||||
|
|
||||||
traverseLoop:
|
traverseLoop:
|
||||||
for i, part := range parts {
|
for i, part := range parts {
|
||||||
switch v := ptr.(type) {
|
switch v := ptr.(type) {
|
||||||
case map[string]any:
|
case map[string]interface{}:
|
||||||
// if the next part enters a slice, and the slice is our destination,
|
// if the next part enters a slice, and the slice is our destination,
|
||||||
// handle it specially (because appending to the slice copies the slice
|
// handle it specially (because appending to the slice copies the slice
|
||||||
// header, which does not replace the original one like we want)
|
// header, which does not replace the original one like we want)
|
||||||
if arr, ok := v[part].([]any); ok && i == len(parts)-2 {
|
if arr, ok := v[part].([]interface{}); ok && i == len(parts)-2 {
|
||||||
var idx int
|
var idx int
|
||||||
if method != http.MethodPost {
|
if method != http.MethodPost {
|
||||||
idxStr := parts[len(parts)-1]
|
idxStr := parts[len(parts)-1]
|
||||||
@@ -1099,7 +1011,7 @@ traverseLoop:
|
|||||||
}
|
}
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
if ellipses {
|
if ellipses {
|
||||||
valArray, ok := val.([]any)
|
valArray, ok := val.([]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("final element is not an array")
|
return fmt.Errorf("final element is not an array")
|
||||||
}
|
}
|
||||||
@@ -1134,9 +1046,9 @@ traverseLoop:
|
|||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
// if the part is an existing list, POST appends to
|
// if the part is an existing list, POST appends to
|
||||||
// it, otherwise it just sets or creates the value
|
// it, otherwise it just sets or creates the value
|
||||||
if arr, ok := v[part].([]any); ok {
|
if arr, ok := v[part].([]interface{}); ok {
|
||||||
if ellipses {
|
if ellipses {
|
||||||
valArray, ok := val.([]any)
|
valArray, ok := val.([]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("final element is not an array")
|
return fmt.Errorf("final element is not an array")
|
||||||
}
|
}
|
||||||
@@ -1167,12 +1079,12 @@ traverseLoop:
|
|||||||
// might not exist yet; that's OK but we need to make them as
|
// might not exist yet; that's OK but we need to make them as
|
||||||
// we go, while we still have a pointer from the level above
|
// we go, while we still have a pointer from the level above
|
||||||
if v[part] == nil && method == http.MethodPut {
|
if v[part] == nil && method == http.MethodPut {
|
||||||
v[part] = make(map[string]any)
|
v[part] = make(map[string]interface{})
|
||||||
}
|
}
|
||||||
ptr = v[part]
|
ptr = v[part]
|
||||||
}
|
}
|
||||||
|
|
||||||
case []any:
|
case []interface{}:
|
||||||
partInt, err := strconv.Atoi(part)
|
partInt, err := strconv.Atoi(part)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("[/%s] invalid array index '%s': %v",
|
return fmt.Errorf("[/%s] invalid array index '%s': %v",
|
||||||
@@ -1194,7 +1106,7 @@ traverseLoop:
|
|||||||
|
|
||||||
// RemoveMetaFields removes meta fields like "@id" from a JSON message
|
// RemoveMetaFields removes meta fields like "@id" from a JSON message
|
||||||
// by using a simple regular expression. (An alternate way to do this
|
// by using a simple regular expression. (An alternate way to do this
|
||||||
// would be to delete them from the raw, map[string]any
|
// would be to delete them from the raw, map[string]interface{}
|
||||||
// representation as they are indexed, then iterate the index we made
|
// representation as they are indexed, then iterate the index we made
|
||||||
// and add them back after encoding as JSON, but this is simpler.)
|
// and add them back after encoding as JSON, but this is simpler.)
|
||||||
func RemoveMetaFields(rawJSON []byte) []byte {
|
func RemoveMetaFields(rawJSON []byte) []byte {
|
||||||
@@ -1269,18 +1181,6 @@ func decodeBase64DERCert(certStr string) (*x509.Certificate, error) {
|
|||||||
return x509.ParseCertificate(derBytes)
|
return x509.ParseCertificate(derBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
type loggableURLArray []*url.URL
|
|
||||||
|
|
||||||
func (ua loggableURLArray) MarshalLogArray(enc zapcore.ArrayEncoder) error {
|
|
||||||
if ua == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, u := range ua {
|
|
||||||
enc.AppendString(u.String())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// DefaultAdminListen is the address for the local admin
|
// DefaultAdminListen is the address for the local admin
|
||||||
// listener, if none is specified at startup.
|
// listener, if none is specified at startup.
|
||||||
@@ -1290,13 +1190,19 @@ var (
|
|||||||
// (TLS-authenticated) admin listener, if enabled and not
|
// (TLS-authenticated) admin listener, if enabled and not
|
||||||
// specified otherwise.
|
// specified otherwise.
|
||||||
DefaultRemoteAdminListen = ":2021"
|
DefaultRemoteAdminListen = ":2021"
|
||||||
|
|
||||||
|
// DefaultAdminConfig is the default configuration
|
||||||
|
// for the local administration endpoint.
|
||||||
|
DefaultAdminConfig = &AdminConfig{
|
||||||
|
Listen: DefaultAdminListen,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// PIDFile writes a pidfile to the file at filename. It
|
// PIDFile writes a pidfile to the file at filename. It
|
||||||
// will get deleted before the process gracefully exits.
|
// will get deleted before the process gracefully exits.
|
||||||
func PIDFile(filename string) error {
|
func PIDFile(filename string) error {
|
||||||
pid := []byte(strconv.Itoa(os.Getpid()) + "\n")
|
pid := []byte(strconv.Itoa(os.Getpid()) + "\n")
|
||||||
err := os.WriteFile(filename, pid, 0600)
|
err := ioutil.WriteFile(filename, pid, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1326,7 +1232,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var bufPool = sync.Pool{
|
var bufPool = sync.Pool{
|
||||||
New: func() any {
|
New: func() interface{} {
|
||||||
return new(bytes.Buffer)
|
return new(bytes.Buffer)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-51
@@ -16,8 +16,6 @@ package caddy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -115,7 +113,7 @@ func TestUnsyncedConfigAccess(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// decode the expected config so we can do a convenient DeepEqual
|
// decode the expected config so we can do a convenient DeepEqual
|
||||||
var expectedDecoded any
|
var expectedDecoded interface{}
|
||||||
err = json.Unmarshal([]byte(tc.expect), &expectedDecoded)
|
err = json.Unmarshal([]byte(tc.expect), &expectedDecoded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Test %d: Unmarshaling expected config: %v", i, err)
|
t.Fatalf("Test %d: Unmarshaling expected config: %v", i, err)
|
||||||
@@ -141,57 +139,10 @@ func TestLoadConcurrent(t *testing.T) {
|
|||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
type fooModule struct {
|
|
||||||
IntField int
|
|
||||||
StrField string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fooModule) CaddyModule() ModuleInfo {
|
|
||||||
return ModuleInfo{
|
|
||||||
ID: "foo",
|
|
||||||
New: func() Module { return new(fooModule) },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (fooModule) Start() error { return nil }
|
|
||||||
func (fooModule) Stop() error { return nil }
|
|
||||||
|
|
||||||
func TestETags(t *testing.T) {
|
|
||||||
RegisterModule(fooModule{})
|
|
||||||
|
|
||||||
if err := Load([]byte(`{"apps": {"foo": {"strField": "abc", "intField": 0}}}`), true); err != nil {
|
|
||||||
t.Fatalf("loading: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
const key = "/" + rawConfigKey + "/apps/foo"
|
|
||||||
|
|
||||||
// try update the config with the wrong etag
|
|
||||||
err := changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}}`), fmt.Sprintf(`"/%s not_an_etag"`, rawConfigKey), false)
|
|
||||||
if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
|
|
||||||
t.Fatalf("expected precondition failed; got %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the etag
|
|
||||||
hash := etagHasher()
|
|
||||||
if err := readConfig(key, hash); err != nil {
|
|
||||||
t.Fatalf("reading: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// do the same update with the correct key
|
|
||||||
err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}`), makeEtag(key, hash), false)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected update to work; got %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// now try another update. The hash should no longer match and we should get precondition failed
|
|
||||||
err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 2}`), makeEtag(key, hash), false)
|
|
||||||
if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
|
|
||||||
t.Fatalf("expected precondition failed; got %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLoad(b *testing.B) {
|
func BenchmarkLoad(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
Load(testCfg, true)
|
Load(testCfg, true)
|
||||||
|
|||||||
@@ -17,11 +17,10 @@ package caddy
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -102,50 +101,26 @@ func Run(cfg *Config) error {
|
|||||||
// if it is different from the current config or
|
// if it is different from the current config or
|
||||||
// forceReload is true.
|
// forceReload is true.
|
||||||
func Load(cfgJSON []byte, forceReload bool) error {
|
func Load(cfgJSON []byte, forceReload bool) error {
|
||||||
if err := notify.Reloading(); err != nil {
|
if err := notify.NotifyReloading(); err != nil {
|
||||||
Log().Error("unable to notify service manager of reloading state", zap.Error(err))
|
Log().Error("unable to notify reloading to service manager", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// after reload, notify system of success or, if
|
|
||||||
// failure, update with status (error message)
|
|
||||||
var err error
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err := notify.NotifyReadiness(); err != nil {
|
||||||
if notifyErr := notify.Error(err, 0); notifyErr != nil {
|
Log().Error("unable to notify readiness to service manager", zap.Error(err))
|
||||||
Log().Error("unable to notify to service manager of reload error",
|
|
||||||
zap.Error(notifyErr),
|
|
||||||
zap.String("reload_err", err.Error()))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := notify.Ready(); err != nil {
|
|
||||||
Log().Error("unable to notify to service manager of ready state", zap.Error(err))
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = changeConfig(http.MethodPost, "/"+rawConfigKey, cfgJSON, "", forceReload)
|
return changeConfig(http.MethodPost, "/"+rawConfigKey, cfgJSON, forceReload)
|
||||||
if errors.Is(err, errSameConfig) {
|
|
||||||
err = nil // not really an error
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// changeConfig changes the current config (rawCfg) according to the
|
// changeConfig changes the current config (rawCfg) according to the
|
||||||
// method, traversed via the given path, and uses the given input as
|
// method, traversed via the given path, and uses the given input as
|
||||||
// the new value (if applicable; i.e. "DELETE" doesn't have an input).
|
// the new value (if applicable; i.e. "DELETE" doesn't have an input).
|
||||||
// If the resulting config is the same as the previous, no reload will
|
// If the resulting config is the same as the previous, no reload will
|
||||||
// occur unless forceReload is true. If the config is unchanged and not
|
// occur unless forceReload is true. This function is safe for
|
||||||
// forcefully reloaded, then errConfigUnchanged This function is safe for
|
|
||||||
// concurrent use.
|
// concurrent use.
|
||||||
// The ifMatchHeader can optionally be given a string of the format:
|
func changeConfig(method, path string, input []byte, forceReload bool) error {
|
||||||
//
|
|
||||||
// "<path> <hash>"
|
|
||||||
//
|
|
||||||
// where <path> is the absolute path in the config and <hash> is the expected hash of
|
|
||||||
// the config at that path. If the hash in the ifMatchHeader doesn't match
|
|
||||||
// the hash of the config, then an APIError with status 412 will be returned.
|
|
||||||
func changeConfig(method, path string, input []byte, ifMatchHeader string, forceReload bool) error {
|
|
||||||
switch method {
|
switch method {
|
||||||
case http.MethodGet,
|
case http.MethodGet,
|
||||||
http.MethodHead,
|
http.MethodHead,
|
||||||
@@ -155,42 +130,8 @@ func changeConfig(method, path string, input []byte, ifMatchHeader string, force
|
|||||||
return fmt.Errorf("method not allowed")
|
return fmt.Errorf("method not allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
currentCtxMu.Lock()
|
currentCfgMu.Lock()
|
||||||
defer currentCtxMu.Unlock()
|
defer currentCfgMu.Unlock()
|
||||||
|
|
||||||
if ifMatchHeader != "" {
|
|
||||||
// expect the first and last character to be quotes
|
|
||||||
if len(ifMatchHeader) < 2 || ifMatchHeader[0] != '"' || ifMatchHeader[len(ifMatchHeader)-1] != '"' {
|
|
||||||
return APIError{
|
|
||||||
HTTPStatus: http.StatusBadRequest,
|
|
||||||
Err: fmt.Errorf("malformed If-Match header; expect quoted string"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// read out the parts
|
|
||||||
parts := strings.Fields(ifMatchHeader[1 : len(ifMatchHeader)-1])
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return APIError{
|
|
||||||
HTTPStatus: http.StatusBadRequest,
|
|
||||||
Err: fmt.Errorf("malformed If-Match header; expect format \"<path> <hash>\""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the current hash of the config
|
|
||||||
// at the given path
|
|
||||||
hash := etagHasher()
|
|
||||||
err := unsyncedConfigAccess(http.MethodGet, parts[0], nil, hash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if hex.EncodeToString(hash.Sum(nil)) != parts[1] {
|
|
||||||
return APIError{
|
|
||||||
HTTPStatus: http.StatusPreconditionFailed,
|
|
||||||
Err: fmt.Errorf("If-Match header did not match current config hash"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := unsyncedConfigAccess(method, path, input, nil)
|
err := unsyncedConfigAccess(method, path, input, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -208,8 +149,8 @@ func changeConfig(method, path string, input []byte, ifMatchHeader string, force
|
|||||||
|
|
||||||
// if nothing changed, no need to do a whole reload unless the client forces it
|
// if nothing changed, no need to do a whole reload unless the client forces it
|
||||||
if !forceReload && bytes.Equal(rawCfgJSON, newCfg) {
|
if !forceReload && bytes.Equal(rawCfgJSON, newCfg) {
|
||||||
Log().Info("config is unchanged")
|
Log().Named("admin.api").Info("config is unchanged")
|
||||||
return errSameConfig
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// find any IDs in this config and index them
|
// find any IDs in this config and index them
|
||||||
@@ -231,7 +172,7 @@ func changeConfig(method, path string, input []byte, ifMatchHeader string, force
|
|||||||
// with what caddy is still running; we need to
|
// with what caddy is still running; we need to
|
||||||
// unmarshal it again because it's likely that
|
// unmarshal it again because it's likely that
|
||||||
// pointers deep in our rawCfg map were modified
|
// pointers deep in our rawCfg map were modified
|
||||||
var oldCfg any
|
var oldCfg interface{}
|
||||||
err2 := json.Unmarshal(rawCfgJSON, &oldCfg)
|
err2 := json.Unmarshal(rawCfgJSON, &oldCfg)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
err = fmt.Errorf("%v; additionally, restoring old config: %v", err, err2)
|
err = fmt.Errorf("%v; additionally, restoring old config: %v", err, err2)
|
||||||
@@ -256,18 +197,18 @@ 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 {
|
||||||
currentCtxMu.RLock()
|
currentCfgMu.RLock()
|
||||||
defer currentCtxMu.RUnlock()
|
defer currentCfgMu.RUnlock()
|
||||||
return unsyncedConfigAccess(http.MethodGet, path, nil, out)
|
return unsyncedConfigAccess(http.MethodGet, path, nil, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
// indexConfigObjects recursively searches ptr for object fields named
|
// indexConfigObjects recursively searches ptr for object fields named
|
||||||
// "@id" and maps that ID value to the full configPath in the index.
|
// "@id" and maps that ID value to the full configPath in the index.
|
||||||
// This function is NOT safe for concurrent access; obtain a write lock
|
// This function is NOT safe for concurrent access; obtain a write lock
|
||||||
// on currentCtxMu.
|
// on currentCfgMu.
|
||||||
func indexConfigObjects(ptr any, configPath string, index map[string]string) error {
|
func indexConfigObjects(ptr interface{}, configPath string, index map[string]string) error {
|
||||||
switch val := ptr.(type) {
|
switch val := ptr.(type) {
|
||||||
case map[string]any:
|
case map[string]interface{}:
|
||||||
for k, v := range val {
|
for k, v := range val {
|
||||||
if k == idKey {
|
if k == idKey {
|
||||||
switch idVal := v.(type) {
|
switch idVal := v.(type) {
|
||||||
@@ -286,7 +227,7 @@ func indexConfigObjects(ptr any, configPath string, index map[string]string) err
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case []any:
|
case []interface{}:
|
||||||
// traverse each element of the array recursively
|
// traverse each element of the array recursively
|
||||||
for i := range val {
|
for i := range val {
|
||||||
err := indexConfigObjects(val[i], path.Join(configPath, strconv.Itoa(i)), index)
|
err := indexConfigObjects(val[i], path.Join(configPath, strconv.Itoa(i)), index)
|
||||||
@@ -304,7 +245,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 currentCtxMu is required! If
|
// instead. A write lock on currentCfgMu 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 {
|
||||||
@@ -328,22 +269,22 @@ func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
|||||||
newCfg.Admin != nil &&
|
newCfg.Admin != nil &&
|
||||||
newCfg.Admin.Config != nil &&
|
newCfg.Admin.Config != nil &&
|
||||||
newCfg.Admin.Config.LoadRaw != nil &&
|
newCfg.Admin.Config.LoadRaw != nil &&
|
||||||
newCfg.Admin.Config.LoadDelay <= 0 {
|
newCfg.Admin.Config.LoadInterval <= 0 {
|
||||||
return fmt.Errorf("recursive config loading detected: pulled configs cannot pull other configs without positive load_delay")
|
return fmt.Errorf("recursive config loading detected: pulled configs cannot pull other configs without positive load_interval")
|
||||||
}
|
}
|
||||||
|
|
||||||
// run the new config and start all its apps
|
// run the new config and start all its apps
|
||||||
ctx, err := run(newCfg, true)
|
err = run(newCfg, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// swap old context (including its config) with the new one
|
// swap old config with the new one
|
||||||
oldCtx := currentCtx
|
oldCfg := currentCfg
|
||||||
currentCtx = ctx
|
currentCfg = newCfg
|
||||||
|
|
||||||
// Stop, Cleanup each old app
|
// Stop, Cleanup each old app
|
||||||
unsyncedStop(oldCtx)
|
unsyncedStop(oldCfg)
|
||||||
|
|
||||||
// autosave a non-nil config, if not disabled
|
// autosave a non-nil config, if not disabled
|
||||||
if allowPersist &&
|
if allowPersist &&
|
||||||
@@ -359,7 +300,7 @@ func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
|||||||
zap.String("dir", dir),
|
zap.String("dir", dir),
|
||||||
zap.Error(err))
|
zap.Error(err))
|
||||||
} else {
|
} else {
|
||||||
err := os.WriteFile(ConfigAutosavePath, cfgJSON, 0600)
|
err := ioutil.WriteFile(ConfigAutosavePath, cfgJSON, 0600)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
Log().Info("autosaved config (load with --resume flag)", zap.String("file", ConfigAutosavePath))
|
Log().Info("autosaved config (load with --resume flag)", zap.String("file", ConfigAutosavePath))
|
||||||
} else {
|
} else {
|
||||||
@@ -387,7 +328,7 @@ func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
|||||||
// This is a low-level function; most callers
|
// This is a low-level function; most callers
|
||||||
// will want to use Run instead, which also
|
// will want to use Run instead, which also
|
||||||
// updates the config's raw state.
|
// updates the config's raw state.
|
||||||
func run(newCfg *Config, start bool) (Context, error) {
|
func run(newCfg *Config, start bool) error {
|
||||||
// because we will need to roll back any state
|
// because we will need to roll back any state
|
||||||
// modifications if this function errors, we
|
// modifications if this function errors, we
|
||||||
// keep a single error value and scope all
|
// keep a single error value and scope all
|
||||||
@@ -418,8 +359,8 @@ func run(newCfg *Config, start bool) (Context, error) {
|
|||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
// also undo any other state changes we made
|
// also undo any other state changes we made
|
||||||
if currentCtx.cfg != nil {
|
if currentCfg != nil {
|
||||||
certmagic.Default.Storage = currentCtx.cfg.storage
|
certmagic.Default.Storage = currentCfg.storage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -431,14 +372,14 @@ func run(newCfg *Config, start bool) (Context, error) {
|
|||||||
}
|
}
|
||||||
err = newCfg.Logging.openLogs(ctx)
|
err = newCfg.Logging.openLogs(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// start the admin endpoint (and stop any prior one)
|
// start the admin endpoint (and stop any prior one)
|
||||||
if start {
|
if start {
|
||||||
err = replaceLocalAdminServer(newCfg)
|
err = replaceLocalAdminServer(newCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx, fmt.Errorf("starting caddy administration endpoint: %v", err)
|
return fmt.Errorf("starting caddy administration endpoint: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,7 +408,7 @@ func run(newCfg *Config, start bool) (Context, error) {
|
|||||||
return nil
|
return nil
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load and Provision each app and their submodules
|
// Load and Provision each app and their submodules
|
||||||
@@ -480,23 +421,16 @@ func run(newCfg *Config, start bool) (Context, error) {
|
|||||||
return nil
|
return nil
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !start {
|
if !start {
|
||||||
return ctx, nil
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
// Provision any admin routers which may need to access
|
|
||||||
// some of the other apps at runtime
|
|
||||||
err = newCfg.Admin.provisionAdminRouters(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return ctx, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start
|
// Start
|
||||||
err = func() error {
|
err = func() error {
|
||||||
started := make([]string, 0, len(newCfg.apps))
|
var started []string
|
||||||
for name, a := range newCfg.apps {
|
for name, a := range newCfg.apps {
|
||||||
err := a.Start()
|
err := a.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -516,12 +450,12 @@ func run(newCfg *Config, start bool) (Context, error) {
|
|||||||
return nil
|
return nil
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// now that the user's config is running, finish setting up anything else,
|
// now that the user's config is running, finish setting up anything else,
|
||||||
// such as remote admin endpoint, config loader, etc.
|
// such as remote admin endpoint, config loader, etc.
|
||||||
return ctx, finishSettingUp(ctx, newCfg)
|
return finishSettingUp(ctx, newCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// finishSettingUp should be run after all apps have successfully started.
|
// finishSettingUp should be run after all apps have successfully started.
|
||||||
@@ -547,74 +481,49 @@ func finishSettingUp(ctx Context, cfg *Config) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading config loader module: %s", err)
|
return fmt.Errorf("loading config loader module: %s", err)
|
||||||
}
|
}
|
||||||
|
runLoadedConfig := func(config []byte) {
|
||||||
logger := Log().Named("config_loader").With(
|
Log().Info("applying dynamically-loaded config", zap.String("loader_module", val.(Module).CaddyModule().ID.Name()), zap.Int("pull_interval", int(cfg.Admin.Config.LoadInterval)))
|
||||||
zap.String("module", val.(Module).CaddyModule().ID.Name()),
|
currentCfgMu.Lock()
|
||||||
zap.Int("load_delay", int(cfg.Admin.Config.LoadDelay)))
|
err := unsyncedDecodeAndRun(config, false)
|
||||||
|
currentCfgMu.Unlock()
|
||||||
runLoadedConfig := func(config []byte) error {
|
if err == nil {
|
||||||
logger.Info("applying dynamically-loaded config")
|
Log().Info("dynamically-loaded config applied successfully")
|
||||||
err := changeConfig(http.MethodPost, "/"+rawConfigKey, config, "", false)
|
} else {
|
||||||
if errors.Is(err, errSameConfig) {
|
Log().Error("running dynamically-loaded config failed", zap.Error(err))
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
logger.Error("failed to run dynamically-loaded config", zap.Error(err))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Info("successfully applied dynamically-loaded config")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
if cfg.Admin.Config.LoadInterval > 0 {
|
||||||
if cfg.Admin.Config.LoadDelay > 0 {
|
|
||||||
go func() {
|
go func() {
|
||||||
// the loop is here to iterate ONLY if there is an error, a no-op config load,
|
select {
|
||||||
// or an unchanged config; in which case we simply wait the delay and try again
|
// if LoadInterval is positive, will wait for the interval and then run with new config
|
||||||
for {
|
case <-time.After(time.Duration(cfg.Admin.Config.LoadInterval)):
|
||||||
timer := time.NewTimer(time.Duration(cfg.Admin.Config.LoadDelay))
|
loadedConfig, err := val.(ConfigLoader).LoadConfig(ctx)
|
||||||
select {
|
if err != nil {
|
||||||
case <-timer.C:
|
Log().Error("loading dynamic config failed", zap.Error(err))
|
||||||
loadedConfig, err := val.(ConfigLoader).LoadConfig(ctx)
|
return
|
||||||
if err != nil {
|
|
||||||
logger.Error("failed loading dynamic config; will retry", zap.Error(err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if loadedConfig == nil {
|
|
||||||
logger.Info("dynamically-loaded config was nil; will retry")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = runLoadedConfig(loadedConfig)
|
|
||||||
if errors.Is(err, errSameConfig) {
|
|
||||||
logger.Info("dynamically-loaded config was unchanged; will retry")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
if !timer.Stop() {
|
|
||||||
<-timer.C
|
|
||||||
}
|
|
||||||
logger.Info("stopping dynamic config loading")
|
|
||||||
}
|
}
|
||||||
break
|
runLoadedConfig(loadedConfig)
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
} else {
|
} else {
|
||||||
// if no LoadDelay is provided, will load config synchronously
|
// if no LoadInterval is provided, will load config synchronously
|
||||||
loadedConfig, err := val.(ConfigLoader).LoadConfig(ctx)
|
loadedConfig, err := val.(ConfigLoader).LoadConfig(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading dynamic config from %T: %v", val, err)
|
return fmt.Errorf("loading dynamic config from %T: %v", val, err)
|
||||||
}
|
}
|
||||||
// do this in a goroutine so current config can finish being loaded; otherwise deadlock
|
// do this in a goroutine so current config can finish being loaded; otherwise deadlock
|
||||||
go func() { _ = runLoadedConfig(loadedConfig) }()
|
go runLoadedConfig(loadedConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigLoader is a type that can load a Caddy config. If
|
// ConfigLoader is a type that can load a Caddy config. The
|
||||||
// the return value is non-nil, it must be valid Caddy JSON;
|
// returned config must be valid Caddy JSON.
|
||||||
// if nil or with non-nil error, it is considered to be a
|
|
||||||
// no-op load and may be retried later.
|
|
||||||
type ConfigLoader interface {
|
type ConfigLoader interface {
|
||||||
LoadConfig(Context) ([]byte, error)
|
LoadConfig(Context) ([]byte, error)
|
||||||
}
|
}
|
||||||
@@ -626,10 +535,10 @@ 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.Lock()
|
currentCfgMu.Lock()
|
||||||
defer currentCtxMu.Unlock()
|
defer currentCfgMu.Unlock()
|
||||||
unsyncedStop(currentCtx)
|
unsyncedStop(currentCfg)
|
||||||
currentCtx = Context{}
|
currentCfg = nil
|
||||||
rawCfgJSON = nil
|
rawCfgJSON = nil
|
||||||
rawCfgIndex = nil
|
rawCfgIndex = nil
|
||||||
rawCfg[rawConfigKey] = nil
|
rawCfg[rawConfigKey] = nil
|
||||||
@@ -642,13 +551,13 @@ func Stop() error {
|
|||||||
// 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
|
||||||
// cfg were successfully started first.
|
// cfg were successfully started first.
|
||||||
func unsyncedStop(ctx Context) {
|
func unsyncedStop(cfg *Config) {
|
||||||
if ctx.cfg == nil {
|
if cfg == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop each app
|
// stop each app
|
||||||
for name, a := range ctx.cfg.apps {
|
for name, a := range cfg.apps {
|
||||||
err := a.Stop()
|
err := a.Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] stop %s: %v", name, err)
|
log.Printf("[ERROR] stop %s: %v", name, err)
|
||||||
@@ -656,13 +565,13 @@ func unsyncedStop(ctx Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// clean up all modules
|
// clean up all modules
|
||||||
ctx.cfg.cancelFunc()
|
cfg.cancelFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate loads, provisions, and validates
|
// Validate loads, provisions, and validates
|
||||||
// cfg, but does not start running it.
|
// cfg, but does not start running it.
|
||||||
func Validate(cfg *Config) error {
|
func Validate(cfg *Config) error {
|
||||||
_, err := run(cfg, false)
|
err := run(cfg, false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
cfg.cancelFunc() // call Cleanup on all modules
|
cfg.cancelFunc() // call Cleanup on all modules
|
||||||
}
|
}
|
||||||
@@ -675,11 +584,7 @@ func Validate(cfg *Config) error {
|
|||||||
// PID file, and shuts down admin endpoint(s) in a goroutine.
|
// PID file, and shuts down admin endpoint(s) in a goroutine.
|
||||||
// Errors are logged along the way, and an appropriate exit
|
// Errors are logged along the way, and an appropriate exit
|
||||||
// code is emitted.
|
// code is emitted.
|
||||||
func exitProcess(ctx context.Context, logger *zap.Logger) {
|
func exitProcess(logger *zap.Logger) {
|
||||||
if err := notify.Stopping(); err != nil {
|
|
||||||
Log().Error("unable to notify service manager of stopping state", zap.Error(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
logger = Log()
|
logger = Log()
|
||||||
}
|
}
|
||||||
@@ -694,7 +599,7 @@ func exitProcess(ctx context.Context, logger *zap.Logger) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// clean up certmagic locks
|
// clean up certmagic locks
|
||||||
certmagic.CleanUpOwnLocks(ctx, logger)
|
certmagic.CleanUpOwnLocks(logger)
|
||||||
|
|
||||||
// remove pidfile
|
// remove pidfile
|
||||||
if pidfile != "" {
|
if pidfile != "" {
|
||||||
@@ -763,12 +668,8 @@ func (d *Duration) UnmarshalJSON(b []byte) error {
|
|||||||
|
|
||||||
// ParseDuration parses a duration string, adding
|
// ParseDuration parses a duration string, adding
|
||||||
// support for the "d" unit meaning number of days,
|
// support for the "d" unit meaning number of days,
|
||||||
// where a day is assumed to be 24h. The maximum
|
// where a day is assumed to be 24h.
|
||||||
// input string length is 1024.
|
|
||||||
func ParseDuration(s string) (time.Duration, error) {
|
func ParseDuration(s string) (time.Duration, error) {
|
||||||
if len(s) > 1024 {
|
|
||||||
return 0, fmt.Errorf("parsing duration: input string too long")
|
|
||||||
}
|
|
||||||
var inNumber bool
|
var inNumber bool
|
||||||
var numStart int
|
var numStart int
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
@@ -799,13 +700,13 @@ func ParseDuration(s string) (time.Duration, error) {
|
|||||||
// have its own unique ID.
|
// have its own unique ID.
|
||||||
func InstanceID() (uuid.UUID, error) {
|
func InstanceID() (uuid.UUID, error) {
|
||||||
uuidFilePath := filepath.Join(AppDataDir(), "instance.uuid")
|
uuidFilePath := filepath.Join(AppDataDir(), "instance.uuid")
|
||||||
uuidFileBytes, err := os.ReadFile(uuidFilePath)
|
uuidFileBytes, err := ioutil.ReadFile(uuidFilePath)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
uuid, err := uuid.NewRandom()
|
uuid, err := uuid.NewRandom()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return uuid, err
|
return uuid, err
|
||||||
}
|
}
|
||||||
err = os.WriteFile(uuidFilePath, []byte(uuid.String()), 0600)
|
err = ioutil.WriteFile(uuidFilePath, []byte(uuid.String()), 0600)
|
||||||
return uuid, err
|
return uuid, err
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return [16]byte{}, err
|
return [16]byte{}, err
|
||||||
@@ -813,106 +714,36 @@ func InstanceID() (uuid.UUID, error) {
|
|||||||
return uuid.ParseBytes(uuidFileBytes)
|
return uuid.ParseBytes(uuidFileBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Version returns the Caddy version in a simple/short form, and
|
// GoModule returns the build info of this Caddy
|
||||||
// a full version string. The short form will not have spaces and
|
// build from debug.BuildInfo (requires Go modules).
|
||||||
// is intended for User-Agent strings and similar, but may be
|
// If no version information is available, a non-nil
|
||||||
// omitting valuable information. Note that Caddy must be compiled
|
// value will still be returned, but with an
|
||||||
// in a special way to properly embed complete version information.
|
// unknown version.
|
||||||
// First this function tries to get the version from the embedded
|
func GoModule() *debug.Module {
|
||||||
// build info provided by go.mod dependencies; then it tries to
|
var mod debug.Module
|
||||||
// get info from embedded VCS information, which requires having
|
return goModule(&mod)
|
||||||
// built Caddy from a git repository. If no version is available,
|
|
||||||
// this function returns "(devel)" becaise Go uses that, but for
|
|
||||||
// the simple form we change it to "unknown".
|
|
||||||
//
|
|
||||||
// See relevant Go issues: https://github.com/golang/go/issues/29228
|
|
||||||
// and https://github.com/golang/go/issues/50603.
|
|
||||||
//
|
|
||||||
// This function is experimental and subject to change or removal.
|
|
||||||
func Version() (simple, full string) {
|
|
||||||
// the currently-recommended way to build Caddy involves
|
|
||||||
// building it as a dependency so we can extract version
|
|
||||||
// information from go.mod tooling; once the upstream
|
|
||||||
// Go issues are fixed, we should just be able to use
|
|
||||||
// bi.Main... hopefully.
|
|
||||||
var module *debug.Module
|
|
||||||
bi, ok := debug.ReadBuildInfo()
|
|
||||||
if ok {
|
|
||||||
// find the Caddy module in the dependency list
|
|
||||||
for _, dep := range bi.Deps {
|
|
||||||
if dep.Path == ImportPath {
|
|
||||||
module = dep
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if module != nil {
|
|
||||||
simple, full = module.Version, module.Version
|
|
||||||
if module.Sum != "" {
|
|
||||||
full += " " + module.Sum
|
|
||||||
}
|
|
||||||
if module.Replace != nil {
|
|
||||||
full += " => " + module.Replace.Path
|
|
||||||
if module.Replace.Version != "" {
|
|
||||||
simple = module.Replace.Version + "_custom"
|
|
||||||
full += "@" + module.Replace.Version
|
|
||||||
}
|
|
||||||
if module.Replace.Sum != "" {
|
|
||||||
full += " " + module.Replace.Sum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if full == "" {
|
|
||||||
var vcsRevision string
|
|
||||||
var vcsTime time.Time
|
|
||||||
var vcsModified bool
|
|
||||||
for _, setting := range bi.Settings {
|
|
||||||
switch setting.Key {
|
|
||||||
case "vcs.revision":
|
|
||||||
vcsRevision = setting.Value
|
|
||||||
case "vcs.time":
|
|
||||||
vcsTime, _ = time.Parse(time.RFC3339, setting.Value)
|
|
||||||
case "vcs.modified":
|
|
||||||
vcsModified, _ = strconv.ParseBool(setting.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if vcsRevision != "" {
|
|
||||||
var modified string
|
|
||||||
if vcsModified {
|
|
||||||
modified = "+modified"
|
|
||||||
}
|
|
||||||
full = fmt.Sprintf("%s%s (%s)", vcsRevision, modified, vcsTime.Format(time.RFC822))
|
|
||||||
simple = vcsRevision
|
|
||||||
|
|
||||||
// use short checksum for simple, if hex-only
|
|
||||||
if _, err := hex.DecodeString(simple); err == nil {
|
|
||||||
simple = simple[:8]
|
|
||||||
}
|
|
||||||
|
|
||||||
// append date to simple since it can be convenient
|
|
||||||
// to know the commit date as part of the version
|
|
||||||
if !vcsTime.IsZero() {
|
|
||||||
simple += "-" + vcsTime.Format("20060102")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if simple == "" || simple == "(devel)" {
|
|
||||||
simple = "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActiveContext returns the currently-active context.
|
// goModule holds the actual implementation of GoModule.
|
||||||
// This function is experimental and might be changed
|
// Allocating debug.Module in GoModule() and passing a
|
||||||
// or removed in the future.
|
// reference to goModule enables mid-stack inlining.
|
||||||
func ActiveContext() Context {
|
func goModule(mod *debug.Module) *debug.Module {
|
||||||
currentCtxMu.RLock()
|
mod.Version = "unknown"
|
||||||
defer currentCtxMu.RUnlock()
|
bi, ok := debug.ReadBuildInfo()
|
||||||
return currentCtx
|
if ok {
|
||||||
|
mod.Path = bi.Main.Path
|
||||||
|
// The recommended way to build Caddy involves
|
||||||
|
// creating a separate main module, which
|
||||||
|
// TODO: track related Go issue: https://github.com/golang/go/issues/29228
|
||||||
|
// once that issue is fixed, we should just be able to use bi.Main... hopefully.
|
||||||
|
for _, dep := range bi.Deps {
|
||||||
|
if dep.Path == ImportPath {
|
||||||
|
return dep
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &bi.Main
|
||||||
|
}
|
||||||
|
return mod
|
||||||
}
|
}
|
||||||
|
|
||||||
// CtxKey is a value type for use with context.WithValue.
|
// CtxKey is a value type for use with context.WithValue.
|
||||||
@@ -920,21 +751,18 @@ 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.
|
// currentCfgMu protects everything in this var block.
|
||||||
currentCtxMu sync.RWMutex
|
currentCfgMu sync.RWMutex
|
||||||
|
|
||||||
// currentCtx is the root context for the currently-running
|
// currentCfg is the currently-running configuration.
|
||||||
// configuration, which can be accessed through this value.
|
currentCfg *Config
|
||||||
// If the Config contained in this value is not nil, then
|
|
||||||
// a config is currently active/running.
|
|
||||||
currentCtx Context
|
|
||||||
|
|
||||||
// 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")
|
||||||
// to maintain parity with the API endpoint and to avoid
|
// to maintain parity with the API endpoint and to avoid
|
||||||
// the special case of having to access/mutate the variable
|
// the special case of having to access/mutate the variable
|
||||||
// directly without traversing into it.
|
// directly without traversing into it.
|
||||||
rawCfg = map[string]any{
|
rawCfg = map[string]interface{}{
|
||||||
rawConfigKey: nil,
|
rawConfigKey: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -947,11 +775,5 @@ var (
|
|||||||
rawCfgIndex map[string]string
|
rawCfgIndex map[string]string
|
||||||
)
|
)
|
||||||
|
|
||||||
// errSameConfig is returned if the new config is the same
|
|
||||||
// as the old one. This isn't usually an actual, actionable
|
|
||||||
// error; it's mostly a sentinel value.
|
|
||||||
var errSameConfig = errors.New("config is unchanged")
|
|
||||||
|
|
||||||
// ImportPath is the package import path for Caddy core.
|
// ImportPath is the package import path for Caddy core.
|
||||||
// This identifier may be removed in the future.
|
|
||||||
const ImportPath = "github.com/caddyserver/caddy/v2"
|
const ImportPath = "github.com/caddyserver/caddy/v2"
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ type Adapter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Adapt converts the Caddyfile config in body to Caddy JSON.
|
// Adapt converts the Caddyfile config in body to Caddy JSON.
|
||||||
func (a Adapter) Adapt(body []byte, options map[string]any) ([]byte, []caddyconfig.Warning, error) {
|
func (a Adapter) Adapt(body []byte, options map[string]interface{}) ([]byte, []caddyconfig.Warning, error) {
|
||||||
if a.ServerType == nil {
|
if a.ServerType == nil {
|
||||||
return nil, nil, fmt.Errorf("no server type")
|
return nil, nil, fmt.Errorf("no server type")
|
||||||
}
|
}
|
||||||
if options == nil {
|
if options == nil {
|
||||||
options = make(map[string]any)
|
options = make(map[string]interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
filename, _ := options["filename"].(string)
|
filename, _ := options["filename"].(string)
|
||||||
@@ -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 the 'caddy fmt' command to fix inconsistencies",
|
Message: "input is not formatted with 'caddy fmt'",
|
||||||
}, true
|
}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ type ServerType interface {
|
|||||||
// (e.g. CLI flags) and creates a Caddy
|
// (e.g. CLI flags) and creates a Caddy
|
||||||
// config, along with any warnings or
|
// config, along with any warnings or
|
||||||
// an error.
|
// an error.
|
||||||
Setup([]ServerBlock, map[string]any) (*caddy.Config, []caddyconfig.Warning, error)
|
Setup([]ServerBlock, map[string]interface{}) (*caddy.Config, []caddyconfig.Warning, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalModule instantiates a module with the given ID and invokes
|
// UnmarshalModule instantiates a module with the given ID and invokes
|
||||||
|
|||||||
Regular → Executable
+8
-129
@@ -19,7 +19,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -146,15 +145,15 @@ func (d *Dispenser) NextLine() bool {
|
|||||||
//
|
//
|
||||||
// Proper use of this method looks like this:
|
// Proper use of this method looks like this:
|
||||||
//
|
//
|
||||||
// for nesting := d.Nesting(); d.NextBlock(nesting); {
|
// for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// However, in simple cases where it is known that the
|
// However, in simple cases where it is known that the
|
||||||
// Dispenser is new and has not already traversed state
|
// Dispenser is new and has not already traversed state
|
||||||
// by a loop over NextBlock(), this will do:
|
// by a loop over NextBlock(), this will do:
|
||||||
//
|
//
|
||||||
// for d.NextBlock(0) {
|
// for d.NextBlock(0) {
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// As with other token parsing logic, a loop over
|
// As with other token parsing logic, a loop over
|
||||||
// NextBlock() should be contained within a loop over
|
// NextBlock() should be contained within a loop over
|
||||||
@@ -202,43 +201,6 @@ func (d *Dispenser) Val() string {
|
|||||||
return d.tokens[d.cursor].Text
|
return d.tokens[d.cursor].Text
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValRaw gets the raw text of the current token (including quotes).
|
|
||||||
// If there is no token loaded, it returns empty string.
|
|
||||||
func (d *Dispenser) ValRaw() string {
|
|
||||||
if d.cursor < 0 || d.cursor >= len(d.tokens) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
quote := d.tokens[d.cursor].wasQuoted
|
|
||||||
if quote > 0 {
|
|
||||||
return string(quote) + d.tokens[d.cursor].Text + string(quote) // string literal
|
|
||||||
}
|
|
||||||
return d.tokens[d.cursor].Text
|
|
||||||
}
|
|
||||||
|
|
||||||
// ScalarVal gets value of the current token, converted to the closest
|
|
||||||
// scalar type. If there is no token loaded, it returns nil.
|
|
||||||
func (d *Dispenser) ScalarVal() any {
|
|
||||||
if d.cursor < 0 || d.cursor >= len(d.tokens) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
quote := d.tokens[d.cursor].wasQuoted
|
|
||||||
text := d.tokens[d.cursor].Text
|
|
||||||
|
|
||||||
if quote > 0 {
|
|
||||||
return text // string literal
|
|
||||||
}
|
|
||||||
if num, err := strconv.Atoi(text); err == nil {
|
|
||||||
return num
|
|
||||||
}
|
|
||||||
if num, err := strconv.ParseFloat(text, 64); err == nil {
|
|
||||||
return num
|
|
||||||
}
|
|
||||||
if bool, err := strconv.ParseBool(text); err == nil {
|
|
||||||
return bool
|
|
||||||
}
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
|
|
||||||
// Line gets the line number of the current token.
|
// Line gets the line number of the current token.
|
||||||
// If there is no token loaded, it returns 0.
|
// If there is no token loaded, it returns 0.
|
||||||
func (d *Dispenser) Line() int {
|
func (d *Dispenser) Line() int {
|
||||||
@@ -287,19 +249,6 @@ func (d *Dispenser) AllArgs(targets ...*string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountRemainingArgs counts the amount of remaining arguments
|
|
||||||
// (tokens on the same line) without consuming the tokens.
|
|
||||||
func (d *Dispenser) CountRemainingArgs() int {
|
|
||||||
count := 0
|
|
||||||
for d.NextArg() {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
d.Prev()
|
|
||||||
}
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemainingArgs loads any more arguments (tokens on the same line)
|
// RemainingArgs loads any more arguments (tokens on the same line)
|
||||||
// into a slice and returns them. Open curly brace tokens also indicate
|
// into a slice and returns them. Open curly brace tokens also indicate
|
||||||
// the end of arguments, and the curly brace is not included in
|
// the end of arguments, and the curly brace is not included in
|
||||||
@@ -312,18 +261,6 @@ func (d *Dispenser) RemainingArgs() []string {
|
|||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemainingArgsRaw loads any more arguments (tokens on the same line,
|
|
||||||
// retaining quotes) into a slice and returns them. Open curly brace
|
|
||||||
// tokens also indicate the end of arguments, and the curly brace is
|
|
||||||
// not included in the return value nor is it loaded.
|
|
||||||
func (d *Dispenser) RemainingArgsRaw() []string {
|
|
||||||
var args []string
|
|
||||||
for d.NextArg() {
|
|
||||||
args = append(args, d.ValRaw())
|
|
||||||
}
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFromNextSegment returns a new dispenser with a copy of
|
// NewFromNextSegment returns a new dispenser with a copy of
|
||||||
// the tokens from the current token until the end of the
|
// the tokens from the current token until the end of the
|
||||||
// "directive" whether that be to the end of the line or
|
// "directive" whether that be to the end of the line or
|
||||||
@@ -412,12 +349,8 @@ func (d *Dispenser) Err(msg string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Errf is like Err, but for formatted error messages
|
// Errf is like Err, but for formatted error messages
|
||||||
func (d *Dispenser) Errf(format string, args ...any) error {
|
func (d *Dispenser) Errf(format string, args ...interface{}) error {
|
||||||
return d.WrapErr(fmt.Errorf(format, args...))
|
err := fmt.Errorf(format, args...)
|
||||||
}
|
|
||||||
|
|
||||||
// WrapErr takes an existing error and adds the Caddyfile file and line number.
|
|
||||||
func (d *Dispenser) WrapErr(err error) error {
|
|
||||||
return fmt.Errorf("%s:%d - Error during parsing: %w", d.File(), d.Line(), err)
|
return fmt.Errorf("%s:%d - Error during parsing: %w", d.File(), d.Line(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,60 +391,6 @@ func (d *Dispenser) isNewLine() bool {
|
|||||||
if d.cursor > len(d.tokens)-1 {
|
if d.cursor > len(d.tokens)-1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
return d.tokens[d.cursor-1].File != d.tokens[d.cursor].File ||
|
||||||
prev := d.tokens[d.cursor-1]
|
d.tokens[d.cursor-1].Line+d.numLineBreaks(d.cursor-1) < d.tokens[d.cursor].Line
|
||||||
curr := d.tokens[d.cursor]
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// line (higher line number) than the next token. It handles imported
|
|
||||||
// tokens correctly. If there isn't a next token, it returns true.
|
|
||||||
func (d *Dispenser) isNextOnNewLine() bool {
|
|
||||||
if d.cursor < 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if d.cursor >= len(d.tokens)-1 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
curr := d.tokens[d.cursor]
|
|
||||||
next := d.tokens[d.cursor+1]
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|||||||
Regular → Executable
@@ -153,10 +153,7 @@ func Format(input []byte) []byte {
|
|||||||
openBraceWritten = true
|
openBraceWritten = true
|
||||||
nextLine()
|
nextLine()
|
||||||
newLines = 0
|
newLines = 0
|
||||||
// prevent infinite nesting from ridiculous inputs (issue #4169)
|
nesting++
|
||||||
if nesting < 10 {
|
|
||||||
nesting++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
// 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 gofuzz
|
// +build gofuzz
|
||||||
|
|
||||||
package caddyfile
|
package caddyfile
|
||||||
|
|
||||||
|
|||||||
@@ -179,11 +179,6 @@ d {
|
|||||||
{$F}
|
{$F}
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
description: "env var placeholders with port",
|
|
||||||
input: `:{$PORT}`,
|
|
||||||
expect: `:{$PORT}`,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
description: "comments",
|
description: "comments",
|
||||||
input: `#a "\n"
|
input: `#a "\n"
|
||||||
|
|||||||
Regular → Executable
+6
-12
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
// Copyright 2015 Light Code Labs, LLC
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -38,7 +38,6 @@ type (
|
|||||||
File string
|
File string
|
||||||
Line int
|
Line int
|
||||||
Text string
|
Text string
|
||||||
wasQuoted rune // enclosing quote character, if any
|
|
||||||
inSnippet bool
|
inSnippet bool
|
||||||
snippetName string
|
snippetName string
|
||||||
}
|
}
|
||||||
@@ -79,9 +78,8 @@ func (l *lexer) next() bool {
|
|||||||
var val []rune
|
var val []rune
|
||||||
var comment, quoted, btQuoted, escaped bool
|
var comment, quoted, btQuoted, escaped bool
|
||||||
|
|
||||||
makeToken := func(quoted rune) bool {
|
makeToken := func() bool {
|
||||||
l.token.Text = string(val)
|
l.token.Text = string(val)
|
||||||
l.token.wasQuoted = quoted
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +87,7 @@ func (l *lexer) next() bool {
|
|||||||
ch, _, err := l.reader.ReadRune()
|
ch, _, err := l.reader.ReadRune()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if len(val) > 0 {
|
if len(val) > 0 {
|
||||||
return makeToken(0)
|
return makeToken()
|
||||||
}
|
}
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
return false
|
return false
|
||||||
@@ -112,10 +110,10 @@ func (l *lexer) next() bool {
|
|||||||
escaped = false
|
escaped = false
|
||||||
} else {
|
} else {
|
||||||
if quoted && ch == '"' {
|
if quoted && ch == '"' {
|
||||||
return makeToken('"')
|
return makeToken()
|
||||||
}
|
}
|
||||||
if btQuoted && ch == '`' {
|
if btQuoted && ch == '`' {
|
||||||
return makeToken('`')
|
return makeToken()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
@@ -141,7 +139,7 @@ func (l *lexer) next() bool {
|
|||||||
comment = false
|
comment = false
|
||||||
}
|
}
|
||||||
if len(val) > 0 {
|
if len(val) > 0 {
|
||||||
return makeToken(0)
|
return makeToken()
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -191,7 +189,3 @@ func Tokenize(input []byte, filename string) ([]Token, error) {
|
|||||||
}
|
}
|
||||||
return tokens, nil
|
return tokens, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t Token) Quoted() bool {
|
|
||||||
return t.wasQuoted > 0
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
// 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 gofuzz
|
// +build gofuzz
|
||||||
|
|
||||||
package caddyfile
|
package caddyfile
|
||||||
|
|
||||||
|
|||||||
Regular → Executable
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
// Copyright 2015 Light Code Labs, LLC
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|||||||
Regular → Executable
+21
-35
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
// Copyright 2015 Light Code Labs, LLC
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -17,14 +17,14 @@ package caddyfile
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parse parses the input just enough to group tokens, in
|
// Parse parses the input just enough to group tokens, in
|
||||||
@@ -37,13 +37,7 @@ import (
|
|||||||
// Environment variables in {$ENVIRONMENT_VARIABLE} notation
|
// Environment variables in {$ENVIRONMENT_VARIABLE} notation
|
||||||
// will be replaced before parsing begins.
|
// will be replaced before parsing begins.
|
||||||
func Parse(filename string, input []byte) ([]ServerBlock, error) {
|
func Parse(filename string, input []byte) ([]ServerBlock, error) {
|
||||||
// unfortunately, we must copy the input because parsing must
|
tokens, err := allTokens(filename, input)
|
||||||
// remain a read-only operation, but we have to expand environment
|
|
||||||
// variables before we parse, which changes the underlying array (#4422)
|
|
||||||
inputCopy := make([]byte, len(input))
|
|
||||||
copy(inputCopy, input)
|
|
||||||
|
|
||||||
tokens, err := allTokens(filename, inputCopy)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -57,23 +51,7 @@ func Parse(filename string, input []byte) ([]ServerBlock, error) {
|
|||||||
return p.parseAll()
|
return p.parseAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
// allTokens lexes the entire input, but does not parse it.
|
|
||||||
// It returns all the tokens from the input, unstructured
|
|
||||||
// and in order. It may mutate input as it expands env vars.
|
|
||||||
func allTokens(filename string, input []byte) ([]Token, error) {
|
|
||||||
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.
|
|
||||||
func replaceEnvVars(input []byte) ([]byte, error) {
|
func replaceEnvVars(input []byte) ([]byte, error) {
|
||||||
var offset int
|
var offset int
|
||||||
for {
|
for {
|
||||||
@@ -118,6 +96,21 @@ func replaceEnvVars(input []byte) ([]byte, error) {
|
|||||||
return input, nil
|
return input, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// allTokens lexes the entire input, but does not parse it.
|
||||||
|
// It returns all the tokens from the input, unstructured
|
||||||
|
// and in order.
|
||||||
|
func allTokens(filename string, input []byte) ([]Token, error) {
|
||||||
|
input, err := replaceEnvVars(input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tokens, err := Tokenize(input, filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tokens, nil
|
||||||
|
}
|
||||||
|
|
||||||
type parser struct {
|
type parser struct {
|
||||||
*Dispenser
|
*Dispenser
|
||||||
block ServerBlock // current server block being parsed
|
block ServerBlock // current server block being parsed
|
||||||
@@ -393,7 +386,7 @@ func (p *parser) doImport() error {
|
|||||||
}
|
}
|
||||||
if len(matches) == 0 {
|
if len(matches) == 0 {
|
||||||
if strings.ContainsAny(globPattern, "*?[]") {
|
if strings.ContainsAny(globPattern, "*?[]") {
|
||||||
caddy.Log().Warn("No files matching import glob pattern", zap.String("pattern", importPattern))
|
log.Printf("[WARNING] No files matching import glob pattern: %s", importPattern)
|
||||||
} else {
|
} else {
|
||||||
return p.Errf("File to import not found: %s", importPattern)
|
return p.Errf("File to import not found: %s", importPattern)
|
||||||
}
|
}
|
||||||
@@ -454,7 +447,7 @@ func (p *parser) doSingleImport(importFile string) ([]Token, error) {
|
|||||||
return nil, p.Errf("Could not import %s: is a directory", importFile)
|
return nil, p.Errf("Could not import %s: is a directory", importFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
input, err := io.ReadAll(file)
|
input, err := ioutil.ReadAll(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
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)
|
||||||
}
|
}
|
||||||
@@ -494,13 +487,6 @@ func (p *parser) directive() error {
|
|||||||
for p.Next() {
|
for p.Next() {
|
||||||
if p.Val() == "{" {
|
if p.Val() == "{" {
|
||||||
p.nesting++
|
p.nesting++
|
||||||
if !p.isNextOnNewLine() && p.Token().wasQuoted == 0 {
|
|
||||||
return p.Err("Unexpected next token after '{' on same line")
|
|
||||||
}
|
|
||||||
} else if p.Val() == "{}" {
|
|
||||||
if p.isNextOnNewLine() && p.Token().wasQuoted == 0 {
|
|
||||||
return p.Err("Unexpected '{}' at end of line")
|
|
||||||
}
|
|
||||||
} else if p.isNewLine() && p.nesting == 0 {
|
} else if p.isNewLine() && p.nesting == 0 {
|
||||||
p.cursor-- // read too far
|
p.cursor-- // read too far
|
||||||
break
|
break
|
||||||
|
|||||||
Regular → Executable
+7
-20
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
// Copyright 2015 Light Code Labs, LLC
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -16,6 +16,7 @@ package caddyfile
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -191,20 +192,6 @@ func TestParseOneAndImport(t *testing.T) {
|
|||||||
|
|
||||||
{``, false, []string{}, []int{}},
|
{``, false, []string{}, []int{}},
|
||||||
|
|
||||||
// Unexpected next token after '{' on same line
|
|
||||||
{`localhost
|
|
||||||
dir1 { a b }`, true, []string{"localhost"}, []int{}},
|
|
||||||
// Workaround with quotes
|
|
||||||
{`localhost
|
|
||||||
dir1 "{" a b "}"`, false, []string{"localhost"}, []int{5}},
|
|
||||||
|
|
||||||
// Unexpected '{}' at end of line
|
|
||||||
{`localhost
|
|
||||||
dir1 {}`, true, []string{"localhost"}, []int{}},
|
|
||||||
// Workaround with quotes
|
|
||||||
{`localhost
|
|
||||||
dir1 "{}"`, false, []string{"localhost"}, []int{2}},
|
|
||||||
|
|
||||||
// import with args
|
// import with args
|
||||||
{`import testdata/import_args0.txt a`, false, []string{"a"}, []int{}},
|
{`import testdata/import_args0.txt a`, false, []string{"a"}, []int{}},
|
||||||
{`import testdata/import_args1.txt a b`, false, []string{"a", "b"}, []int{}},
|
{`import testdata/import_args1.txt a b`, false, []string{"a", "b"}, []int{}},
|
||||||
@@ -293,7 +280,7 @@ func TestRecursiveImport(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// test relative recursive import
|
// test relative recursive import
|
||||||
err = os.WriteFile(recursiveFile1, []byte(
|
err = ioutil.WriteFile(recursiveFile1, []byte(
|
||||||
`localhost
|
`localhost
|
||||||
dir1
|
dir1
|
||||||
import recursive_import_test2`), 0644)
|
import recursive_import_test2`), 0644)
|
||||||
@@ -302,7 +289,7 @@ func TestRecursiveImport(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer os.Remove(recursiveFile1)
|
defer os.Remove(recursiveFile1)
|
||||||
|
|
||||||
err = os.WriteFile(recursiveFile2, []byte("dir2 1"), 0644)
|
err = ioutil.WriteFile(recursiveFile2, []byte("dir2 1"), 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -327,7 +314,7 @@ func TestRecursiveImport(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// test absolute recursive import
|
// test absolute recursive import
|
||||||
err = os.WriteFile(recursiveFile1, []byte(
|
err = ioutil.WriteFile(recursiveFile1, []byte(
|
||||||
`localhost
|
`localhost
|
||||||
dir1
|
dir1
|
||||||
import `+recursiveFile2), 0644)
|
import `+recursiveFile2), 0644)
|
||||||
@@ -383,7 +370,7 @@ func TestDirectiveImport(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(directiveFile, []byte(`prop1 1
|
err = ioutil.WriteFile(directiveFile, []byte(`prop1 1
|
||||||
prop2 2`), 0644)
|
prop2 2`), 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -646,7 +633,7 @@ func TestSnippets(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func writeStringToTempFileOrDie(t *testing.T, str string) (pathToFile string) {
|
func writeStringToTempFileOrDie(t *testing.T, str string) (pathToFile string) {
|
||||||
file, err := os.CreateTemp("", t.Name())
|
file, err := ioutil.TempFile("", t.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err) // get a stack trace so we know where this was called from.
|
panic(err) // get a stack trace so we know where this was called from.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
// Adapter is a type which can adapt a configuration to Caddy JSON.
|
// Adapter is a type which can adapt a configuration to Caddy JSON.
|
||||||
// It returns the results and any warnings, or an error.
|
// It returns the results and any warnings, or an error.
|
||||||
type Adapter interface {
|
type Adapter interface {
|
||||||
Adapt(body []byte, options map[string]any) ([]byte, []Warning, error)
|
Adapt(body []byte, options map[string]interface{}) ([]byte, []Warning, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning represents a warning or notice related to conversion.
|
// Warning represents a warning or notice related to conversion.
|
||||||
@@ -48,7 +48,7 @@ func (w Warning) String() string {
|
|||||||
// are converted to warnings. This is convenient when filling config
|
// are converted to warnings. This is convenient when filling config
|
||||||
// structs that require a json.RawMessage, without having to worry
|
// structs that require a json.RawMessage, without having to worry
|
||||||
// about errors.
|
// about errors.
|
||||||
func JSON(val any, warnings *[]Warning) json.RawMessage {
|
func JSON(val interface{}, warnings *[]Warning) json.RawMessage {
|
||||||
b, err := json.Marshal(val)
|
b, err := json.Marshal(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if warnings != nil {
|
if warnings != nil {
|
||||||
@@ -64,9 +64,9 @@ func JSON(val any, warnings *[]Warning) json.RawMessage {
|
|||||||
// for encoding module values where the module name has to be described within
|
// for encoding module values where the module name has to be described within
|
||||||
// the object by a certain key; for example, `"handler": "file_server"` for a
|
// the object by a certain key; for example, `"handler": "file_server"` for a
|
||||||
// file server HTTP handler (fieldName="handler" and fieldVal="file_server").
|
// file server HTTP handler (fieldName="handler" and fieldVal="file_server").
|
||||||
// The val parameter must encode into a map[string]any (i.e. it must be
|
// The val parameter must encode into a map[string]interface{} (i.e. it must be
|
||||||
// a struct or map). Any errors are converted into warnings.
|
// a struct or map). Any errors are converted into warnings.
|
||||||
func JSONModuleObject(val any, fieldName, fieldVal string, warnings *[]Warning) json.RawMessage {
|
func JSONModuleObject(val interface{}, fieldName, fieldVal string, warnings *[]Warning) json.RawMessage {
|
||||||
// encode to a JSON object first
|
// encode to a JSON object first
|
||||||
enc, err := json.Marshal(val)
|
enc, err := json.Marshal(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -77,7 +77,7 @@ func JSONModuleObject(val any, fieldName, fieldVal string, warnings *[]Warning)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// then decode the object
|
// then decode the object
|
||||||
var tmp map[string]any
|
var tmp map[string]interface{}
|
||||||
err = json.Unmarshal(enc, &tmp)
|
err = json.Unmarshal(enc, &tmp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if warnings != nil {
|
if warnings != nil {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ package httpcaddyfile
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -36,12 +35,12 @@ import (
|
|||||||
// server block that share the same address stay grouped together so the config
|
// server block that share the same address stay grouped together so the config
|
||||||
// isn't repeated unnecessarily. For example, this Caddyfile:
|
// isn't repeated unnecessarily. For example, this Caddyfile:
|
||||||
//
|
//
|
||||||
// example.com {
|
// example.com {
|
||||||
// bind 127.0.0.1
|
// bind 127.0.0.1
|
||||||
// }
|
// }
|
||||||
// www.example.com, example.net/path, localhost:9999 {
|
// www.example.com, example.net/path, localhost:9999 {
|
||||||
// bind 127.0.0.1 1.2.3.4
|
// bind 127.0.0.1 1.2.3.4
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// has two server blocks to start with. But expressed in this Caddyfile are
|
// has two server blocks to start with. But expressed in this Caddyfile are
|
||||||
// actually 4 listener addresses: 127.0.0.1:443, 1.2.3.4:443, 127.0.0.1:9999,
|
// actually 4 listener addresses: 127.0.0.1:443, 1.2.3.4:443, 127.0.0.1:9999,
|
||||||
@@ -77,7 +76,7 @@ import (
|
|||||||
// multiple addresses to the same lists of server blocks (a many:many mapping).
|
// multiple addresses to the same lists of server blocks (a many:many mapping).
|
||||||
// (Doing this is essentially a map-reduce technique.)
|
// (Doing this is essentially a map-reduce technique.)
|
||||||
func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock,
|
func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock,
|
||||||
options map[string]any) (map[string][]serverBlock, error) {
|
options map[string]interface{}) (map[string][]serverBlock, error) {
|
||||||
sbmap := make(map[string][]serverBlock)
|
sbmap := make(map[string][]serverBlock)
|
||||||
|
|
||||||
for i, sblock := range originalServerBlocks {
|
for i, sblock := range originalServerBlocks {
|
||||||
@@ -103,20 +102,12 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// make a slice of the map keys so we can iterate in sorted order
|
|
||||||
addrs := make([]string, 0, len(addrToKeys))
|
|
||||||
for k := range addrToKeys {
|
|
||||||
addrs = append(addrs, k)
|
|
||||||
}
|
|
||||||
sort.Strings(addrs)
|
|
||||||
|
|
||||||
// now that we know which addresses serve which keys of this
|
// now that we know which addresses serve which keys of this
|
||||||
// server block, we iterate that mapping and create a list of
|
// server block, we iterate that mapping and create a list of
|
||||||
// new server blocks for each address where the keys of the
|
// new server blocks for each address where the keys of the
|
||||||
// server block are only the ones which use the address; but
|
// server block are only the ones which use the address; but
|
||||||
// the contents (tokens) are of course the same
|
// the contents (tokens) are of course the same
|
||||||
for _, addr := range addrs {
|
for addr, keys := range addrToKeys {
|
||||||
keys := addrToKeys[addr]
|
|
||||||
// parse keys so that we only have to do it once
|
// parse keys so that we only have to do it once
|
||||||
parsedKeys := make([]Address, 0, len(keys))
|
parsedKeys := make([]Address, 0, len(keys))
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
@@ -170,7 +161,6 @@ func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]se
|
|||||||
delete(addrToServerBlocks, otherAddr)
|
delete(addrToServerBlocks, otherAddr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Strings(a.addresses)
|
|
||||||
|
|
||||||
sbaddrs = append(sbaddrs, a)
|
sbaddrs = append(sbaddrs, a)
|
||||||
}
|
}
|
||||||
@@ -184,10 +174,8 @@ func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]se
|
|||||||
return sbaddrs
|
return sbaddrs
|
||||||
}
|
}
|
||||||
|
|
||||||
// listenerAddrsForServerBlockKey essentially converts the Caddyfile
|
|
||||||
// site addresses to Caddy listener addresses for each server block.
|
|
||||||
func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string,
|
func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string,
|
||||||
options map[string]any) ([]string, error) {
|
options map[string]interface{}) ([]string, error) {
|
||||||
addr, err := ParseAddress(key)
|
addr, err := ParseAddress(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parsing key: %v", err)
|
return nil, fmt.Errorf("parsing key: %v", err)
|
||||||
@@ -219,42 +207,24 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
|
|||||||
return nil, fmt.Errorf("[%s] scheme and port violate convention", key)
|
return nil, fmt.Errorf("[%s] scheme and port violate convention", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the bind directive specifies hosts (and potentially network), but is optional
|
// the bind directive specifies hosts, but is optional
|
||||||
lnHosts := make([]string, 0, len(sblock.pile["bind"]))
|
lnHosts := make([]string, 0, len(sblock.pile))
|
||||||
for _, cfgVal := range sblock.pile["bind"] {
|
for _, cfgVal := range sblock.pile["bind"] {
|
||||||
lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
|
lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
|
||||||
}
|
}
|
||||||
if len(lnHosts) == 0 {
|
if len(lnHosts) == 0 {
|
||||||
if defaultBind, ok := options["default_bind"].([]string); ok {
|
lnHosts = []string{""}
|
||||||
lnHosts = defaultBind
|
|
||||||
} else {
|
|
||||||
lnHosts = []string{""}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// use a map to prevent duplication
|
// use a map to prevent duplication
|
||||||
listeners := make(map[string]struct{})
|
listeners := make(map[string]struct{})
|
||||||
for _, lnHost := range lnHosts {
|
for _, host := range lnHosts {
|
||||||
// normally we would simply append the port,
|
addr, err := caddy.ParseNetworkAddress(host)
|
||||||
// but if lnHost is IPv6, we need to ensure it
|
if err == nil && addr.IsUnixNetwork() {
|
||||||
// is enclosed in [ ]; net.JoinHostPort does
|
listeners[host] = struct{}{}
|
||||||
// this for us, but lnHost might also have a
|
} else {
|
||||||
// network type in front (e.g. "tcp/") leading
|
listeners[net.JoinHostPort(host, lnPort)] = struct{}{}
|
||||||
// to "[tcp/::1]" which causes parsing failures
|
|
||||||
// later; what we need is "tcp/[::1]", so we have
|
|
||||||
// to split the network and host, then re-combine
|
|
||||||
network, host, ok := strings.Cut(lnHost, "/")
|
|
||||||
if !ok {
|
|
||||||
host = network
|
|
||||||
network = ""
|
|
||||||
}
|
}
|
||||||
host = strings.Trim(host, "[]") // IPv6
|
|
||||||
networkAddr := caddy.JoinNetworkAddress(network, host, lnPort)
|
|
||||||
addr, err := caddy.ParseNetworkAddress(networkAddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing network address: %v", err)
|
|
||||||
}
|
|
||||||
listeners[addr.String()] = struct{}{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// now turn map into list
|
// now turn map into list
|
||||||
@@ -262,7 +232,6 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
|
|||||||
for lnStr := range listeners {
|
for lnStr := range listeners {
|
||||||
listenersList = append(listenersList, lnStr)
|
listenersList = append(listenersList, lnStr)
|
||||||
}
|
}
|
||||||
sort.Strings(listenersList)
|
|
||||||
|
|
||||||
return listenersList, nil
|
return listenersList, nil
|
||||||
}
|
}
|
||||||
@@ -367,10 +336,8 @@ func (a Address) Normalize() Address {
|
|||||||
|
|
||||||
// ensure host is normalized if it's an IP address
|
// ensure host is normalized if it's an IP address
|
||||||
host := strings.TrimSpace(a.Host)
|
host := strings.TrimSpace(a.Host)
|
||||||
if ip, err := netip.ParseAddr(host); err == nil {
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
if ip.Is6() && !ip.Is4() && !ip.Is4In6() {
|
host = ip.String()
|
||||||
host = ip.String()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Address{
|
return Address{
|
||||||
@@ -382,6 +349,28 @@ func (a Address) Normalize() Address {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Key returns a string form of a, much like String() does, but this
|
||||||
|
// method doesn't add anything default that wasn't in the original.
|
||||||
|
func (a Address) Key() string {
|
||||||
|
res := ""
|
||||||
|
if a.Scheme != "" {
|
||||||
|
res += a.Scheme + "://"
|
||||||
|
}
|
||||||
|
if a.Host != "" {
|
||||||
|
res += a.Host
|
||||||
|
}
|
||||||
|
// insert port only if the original has its own explicit port
|
||||||
|
if a.Port != "" &&
|
||||||
|
len(a.Original) >= len(res) &&
|
||||||
|
strings.HasPrefix(a.Original[len(res):], ":"+a.Port) {
|
||||||
|
res += ":" + a.Port
|
||||||
|
}
|
||||||
|
if a.Path != "" {
|
||||||
|
res += a.Path
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
// lowerExceptPlaceholders lowercases s except within
|
// lowerExceptPlaceholders lowercases s except within
|
||||||
// placeholders (substrings in non-escaped '{ }' spans).
|
// placeholders (substrings in non-escaped '{ }' spans).
|
||||||
// See https://github.com/caddyserver/caddy/issues/3264
|
// See https://github.com/caddyserver/caddy/issues/3264
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
// 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 gofuzz
|
// +build gofuzz
|
||||||
|
|
||||||
package httpcaddyfile
|
package httpcaddyfile
|
||||||
|
|
||||||
|
|||||||
@@ -106,128 +106,67 @@ func TestAddressString(t *testing.T) {
|
|||||||
func TestKeyNormalization(t *testing.T) {
|
func TestKeyNormalization(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
input string
|
input string
|
||||||
expect Address
|
expect string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
input: "example.com",
|
input: "example.com",
|
||||||
expect: Address{
|
expect: "example.com",
|
||||||
Host: "example.com",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "http://host:1234/path",
|
input: "http://host:1234/path",
|
||||||
expect: Address{
|
expect: "http://host:1234/path",
|
||||||
Scheme: "http",
|
|
||||||
Host: "host",
|
|
||||||
Port: "1234",
|
|
||||||
Path: "/path",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "HTTP://A/ABCDEF",
|
input: "HTTP://A/ABCDEF",
|
||||||
expect: Address{
|
expect: "http://a/ABCDEF",
|
||||||
Scheme: "http",
|
|
||||||
Host: "a",
|
|
||||||
Path: "/ABCDEF",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "A/ABCDEF",
|
input: "A/ABCDEF",
|
||||||
expect: Address{
|
expect: "a/ABCDEF",
|
||||||
Host: "a",
|
|
||||||
Path: "/ABCDEF",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "A:2015/Path",
|
input: "A:2015/Path",
|
||||||
expect: Address{
|
expect: "a:2015/Path",
|
||||||
Host: "a",
|
|
||||||
Port: "2015",
|
|
||||||
Path: "/Path",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "sub.{env.MY_DOMAIN}",
|
input: "sub.{env.MY_DOMAIN}",
|
||||||
expect: Address{
|
expect: "sub.{env.MY_DOMAIN}",
|
||||||
Host: "sub.{env.MY_DOMAIN}",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "sub.ExAmPle",
|
input: "sub.ExAmPle",
|
||||||
expect: Address{
|
expect: "sub.example",
|
||||||
Host: "sub.example",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "sub.\\{env.MY_DOMAIN\\}",
|
input: "sub.\\{env.MY_DOMAIN\\}",
|
||||||
expect: Address{
|
expect: "sub.\\{env.my_domain\\}",
|
||||||
Host: "sub.\\{env.my_domain\\}",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "sub.{env.MY_DOMAIN}.com",
|
input: "sub.{env.MY_DOMAIN}.com",
|
||||||
expect: Address{
|
expect: "sub.{env.MY_DOMAIN}.com",
|
||||||
Host: "sub.{env.MY_DOMAIN}.com",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: ":80",
|
input: ":80",
|
||||||
expect: Address{
|
expect: ":80",
|
||||||
Port: "80",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: ":443",
|
input: ":443",
|
||||||
expect: Address{
|
expect: ":443",
|
||||||
Port: "443",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: ":1234",
|
input: ":1234",
|
||||||
expect: Address{
|
expect: ":1234",
|
||||||
Port: "1234",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "",
|
input: "",
|
||||||
expect: Address{},
|
expect: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: ":",
|
input: ":",
|
||||||
expect: Address{},
|
expect: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "[::]",
|
input: "[::]",
|
||||||
expect: Address{
|
expect: "::",
|
||||||
Host: "::",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "127.0.0.1",
|
|
||||||
expect: Address{
|
|
||||||
Host: "127.0.0.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:1234",
|
|
||||||
expect: Address{
|
|
||||||
Host: "2001:db8:85a3:8d3:1319:8a2e:370:7348",
|
|
||||||
Port: "1234",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// IPv4 address in IPv6 form (#4381)
|
|
||||||
input: "[::ffff:cff4:e77d]:1234",
|
|
||||||
expect: Address{
|
|
||||||
Host: "::ffff:cff4:e77d",
|
|
||||||
Port: "1234",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "::ffff:cff4:e77d",
|
|
||||||
expect: Address{
|
|
||||||
Host: "::ffff:cff4:e77d",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i, tc := range testCases {
|
for i, tc := range testCases {
|
||||||
@@ -236,18 +175,9 @@ func TestKeyNormalization(t *testing.T) {
|
|||||||
t.Errorf("Test %d: Parsing address '%s': %v", i, tc.input, err)
|
t.Errorf("Test %d: Parsing address '%s': %v", i, tc.input, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
actual := addr.Normalize()
|
if actual := addr.Normalize().Key(); actual != tc.expect {
|
||||||
if actual.Scheme != tc.expect.Scheme {
|
t.Errorf("Test %d: Input '%s': Expected '%s' but got '%s'", i, tc.input, tc.expect, actual)
|
||||||
t.Errorf("Test %d: Input '%s': Expected Scheme='%s' but got Scheme='%s'", i, tc.input, tc.expect.Scheme, actual.Scheme)
|
|
||||||
}
|
|
||||||
if actual.Host != tc.expect.Host {
|
|
||||||
t.Errorf("Test %d: Input '%s': Expected Host='%s' but got Host='%s'", i, tc.input, tc.expect.Host, actual.Host)
|
|
||||||
}
|
|
||||||
if actual.Port != tc.expect.Port {
|
|
||||||
t.Errorf("Test %d: Input '%s': Expected Port='%s' but got Port='%s'", i, tc.input, tc.expect.Port, actual.Port)
|
|
||||||
}
|
|
||||||
if actual.Path != tc.expect.Path {
|
|
||||||
t.Errorf("Test %d: Input '%s': Expected Path='%s' but got Path='%s'", i, tc.input, tc.expect.Path, actual.Path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -39,7 +39,6 @@ func init() {
|
|||||||
RegisterDirective("bind", parseBind)
|
RegisterDirective("bind", parseBind)
|
||||||
RegisterDirective("tls", parseTLS)
|
RegisterDirective("tls", parseTLS)
|
||||||
RegisterHandlerDirective("root", parseRoot)
|
RegisterHandlerDirective("root", parseRoot)
|
||||||
RegisterHandlerDirective("vars", parseVars)
|
|
||||||
RegisterHandlerDirective("redir", parseRedir)
|
RegisterHandlerDirective("redir", parseRedir)
|
||||||
RegisterHandlerDirective("respond", parseRespond)
|
RegisterHandlerDirective("respond", parseRespond)
|
||||||
RegisterHandlerDirective("abort", parseAbort)
|
RegisterHandlerDirective("abort", parseAbort)
|
||||||
@@ -48,12 +47,12 @@ func init() {
|
|||||||
RegisterHandlerDirective("handle", parseHandle)
|
RegisterHandlerDirective("handle", parseHandle)
|
||||||
RegisterDirective("handle_errors", parseHandleErrors)
|
RegisterDirective("handle_errors", parseHandleErrors)
|
||||||
RegisterDirective("log", parseLog)
|
RegisterDirective("log", parseLog)
|
||||||
RegisterHandlerDirective("skip_log", parseSkipLog)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseBind parses the bind directive. Syntax:
|
// parseBind parses the bind directive. Syntax:
|
||||||
//
|
//
|
||||||
// bind <addresses...>
|
// bind <addresses...>
|
||||||
|
//
|
||||||
func parseBind(h Helper) ([]ConfigValue, error) {
|
func parseBind(h Helper) ([]ConfigValue, error) {
|
||||||
var lnHosts []string
|
var lnHosts []string
|
||||||
for h.Next() {
|
for h.Next() {
|
||||||
@@ -64,28 +63,27 @@ func parseBind(h Helper) ([]ConfigValue, error) {
|
|||||||
|
|
||||||
// parseTLS parses the tls directive. Syntax:
|
// parseTLS parses the tls directive. Syntax:
|
||||||
//
|
//
|
||||||
// tls [<email>|internal]|[<cert_file> <key_file>] {
|
// tls [<email>|internal]|[<cert_file> <key_file>] {
|
||||||
// protocols <min> [<max>]
|
// protocols <min> [<max>]
|
||||||
// ciphers <cipher_suites...>
|
// ciphers <cipher_suites...>
|
||||||
// curves <curves...>
|
// curves <curves...>
|
||||||
// client_auth {
|
// client_auth {
|
||||||
// mode [request|require|verify_if_given|require_and_verify]
|
// mode [request|require|verify_if_given|require_and_verify]
|
||||||
// trusted_ca_cert <base64_der>
|
// trusted_ca_cert <base64_der>
|
||||||
// trusted_ca_cert_file <filename>
|
// trusted_ca_cert_file <filename>
|
||||||
// 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>
|
||||||
// dns <provider_name> [...]
|
// dns <provider_name> [...]
|
||||||
// on_demand
|
// on_demand
|
||||||
// eab <key_id> <mac_key>
|
// eab <key_id> <mac_key>
|
||||||
// issuer <module_name> [...]
|
// issuer <module_name> [...]
|
||||||
// get_certificate <module_name> [...]
|
// }
|
||||||
// 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)
|
||||||
var fileLoader caddytls.FileLoader
|
var fileLoader caddytls.FileLoader
|
||||||
@@ -95,7 +93,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||||||
var keyType string
|
var keyType string
|
||||||
var internalIssuer *caddytls.InternalIssuer
|
var internalIssuer *caddytls.InternalIssuer
|
||||||
var issuers []certmagic.Issuer
|
var issuers []certmagic.Issuer
|
||||||
var certManagers []certmagic.Manager
|
|
||||||
var onDemand bool
|
var onDemand bool
|
||||||
|
|
||||||
for h.Next() {
|
for h.Next() {
|
||||||
@@ -233,7 +230,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||||||
return nil, h.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
filename := h.Val()
|
filename := h.Val()
|
||||||
certDataPEM, err := os.ReadFile(filename)
|
certDataPEM, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -310,22 +307,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||||||
}
|
}
|
||||||
issuers = append(issuers, issuer)
|
issuers = append(issuers, issuer)
|
||||||
|
|
||||||
case "get_certificate":
|
|
||||||
if !h.NextArg() {
|
|
||||||
return nil, h.ArgErr()
|
|
||||||
}
|
|
||||||
modName := h.Val()
|
|
||||||
modID := "tls.get_certificate." + modName
|
|
||||||
unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
certManager, ok := unm.(certmagic.Manager)
|
|
||||||
if !ok {
|
|
||||||
return nil, h.Errf("module %s (%T) is not a certmagic.CertificateManager", modID, unm)
|
|
||||||
}
|
|
||||||
certManagers = append(certManagers, certManager)
|
|
||||||
|
|
||||||
case "dns":
|
case "dns":
|
||||||
if !h.NextArg() {
|
if !h.NextArg() {
|
||||||
return nil, h.ArgErr()
|
return nil, h.ArgErr()
|
||||||
@@ -363,22 +344,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||||||
}
|
}
|
||||||
acmeIssuer.Challenges.DNS.Resolvers = args
|
acmeIssuer.Challenges.DNS.Resolvers = args
|
||||||
|
|
||||||
case "dns_challenge_override_domain":
|
|
||||||
arg := h.RemainingArgs()
|
|
||||||
if len(arg) != 1 {
|
|
||||||
return nil, h.ArgErr()
|
|
||||||
}
|
|
||||||
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.OverrideDomain = arg[0]
|
|
||||||
|
|
||||||
case "ca_root":
|
case "ca_root":
|
||||||
arg := h.RemainingArgs()
|
arg := h.RemainingArgs()
|
||||||
if len(arg) != 1 {
|
if len(arg) != 1 {
|
||||||
@@ -395,12 +360,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||||||
}
|
}
|
||||||
onDemand = true
|
onDemand = true
|
||||||
|
|
||||||
case "insecure_secrets_log":
|
|
||||||
if !h.NextArg() {
|
|
||||||
return nil, h.ArgErr()
|
|
||||||
}
|
|
||||||
cp.InsecureSecretsLog = h.Val()
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, h.Errf("unknown subdirective: %s", h.Val())
|
return nil, h.Errf("unknown subdirective: %s", h.Val())
|
||||||
}
|
}
|
||||||
@@ -494,12 +453,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||||||
Value: true,
|
Value: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for _, certManager := range certManagers {
|
|
||||||
configVals = append(configVals, ConfigValue{
|
|
||||||
Class: "tls.cert_manager",
|
|
||||||
Value: certManager,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// custom certificate selection
|
// custom certificate selection
|
||||||
if len(certSelector.AnyTag) > 0 {
|
if len(certSelector.AnyTag) > 0 {
|
||||||
@@ -521,7 +474,8 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||||||
|
|
||||||
// parseRoot parses the root directive. Syntax:
|
// parseRoot parses the root directive. Syntax:
|
||||||
//
|
//
|
||||||
// root [<matcher>] <path>
|
// root [<matcher>] <path>
|
||||||
|
//
|
||||||
func parseRoot(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
func parseRoot(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||||
var root string
|
var root string
|
||||||
for h.Next() {
|
for h.Next() {
|
||||||
@@ -536,22 +490,10 @@ func parseRoot(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
|||||||
return caddyhttp.VarsMiddleware{"root": root}, nil
|
return caddyhttp.VarsMiddleware{"root": root}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseVars parses the vars directive. See its UnmarshalCaddyfile method for syntax.
|
|
||||||
func parseVars(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
|
||||||
v := new(caddyhttp.VarsMiddleware)
|
|
||||||
err := v.UnmarshalCaddyfile(h.Dispenser)
|
|
||||||
return v, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseRedir parses the redir directive. Syntax:
|
// parseRedir parses the redir directive. Syntax:
|
||||||
//
|
//
|
||||||
// redir [<matcher>] <to> [<code>]
|
// redir [<matcher>] <to> [<code>]
|
||||||
//
|
//
|
||||||
// <code> can be "permanent" for 301, "temporary" for 302 (default),
|
|
||||||
// a placeholder, or any number in the 3xx range or 401. The special
|
|
||||||
// code "html" can be used to redirect only browser clients (will
|
|
||||||
// respond with HTTP 200 and no Location header; redirect is performed
|
|
||||||
// with JS and a meta tag).
|
|
||||||
func parseRedir(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
func parseRedir(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||||
if !h.Next() {
|
if !h.Next() {
|
||||||
return nil, h.ArgErr()
|
return nil, h.ArgErr()
|
||||||
@@ -568,7 +510,6 @@ func parseRedir(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body string
|
var body string
|
||||||
var hdr http.Header
|
|
||||||
switch code {
|
switch code {
|
||||||
case "permanent":
|
case "permanent":
|
||||||
code = "301"
|
code = "301"
|
||||||
@@ -589,37 +530,20 @@ func parseRedir(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
|||||||
`
|
`
|
||||||
safeTo := html.EscapeString(to)
|
safeTo := html.EscapeString(to)
|
||||||
body = fmt.Sprintf(metaRedir, safeTo, safeTo, safeTo, safeTo)
|
body = fmt.Sprintf(metaRedir, safeTo, safeTo, safeTo, safeTo)
|
||||||
code = "200" // don't redirect non-browser clients
|
code = "302"
|
||||||
default:
|
default:
|
||||||
// Allow placeholders for the code
|
|
||||||
if strings.HasPrefix(code, "{") {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Try to validate as an integer otherwise
|
|
||||||
codeInt, err := strconv.Atoi(code)
|
codeInt, err := strconv.Atoi(code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, h.Errf("Not a supported redir code type or not valid integer: '%s'", code)
|
return nil, h.Errf("Not a supported redir code type or not valid integer: '%s'", code)
|
||||||
}
|
}
|
||||||
// Sometimes, a 401 with Location header is desirable because
|
if codeInt < 300 || codeInt > 399 {
|
||||||
// requests made with XHR will "eat" the 3xx redirect; so if
|
return nil, h.Errf("Redir code not in the 3xx range: '%v'", codeInt)
|
||||||
// the intent was to redirect to an auth page, a 3xx won't
|
|
||||||
// work. Responding with 401 allows JS code to read the
|
|
||||||
// Location header and do a window.location redirect manually.
|
|
||||||
// see https://stackoverflow.com/a/2573589/846934
|
|
||||||
// see https://github.com/oauth2-proxy/oauth2-proxy/issues/1522
|
|
||||||
if codeInt < 300 || (codeInt > 399 && codeInt != 401) {
|
|
||||||
return nil, h.Errf("Redir code not in the 3xx range or 401: '%v'", codeInt)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't redirect non-browser clients
|
|
||||||
if code != "200" {
|
|
||||||
hdr = http.Header{"Location": []string{to}}
|
|
||||||
}
|
|
||||||
|
|
||||||
return caddyhttp.StaticResponse{
|
return caddyhttp.StaticResponse{
|
||||||
StatusCode: caddyhttp.WeakString(code),
|
StatusCode: caddyhttp.WeakString(code),
|
||||||
Headers: hdr,
|
Headers: http.Header{"Location": []string{to}},
|
||||||
Body: body,
|
Body: body,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -699,11 +623,12 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
|
|||||||
|
|
||||||
// parseLog parses the log directive. Syntax:
|
// parseLog parses the log directive. Syntax:
|
||||||
//
|
//
|
||||||
// log {
|
// log {
|
||||||
// output <writer_module> ...
|
// output <writer_module> ...
|
||||||
// format <encoder_module> ...
|
// format <encoder_module> ...
|
||||||
// level <level>
|
// level <level>
|
||||||
// }
|
// }
|
||||||
|
//
|
||||||
func parseLog(h Helper) ([]ConfigValue, error) {
|
func parseLog(h Helper) ([]ConfigValue, error) {
|
||||||
return parseLogHelper(h, nil)
|
return parseLogHelper(h, nil)
|
||||||
}
|
}
|
||||||
@@ -862,15 +787,3 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
|||||||
}
|
}
|
||||||
return configValues, nil
|
return configValues, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseSkipLog parses the skip_log directive. Syntax:
|
|
||||||
//
|
|
||||||
// skip_log [<matcher>]
|
|
||||||
func parseSkipLog(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
|
||||||
for h.Next() {
|
|
||||||
if h.NextArg() {
|
|
||||||
return nil, h.ArgErr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return caddyhttp.VarsMiddleware{"skip_log": true}, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ func TestLogDirectiveSyntax(t *testing.T) {
|
|||||||
format filter {
|
format filter {
|
||||||
wrap console
|
wrap console
|
||||||
fields {
|
fields {
|
||||||
request>remote_ip ip_mask {
|
common_log delete
|
||||||
|
request>remote_addr ip_mask {
|
||||||
ipv4 24
|
ipv4 24
|
||||||
ipv6 32
|
ipv6 32
|
||||||
}
|
}
|
||||||
@@ -46,7 +47,7 @@ func TestLogDirectiveSyntax(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.log0"]},"log0":{"encoder":{"fields":{"request\u003eremote_ip":{"filter":"ip_mask","ipv4_cidr":24,"ipv6_cidr":32}},"format":"filter","wrap":{"format":"console"}},"include":["http.log.access.log0"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"log0"}}}}}}`,
|
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.log0"]},"log0":{"encoder":{"fields":{"common_log":{"filter":"delete"},"request\u003eremote_addr":{"filter":"ip_mask","ipv4_cidr":24,"ipv6_cidr":32}},"format":"filter","wrap":{"format":"console"}},"include":["http.log.access.log0"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"log0"}}}}}}`,
|
||||||
expectError: false,
|
expectError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -148,27 +149,6 @@ func TestRedirDirectiveSyntax(t *testing.T) {
|
|||||||
}`,
|
}`,
|
||||||
expectError: false,
|
expectError: false,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
// this is now allowed so a Location header
|
|
||||||
// can be written and consumed by JS
|
|
||||||
// in the case of XHR requests
|
|
||||||
input: `:8080 {
|
|
||||||
redir * :8081 401
|
|
||||||
}`,
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `:8080 {
|
|
||||||
redir * :8081 402
|
|
||||||
}`,
|
|
||||||
expectError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `:8080 {
|
|
||||||
redir * :8081 {http.reverse_proxy.status_code}
|
|
||||||
}`,
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
input: `:8080 {
|
input: `:8080 {
|
||||||
redir /old.html /new.html htlm
|
redir /old.html /new.html htlm
|
||||||
@@ -181,6 +161,12 @@ func TestRedirDirectiveSyntax(t *testing.T) {
|
|||||||
}`,
|
}`,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: `:8080 {
|
||||||
|
redir * :8081 400
|
||||||
|
}`,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
input: `:8080 {
|
input: `:8080 {
|
||||||
redir * :8081 temp
|
redir * :8081 temp
|
||||||
|
|||||||
@@ -37,28 +37,21 @@ import (
|
|||||||
// The header directive goes second so that headers
|
// The header directive goes second so that headers
|
||||||
// can be manipulated before doing redirects.
|
// can be manipulated before doing redirects.
|
||||||
var directiveOrder = []string{
|
var directiveOrder = []string{
|
||||||
"tracing",
|
|
||||||
|
|
||||||
"map",
|
"map",
|
||||||
"vars",
|
|
||||||
"root",
|
"root",
|
||||||
"skip_log",
|
|
||||||
|
|
||||||
"header",
|
"header",
|
||||||
"copy_response_headers", // only in reverse_proxy's handle_response
|
|
||||||
"request_body",
|
"request_body",
|
||||||
|
|
||||||
"redir",
|
"redir",
|
||||||
|
|
||||||
// incoming request manipulation
|
// URI manipulation
|
||||||
"method",
|
|
||||||
"rewrite",
|
"rewrite",
|
||||||
"uri",
|
"uri",
|
||||||
"try_files",
|
"try_files",
|
||||||
|
|
||||||
// middleware handlers; some wrap responses
|
// middleware handlers; some wrap responses
|
||||||
"basicauth",
|
"basicauth",
|
||||||
"forward_auth",
|
|
||||||
"request_header",
|
"request_header",
|
||||||
"encode",
|
"encode",
|
||||||
"push",
|
"push",
|
||||||
@@ -72,7 +65,6 @@ var directiveOrder = []string{
|
|||||||
// handlers that typically respond to requests
|
// handlers that typically respond to requests
|
||||||
"abort",
|
"abort",
|
||||||
"error",
|
"error",
|
||||||
"copy_response", // only in reverse_proxy's handle_response
|
|
||||||
"respond",
|
"respond",
|
||||||
"metrics",
|
"metrics",
|
||||||
"reverse_proxy",
|
"reverse_proxy",
|
||||||
@@ -143,8 +135,8 @@ func RegisterGlobalOption(opt string, setupFunc UnmarshalGlobalFunc) {
|
|||||||
type Helper struct {
|
type Helper struct {
|
||||||
*caddyfile.Dispenser
|
*caddyfile.Dispenser
|
||||||
// State stores intermediate variables during caddyfile adaptation.
|
// State stores intermediate variables during caddyfile adaptation.
|
||||||
State map[string]any
|
State map[string]interface{}
|
||||||
options map[string]any
|
options map[string]interface{}
|
||||||
warnings *[]caddyconfig.Warning
|
warnings *[]caddyconfig.Warning
|
||||||
matcherDefs map[string]caddy.ModuleMap
|
matcherDefs map[string]caddy.ModuleMap
|
||||||
parentBlock caddyfile.ServerBlock
|
parentBlock caddyfile.ServerBlock
|
||||||
@@ -152,7 +144,7 @@ type Helper struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Option gets the option keyed by name.
|
// Option gets the option keyed by name.
|
||||||
func (h Helper) Option(name string) any {
|
func (h Helper) Option(name string) interface{} {
|
||||||
return h.options[name]
|
return h.options[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +168,7 @@ func (h Helper) Caddyfiles() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// JSON converts val into JSON. Any errors are added to warnings.
|
// JSON converts val into JSON. Any errors are added to warnings.
|
||||||
func (h Helper) JSON(val any) json.RawMessage {
|
func (h Helper) JSON(val interface{}) json.RawMessage {
|
||||||
return caddyconfig.JSON(val, h.warnings)
|
return caddyconfig.JSON(val, h.warnings)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,9 +340,6 @@ func parseSegmentAsConfig(h Helper) ([]ConfigValue, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, h.Errf("parsing caddyfile tokens for '%s': %v", dir, err)
|
return nil, h.Errf("parsing caddyfile tokens for '%s': %v", dir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dir = normalizeDirectiveName(dir)
|
|
||||||
|
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
result.directive = dir
|
result.directive = dir
|
||||||
allResults = append(allResults, result)
|
allResults = append(allResults, result)
|
||||||
@@ -376,7 +365,7 @@ type ConfigValue struct {
|
|||||||
// The value to be used when building the config.
|
// The value to be used when building the config.
|
||||||
// Generally its type is associated with the
|
// Generally its type is associated with the
|
||||||
// name of the Class.
|
// name of the Class.
|
||||||
Value any
|
Value interface{}
|
||||||
|
|
||||||
directive string
|
directive string
|
||||||
}
|
}
|
||||||
@@ -407,7 +396,7 @@ func sortRoutes(routes []ConfigValue) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode the path matchers if there is just one matcher set
|
// decode the path matchers, if there is just one of them
|
||||||
var iPM, jPM caddyhttp.MatchPath
|
var iPM, jPM caddyhttp.MatchPath
|
||||||
if len(iRoute.MatcherSetsRaw) == 1 {
|
if len(iRoute.MatcherSetsRaw) == 1 {
|
||||||
_ = json.Unmarshal(iRoute.MatcherSetsRaw[0]["path"], &iPM)
|
_ = json.Unmarshal(iRoute.MatcherSetsRaw[0]["path"], &iPM)
|
||||||
@@ -416,46 +405,24 @@ func sortRoutes(routes []ConfigValue) {
|
|||||||
_ = json.Unmarshal(jRoute.MatcherSetsRaw[0]["path"], &jPM)
|
_ = json.Unmarshal(jRoute.MatcherSetsRaw[0]["path"], &jPM)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there is only one path in the path matcher, sort by longer path
|
// sort by longer path (more specific) first; missing path
|
||||||
// (more specific) first; missing path matchers or multi-matchers are
|
// matchers or multi-matchers are treated as zero-length paths
|
||||||
// treated as zero-length paths
|
|
||||||
var iPathLen, jPathLen int
|
var iPathLen, jPathLen int
|
||||||
if len(iPM) == 1 {
|
if len(iPM) > 0 {
|
||||||
iPathLen = len(iPM[0])
|
iPathLen = len(iPM[0])
|
||||||
}
|
}
|
||||||
if len(jPM) == 1 {
|
if len(jPM) > 0 {
|
||||||
jPathLen = len(jPM[0])
|
jPathLen = len(jPM[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// some directives involve setting values which can overwrite
|
// if both directives have no path matcher, use whichever one
|
||||||
// each other, so it makes most sense to reverse the order so
|
// has any kind of matcher defined first.
|
||||||
// that the lease specific matcher is first; everything else
|
if iPathLen == 0 && jPathLen == 0 {
|
||||||
// has most-specific matcher first
|
|
||||||
if iDir == "vars" {
|
|
||||||
// we can only confidently compare path lengths if both
|
|
||||||
// directives have a single path to match (issue #5037)
|
|
||||||
if iPathLen > 0 && jPathLen > 0 {
|
|
||||||
// sort least-specific (shortest) path first
|
|
||||||
return iPathLen < jPathLen
|
|
||||||
}
|
|
||||||
|
|
||||||
// if both directives don't have a single path to compare,
|
|
||||||
// sort whichever one has no matcher first; if both have
|
|
||||||
// no matcher, sort equally (stable sort preserves order)
|
|
||||||
return len(iRoute.MatcherSetsRaw) == 0 && len(jRoute.MatcherSetsRaw) > 0
|
|
||||||
} else {
|
|
||||||
// we can only confidently compare path lengths if both
|
|
||||||
// directives have a single path to match (issue #5037)
|
|
||||||
if iPathLen > 0 && jPathLen > 0 {
|
|
||||||
// sort most-specific (longest) path first
|
|
||||||
return iPathLen > jPathLen
|
|
||||||
}
|
|
||||||
|
|
||||||
// if both directives don't have a single path to compare,
|
|
||||||
// sort whichever one has a matcher first; if both have
|
|
||||||
// a matcher, sort equally (stable sort preserves order)
|
|
||||||
return len(iRoute.MatcherSetsRaw) > 0 && len(jRoute.MatcherSetsRaw) == 0
|
return len(iRoute.MatcherSetsRaw) > 0 && len(jRoute.MatcherSetsRaw) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sort with the most-specific (longest) path first
|
||||||
|
return iPathLen > jPathLen
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,17 +510,6 @@ func (sb serverBlock) hasHostCatchAllKey() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// isAllHTTP returns true if all sb keys explicitly specify
|
|
||||||
// the http:// scheme
|
|
||||||
func (sb serverBlock) isAllHTTP() bool {
|
|
||||||
for _, addr := range sb.keys {
|
|
||||||
if addr.Scheme != "http" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// UnmarshalFunc is a function which can unmarshal Caddyfile
|
// UnmarshalFunc is a function which can unmarshal Caddyfile
|
||||||
// tokens into zero or more config values using a Helper type.
|
// tokens into zero or more config values using a Helper type.
|
||||||
@@ -575,7 +531,7 @@ type (
|
|||||||
// tokens from a global option. It is passed the tokens to parse and
|
// tokens from a global option. It is passed the tokens to parse and
|
||||||
// existing value from the previous instance of this global option
|
// existing value from the previous instance of this global option
|
||||||
// (if any). It returns the value to associate with this global option.
|
// (if any). It returns the value to associate with this global option.
|
||||||
UnmarshalGlobalFunc func(d *caddyfile.Dispenser, existingVal any) (any, error)
|
UnmarshalGlobalFunc func(d *caddyfile.Dispenser, existingVal interface{}) (interface{}, error)
|
||||||
)
|
)
|
||||||
|
|
||||||
var registeredDirectives = make(map[string]UnmarshalFunc)
|
var registeredDirectives = make(map[string]UnmarshalFunc)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package httpcaddyfile
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -29,7 +30,6 @@ import (
|
|||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
"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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -53,18 +53,27 @@ type ServerType struct {
|
|||||||
|
|
||||||
// Setup makes a config from the tokens.
|
// Setup makes a config from the tokens.
|
||||||
func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
||||||
options map[string]any) (*caddy.Config, []caddyconfig.Warning, error) {
|
options map[string]interface{}) (*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]interface{})
|
||||||
|
|
||||||
// load all the server blocks and associate them with a "pile" of config values
|
// load all the server blocks and associate them with a "pile"
|
||||||
|
// of config values; also prohibit duplicate keys because they
|
||||||
|
// can make a config confusing if more than one server block is
|
||||||
|
// chosen to handle a request - we actually will make each
|
||||||
|
// server block's route terminal so that only one will run
|
||||||
|
sbKeys := make(map[string]struct{})
|
||||||
originalServerBlocks := make([]serverBlock, 0, len(inputServerBlocks))
|
originalServerBlocks := make([]serverBlock, 0, len(inputServerBlocks))
|
||||||
for _, sblock := range inputServerBlocks {
|
for i, sblock := range inputServerBlocks {
|
||||||
for j, k := range sblock.Keys {
|
for j, k := range sblock.Keys {
|
||||||
if j == 0 && strings.HasPrefix(k, "@") {
|
if j == 0 && strings.HasPrefix(k, "@") {
|
||||||
return nil, warnings, fmt.Errorf("cannot define a matcher outside of a site block: '%s'", k)
|
return nil, warnings, fmt.Errorf("cannot define a matcher outside of a site block: '%s'", k)
|
||||||
}
|
}
|
||||||
|
if _, ok := sbKeys[k]; ok {
|
||||||
|
return nil, warnings, fmt.Errorf("duplicate site address not allowed: '%s' in %v (site block %d, key %d)", k, sblock.Keys, i, j)
|
||||||
|
}
|
||||||
|
sbKeys[k] = struct{}{}
|
||||||
}
|
}
|
||||||
originalServerBlocks = append(originalServerBlocks, serverBlock{
|
originalServerBlocks = append(originalServerBlocks, serverBlock{
|
||||||
block: sblock,
|
block: sblock,
|
||||||
@@ -79,10 +88,33 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
|||||||
return nil, warnings, err
|
return nil, warnings, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// replace shorthand placeholders (which are convenient
|
// replace shorthand placeholders (which are
|
||||||
// when writing a Caddyfile) with their actual placeholder
|
// convenient when writing a Caddyfile) with
|
||||||
// identifiers or variable names
|
// their actual placeholder identifiers or
|
||||||
replacer := strings.NewReplacer(placeholderShorthands()...)
|
// variable names
|
||||||
|
replacer := strings.NewReplacer(
|
||||||
|
"{dir}", "{http.request.uri.path.dir}",
|
||||||
|
"{file}", "{http.request.uri.path.file}",
|
||||||
|
"{host}", "{http.request.host}",
|
||||||
|
"{hostport}", "{http.request.hostport}",
|
||||||
|
"{port}", "{http.request.port}",
|
||||||
|
"{method}", "{http.request.method}",
|
||||||
|
"{path}", "{http.request.uri.path}",
|
||||||
|
"{query}", "{http.request.uri.query}",
|
||||||
|
"{remote}", "{http.request.remote}",
|
||||||
|
"{remote_host}", "{http.request.remote.host}",
|
||||||
|
"{remote_port}", "{http.request.remote.port}",
|
||||||
|
"{scheme}", "{http.request.scheme}",
|
||||||
|
"{uri}", "{http.request.uri}",
|
||||||
|
"{tls_cipher}", "{http.request.tls.cipher_suite}",
|
||||||
|
"{tls_version}", "{http.request.tls.version}",
|
||||||
|
"{tls_client_fingerprint}", "{http.request.tls.client.fingerprint}",
|
||||||
|
"{tls_client_issuer}", "{http.request.tls.client.issuer}",
|
||||||
|
"{tls_client_serial}", "{http.request.tls.client.serial}",
|
||||||
|
"{tls_client_subject}", "{http.request.tls.client.subject}",
|
||||||
|
"{tls_client_certificate_pem}", "{http.request.tls.client.certificate_pem}",
|
||||||
|
"{upstream_hostport}", "{http.reverse_proxy.upstream.hostport}",
|
||||||
|
)
|
||||||
|
|
||||||
// these are placeholders that allow a user-defined final
|
// these are placeholders that allow a user-defined final
|
||||||
// parameters, but we still want to provide a shorthand
|
// parameters, but we still want to provide a shorthand
|
||||||
@@ -91,17 +123,11 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
|||||||
search *regexp.Regexp
|
search *regexp.Regexp
|
||||||
replace string
|
replace string
|
||||||
}{
|
}{
|
||||||
{regexp.MustCompile(`{header\.([\w-]*)}`), "{http.request.header.$1}"},
|
|
||||||
{regexp.MustCompile(`{cookie\.([\w-]*)}`), "{http.request.cookie.$1}"},
|
|
||||||
{regexp.MustCompile(`{labels\.([\w-]*)}`), "{http.request.host.labels.$1}"},
|
|
||||||
{regexp.MustCompile(`{path\.([\w-]*)}`), "{http.request.uri.path.$1}"},
|
|
||||||
{regexp.MustCompile(`{file\.([\w-]*)}`), "{http.request.uri.path.file.$1}"},
|
|
||||||
{regexp.MustCompile(`{query\.([\w-]*)}`), "{http.request.uri.query.$1}"},
|
{regexp.MustCompile(`{query\.([\w-]*)}`), "{http.request.uri.query.$1}"},
|
||||||
|
{regexp.MustCompile(`{labels\.([\w-]*)}`), "{http.request.host.labels.$1}"},
|
||||||
|
{regexp.MustCompile(`{header\.([\w-]*)}`), "{http.request.header.$1}"},
|
||||||
|
{regexp.MustCompile(`{path\.([\w-]*)}`), "{http.request.uri.path.$1}"},
|
||||||
{regexp.MustCompile(`{re\.([\w-]*)\.([\w-]*)}`), "{http.regexp.$1.$2}"},
|
{regexp.MustCompile(`{re\.([\w-]*)\.([\w-]*)}`), "{http.regexp.$1.$2}"},
|
||||||
{regexp.MustCompile(`{vars\.([\w-]*)}`), "{http.vars.$1}"},
|
|
||||||
{regexp.MustCompile(`{rp\.([\w-\.]*)}`), "{http.reverse_proxy.$1}"},
|
|
||||||
{regexp.MustCompile(`{err\.([\w-\.]*)}`), "{http.error.$1}"},
|
|
||||||
{regexp.MustCompile(`{file_match\.([\w-]*)}`), "{http.matchers.file.$1}"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, sb := range originalServerBlocks {
|
for _, sb := range originalServerBlocks {
|
||||||
@@ -166,7 +192,13 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
|||||||
return nil, warnings, fmt.Errorf("parsing caddyfile tokens for '%s': %v", dir, err)
|
return nil, warnings, fmt.Errorf("parsing caddyfile tokens for '%s': %v", dir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dir = normalizeDirectiveName(dir)
|
// As a special case, we want "handle_path" to be sorted
|
||||||
|
// at the same level as "handle", so we force them to use
|
||||||
|
// the same directive name after their parsing is complete.
|
||||||
|
// See https://github.com/caddyserver/caddy/issues/3675#issuecomment-678042377
|
||||||
|
if dir == "handle_path" {
|
||||||
|
dir = "handle"
|
||||||
|
}
|
||||||
|
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
result.directive = dir
|
result.directive = dir
|
||||||
@@ -193,11 +225,10 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
|||||||
|
|
||||||
// now that each server is configured, make the HTTP app
|
// now that each server is configured, make the HTTP app
|
||||||
httpApp := caddyhttp.App{
|
httpApp := caddyhttp.App{
|
||||||
HTTPPort: tryInt(options["http_port"], &warnings),
|
HTTPPort: tryInt(options["http_port"], &warnings),
|
||||||
HTTPSPort: tryInt(options["https_port"], &warnings),
|
HTTPSPort: tryInt(options["https_port"], &warnings),
|
||||||
GracePeriod: tryDuration(options["grace_period"], &warnings),
|
GracePeriod: tryDuration(options["grace_period"], &warnings),
|
||||||
ShutdownDelay: tryDuration(options["shutdown_delay"], &warnings),
|
Servers: servers,
|
||||||
Servers: servers,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// then make the TLS app
|
// then make the TLS app
|
||||||
@@ -223,17 +254,24 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
|||||||
hasDefaultLog = true
|
hasDefaultLog = true
|
||||||
}
|
}
|
||||||
if _, ok := options["debug"]; ok && ncl.log.Level == "" {
|
if _, ok := options["debug"]; ok && ncl.log.Level == "" {
|
||||||
ncl.log.Level = zap.DebugLevel.CapitalString()
|
ncl.log.Level = "DEBUG"
|
||||||
}
|
}
|
||||||
customLogs = append(customLogs, ncl)
|
customLogs = append(customLogs, ncl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply global log options, when set
|
// Apply global log options, when set
|
||||||
if options["log"] != nil {
|
if options["log"] != nil {
|
||||||
for _, logValue := range options["log"].([]ConfigValue) {
|
for _, logValue := range options["log"].([]ConfigValue) {
|
||||||
addCustomLog(logValue.Value.(namedCustomLog))
|
addCustomLog(logValue.Value.(namedCustomLog))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Apply server-specific log options
|
||||||
|
for _, p := range pairings {
|
||||||
|
for _, sb := range p.serverBlocks {
|
||||||
|
for _, clVal := range sb.pile["custom_log"] {
|
||||||
|
addCustomLog(clVal.Value.(namedCustomLog))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !hasDefaultLog {
|
if !hasDefaultLog {
|
||||||
// if the default log was not customized, ensure we
|
// if the default log was not customized, ensure we
|
||||||
@@ -241,20 +279,11 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
|||||||
if _, ok := options["debug"]; ok {
|
if _, ok := options["debug"]; ok {
|
||||||
customLogs = append(customLogs, namedCustomLog{
|
customLogs = append(customLogs, namedCustomLog{
|
||||||
name: "default",
|
name: "default",
|
||||||
log: &caddy.CustomLog{Level: zap.DebugLevel.CapitalString()},
|
log: &caddy.CustomLog{Level: "DEBUG"},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply server-specific log options
|
|
||||||
for _, p := range pairings {
|
|
||||||
for _, sb := range p.serverBlocks {
|
|
||||||
for _, clVal := range sb.pile["custom_log"] {
|
|
||||||
addCustomLog(clVal.Value.(namedCustomLog))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// annnd the top-level config, then we're done!
|
// annnd the top-level config, then we're done!
|
||||||
cfg := &caddy.Config{AppsRaw: make(caddy.ModuleMap)}
|
cfg := &caddy.Config{AppsRaw: make(caddy.ModuleMap)}
|
||||||
|
|
||||||
@@ -317,14 +346,14 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
|||||||
// which is expected to be the first server block if it has zero
|
// which is expected to be the first server block if it has zero
|
||||||
// keys. It returns the updated list of server blocks with the
|
// keys. It returns the updated list of server blocks with the
|
||||||
// global options block removed, and updates options accordingly.
|
// global options block removed, and updates options accordingly.
|
||||||
func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options map[string]any) ([]serverBlock, error) {
|
func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options map[string]interface{}) ([]serverBlock, error) {
|
||||||
if len(serverBlocks) == 0 || len(serverBlocks[0].block.Keys) > 0 {
|
if len(serverBlocks) == 0 || len(serverBlocks[0].block.Keys) > 0 {
|
||||||
return serverBlocks, nil
|
return serverBlocks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, segment := range serverBlocks[0].block.Segments {
|
for _, segment := range serverBlocks[0].block.Segments {
|
||||||
opt := segment.Directive()
|
opt := segment.Directive()
|
||||||
var val any
|
var val interface{}
|
||||||
var err error
|
var err error
|
||||||
disp := caddyfile.NewDispenser(segment)
|
disp := caddyfile.NewDispenser(segment)
|
||||||
|
|
||||||
@@ -394,7 +423,7 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options
|
|||||||
// 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(
|
||||||
pairings []sbAddrAssociation,
|
pairings []sbAddrAssociation,
|
||||||
options map[string]any,
|
options map[string]interface{},
|
||||||
warnings *[]caddyconfig.Warning,
|
warnings *[]caddyconfig.Warning,
|
||||||
groupCounter counter,
|
groupCounter counter,
|
||||||
) (map[string]*caddyhttp.Server, error) {
|
) (map[string]*caddyhttp.Server, error) {
|
||||||
@@ -415,23 +444,6 @@ func (st *ServerType) serversFromPairings(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, p := range pairings {
|
for i, p := range pairings {
|
||||||
// detect ambiguous site definitions: server blocks which
|
|
||||||
// have the same host bound to the same interface (listener
|
|
||||||
// address), otherwise their routes will improperly be added
|
|
||||||
// to the same server (see issue #4635)
|
|
||||||
for j, sblock1 := range p.serverBlocks {
|
|
||||||
for _, key := range sblock1.block.Keys {
|
|
||||||
for k, sblock2 := range p.serverBlocks {
|
|
||||||
if k == j {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if sliceContains(sblock2.block.Keys, key) {
|
|
||||||
return nil, fmt.Errorf("ambiguous site definition: %s", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
srv := &caddyhttp.Server{
|
srv := &caddyhttp.Server{
|
||||||
Listen: p.addresses,
|
Listen: p.addresses,
|
||||||
}
|
}
|
||||||
@@ -439,26 +451,14 @@ func (st *ServerType) serversFromPairings(
|
|||||||
// handle the auto_https global option
|
// handle the auto_https global option
|
||||||
if autoHTTPS != "on" {
|
if autoHTTPS != "on" {
|
||||||
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
||||||
switch autoHTTPS {
|
if autoHTTPS == "off" {
|
||||||
case "off":
|
|
||||||
srv.AutoHTTPS.Disabled = true
|
srv.AutoHTTPS.Disabled = true
|
||||||
case "disable_redirects":
|
|
||||||
srv.AutoHTTPS.DisableRedir = true
|
|
||||||
case "disable_certs":
|
|
||||||
srv.AutoHTTPS.DisableCerts = true
|
|
||||||
case "ignore_loaded_certs":
|
|
||||||
srv.AutoHTTPS.IgnoreLoadedCerts = true
|
|
||||||
}
|
}
|
||||||
}
|
if autoHTTPS == "disable_redirects" {
|
||||||
|
srv.AutoHTTPS.DisableRedir = true
|
||||||
// Using paths in site addresses is deprecated
|
}
|
||||||
// See ParseAddress() where parsing should later reject paths
|
if autoHTTPS == "ignore_loaded_certs" {
|
||||||
// See https://github.com/caddyserver/caddy/pull/4728 for a full explanation
|
srv.AutoHTTPS.IgnoreLoadedCerts = true
|
||||||
for _, sblock := range p.serverBlocks {
|
|
||||||
for _, addr := range sblock.keys {
|
|
||||||
if addr.Path != "" {
|
|
||||||
caddy.Log().Named("caddyfile").Warn("Using a path in a site address is deprecated; please use the 'handle' directive instead", zap.String("address", addr.String()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,7 +549,7 @@ func (st *ServerType) serversFromPairings(
|
|||||||
// emit warnings if user put unspecified IP addresses; they probably want the bind directive
|
// emit warnings if user put unspecified IP addresses; they probably want the bind directive
|
||||||
for _, h := range hosts {
|
for _, h := range hosts {
|
||||||
if h == "0.0.0.0" || h == "::" {
|
if h == "0.0.0.0" || h == "::" {
|
||||||
caddy.Log().Named("caddyfile").Warn("Site block has an unspecified IP address which only matches requests having that Host header; you probably want the 'bind' directive to configure the socket", zap.String("address", h))
|
log.Printf("[WARNING] Site block has unspecified IP address %s which only matches requests having that Host header; you probably want the 'bind' directive to configure the socket", h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,7 +585,7 @@ func (st *ServerType) serversFromPairings(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, addr := range sblock.keys {
|
for _, addr := range sblock.keys {
|
||||||
// if server only uses HTTP port, auto-HTTPS will not apply
|
// if server only uses HTTPS port, auto-HTTPS will not apply
|
||||||
if listenersUseAnyPortOtherThan(srv.Listen, httpPort) {
|
if listenersUseAnyPortOtherThan(srv.Listen, httpPort) {
|
||||||
// exclude any hosts that were defined explicitly with "http://"
|
// exclude any hosts that were defined explicitly with "http://"
|
||||||
// in the key from automated cert management (issue #2998)
|
// in the key from automated cert management (issue #2998)
|
||||||
@@ -729,7 +729,7 @@ func (st *ServerType) serversFromPairings(
|
|||||||
return servers, nil
|
return servers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []serverBlock, options map[string]any) error {
|
func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []serverBlock, options map[string]interface{}) error {
|
||||||
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 {
|
||||||
httpPort = strconv.Itoa(hp)
|
httpPort = strconv.Itoa(hp)
|
||||||
@@ -955,7 +955,7 @@ func appendSubrouteToRouteList(routeList caddyhttp.RouteList,
|
|||||||
func buildSubroute(routes []ConfigValue, groupCounter counter) (*caddyhttp.Subroute, error) {
|
func buildSubroute(routes []ConfigValue, groupCounter counter) (*caddyhttp.Subroute, error) {
|
||||||
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 ordered, so it cannot be used here", val.directive)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1060,19 +1060,6 @@ func buildSubroute(routes []ConfigValue, groupCounter counter) (*caddyhttp.Subro
|
|||||||
return subroute, nil
|
return subroute, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalizeDirectiveName ensures directives that should be sorted
|
|
||||||
// at the same level are named the same before sorting happens.
|
|
||||||
func normalizeDirectiveName(directive string) string {
|
|
||||||
// As a special case, we want "handle_path" to be sorted
|
|
||||||
// at the same level as "handle", so we force them to use
|
|
||||||
// the same directive name after their parsing is complete.
|
|
||||||
// See https://github.com/caddyserver/caddy/issues/3675#issuecomment-678042377
|
|
||||||
if directive == "handle_path" {
|
|
||||||
directive = "handle"
|
|
||||||
}
|
|
||||||
return directive
|
|
||||||
}
|
|
||||||
|
|
||||||
// consolidateRoutes combines routes with the same properties
|
// consolidateRoutes combines routes with the same properties
|
||||||
// (same matchers, same Terminal and Group settings) for a
|
// (same matchers, same Terminal and Group settings) for a
|
||||||
// cleaner overall output.
|
// cleaner overall output.
|
||||||
@@ -1203,7 +1190,6 @@ func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.Mod
|
|||||||
|
|
||||||
func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.ModuleMap) error {
|
func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.ModuleMap) error {
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
// this is the "name" for "named matchers"
|
|
||||||
definitionName := d.Val()
|
definitionName := d.Val()
|
||||||
|
|
||||||
if _, ok := matchers[definitionName]; ok {
|
if _, ok := matchers[definitionName]; ok {
|
||||||
@@ -1211,9 +1197,16 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M
|
|||||||
}
|
}
|
||||||
matchers[definitionName] = make(caddy.ModuleMap)
|
matchers[definitionName] = make(caddy.ModuleMap)
|
||||||
|
|
||||||
// given a matcher name and the tokens following it, parse
|
// in case there are multiple instances of the same matcher, concatenate
|
||||||
// the tokens as a matcher module and record it
|
// their tokens (we expect that UnmarshalCaddyfile should be able to
|
||||||
makeMatcher := func(matcherName string, tokens []caddyfile.Token) error {
|
// handle more than one segment); otherwise, we'd overwrite other
|
||||||
|
// instances of the matcher in this set
|
||||||
|
tokensByMatcherName := make(map[string][]caddyfile.Token)
|
||||||
|
for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
|
||||||
|
matcherName := d.Val()
|
||||||
|
tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)
|
||||||
|
}
|
||||||
|
for matcherName, tokens := range tokensByMatcherName {
|
||||||
mod, err := caddy.GetModule("http.matchers." + matcherName)
|
mod, err := caddy.GetModule("http.matchers." + matcherName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getting matcher module '%s': %v", matcherName, err)
|
return fmt.Errorf("getting matcher module '%s': %v", matcherName, err)
|
||||||
@@ -1231,39 +1224,6 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M
|
|||||||
return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
|
return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
|
||||||
}
|
}
|
||||||
matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
|
matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the next token is quoted, we can assume it's not a matcher name
|
|
||||||
// and that it's probably an 'expression' matcher
|
|
||||||
if d.NextArg() {
|
|
||||||
if d.Token().Quoted() {
|
|
||||||
err := makeMatcher("expression", []caddyfile.Token{d.Token()})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it wasn't quoted, then we need to rewind after calling
|
|
||||||
// d.NextArg() so the below properly grabs the matcher name
|
|
||||||
d.Prev()
|
|
||||||
}
|
|
||||||
|
|
||||||
// in case there are multiple instances of the same matcher, concatenate
|
|
||||||
// their tokens (we expect that UnmarshalCaddyfile should be able to
|
|
||||||
// handle more than one segment); otherwise, we'd overwrite other
|
|
||||||
// instances of the matcher in this set
|
|
||||||
tokensByMatcherName := make(map[string][]caddyfile.Token)
|
|
||||||
for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
|
|
||||||
matcherName := d.Val()
|
|
||||||
tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)
|
|
||||||
}
|
|
||||||
for matcherName, tokens := range tokensByMatcherName {
|
|
||||||
err := makeMatcher(matcherName, tokens)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -1281,61 +1241,9 @@ func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (caddy.Modul
|
|||||||
return msEncoded, nil
|
return msEncoded, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// placeholderShorthands returns a slice of old-new string pairs,
|
|
||||||
// where the left of the pair is a placeholder shorthand that may
|
|
||||||
// be used in the Caddyfile, and the right is the replacement.
|
|
||||||
func placeholderShorthands() []string {
|
|
||||||
return []string{
|
|
||||||
"{dir}", "{http.request.uri.path.dir}",
|
|
||||||
"{file}", "{http.request.uri.path.file}",
|
|
||||||
"{host}", "{http.request.host}",
|
|
||||||
"{hostport}", "{http.request.hostport}",
|
|
||||||
"{port}", "{http.request.port}",
|
|
||||||
"{method}", "{http.request.method}",
|
|
||||||
"{path}", "{http.request.uri.path}",
|
|
||||||
"{query}", "{http.request.uri.query}",
|
|
||||||
"{remote}", "{http.request.remote}",
|
|
||||||
"{remote_host}", "{http.request.remote.host}",
|
|
||||||
"{remote_port}", "{http.request.remote.port}",
|
|
||||||
"{scheme}", "{http.request.scheme}",
|
|
||||||
"{uri}", "{http.request.uri}",
|
|
||||||
"{tls_cipher}", "{http.request.tls.cipher_suite}",
|
|
||||||
"{tls_version}", "{http.request.tls.version}",
|
|
||||||
"{tls_client_fingerprint}", "{http.request.tls.client.fingerprint}",
|
|
||||||
"{tls_client_issuer}", "{http.request.tls.client.issuer}",
|
|
||||||
"{tls_client_serial}", "{http.request.tls.client.serial}",
|
|
||||||
"{tls_client_subject}", "{http.request.tls.client.subject}",
|
|
||||||
"{tls_client_certificate_pem}", "{http.request.tls.client.certificate_pem}",
|
|
||||||
"{tls_client_certificate_der_base64}", "{http.request.tls.client.certificate_der_base64}",
|
|
||||||
"{upstream_hostport}", "{http.reverse_proxy.upstream.hostport}",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WasReplacedPlaceholderShorthand checks if a token string was
|
|
||||||
// likely a replaced shorthand of the known Caddyfile placeholder
|
|
||||||
// replacement outputs. Useful to prevent some user-defined map
|
|
||||||
// output destinations from overlapping with one of the
|
|
||||||
// predefined shorthands.
|
|
||||||
func WasReplacedPlaceholderShorthand(token string) string {
|
|
||||||
prev := ""
|
|
||||||
for i, item := range placeholderShorthands() {
|
|
||||||
// only look at every 2nd item, which is the replacement
|
|
||||||
if i%2 == 0 {
|
|
||||||
prev = item
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Trim(token, "{}") == strings.Trim(item, "{}") {
|
|
||||||
// we return the original shorthand so it
|
|
||||||
// can be used for an error message
|
|
||||||
return prev
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// tryInt tries to convert val to an integer. If it fails,
|
// tryInt tries to convert val to an integer. If it fails,
|
||||||
// it downgrades the error to a warning and returns 0.
|
// it downgrades the error to a warning and returns 0.
|
||||||
func tryInt(val any, warnings *[]caddyconfig.Warning) int {
|
func tryInt(val interface{}, warnings *[]caddyconfig.Warning) int {
|
||||||
intVal, ok := val.(int)
|
intVal, ok := val.(int)
|
||||||
if val != nil && !ok && warnings != nil {
|
if val != nil && !ok && warnings != nil {
|
||||||
*warnings = append(*warnings, caddyconfig.Warning{Message: "not an integer type"})
|
*warnings = append(*warnings, caddyconfig.Warning{Message: "not an integer type"})
|
||||||
@@ -1343,7 +1251,7 @@ func tryInt(val any, warnings *[]caddyconfig.Warning) int {
|
|||||||
return intVal
|
return intVal
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryString(val any, warnings *[]caddyconfig.Warning) string {
|
func tryString(val interface{}, warnings *[]caddyconfig.Warning) string {
|
||||||
stringVal, ok := val.(string)
|
stringVal, ok := val.(string)
|
||||||
if val != nil && !ok && warnings != nil {
|
if val != nil && !ok && warnings != nil {
|
||||||
*warnings = append(*warnings, caddyconfig.Warning{Message: "not a string type"})
|
*warnings = append(*warnings, caddyconfig.Warning{Message: "not a string type"})
|
||||||
@@ -1351,7 +1259,7 @@ func tryString(val any, warnings *[]caddyconfig.Warning) string {
|
|||||||
return stringVal
|
return stringVal
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryDuration(val any, warnings *[]caddyconfig.Warning) caddy.Duration {
|
func tryDuration(val interface{}, warnings *[]caddyconfig.Warning) caddy.Duration {
|
||||||
durationVal, ok := val.(caddy.Duration)
|
durationVal, ok := val.(caddy.Duration)
|
||||||
if val != nil && !ok && warnings != nil {
|
if val != nil && !ok && warnings != nil {
|
||||||
*warnings = append(*warnings, caddyconfig.Warning{Message: "not a duration type"})
|
*warnings = append(*warnings, caddyconfig.Warning{Message: "not a duration type"})
|
||||||
|
|||||||
@@ -29,15 +29,11 @@ func init() {
|
|||||||
RegisterGlobalOption("debug", parseOptTrue)
|
RegisterGlobalOption("debug", parseOptTrue)
|
||||||
RegisterGlobalOption("http_port", parseOptHTTPPort)
|
RegisterGlobalOption("http_port", parseOptHTTPPort)
|
||||||
RegisterGlobalOption("https_port", parseOptHTTPSPort)
|
RegisterGlobalOption("https_port", parseOptHTTPSPort)
|
||||||
RegisterGlobalOption("default_bind", parseOptStringList)
|
|
||||||
RegisterGlobalOption("grace_period", parseOptDuration)
|
RegisterGlobalOption("grace_period", parseOptDuration)
|
||||||
RegisterGlobalOption("shutdown_delay", parseOptDuration)
|
|
||||||
RegisterGlobalOption("default_sni", parseOptSingleString)
|
RegisterGlobalOption("default_sni", parseOptSingleString)
|
||||||
RegisterGlobalOption("order", parseOptOrder)
|
RegisterGlobalOption("order", parseOptOrder)
|
||||||
RegisterGlobalOption("storage", parseOptStorage)
|
RegisterGlobalOption("storage", parseOptStorage)
|
||||||
RegisterGlobalOption("storage_clean_interval", parseOptDuration)
|
RegisterGlobalOption("storage_clean_interval", parseOptDuration)
|
||||||
RegisterGlobalOption("renew_interval", parseOptDuration)
|
|
||||||
RegisterGlobalOption("ocsp_interval", parseOptDuration)
|
|
||||||
RegisterGlobalOption("acme_ca", parseOptSingleString)
|
RegisterGlobalOption("acme_ca", parseOptSingleString)
|
||||||
RegisterGlobalOption("acme_ca_root", parseOptSingleString)
|
RegisterGlobalOption("acme_ca_root", parseOptSingleString)
|
||||||
RegisterGlobalOption("acme_dns", parseOptACMEDNS)
|
RegisterGlobalOption("acme_dns", parseOptACMEDNS)
|
||||||
@@ -56,9 +52,9 @@ func init() {
|
|||||||
RegisterGlobalOption("preferred_chains", parseOptPreferredChains)
|
RegisterGlobalOption("preferred_chains", parseOptPreferredChains)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil }
|
func parseOptTrue(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) { return true, nil }
|
||||||
|
|
||||||
func parseOptHTTPPort(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptHTTPPort(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
|
||||||
var httpPort int
|
var httpPort int
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
var httpPortStr string
|
var httpPortStr string
|
||||||
@@ -74,7 +70,7 @@ func parseOptHTTPPort(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
return httpPort, nil
|
return httpPort, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptHTTPSPort(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptHTTPSPort(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
|
||||||
var httpsPort int
|
var httpsPort int
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
var httpsPortStr string
|
var httpsPortStr string
|
||||||
@@ -90,7 +86,7 @@ func parseOptHTTPSPort(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
return httpsPort, nil
|
return httpsPort, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptOrder(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
|
||||||
newOrder := directiveOrder
|
newOrder := directiveOrder
|
||||||
|
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
@@ -166,7 +162,7 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
return newOrder, nil
|
return newOrder, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptStorage(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptStorage(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
|
||||||
if !d.Next() { // consume option name
|
if !d.Next() { // consume option name
|
||||||
return nil, d.ArgErr()
|
return nil, d.ArgErr()
|
||||||
}
|
}
|
||||||
@@ -185,7 +181,7 @@ func parseOptStorage(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
return storage, nil
|
return storage, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptDuration(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptDuration(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
|
||||||
if !d.Next() { // consume option name
|
if !d.Next() { // consume option name
|
||||||
return nil, d.ArgErr()
|
return nil, d.ArgErr()
|
||||||
}
|
}
|
||||||
@@ -199,7 +195,7 @@ func parseOptDuration(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
return caddy.Duration(dur), nil
|
return caddy.Duration(dur), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptACMEDNS(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptACMEDNS(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
|
||||||
if !d.Next() { // consume option name
|
if !d.Next() { // consume option name
|
||||||
return nil, d.ArgErr()
|
return nil, d.ArgErr()
|
||||||
}
|
}
|
||||||
@@ -218,7 +214,7 @@ func parseOptACMEDNS(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
return prov, nil
|
return prov, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptACMEEAB(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptACMEEAB(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
|
||||||
eab := new(acme.EAB)
|
eab := new(acme.EAB)
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
if d.NextArg() {
|
if d.NextArg() {
|
||||||
@@ -246,7 +242,7 @@ func parseOptACMEEAB(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
return eab, nil
|
return eab, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptCertIssuer(d *caddyfile.Dispenser, existing any) (any, error) {
|
func parseOptCertIssuer(d *caddyfile.Dispenser, existing interface{}) (interface{}, error) {
|
||||||
var issuers []certmagic.Issuer
|
var issuers []certmagic.Issuer
|
||||||
if existing != nil {
|
if existing != nil {
|
||||||
issuers = existing.([]certmagic.Issuer)
|
issuers = existing.([]certmagic.Issuer)
|
||||||
@@ -269,7 +265,7 @@ func parseOptCertIssuer(d *caddyfile.Dispenser, existing any) (any, error) {
|
|||||||
return issuers, nil
|
return issuers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptSingleString(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptSingleString(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
|
||||||
d.Next() // consume parameter name
|
d.Next() // consume parameter name
|
||||||
if !d.Next() {
|
if !d.Next() {
|
||||||
return "", d.ArgErr()
|
return "", d.ArgErr()
|
||||||
@@ -281,16 +277,7 @@ func parseOptSingleString(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptStringList(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptAdmin(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
|
||||||
d.Next() // consume parameter name
|
|
||||||
val := d.RemainingArgs()
|
|
||||||
if len(val) == 0 {
|
|
||||||
return "", d.ArgErr()
|
|
||||||
}
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseOptAdmin(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
||||||
adminCfg := new(caddy.AdminConfig)
|
adminCfg := new(caddy.AdminConfig)
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
if d.NextArg() {
|
if d.NextArg() {
|
||||||
@@ -326,7 +313,7 @@ func parseOptAdmin(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
return adminCfg, nil
|
return adminCfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptOnDemand(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
|
||||||
var ond *caddytls.OnDemandConfig
|
var ond *caddytls.OnDemandConfig
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
if d.NextArg() {
|
if d.NextArg() {
|
||||||
@@ -386,7 +373,7 @@ func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
return ond, nil
|
return ond, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
|
||||||
d.Next() // consume parameter name
|
d.Next() // consume parameter name
|
||||||
if !d.Next() {
|
if !d.Next() {
|
||||||
return "", d.ArgErr()
|
return "", d.ArgErr()
|
||||||
@@ -395,17 +382,17 @@ func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
if d.Next() {
|
if d.Next() {
|
||||||
return "", d.ArgErr()
|
return "", d.ArgErr()
|
||||||
}
|
}
|
||||||
if val != "off" && val != "disable_redirects" && val != "disable_certs" && val != "ignore_loaded_certs" {
|
if val != "off" && val != "disable_redirects" && val != "ignore_loaded_certs" {
|
||||||
return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', or 'ignore_loaded_certs'")
|
return "", d.Errf("auto_https must be one of 'off', 'disable_redirects' or 'ignore_loaded_certs'")
|
||||||
}
|
}
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseServerOptions(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
|
||||||
return unmarshalCaddyfileServerOptions(d)
|
return unmarshalCaddyfileServerOptions(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOCSPStaplingOptions(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOCSPStaplingOptions(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
|
||||||
d.Next() // consume option name
|
d.Next() // consume option name
|
||||||
var val string
|
var val string
|
||||||
if !d.AllArgs(&val) {
|
if !d.AllArgs(&val) {
|
||||||
@@ -421,17 +408,18 @@ func parseOCSPStaplingOptions(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
|
|
||||||
// parseLogOptions parses the global log option. Syntax:
|
// parseLogOptions parses the global log option. Syntax:
|
||||||
//
|
//
|
||||||
// log [name] {
|
// log [name] {
|
||||||
// output <writer_module> ...
|
// output <writer_module> ...
|
||||||
// format <encoder_module> ...
|
// format <encoder_module> ...
|
||||||
// level <level>
|
// level <level>
|
||||||
// include <namespaces...>
|
// include <namespaces...>
|
||||||
// exclude <namespaces...>
|
// exclude <namespaces...>
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// When the name argument is unspecified, this directive modifies the default
|
// When the name argument is unspecified, this directive modifies the default
|
||||||
// logger.
|
// logger.
|
||||||
func parseLogOptions(d *caddyfile.Dispenser, existingVal any) (any, error) {
|
//
|
||||||
|
func parseLogOptions(d *caddyfile.Dispenser, existingVal interface{}) (interface{}, error) {
|
||||||
currentNames := make(map[string]struct{})
|
currentNames := make(map[string]struct{})
|
||||||
if existingVal != nil {
|
if existingVal != nil {
|
||||||
innerVals, ok := existingVal.([]ConfigValue)
|
innerVals, ok := existingVal.([]ConfigValue)
|
||||||
@@ -466,7 +454,7 @@ func parseLogOptions(d *caddyfile.Dispenser, existingVal any) (any, error) {
|
|||||||
return configValues, nil
|
return configValues, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptPreferredChains(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptPreferredChains(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
|
||||||
d.Next()
|
d.Next()
|
||||||
return caddytls.ParseCaddyfilePreferredChainsOptions(d)
|
return caddytls.ParseCaddyfilePreferredChainsOptions(d)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,175 +16,23 @@ package httpcaddyfile
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterGlobalOption("pki", parsePKIApp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parsePKIApp parses the global log option. Syntax:
|
|
||||||
//
|
|
||||||
// pki {
|
|
||||||
// ca [<id>] {
|
|
||||||
// name <name>
|
|
||||||
// root_cn <name>
|
|
||||||
// intermediate_cn <name>
|
|
||||||
// root {
|
|
||||||
// cert <path>
|
|
||||||
// key <path>
|
|
||||||
// format <format>
|
|
||||||
// }
|
|
||||||
// intermediate {
|
|
||||||
// cert <path>
|
|
||||||
// key <path>
|
|
||||||
// format <format>
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// When the CA ID is unspecified, 'local' is assumed.
|
|
||||||
func parsePKIApp(d *caddyfile.Dispenser, existingVal any) (any, error) {
|
|
||||||
pki := &caddypki.PKI{CAs: make(map[string]*caddypki.CA)}
|
|
||||||
|
|
||||||
for d.Next() {
|
|
||||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
|
||||||
switch d.Val() {
|
|
||||||
case "ca":
|
|
||||||
pkiCa := new(caddypki.CA)
|
|
||||||
if d.NextArg() {
|
|
||||||
pkiCa.ID = d.Val()
|
|
||||||
if d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pkiCa.ID == "" {
|
|
||||||
pkiCa.ID = caddypki.DefaultCAID
|
|
||||||
}
|
|
||||||
|
|
||||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
|
||||||
switch d.Val() {
|
|
||||||
case "name":
|
|
||||||
if !d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
pkiCa.Name = d.Val()
|
|
||||||
|
|
||||||
case "root_cn":
|
|
||||||
if !d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
pkiCa.RootCommonName = d.Val()
|
|
||||||
|
|
||||||
case "intermediate_cn":
|
|
||||||
if !d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
pkiCa.IntermediateCommonName = d.Val()
|
|
||||||
|
|
||||||
case "root":
|
|
||||||
if pkiCa.Root == nil {
|
|
||||||
pkiCa.Root = new(caddypki.KeyPair)
|
|
||||||
}
|
|
||||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
|
||||||
switch d.Val() {
|
|
||||||
case "cert":
|
|
||||||
if !d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
pkiCa.Root.Certificate = d.Val()
|
|
||||||
|
|
||||||
case "key":
|
|
||||||
if !d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
pkiCa.Root.PrivateKey = d.Val()
|
|
||||||
|
|
||||||
case "format":
|
|
||||||
if !d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
pkiCa.Root.Format = d.Val()
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, d.Errf("unrecognized pki ca root option '%s'", d.Val())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "intermediate":
|
|
||||||
if pkiCa.Intermediate == nil {
|
|
||||||
pkiCa.Intermediate = new(caddypki.KeyPair)
|
|
||||||
}
|
|
||||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
|
||||||
switch d.Val() {
|
|
||||||
case "cert":
|
|
||||||
if !d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
pkiCa.Intermediate.Certificate = d.Val()
|
|
||||||
|
|
||||||
case "key":
|
|
||||||
if !d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
pkiCa.Intermediate.PrivateKey = d.Val()
|
|
||||||
|
|
||||||
case "format":
|
|
||||||
if !d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
pkiCa.Intermediate.Format = d.Val()
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, d.Errf("unrecognized pki ca intermediate option '%s'", d.Val())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, d.Errf("unrecognized pki ca option '%s'", d.Val())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pki.CAs[pkiCa.ID] = pkiCa
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, d.Errf("unrecognized pki option '%s'", d.Val())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pki, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st ServerType) buildPKIApp(
|
func (st ServerType) buildPKIApp(
|
||||||
pairings []sbAddrAssociation,
|
pairings []sbAddrAssociation,
|
||||||
options map[string]any,
|
options map[string]interface{},
|
||||||
warnings []caddyconfig.Warning,
|
warnings []caddyconfig.Warning,
|
||||||
) (*caddypki.PKI, []caddyconfig.Warning, error) {
|
) (*caddypki.PKI, []caddyconfig.Warning, error) {
|
||||||
|
|
||||||
|
pkiApp := &caddypki.PKI{CAs: make(map[string]*caddypki.CA)}
|
||||||
|
|
||||||
skipInstallTrust := false
|
skipInstallTrust := false
|
||||||
if _, ok := options["skip_install_trust"]; ok {
|
if _, ok := options["skip_install_trust"]; ok {
|
||||||
skipInstallTrust = true
|
skipInstallTrust = true
|
||||||
}
|
}
|
||||||
falseBool := false
|
falseBool := false
|
||||||
|
|
||||||
// Load the PKI app configured via global options
|
|
||||||
var pkiApp *caddypki.PKI
|
|
||||||
unwrappedPki, ok := options["pki"].(*caddypki.PKI)
|
|
||||||
if ok {
|
|
||||||
pkiApp = unwrappedPki
|
|
||||||
} else {
|
|
||||||
pkiApp = &caddypki.PKI{CAs: make(map[string]*caddypki.CA)}
|
|
||||||
}
|
|
||||||
for _, ca := range pkiApp.CAs {
|
|
||||||
if skipInstallTrust {
|
|
||||||
ca.InstallTrust = &falseBool
|
|
||||||
}
|
|
||||||
pkiApp.CAs[ca.ID] = ca
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add in the CAs configured via directives
|
|
||||||
for _, p := range pairings {
|
for _, p := range pairings {
|
||||||
for _, sblock := range p.serverBlocks {
|
for _, sblock := range p.serverBlocks {
|
||||||
// find all the CAs that were defined and add them to the app config
|
// find all the CAs that were defined and add them to the app config
|
||||||
@@ -194,12 +42,7 @@ func (st ServerType) buildPKIApp(
|
|||||||
if skipInstallTrust {
|
if skipInstallTrust {
|
||||||
ca.InstallTrust = &falseBool
|
ca.InstallTrust = &falseBool
|
||||||
}
|
}
|
||||||
|
pkiApp.CAs[ca.ID] = ca
|
||||||
// the CA might already exist from global options, so
|
|
||||||
// don't overwrite it in that case
|
|
||||||
if _, ok := pkiApp.CAs[ca.ID]; !ok {
|
|
||||||
pkiApp.CAs[ca.ID] = ca
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,20 +33,18 @@ 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
|
||||||
ListenerWrappersRaw []json.RawMessage
|
ListenerWrappersRaw []json.RawMessage
|
||||||
ReadTimeout caddy.Duration
|
ReadTimeout caddy.Duration
|
||||||
ReadHeaderTimeout caddy.Duration
|
ReadHeaderTimeout caddy.Duration
|
||||||
WriteTimeout caddy.Duration
|
WriteTimeout caddy.Duration
|
||||||
IdleTimeout caddy.Duration
|
IdleTimeout caddy.Duration
|
||||||
KeepAliveInterval caddy.Duration
|
MaxHeaderBytes int
|
||||||
MaxHeaderBytes int
|
AllowH2C bool
|
||||||
Protocols []string
|
ExperimentalHTTP3 bool
|
||||||
StrictSNIHost *bool
|
StrictSNIHost *bool
|
||||||
ShouldLogCredentials bool
|
|
||||||
Metrics *caddyhttp.Metrics
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (interface{}, error) {
|
||||||
serverOpts := serverOptions{}
|
serverOpts := serverOptions{}
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
if d.NextArg() {
|
if d.NextArg() {
|
||||||
@@ -124,15 +122,6 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
|||||||
return nil, d.Errf("unrecognized timeouts option '%s'", d.Val())
|
return nil, d.Errf("unrecognized timeouts option '%s'", d.Val())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "keepalive_interval":
|
|
||||||
if !d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
dur, err := caddy.ParseDuration(d.Val())
|
|
||||||
if err != nil {
|
|
||||||
return nil, d.Errf("parsing keepalive interval duration: %v", err)
|
|
||||||
}
|
|
||||||
serverOpts.KeepAliveInterval = caddy.Duration(dur)
|
|
||||||
|
|
||||||
case "max_header_size":
|
case "max_header_size":
|
||||||
var sizeStr string
|
var sizeStr string
|
||||||
@@ -145,74 +134,27 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
|||||||
}
|
}
|
||||||
serverOpts.MaxHeaderBytes = int(size)
|
serverOpts.MaxHeaderBytes = int(size)
|
||||||
|
|
||||||
case "log_credentials":
|
|
||||||
if d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
serverOpts.ShouldLogCredentials = true
|
|
||||||
|
|
||||||
case "protocols":
|
|
||||||
protos := d.RemainingArgs()
|
|
||||||
for _, proto := range protos {
|
|
||||||
if proto != "h1" && proto != "h2" && proto != "h2c" && proto != "h3" {
|
|
||||||
return nil, d.Errf("unknown protocol '%s': expected h1, h2, h2c, or h3", proto)
|
|
||||||
}
|
|
||||||
if sliceContains(serverOpts.Protocols, proto) {
|
|
||||||
return nil, d.Errf("protocol %s specified more than once", proto)
|
|
||||||
}
|
|
||||||
serverOpts.Protocols = append(serverOpts.Protocols, proto)
|
|
||||||
}
|
|
||||||
if d.NextBlock(0) {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
case "strict_sni_host":
|
|
||||||
if d.NextArg() && d.Val() != "insecure_off" && d.Val() != "on" {
|
|
||||||
return nil, d.Errf("strict_sni_host only supports 'on' or 'insecure_off', got '%s'", d.Val())
|
|
||||||
}
|
|
||||||
boolVal := true
|
|
||||||
if d.Val() == "insecure_off" {
|
|
||||||
boolVal = false
|
|
||||||
}
|
|
||||||
serverOpts.StrictSNIHost = &boolVal
|
|
||||||
|
|
||||||
case "metrics":
|
|
||||||
if d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
if d.NextBlock(0) {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
serverOpts.Metrics = new(caddyhttp.Metrics)
|
|
||||||
|
|
||||||
// TODO: DEPRECATED. (August 2022)
|
|
||||||
case "protocol":
|
case "protocol":
|
||||||
caddy.Log().Named("caddyfile").Warn("DEPRECATED: protocol sub-option will be removed soon")
|
|
||||||
|
|
||||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||||
switch d.Val() {
|
switch d.Val() {
|
||||||
case "allow_h2c":
|
case "allow_h2c":
|
||||||
caddy.Log().Named("caddyfile").Warn("DEPRECATED: allow_h2c will be removed soon; use protocols option instead")
|
|
||||||
|
|
||||||
if d.NextArg() {
|
if d.NextArg() {
|
||||||
return nil, d.ArgErr()
|
return nil, d.ArgErr()
|
||||||
}
|
}
|
||||||
if sliceContains(serverOpts.Protocols, "h2c") {
|
serverOpts.AllowH2C = true
|
||||||
return nil, d.Errf("protocol h2c already specified")
|
|
||||||
|
case "experimental_http3":
|
||||||
|
if d.NextArg() {
|
||||||
|
return nil, d.ArgErr()
|
||||||
}
|
}
|
||||||
serverOpts.Protocols = append(serverOpts.Protocols, "h2c")
|
serverOpts.ExperimentalHTTP3 = true
|
||||||
|
|
||||||
case "strict_sni_host":
|
case "strict_sni_host":
|
||||||
caddy.Log().Named("caddyfile").Warn("DEPRECATED: protocol > strict_sni_host in this position will be removed soon; move up to the servers block instead")
|
if d.NextArg() {
|
||||||
|
return nil, d.ArgErr()
|
||||||
if d.NextArg() && d.Val() != "insecure_off" && d.Val() != "on" {
|
|
||||||
return nil, d.Errf("strict_sni_host only supports 'on' or 'insecure_off', got '%s'", d.Val())
|
|
||||||
}
|
}
|
||||||
boolVal := true
|
trueBool := true
|
||||||
if d.Val() == "insecure_off" {
|
serverOpts.StrictSNIHost = &trueBool
|
||||||
boolVal = false
|
|
||||||
}
|
|
||||||
serverOpts.StrictSNIHost = &boolVal
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, d.Errf("unrecognized protocol option '%s'", d.Val())
|
return nil, d.Errf("unrecognized protocol option '%s'", d.Val())
|
||||||
@@ -230,9 +172,20 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
|||||||
// applyServerOptions sets the server options on the appropriate servers
|
// applyServerOptions sets the server options on the appropriate servers
|
||||||
func applyServerOptions(
|
func applyServerOptions(
|
||||||
servers map[string]*caddyhttp.Server,
|
servers map[string]*caddyhttp.Server,
|
||||||
options map[string]any,
|
options map[string]interface{},
|
||||||
warnings *[]caddyconfig.Warning,
|
warnings *[]caddyconfig.Warning,
|
||||||
) error {
|
) error {
|
||||||
|
// If experimental HTTP/3 is enabled, enable it on each server.
|
||||||
|
// We already know there won't be a conflict with serverOptions because
|
||||||
|
// we validated earlier that "experimental_http3" cannot be set at the same
|
||||||
|
// time as "servers"
|
||||||
|
if enableH3, ok := options["experimental_http3"].(bool); ok && enableH3 {
|
||||||
|
*warnings = append(*warnings, caddyconfig.Warning{Message: "the 'experimental_http3' global option is deprecated, please use the 'servers > protocol > experimental_http3' option instead"})
|
||||||
|
for _, srv := range servers {
|
||||||
|
srv.ExperimentalHTTP3 = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
serverOpts, ok := options["servers"].([]serverOptions)
|
serverOpts, ok := options["servers"].([]serverOptions)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
@@ -265,17 +218,10 @@ func applyServerOptions(
|
|||||||
server.ReadHeaderTimeout = opts.ReadHeaderTimeout
|
server.ReadHeaderTimeout = opts.ReadHeaderTimeout
|
||||||
server.WriteTimeout = opts.WriteTimeout
|
server.WriteTimeout = opts.WriteTimeout
|
||||||
server.IdleTimeout = opts.IdleTimeout
|
server.IdleTimeout = opts.IdleTimeout
|
||||||
server.KeepAliveInterval = opts.KeepAliveInterval
|
|
||||||
server.MaxHeaderBytes = opts.MaxHeaderBytes
|
server.MaxHeaderBytes = opts.MaxHeaderBytes
|
||||||
server.Protocols = opts.Protocols
|
server.AllowH2C = opts.AllowH2C
|
||||||
|
server.ExperimentalHTTP3 = opts.ExperimentalHTTP3
|
||||||
server.StrictSNIHost = opts.StrictSNIHost
|
server.StrictSNIHost = opts.StrictSNIHost
|
||||||
server.Metrics = opts.Metrics
|
|
||||||
if opts.ShouldLogCredentials {
|
|
||||||
if server.Logs == nil {
|
|
||||||
server.Logs = &caddyhttp.ServerLogConfig{}
|
|
||||||
}
|
|
||||||
server.Logs.ShouldLogCredentials = opts.ShouldLogCredentials
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import (
|
|||||||
|
|
||||||
func (st ServerType) buildTLSApp(
|
func (st ServerType) buildTLSApp(
|
||||||
pairings []sbAddrAssociation,
|
pairings []sbAddrAssociation,
|
||||||
options map[string]any,
|
options map[string]interface{},
|
||||||
warnings []caddyconfig.Warning,
|
warnings []caddyconfig.Warning,
|
||||||
) (*caddytls.TLS, []caddyconfig.Warning, error) {
|
) (*caddytls.TLS, []caddyconfig.Warning, error) {
|
||||||
|
|
||||||
@@ -101,12 +101,6 @@ func (st ServerType) buildTLSApp(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, sblock := range p.serverBlocks {
|
for _, sblock := range p.serverBlocks {
|
||||||
// check the scheme of all the site addresses,
|
|
||||||
// skip building AP if they all had http://
|
|
||||||
if sblock.isAllHTTP() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// get values that populate an automation policy for this block
|
// get values that populate an automation policy for this block
|
||||||
ap, err := newBaseAutomationPolicy(options, warnings, true)
|
ap, err := newBaseAutomationPolicy(options, warnings, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -139,13 +133,6 @@ func (st ServerType) buildTLSApp(
|
|||||||
ap.Issuers = issuers
|
ap.Issuers = issuers
|
||||||
}
|
}
|
||||||
|
|
||||||
// certificate managers
|
|
||||||
if certManagerVals, ok := sblock.pile["tls.cert_manager"]; ok {
|
|
||||||
for _, certManager := range certManagerVals {
|
|
||||||
certGetterName := certManager.Value.(caddy.Module).CaddyModule().ID.Name()
|
|
||||||
ap.ManagersRaw = append(ap.ManagersRaw, caddyconfig.JSONModuleObject(certManager.Value, "via", certGetterName, &warnings))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// custom bind host
|
// custom bind host
|
||||||
for _, cfgVal := range sblock.pile["bind"] {
|
for _, cfgVal := range sblock.pile["bind"] {
|
||||||
for _, iss := range ap.Issuers {
|
for _, iss := range ap.Issuers {
|
||||||
@@ -299,27 +286,6 @@ func (st ServerType) buildTLSApp(
|
|||||||
tlsApp.Automation.StorageCleanInterval = storageCleanInterval
|
tlsApp.Automation.StorageCleanInterval = storageCleanInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the expired certificates renew interval if configured
|
|
||||||
if renewCheckInterval, ok := options["renew_interval"].(caddy.Duration); ok {
|
|
||||||
if tlsApp.Automation == nil {
|
|
||||||
tlsApp.Automation = new(caddytls.AutomationConfig)
|
|
||||||
}
|
|
||||||
tlsApp.Automation.RenewCheckInterval = renewCheckInterval
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the OCSP check interval if configured
|
|
||||||
if ocspCheckInterval, ok := options["ocsp_interval"].(caddy.Duration); ok {
|
|
||||||
if tlsApp.Automation == nil {
|
|
||||||
tlsApp.Automation = new(caddytls.AutomationConfig)
|
|
||||||
}
|
|
||||||
tlsApp.Automation.OCSPCheckInterval = ocspCheckInterval
|
|
||||||
}
|
|
||||||
|
|
||||||
// set whether OCSP stapling should be disabled for manually-managed certificates
|
|
||||||
if ocspConfig, ok := options["ocsp_stapling"].(certmagic.OCSPConfig); ok {
|
|
||||||
tlsApp.DisableOCSPStapling = ocspConfig.DisableStapling
|
|
||||||
}
|
|
||||||
|
|
||||||
// if any hostnames appear on the same server block as a key with
|
// if any hostnames appear on the same server block as a key with
|
||||||
// no host, they will not be used with route matchers because the
|
// no host, they will not be used with route matchers because the
|
||||||
// hostless key matches all hosts, therefore, it wouldn't be
|
// hostless key matches all hosts, therefore, it wouldn't be
|
||||||
@@ -358,6 +324,7 @@ func (st ServerType) buildTLSApp(
|
|||||||
globalPreferredChains := options["preferred_chains"]
|
globalPreferredChains := options["preferred_chains"]
|
||||||
hasGlobalACMEDefaults := globalEmail != nil || globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS != nil || globalACMEEAB != nil || globalPreferredChains != nil
|
hasGlobalACMEDefaults := globalEmail != nil || globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS != nil || globalACMEEAB != nil || globalPreferredChains != nil
|
||||||
if hasGlobalACMEDefaults {
|
if hasGlobalACMEDefaults {
|
||||||
|
// for _, ap := range tlsApp.Automation.Policies {
|
||||||
for i := 0; i < len(tlsApp.Automation.Policies); i++ {
|
for i := 0; i < len(tlsApp.Automation.Policies); i++ {
|
||||||
ap := tlsApp.Automation.Policies[i]
|
ap := tlsApp.Automation.Policies[i]
|
||||||
if len(ap.Issuers) == 0 && automationPolicyHasAllPublicNames(ap) {
|
if len(ap.Issuers) == 0 && automationPolicyHasAllPublicNames(ap) {
|
||||||
@@ -428,7 +395,7 @@ func (st ServerType) buildTLSApp(
|
|||||||
|
|
||||||
type acmeCapable interface{ GetACMEIssuer() *caddytls.ACMEIssuer }
|
type acmeCapable interface{ GetACMEIssuer() *caddytls.ACMEIssuer }
|
||||||
|
|
||||||
func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) error {
|
func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]interface{}) error {
|
||||||
acmeWrapper, ok := issuer.(acmeCapable)
|
acmeWrapper, ok := issuer.(acmeCapable)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
@@ -475,7 +442,7 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
|
|||||||
// for any other automation policies. A nil policy (and no error) will be
|
// for any other automation policies. A nil policy (and no error) will be
|
||||||
// returned if there are no default/global options. However, if always is
|
// returned if there are no default/global options. However, if always is
|
||||||
// true, a non-nil value will always be returned (unless there is an error).
|
// true, a non-nil value will always be returned (unless there is an error).
|
||||||
func newBaseAutomationPolicy(options map[string]any, warnings []caddyconfig.Warning, always bool) (*caddytls.AutomationPolicy, error) {
|
func newBaseAutomationPolicy(options map[string]interface{}, warnings []caddyconfig.Warning, always bool) (*caddytls.AutomationPolicy, error) {
|
||||||
issuers, hasIssuers := options["cert_issuer"]
|
issuers, hasIssuers := options["cert_issuer"]
|
||||||
_, hasLocalCerts := options["local_certs"]
|
_, hasLocalCerts := options["local_certs"]
|
||||||
keyType, hasKeyType := options["key_type"]
|
keyType, hasKeyType := options["key_type"]
|
||||||
|
|||||||
@@ -1,26 +1,11 @@
|
|||||||
// 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 caddyconfig
|
package caddyconfig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
@@ -71,28 +56,21 @@ func (HTTPLoader) CaddyModule() caddy.ModuleInfo {
|
|||||||
|
|
||||||
// LoadConfig loads a Caddy config.
|
// LoadConfig loads a Caddy config.
|
||||||
func (hl HTTPLoader) LoadConfig(ctx caddy.Context) ([]byte, error) {
|
func (hl HTTPLoader) LoadConfig(ctx caddy.Context) ([]byte, error) {
|
||||||
repl := caddy.NewReplacer()
|
|
||||||
|
|
||||||
client, err := hl.makeClient(ctx)
|
client, err := hl.makeClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
method := repl.ReplaceAll(hl.Method, "")
|
method := hl.Method
|
||||||
if method == "" {
|
if method == "" {
|
||||||
method = http.MethodGet
|
method = http.MethodGet
|
||||||
}
|
}
|
||||||
|
|
||||||
url := repl.ReplaceAll(hl.URL, "")
|
req, err := http.NewRequest(method, hl.URL, nil)
|
||||||
req, err := http.NewRequest(method, url, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for key, vals := range hl.Headers {
|
req.Header = hl.Headers
|
||||||
for _, val := range vals {
|
|
||||||
req.Header.Add(repl.ReplaceAll(key, ""), repl.ReplaceKnown(val, ""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -103,7 +81,7 @@ func (hl HTTPLoader) LoadConfig(ctx caddy.Context) ([]byte, error) {
|
|||||||
return nil, fmt.Errorf("server responded with HTTP %d", resp.StatusCode)
|
return nil, fmt.Errorf("server responded with HTTP %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -113,7 +91,7 @@ func (hl HTTPLoader) LoadConfig(ctx caddy.Context) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, warn := range warnings {
|
for _, warn := range warnings {
|
||||||
ctx.Logger().Warn(warn.String())
|
ctx.Logger(hl).Warn(warn.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
@@ -129,7 +107,7 @@ func (hl HTTPLoader) makeClient(ctx caddy.Context) (*http.Client, error) {
|
|||||||
|
|
||||||
// client authentication
|
// client authentication
|
||||||
if hl.TLS.UseServerIdentity {
|
if hl.TLS.UseServerIdentity {
|
||||||
certs, err := ctx.IdentityCredentials(ctx.Logger())
|
certs, err := ctx.IdentityCredentials(ctx.Logger(hl))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("getting server identity credentials: %v", err)
|
return nil, fmt.Errorf("getting server identity credentials: %v", err)
|
||||||
}
|
}
|
||||||
@@ -152,7 +130,7 @@ func (hl HTTPLoader) makeClient(ctx caddy.Context) (*http.Client, error) {
|
|||||||
if len(hl.TLS.RootCAPEMFiles) > 0 {
|
if len(hl.TLS.RootCAPEMFiles) > 0 {
|
||||||
rootPool := x509.NewCertPool()
|
rootPool := x509.NewCertPool()
|
||||||
for _, pemFile := range hl.TLS.RootCAPEMFiles {
|
for _, pemFile := range hl.TLS.RootCAPEMFiles {
|
||||||
pemData, err := os.ReadFile(pemFile)
|
pemData, err := ioutil.ReadFile(pemFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed reading ca cert: %v", err)
|
return nil, fmt.Errorf("failed reading ca cert: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-49
@@ -58,10 +58,6 @@ func (al adminLoad) Routes() []caddy.AdminRoute {
|
|||||||
Pattern: "/load",
|
Pattern: "/load",
|
||||||
Handler: caddy.AdminHandlerFunc(al.handleLoad),
|
Handler: caddy.AdminHandlerFunc(al.handleLoad),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Pattern: "/adapt",
|
|
||||||
Handler: caddy.AdminHandlerFunc(al.handleAdapt),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,48 +122,7 @@ func (adminLoad) handleLoad(w http.ResponseWriter, r *http.Request) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleAdapt adapts the given Caddy config to JSON and responds with the result.
|
// adaptByContentType adapts body to Caddy JSON using the adapter specified by contenType.
|
||||||
func (adminLoad) handleAdapt(w http.ResponseWriter, r *http.Request) error {
|
|
||||||
if r.Method != http.MethodPost {
|
|
||||||
return caddy.APIError{
|
|
||||||
HTTPStatus: http.StatusMethodNotAllowed,
|
|
||||||
Err: fmt.Errorf("method not allowed"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bufPool.Get().(*bytes.Buffer)
|
|
||||||
buf.Reset()
|
|
||||||
defer bufPool.Put(buf)
|
|
||||||
|
|
||||||
_, err := io.Copy(buf, r.Body)
|
|
||||||
if err != nil {
|
|
||||||
return caddy.APIError{
|
|
||||||
HTTPStatus: http.StatusBadRequest,
|
|
||||||
Err: fmt.Errorf("reading request body: %v", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result, warnings, err := adaptByContentType(r.Header.Get("Content-Type"), buf.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return caddy.APIError{
|
|
||||||
HTTPStatus: http.StatusBadRequest,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out := struct {
|
|
||||||
Warnings []Warning `json:"warnings,omitempty"`
|
|
||||||
Result json.RawMessage `json:"result"`
|
|
||||||
}{
|
|
||||||
Warnings: warnings,
|
|
||||||
Result: result,
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
return json.NewEncoder(w).Encode(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// adaptByContentType adapts body to Caddy JSON using the adapter specified by contentType.
|
|
||||||
// If contentType is empty or ends with "/json", the input will be returned, as a no-op.
|
// If contentType is empty or ends with "/json", the input will be returned, as a no-op.
|
||||||
func adaptByContentType(contentType string, body []byte) ([]byte, []Warning, error) {
|
func adaptByContentType(contentType string, body []byte) ([]byte, []Warning, error) {
|
||||||
// assume JSON as the default
|
// assume JSON as the default
|
||||||
@@ -189,11 +144,12 @@ func adaptByContentType(contentType string, body []byte) ([]byte, []Warning, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// adapter name should be suffix of MIME type
|
// adapter name should be suffix of MIME type
|
||||||
_, adapterName, slashFound := strings.Cut(ct, "/")
|
slashIdx := strings.Index(ct, "/")
|
||||||
if !slashFound {
|
if slashIdx < 0 {
|
||||||
return nil, nil, fmt.Errorf("malformed Content-Type")
|
return nil, nil, fmt.Errorf("malformed Content-Type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
adapterName := ct[slashIdx+1:]
|
||||||
cfgAdapter := GetAdapter(adapterName)
|
cfgAdapter := GetAdapter(adapterName)
|
||||||
if cfgAdapter == nil {
|
if cfgAdapter == nil {
|
||||||
return nil, nil, fmt.Errorf("unrecognized config adapter '%s'", adapterName)
|
return nil, nil, fmt.Errorf("unrecognized config adapter '%s'", adapterName)
|
||||||
@@ -208,7 +164,7 @@ func adaptByContentType(contentType string, body []byte) ([]byte, []Warning, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
var bufPool = sync.Pool{
|
var bufPool = sync.Pool{
|
||||||
New: func() any {
|
New: func() interface{} {
|
||||||
return new(bytes.Buffer)
|
return new(bytes.Buffer)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -7,7 +7,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -100,7 +100,7 @@ func (tc *Tester) InitServer(rawConfig string, configType string) {
|
|||||||
tc.t.Fail()
|
tc.t.Fail()
|
||||||
}
|
}
|
||||||
if err := tc.ensureConfigRunning(rawConfig, configType); err != nil {
|
if err := tc.ensureConfigRunning(rawConfig, configType); err != nil {
|
||||||
tc.t.Logf("failed ensuring config is running: %s", err)
|
tc.t.Logf("failed ensurng config is running: %s", err)
|
||||||
tc.t.Fail()
|
tc.t.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,7 +129,7 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
body, _ := io.ReadAll(res.Body)
|
body, _ := ioutil.ReadAll(res.Body)
|
||||||
|
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
_ = json.Indent(&out, body, "", " ")
|
_ = json.Indent(&out, body, "", " ")
|
||||||
@@ -162,7 +162,7 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
|
|||||||
timeElapsed(start, "caddytest: config load time")
|
timeElapsed(start, "caddytest: config load time")
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
body, err := io.ReadAll(res.Body)
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Errorf("unable to read response. %s", err)
|
tc.t.Errorf("unable to read response. %s", err)
|
||||||
return err
|
return err
|
||||||
@@ -186,7 +186,7 @@ func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error
|
|||||||
expectedBytes, _, _ = adapter.Adapt([]byte(rawConfig), nil)
|
expectedBytes, _, _ = adapter.Adapt([]byte(rawConfig), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
var expected any
|
var expected interface{}
|
||||||
err := json.Unmarshal(expectedBytes, &expected)
|
err := json.Unmarshal(expectedBytes, &expected)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -196,17 +196,17 @@ func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error
|
|||||||
Timeout: Default.LoadRequestTimeout,
|
Timeout: Default.LoadRequestTimeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchConfig := func(client *http.Client) any {
|
fetchConfig := func(client *http.Client) interface{} {
|
||||||
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
|
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
actualBytes, err := io.ReadAll(resp.Body)
|
actualBytes, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var actual any
|
var actual interface{}
|
||||||
err = json.Unmarshal(actualBytes, &actual)
|
err = json.Unmarshal(actualBytes, &actual)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -371,7 +371,7 @@ func CompareAdapt(t *testing.T, filename, rawConfig string, adapterName string,
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
options := make(map[string]any)
|
options := make(map[string]interface{})
|
||||||
|
|
||||||
result, warnings, err := cfgAdapter.Adapt([]byte(rawConfig), options)
|
result, warnings, err := cfgAdapter.Adapt([]byte(rawConfig), options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -471,7 +471,7 @@ func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expe
|
|||||||
resp := tc.AssertResponseCode(req, expectedStatusCode)
|
resp := tc.AssertResponseCode(req, expectedStatusCode)
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
bytes, err := io.ReadAll(resp.Body)
|
bytes, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Fatalf("unable to read the response body %s", err)
|
tc.t.Fatalf("unable to read the response body %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
example.com {
|
|
||||||
bind tcp6/[::]
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
"tcp6/[::]:443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
example.com
|
|
||||||
|
|
||||||
@a expression {http.error.status_code} == 400
|
|
||||||
abort @a
|
|
||||||
|
|
||||||
@b expression {http.error.status_code} == "401"
|
|
||||||
abort @b
|
|
||||||
|
|
||||||
@c expression {http.error.status_code} == `402`
|
|
||||||
abort @c
|
|
||||||
|
|
||||||
@d expression "{http.error.status_code} == 403"
|
|
||||||
abort @d
|
|
||||||
|
|
||||||
@e expression `{http.error.status_code} == 404`
|
|
||||||
abort @e
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"abort": true,
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"expression": "{http.error.status_code} == 400"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"abort": true,
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"expression": "{http.error.status_code} == \"401\""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"abort": true,
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"expression": "{http.error.status_code} == `402`"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"abort": true,
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"expression": "{http.error.status_code} == 403"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"abort": true,
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"expression": "{http.error.status_code} == 404"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
:80
|
|
||||||
|
|
||||||
file_server {
|
|
||||||
pass_thru
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":80"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "file_server",
|
|
||||||
"hide": [
|
|
||||||
"./Caddyfile"
|
|
||||||
],
|
|
||||||
"pass_thru": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
app.example.com {
|
|
||||||
forward_auth authelia:9091 {
|
|
||||||
uri /api/verify?rd=https://authelia.example.com
|
|
||||||
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
|
|
||||||
}
|
|
||||||
|
|
||||||
reverse_proxy backend:8080
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"app.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handle_response": [
|
|
||||||
{
|
|
||||||
"match": {
|
|
||||||
"status_code": [
|
|
||||||
2
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "headers",
|
|
||||||
"request": {
|
|
||||||
"set": {
|
|
||||||
"Remote-Email": [
|
|
||||||
"{http.reverse_proxy.header.Remote-Email}"
|
|
||||||
],
|
|
||||||
"Remote-Groups": [
|
|
||||||
"{http.reverse_proxy.header.Remote-Groups}"
|
|
||||||
],
|
|
||||||
"Remote-Name": [
|
|
||||||
"{http.reverse_proxy.header.Remote-Name}"
|
|
||||||
],
|
|
||||||
"Remote-User": [
|
|
||||||
"{http.reverse_proxy.header.Remote-User}"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handler": "reverse_proxy",
|
|
||||||
"headers": {
|
|
||||||
"request": {
|
|
||||||
"set": {
|
|
||||||
"X-Forwarded-Method": [
|
|
||||||
"{http.request.method}"
|
|
||||||
],
|
|
||||||
"X-Forwarded-Uri": [
|
|
||||||
"{http.request.uri}"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rewrite": {
|
|
||||||
"method": "GET",
|
|
||||||
"uri": "/api/verify?rd=https://authelia.example.com"
|
|
||||||
},
|
|
||||||
"upstreams": [
|
|
||||||
{
|
|
||||||
"dial": "authelia:9091"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"handler": "reverse_proxy",
|
|
||||||
"upstreams": [
|
|
||||||
{
|
|
||||||
"dial": "backend:8080"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
:8881
|
|
||||||
|
|
||||||
forward_auth localhost:9000 {
|
|
||||||
uri /auth
|
|
||||||
copy_headers A>1 B C>3 {
|
|
||||||
D
|
|
||||||
E>5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":8881"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handle_response": [
|
|
||||||
{
|
|
||||||
"match": {
|
|
||||||
"status_code": [
|
|
||||||
2
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "headers",
|
|
||||||
"request": {
|
|
||||||
"set": {
|
|
||||||
"1": [
|
|
||||||
"{http.reverse_proxy.header.A}"
|
|
||||||
],
|
|
||||||
"3": [
|
|
||||||
"{http.reverse_proxy.header.C}"
|
|
||||||
],
|
|
||||||
"5": [
|
|
||||||
"{http.reverse_proxy.header.E}"
|
|
||||||
],
|
|
||||||
"B": [
|
|
||||||
"{http.reverse_proxy.header.B}"
|
|
||||||
],
|
|
||||||
"D": [
|
|
||||||
"{http.reverse_proxy.header.D}"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handler": "reverse_proxy",
|
|
||||||
"headers": {
|
|
||||||
"request": {
|
|
||||||
"set": {
|
|
||||||
"X-Forwarded-Method": [
|
|
||||||
"{http.request.method}"
|
|
||||||
],
|
|
||||||
"X-Forwarded-Uri": [
|
|
||||||
"{http.request.uri}"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rewrite": {
|
|
||||||
"method": "GET",
|
|
||||||
"uri": "/auth"
|
|
||||||
},
|
|
||||||
"upstreams": [
|
|
||||||
{
|
|
||||||
"dial": "localhost:9000"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
http_port 8080
|
http_port 8080
|
||||||
https_port 8443
|
https_port 8443
|
||||||
grace_period 5s
|
grace_period 5s
|
||||||
shutdown_delay 10s
|
|
||||||
default_sni localhost
|
default_sni localhost
|
||||||
order root first
|
order root first
|
||||||
storage file_system {
|
storage file_system {
|
||||||
@@ -11,7 +10,6 @@
|
|||||||
}
|
}
|
||||||
acme_ca https://example.com
|
acme_ca https://example.com
|
||||||
acme_ca_root /path/to/ca.crt
|
acme_ca_root /path/to/ca.crt
|
||||||
ocsp_stapling off
|
|
||||||
|
|
||||||
email test@example.com
|
email test@example.com
|
||||||
admin off
|
admin off
|
||||||
@@ -46,7 +44,6 @@
|
|||||||
"http_port": 8080,
|
"http_port": 8080,
|
||||||
"https_port": 8443,
|
"https_port": 8443,
|
||||||
"grace_period": 5000000000,
|
"grace_period": 5000000000,
|
||||||
"shutdown_delay": 10000000000,
|
|
||||||
"servers": {
|
"servers": {
|
||||||
"srv0": {
|
"srv0": {
|
||||||
"listen": [
|
"listen": [
|
||||||
@@ -64,8 +61,7 @@
|
|||||||
"module": "internal"
|
"module": "internal"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"key_type": "ed25519",
|
"key_type": "ed25519"
|
||||||
"disable_ocsp_stapling": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"on_demand": {
|
"on_demand": {
|
||||||
@@ -75,8 +71,7 @@
|
|||||||
},
|
},
|
||||||
"ask": "https://example.com"
|
"ask": "https://example.com"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"disable_ocsp_stapling": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,6 @@
|
|||||||
burst 20
|
burst 20
|
||||||
}
|
}
|
||||||
storage_clean_interval 7d
|
storage_clean_interval 7d
|
||||||
renew_interval 1d
|
|
||||||
ocsp_interval 2d
|
|
||||||
|
|
||||||
key_type ed25519
|
key_type ed25519
|
||||||
}
|
}
|
||||||
@@ -84,8 +82,6 @@
|
|||||||
},
|
},
|
||||||
"ask": "https://example.com"
|
"ask": "https://example.com"
|
||||||
},
|
},
|
||||||
"ocsp_interval": 172800000000000,
|
|
||||||
"renew_interval": 86400000000000,
|
|
||||||
"storage_clean_interval": 604800000000000
|
"storage_clean_interval": 604800000000000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
{
|
|
||||||
debug
|
|
||||||
}
|
|
||||||
|
|
||||||
:8881 {
|
|
||||||
log {
|
|
||||||
format console
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"logging": {
|
|
||||||
"logs": {
|
|
||||||
"default": {
|
|
||||||
"level": "DEBUG",
|
|
||||||
"exclude": [
|
|
||||||
"http.log.access.log0"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"log0": {
|
|
||||||
"encoder": {
|
|
||||||
"format": "console"
|
|
||||||
},
|
|
||||||
"level": "DEBUG",
|
|
||||||
"include": [
|
|
||||||
"http.log.access.log0"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":8881"
|
|
||||||
],
|
|
||||||
"logs": {
|
|
||||||
"default_logger_name": "log0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
{
|
|
||||||
default_bind tcp4/0.0.0.0 tcp6/[::]
|
|
||||||
}
|
|
||||||
|
|
||||||
example.com {
|
|
||||||
}
|
|
||||||
|
|
||||||
example.org:12345 {
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
"tcp4/0.0.0.0:12345",
|
|
||||||
"tcp6/[::]:12345"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"example.org"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"srv1": {
|
|
||||||
"listen": [
|
|
||||||
"tcp4/0.0.0.0:443",
|
|
||||||
"tcp6/[::]:443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
format filter {
|
format filter {
|
||||||
wrap console
|
wrap console
|
||||||
fields {
|
fields {
|
||||||
request>remote_ip ip_mask {
|
common_log delete
|
||||||
|
request>remote_addr ip_mask {
|
||||||
ipv4 24
|
ipv4 24
|
||||||
ipv6 32
|
ipv6 32
|
||||||
}
|
}
|
||||||
@@ -18,7 +19,10 @@
|
|||||||
"custom-logger": {
|
"custom-logger": {
|
||||||
"encoder": {
|
"encoder": {
|
||||||
"fields": {
|
"fields": {
|
||||||
"request\u003eremote_ip": {
|
"common_log": {
|
||||||
|
"filter": "delete"
|
||||||
|
},
|
||||||
|
"request\u003eremote_addr": {
|
||||||
"filter": "ip_mask",
|
"filter": "ip_mask",
|
||||||
"ipv4_cidr": 24,
|
"ipv4_cidr": 24,
|
||||||
"ipv6_cidr": 32
|
"ipv6_cidr": 32
|
||||||
|
|||||||
@@ -1,44 +1,10 @@
|
|||||||
{
|
{
|
||||||
skip_install_trust
|
skip_install_trust
|
||||||
pki {
|
|
||||||
ca {
|
|
||||||
name "Local"
|
|
||||||
root_cn "Custom Local Root Name"
|
|
||||||
intermediate_cn "Custom Local Intermediate Name"
|
|
||||||
root {
|
|
||||||
cert /path/to/cert.pem
|
|
||||||
key /path/to/key.pem
|
|
||||||
format pem_file
|
|
||||||
}
|
|
||||||
intermediate {
|
|
||||||
cert /path/to/cert.pem
|
|
||||||
key /path/to/key.pem
|
|
||||||
format pem_file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ca foo {
|
|
||||||
name "Foo"
|
|
||||||
root_cn "Custom Foo Root Name"
|
|
||||||
intermediate_cn "Custom Foo Intermediate Name"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a.example.com {
|
a.example.com {
|
||||||
tls internal
|
tls internal
|
||||||
}
|
}
|
||||||
|
|
||||||
acme.example.com {
|
|
||||||
acme_server {
|
|
||||||
ca foo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
acme-bar.example.com {
|
|
||||||
acme_server {
|
|
||||||
ca bar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----------
|
----------
|
||||||
{
|
{
|
||||||
"apps": {
|
"apps": {
|
||||||
@@ -49,56 +15,6 @@ acme-bar.example.com {
|
|||||||
":443"
|
":443"
|
||||||
],
|
],
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"acme-bar.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"ca": "bar",
|
|
||||||
"handler": "acme_server"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"acme.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"ca": "foo",
|
|
||||||
"handler": "acme_server"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"match": [
|
"match": [
|
||||||
{
|
{
|
||||||
@@ -115,42 +31,14 @@ acme-bar.example.com {
|
|||||||
},
|
},
|
||||||
"pki": {
|
"pki": {
|
||||||
"certificate_authorities": {
|
"certificate_authorities": {
|
||||||
"bar": {
|
|
||||||
"install_trust": false
|
|
||||||
},
|
|
||||||
"foo": {
|
|
||||||
"name": "Foo",
|
|
||||||
"root_common_name": "Custom Foo Root Name",
|
|
||||||
"intermediate_common_name": "Custom Foo Intermediate Name",
|
|
||||||
"install_trust": false
|
|
||||||
},
|
|
||||||
"local": {
|
"local": {
|
||||||
"name": "Local",
|
"install_trust": false
|
||||||
"root_common_name": "Custom Local Root Name",
|
|
||||||
"intermediate_common_name": "Custom Local Intermediate Name",
|
|
||||||
"install_trust": false,
|
|
||||||
"root": {
|
|
||||||
"certificate": "/path/to/cert.pem",
|
|
||||||
"private_key": "/path/to/key.pem",
|
|
||||||
"format": "pem_file"
|
|
||||||
},
|
|
||||||
"intermediate": {
|
|
||||||
"certificate": "/path/to/cert.pem",
|
|
||||||
"private_key": "/path/to/key.pem",
|
|
||||||
"format": "pem_file"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tls": {
|
"tls": {
|
||||||
"automation": {
|
"automation": {
|
||||||
"policies": [
|
"policies": [
|
||||||
{
|
|
||||||
"subjects": [
|
|
||||||
"acme-bar.example.com",
|
|
||||||
"acme.example.com"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"subjects": [
|
"subjects": [
|
||||||
"a.example.com"
|
"a.example.com"
|
||||||
|
|||||||
@@ -3,9 +3,6 @@
|
|||||||
timeouts {
|
timeouts {
|
||||||
idle 90s
|
idle 90s
|
||||||
}
|
}
|
||||||
protocol {
|
|
||||||
strict_sni_host insecure_off
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
servers :80 {
|
servers :80 {
|
||||||
timeouts {
|
timeouts {
|
||||||
@@ -16,9 +13,6 @@
|
|||||||
timeouts {
|
timeouts {
|
||||||
idle 30s
|
idle 30s
|
||||||
}
|
}
|
||||||
protocol {
|
|
||||||
strict_sni_host
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,8 +46,7 @@ http://bar.com {
|
|||||||
],
|
],
|
||||||
"terminal": true
|
"terminal": true
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"strict_sni_host": true
|
|
||||||
},
|
},
|
||||||
"srv1": {
|
"srv1": {
|
||||||
"listen": [
|
"listen": [
|
||||||
@@ -77,8 +70,7 @@ http://bar.com {
|
|||||||
"listen": [
|
"listen": [
|
||||||
":8080"
|
":8080"
|
||||||
],
|
],
|
||||||
"idle_timeout": 90000000000,
|
"idle_timeout": 90000000000
|
||||||
"strict_sni_host": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
servers {
|
servers {
|
||||||
listener_wrappers {
|
listener_wrappers {
|
||||||
http_redirect
|
|
||||||
tls
|
tls
|
||||||
}
|
}
|
||||||
timeouts {
|
timeouts {
|
||||||
@@ -11,9 +10,11 @@
|
|||||||
idle 30s
|
idle 30s
|
||||||
}
|
}
|
||||||
max_header_size 100MB
|
max_header_size 100MB
|
||||||
log_credentials
|
protocol {
|
||||||
strict_sni_host
|
allow_h2c
|
||||||
protocols h1 h2 h2c h3
|
experimental_http3
|
||||||
|
strict_sni_host
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,9 +31,6 @@ foo.com {
|
|||||||
":443"
|
":443"
|
||||||
],
|
],
|
||||||
"listener_wrappers": [
|
"listener_wrappers": [
|
||||||
{
|
|
||||||
"wrapper": "http_redirect"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"wrapper": "tls"
|
"wrapper": "tls"
|
||||||
}
|
}
|
||||||
@@ -55,15 +53,8 @@ foo.com {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"strict_sni_host": true,
|
"strict_sni_host": true,
|
||||||
"logs": {
|
"experimental_http3": true,
|
||||||
"should_log_credentials": true
|
"allow_h2c": true
|
||||||
},
|
|
||||||
"protocols": [
|
|
||||||
"h1",
|
|
||||||
"h2",
|
|
||||||
"h2c",
|
|
||||||
"h3"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,6 @@
|
|||||||
header @images {
|
header @images {
|
||||||
Cache-Control "public, max-age=3600, stale-while-revalidate=86400"
|
Cache-Control "public, max-age=3600, stale-while-revalidate=86400"
|
||||||
}
|
}
|
||||||
header {
|
|
||||||
+Link "Foo"
|
|
||||||
+Link "Bar"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
----------
|
----------
|
||||||
{
|
{
|
||||||
@@ -125,17 +121,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"handler": "headers",
|
|
||||||
"response": {
|
|
||||||
"add": {
|
|
||||||
"Link": [
|
|
||||||
"Foo",
|
|
||||||
"Bar"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
http://localhost:2020 {
|
http://localhost:2020 {
|
||||||
log
|
log
|
||||||
skip_log /first-hidden*
|
|
||||||
skip_log /second-hidden*
|
|
||||||
respond 200
|
respond 200
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,36 +28,6 @@ http://localhost:2020 {
|
|||||||
{
|
{
|
||||||
"handler": "subroute",
|
"handler": "subroute",
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "vars",
|
|
||||||
"skip_log": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"path": [
|
|
||||||
"/second-hidden*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "vars",
|
|
||||||
"skip_log": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"path": [
|
|
||||||
"/first-hidden*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,24 +5,12 @@ log {
|
|||||||
format filter {
|
format filter {
|
||||||
wrap console
|
wrap console
|
||||||
fields {
|
fields {
|
||||||
uri query {
|
|
||||||
replace foo REDACTED
|
|
||||||
delete bar
|
|
||||||
hash baz
|
|
||||||
}
|
|
||||||
request>headers>Authorization replace REDACTED
|
request>headers>Authorization replace REDACTED
|
||||||
request>headers>Server delete
|
request>headers>Server delete
|
||||||
request>headers>Cookie cookie {
|
request>remote_addr ip_mask {
|
||||||
replace foo REDACTED
|
|
||||||
delete bar
|
|
||||||
hash baz
|
|
||||||
}
|
|
||||||
request>remote_ip ip_mask {
|
|
||||||
ipv4 24
|
ipv4 24
|
||||||
ipv6 32
|
ipv6 32
|
||||||
}
|
}
|
||||||
request>headers>Regexp regexp secret REDACTED
|
|
||||||
request>headers>Hash hash
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,57 +33,13 @@ log {
|
|||||||
"filter": "replace",
|
"filter": "replace",
|
||||||
"value": "REDACTED"
|
"value": "REDACTED"
|
||||||
},
|
},
|
||||||
"request\u003eheaders\u003eCookie": {
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"name": "foo",
|
|
||||||
"type": "replace",
|
|
||||||
"value": "REDACTED"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "bar",
|
|
||||||
"type": "delete"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "baz",
|
|
||||||
"type": "hash"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filter": "cookie"
|
|
||||||
},
|
|
||||||
"request\u003eheaders\u003eHash": {
|
|
||||||
"filter": "hash"
|
|
||||||
},
|
|
||||||
"request\u003eheaders\u003eRegexp": {
|
|
||||||
"filter": "regexp",
|
|
||||||
"regexp": "secret",
|
|
||||||
"value": "REDACTED"
|
|
||||||
},
|
|
||||||
"request\u003eheaders\u003eServer": {
|
"request\u003eheaders\u003eServer": {
|
||||||
"filter": "delete"
|
"filter": "delete"
|
||||||
},
|
},
|
||||||
"request\u003eremote_ip": {
|
"request\u003eremote_addr": {
|
||||||
"filter": "ip_mask",
|
"filter": "ip_mask",
|
||||||
"ipv4_cidr": 24,
|
"ipv4_cidr": 24,
|
||||||
"ipv6_cidr": 32
|
"ipv6_cidr": 32
|
||||||
},
|
|
||||||
"uri": {
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"parameter": "foo",
|
|
||||||
"type": "replace",
|
|
||||||
"value": "REDACTED"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameter": "bar",
|
|
||||||
"type": "delete"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameter": "baz",
|
|
||||||
"type": "hash"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filter": "query"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"format": "filter",
|
"format": "filter",
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
log {
|
log {
|
||||||
output file /var/log/access.log {
|
output file /var/log/access.log {
|
||||||
roll_size 1gb
|
roll_size 1gb
|
||||||
roll_uncompressed
|
|
||||||
roll_local_time
|
|
||||||
roll_keep 5
|
roll_keep 5
|
||||||
roll_keep_for 90d
|
roll_keep_for 90d
|
||||||
}
|
}
|
||||||
@@ -22,10 +20,8 @@ log {
|
|||||||
"writer": {
|
"writer": {
|
||||||
"filename": "/var/log/access.log",
|
"filename": "/var/log/access.log",
|
||||||
"output": "file",
|
"output": "file",
|
||||||
"roll_gzip": false,
|
|
||||||
"roll_keep": 5,
|
"roll_keep": 5,
|
||||||
"roll_keep_days": 90,
|
"roll_keep_days": 90,
|
||||||
"roll_local_time": true,
|
|
||||||
"roll_size_mb": 954
|
"roll_size_mb": 954
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
|
|||||||
@@ -1,126 +0,0 @@
|
|||||||
example.com
|
|
||||||
|
|
||||||
map {host} {my_placeholder} {magic_number} {
|
|
||||||
# Should output boolean "true" and an integer
|
|
||||||
example.com true 3
|
|
||||||
|
|
||||||
# Should output a string and null
|
|
||||||
foo.example.com "string value"
|
|
||||||
|
|
||||||
# Should output two strings (quoted int)
|
|
||||||
(.*)\.example.com "${1} subdomain" "5"
|
|
||||||
|
|
||||||
# Should output null and a string (quoted int)
|
|
||||||
~.*\.net$ - `7`
|
|
||||||
|
|
||||||
# Should output a float and the string "false"
|
|
||||||
~.*\.xyz$ 123.456 "false"
|
|
||||||
|
|
||||||
# Should output two strings, second being escaped quote
|
|
||||||
default "unknown domain" \"""
|
|
||||||
}
|
|
||||||
|
|
||||||
vars foo bar
|
|
||||||
vars {
|
|
||||||
abc true
|
|
||||||
def 1
|
|
||||||
ghi 2.3
|
|
||||||
jkl "mn op"
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"defaults": [
|
|
||||||
"unknown domain",
|
|
||||||
"\""
|
|
||||||
],
|
|
||||||
"destinations": [
|
|
||||||
"{my_placeholder}",
|
|
||||||
"{magic_number}"
|
|
||||||
],
|
|
||||||
"handler": "map",
|
|
||||||
"mappings": [
|
|
||||||
{
|
|
||||||
"input": "example.com",
|
|
||||||
"outputs": [
|
|
||||||
true,
|
|
||||||
3
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"input": "foo.example.com",
|
|
||||||
"outputs": [
|
|
||||||
"string value",
|
|
||||||
null
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"input": "(.*)\\.example.com",
|
|
||||||
"outputs": [
|
|
||||||
"${1} subdomain",
|
|
||||||
"5"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"input_regexp": ".*\\.net$",
|
|
||||||
"outputs": [
|
|
||||||
null,
|
|
||||||
"7"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"input_regexp": ".*\\.xyz$",
|
|
||||||
"outputs": [
|
|
||||||
123.456,
|
|
||||||
"false"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": "{http.request.host}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo": "bar",
|
|
||||||
"handler": "vars"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abc": true,
|
|
||||||
"def": 1,
|
|
||||||
"ghi": 2.3,
|
|
||||||
"handler": "vars",
|
|
||||||
"jkl": "mn op"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,30 +19,24 @@
|
|||||||
@matcher6 vars_regexp "{http.request.uri}" `\.([a-f0-9]{6})\.(css|js)$`
|
@matcher6 vars_regexp "{http.request.uri}" `\.([a-f0-9]{6})\.(css|js)$`
|
||||||
respond @matcher6 "from vars_regexp matcher without name"
|
respond @matcher6 "from vars_regexp matcher without name"
|
||||||
|
|
||||||
@matcher7 `path('/foo*') && method('GET')`
|
@matcher7 {
|
||||||
respond @matcher7 "inline expression matcher shortcut"
|
|
||||||
|
|
||||||
@matcher8 {
|
|
||||||
header Foo bar
|
header Foo bar
|
||||||
header Foo foobar
|
header Foo foobar
|
||||||
header Bar foo
|
header Bar foo
|
||||||
}
|
}
|
||||||
respond @matcher8 "header matcher merging values of the same field"
|
respond @matcher7 "header matcher merging values of the same field"
|
||||||
|
|
||||||
@matcher9 {
|
@matcher8 {
|
||||||
query foo=bar foo=baz bar=foo
|
query foo=bar foo=baz bar=foo
|
||||||
query bar=baz
|
query bar=baz
|
||||||
}
|
}
|
||||||
respond @matcher9 "query matcher merging pairs with the same keys"
|
respond @matcher8 "query matcher merging pairs with the same keys"
|
||||||
|
|
||||||
@matcher10 {
|
@matcher9 {
|
||||||
header !Foo
|
header !Foo
|
||||||
header Bar foo
|
header Bar foo
|
||||||
}
|
}
|
||||||
respond @matcher10 "header matcher with null field matcher"
|
respond @matcher9 "header matcher with null field matcher"
|
||||||
|
|
||||||
@matcher11 remote_ip private_ranges
|
|
||||||
respond @matcher11 "remote_ip matcher with private ranges"
|
|
||||||
}
|
}
|
||||||
----------
|
----------
|
||||||
{
|
{
|
||||||
@@ -107,9 +101,7 @@
|
|||||||
"match": [
|
"match": [
|
||||||
{
|
{
|
||||||
"vars": {
|
"vars": {
|
||||||
"{http.request.uri}": [
|
"{http.request.uri}": "/vars-matcher"
|
||||||
"/vars-matcher"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -155,19 +147,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"expression": "path('/foo*') \u0026\u0026 method('GET')"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "inline expression matcher shortcut",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"match": [
|
"match": [
|
||||||
{
|
{
|
||||||
@@ -228,28 +207,6 @@
|
|||||||
"handler": "static_response"
|
"handler": "static_response"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"remote_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": "remote_ip matcher with private ranges",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
:8080 {
|
|
||||||
method FOO
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":8080"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "rewrite",
|
|
||||||
"method": "FOO"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,145 +1,145 @@
|
|||||||
:8881 {
|
:8881 {
|
||||||
php_fastcgi app:9000 {
|
php_fastcgi app:9000 {
|
||||||
env FOO bar
|
env FOO bar
|
||||||
|
|
||||||
@error status 4xx
|
@error status 4xx
|
||||||
handle_response @error {
|
handle_response @error {
|
||||||
root * /errors
|
root * /errors
|
||||||
rewrite * /{http.reverse_proxy.status_code}.html
|
rewrite * /{http.reverse_proxy.status_code}.html
|
||||||
file_server
|
file_server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
----------
|
----------
|
||||||
{
|
{
|
||||||
"apps": {
|
"apps": {
|
||||||
"http": {
|
"http": {
|
||||||
"servers": {
|
"servers": {
|
||||||
"srv0": {
|
"srv0": {
|
||||||
"listen": [
|
"listen": [
|
||||||
":8881"
|
":8881"
|
||||||
],
|
],
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"match": [
|
"match": [
|
||||||
{
|
{
|
||||||
"file": {
|
"file": {
|
||||||
"try_files": [
|
"try_files": [
|
||||||
"{http.request.uri.path}/index.php"
|
"{http.request.uri.path}/index.php"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"not": [
|
"not": [
|
||||||
{
|
{
|
||||||
"path": [
|
"path": [
|
||||||
"*/"
|
"*/"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "static_response",
|
"handler": "static_response",
|
||||||
"headers": {
|
"headers": {
|
||||||
"Location": [
|
"Location": [
|
||||||
"{http.request.uri.path}/"
|
"{http.request.uri.path}/"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"status_code": 308
|
"status_code": 308
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"match": [
|
"match": [
|
||||||
{
|
{
|
||||||
"file": {
|
"file": {
|
||||||
"try_files": [
|
"try_files": [
|
||||||
"{http.request.uri.path}",
|
"{http.request.uri.path}",
|
||||||
"{http.request.uri.path}/index.php",
|
"{http.request.uri.path}/index.php",
|
||||||
"index.php"
|
"index.php"
|
||||||
],
|
],
|
||||||
"split_path": [
|
"split_path": [
|
||||||
".php"
|
".php"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "rewrite",
|
"handler": "rewrite",
|
||||||
"uri": "{http.matchers.file.relative}"
|
"uri": "{http.matchers.file.relative}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"match": [
|
"match": [
|
||||||
{
|
{
|
||||||
"path": [
|
"path": [
|
||||||
"*.php"
|
"*.php"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handle_response": [
|
"handle_response": [
|
||||||
{
|
{
|
||||||
"match": {
|
"match": {
|
||||||
"status_code": [
|
"status_code": [
|
||||||
4
|
4
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "vars",
|
"handler": "vars",
|
||||||
"root": "/errors"
|
"root": "/errors"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "group0",
|
"group": "group0",
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "rewrite",
|
"handler": "rewrite",
|
||||||
"uri": "/{http.reverse_proxy.status_code}.html"
|
"uri": "/{http.reverse_proxy.status_code}.html"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "file_server",
|
"handler": "file_server",
|
||||||
"hide": [
|
"hide": [
|
||||||
"./Caddyfile"
|
"./Caddyfile"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"handler": "reverse_proxy",
|
"handler": "reverse_proxy",
|
||||||
"transport": {
|
"transport": {
|
||||||
"env": {
|
"env": {
|
||||||
"FOO": "bar"
|
"FOO": "bar"
|
||||||
},
|
},
|
||||||
"protocol": "fastcgi",
|
"protocol": "fastcgi",
|
||||||
"split_path": [
|
"split_path": [
|
||||||
".php"
|
".php"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"upstreams": [
|
"upstreams": [
|
||||||
{
|
{
|
||||||
"dial": "app:9000"
|
"dial": "app:9000"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
:8884
|
|
||||||
|
|
||||||
php_fastcgi localhost:9000 {
|
|
||||||
# some php_fastcgi-specific subdirectives
|
|
||||||
split .php .php5
|
|
||||||
env VAR1 value1
|
|
||||||
env VAR2 value2
|
|
||||||
root /var/www
|
|
||||||
try_files {path} {path}/index.php =404
|
|
||||||
dial_timeout 3s
|
|
||||||
read_timeout 10s
|
|
||||||
write_timeout 20s
|
|
||||||
|
|
||||||
# passed through to reverse_proxy (directive order doesn't matter!)
|
|
||||||
lb_policy random
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":8884"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"file": {
|
|
||||||
"try_files": [
|
|
||||||
"{http.request.uri.path}/index.php"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"not": [
|
|
||||||
{
|
|
||||||
"path": [
|
|
||||||
"*/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "static_response",
|
|
||||||
"headers": {
|
|
||||||
"Location": [
|
|
||||||
"{http.request.uri.path}/"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"status_code": 308
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"file": {
|
|
||||||
"try_files": [
|
|
||||||
"{http.request.uri.path}",
|
|
||||||
"{http.request.uri.path}/index.php",
|
|
||||||
"=404"
|
|
||||||
],
|
|
||||||
"split_path": [
|
|
||||||
".php",
|
|
||||||
".php5"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "rewrite",
|
|
||||||
"uri": "{http.matchers.file.relative}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"path": [
|
|
||||||
"*.php",
|
|
||||||
"*.php5"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "reverse_proxy",
|
|
||||||
"load_balancing": {
|
|
||||||
"selection_policy": {
|
|
||||||
"policy": "random"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"transport": {
|
|
||||||
"dial_timeout": 3000000000,
|
|
||||||
"env": {
|
|
||||||
"VAR1": "value1",
|
|
||||||
"VAR2": "value2"
|
|
||||||
},
|
|
||||||
"protocol": "fastcgi",
|
|
||||||
"read_timeout": 10000000000,
|
|
||||||
"root": "/var/www",
|
|
||||||
"split_path": [
|
|
||||||
".php",
|
|
||||||
".php5"
|
|
||||||
],
|
|
||||||
"write_timeout": 20000000000
|
|
||||||
},
|
|
||||||
"upstreams": [
|
|
||||||
{
|
|
||||||
"dial": "localhost:9000"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
:8884 {
|
|
||||||
reverse_proxy {
|
|
||||||
dynamic a foo 9000
|
|
||||||
}
|
|
||||||
|
|
||||||
reverse_proxy {
|
|
||||||
dynamic a {
|
|
||||||
name foo
|
|
||||||
port 9000
|
|
||||||
refresh 5m
|
|
||||||
resolvers 8.8.8.8 8.8.4.4
|
|
||||||
dial_timeout 2s
|
|
||||||
dial_fallback_delay 300ms
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:8885 {
|
|
||||||
reverse_proxy {
|
|
||||||
dynamic srv _api._tcp.example.com
|
|
||||||
}
|
|
||||||
|
|
||||||
reverse_proxy {
|
|
||||||
dynamic srv {
|
|
||||||
service api
|
|
||||||
proto tcp
|
|
||||||
name example.com
|
|
||||||
refresh 5m
|
|
||||||
resolvers 8.8.8.8 8.8.4.4
|
|
||||||
dial_timeout 1s
|
|
||||||
dial_fallback_delay -1s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":8884"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"dynamic_upstreams": {
|
|
||||||
"name": "foo",
|
|
||||||
"port": "9000",
|
|
||||||
"source": "a"
|
|
||||||
},
|
|
||||||
"handler": "reverse_proxy"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dynamic_upstreams": {
|
|
||||||
"dial_fallback_delay": 300000000,
|
|
||||||
"dial_timeout": 2000000000,
|
|
||||||
"name": "foo",
|
|
||||||
"port": "9000",
|
|
||||||
"refresh": 300000000000,
|
|
||||||
"resolver": {
|
|
||||||
"addresses": [
|
|
||||||
"8.8.8.8",
|
|
||||||
"8.8.4.4"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"source": "a"
|
|
||||||
},
|
|
||||||
"handler": "reverse_proxy"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"srv1": {
|
|
||||||
"listen": [
|
|
||||||
":8885"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"dynamic_upstreams": {
|
|
||||||
"name": "_api._tcp.example.com",
|
|
||||||
"source": "srv"
|
|
||||||
},
|
|
||||||
"handler": "reverse_proxy"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dynamic_upstreams": {
|
|
||||||
"dial_fallback_delay": -1000000000,
|
|
||||||
"dial_timeout": 1000000000,
|
|
||||||
"name": "example.com",
|
|
||||||
"proto": "tcp",
|
|
||||||
"refresh": 300000000000,
|
|
||||||
"resolver": {
|
|
||||||
"addresses": [
|
|
||||||
"8.8.8.8",
|
|
||||||
"8.8.4.4"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"service": "api",
|
|
||||||
"source": "srv"
|
|
||||||
},
|
|
||||||
"handler": "reverse_proxy"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
:8884
|
:8884
|
||||||
|
|
||||||
reverse_proxy h2c://localhost:8080
|
reverse_proxy h2c://localhost:8080
|
||||||
|
|
||||||
reverse_proxy unix+h2c//run/app.sock
|
|
||||||
----------
|
----------
|
||||||
{
|
{
|
||||||
"apps": {
|
"apps": {
|
||||||
@@ -29,21 +27,6 @@ reverse_proxy unix+h2c//run/app.sock
|
|||||||
"dial": "localhost:8080"
|
"dial": "localhost:8080"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"handler": "reverse_proxy",
|
|
||||||
"transport": {
|
|
||||||
"protocol": "http",
|
|
||||||
"versions": [
|
|
||||||
"h2c",
|
|
||||||
"2"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"upstreams": [
|
|
||||||
{
|
|
||||||
"dial": "unix//run/app.sock"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
:8884
|
:8884
|
||||||
|
|
||||||
reverse_proxy 127.0.0.1:65535 {
|
reverse_proxy 127.0.0.1:65535 {
|
||||||
@500 status 500
|
|
||||||
replace_status @500 400
|
|
||||||
|
|
||||||
@all status 2xx 3xx 4xx 5xx
|
|
||||||
replace_status @all {http.error.status_code}
|
|
||||||
|
|
||||||
replace_status {http.error.status_code}
|
|
||||||
|
|
||||||
@accel header X-Accel-Redirect *
|
@accel header X-Accel-Redirect *
|
||||||
handle_response @accel {
|
handle_response @accel {
|
||||||
respond "Header X-Accel-Redirect!"
|
respond "Header X-Accel-Redirect!"
|
||||||
@@ -47,19 +39,8 @@ reverse_proxy 127.0.0.1:65535 {
|
|||||||
respond "Headers Foo, Bar AND statuses 401, 403 and 404!"
|
respond "Headers Foo, Bar AND statuses 401, 403 and 404!"
|
||||||
}
|
}
|
||||||
|
|
||||||
@200 status 200
|
@changeStatus status 500
|
||||||
handle_response @200 {
|
handle_response @changeStatus 400
|
||||||
copy_response_headers {
|
|
||||||
include Foo Bar
|
|
||||||
}
|
|
||||||
respond "Copied headers from the response"
|
|
||||||
}
|
|
||||||
|
|
||||||
@201 status 201
|
|
||||||
handle_response @201 {
|
|
||||||
header Foo "Copying the response"
|
|
||||||
copy_response 404
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
----------
|
----------
|
||||||
{
|
{
|
||||||
@@ -75,25 +56,6 @@ reverse_proxy 127.0.0.1:65535 {
|
|||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handle_response": [
|
"handle_response": [
|
||||||
{
|
|
||||||
"match": {
|
|
||||||
"status_code": [
|
|
||||||
500
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"status_code": 400
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": {
|
|
||||||
"status_code": [
|
|
||||||
2,
|
|
||||||
3,
|
|
||||||
4,
|
|
||||||
5
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"status_code": "{http.error.status_code}"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"match": {
|
"match": {
|
||||||
"headers": {
|
"headers": {
|
||||||
@@ -196,56 +158,10 @@ reverse_proxy 127.0.0.1:65535 {
|
|||||||
{
|
{
|
||||||
"match": {
|
"match": {
|
||||||
"status_code": [
|
"status_code": [
|
||||||
200
|
500
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"routes": [
|
"status_code": 400
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "copy_response_headers",
|
|
||||||
"include": [
|
|
||||||
"Foo",
|
|
||||||
"Bar"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"body": "Copied headers from the response",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": {
|
|
||||||
"status_code": [
|
|
||||||
201
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "headers",
|
|
||||||
"response": {
|
|
||||||
"set": {
|
|
||||||
"Foo": [
|
|
||||||
"Copying the response"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"handler": "copy_response",
|
|
||||||
"status_code": 404
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"status_code": "{http.error.status_code}"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"routes": [
|
"routes": [
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ reverse_proxy 127.0.0.1:65535 {
|
|||||||
X-Header-Keys VbG4NZwWnipo 335Q9/MhqcNU3s2TO
|
X-Header-Keys VbG4NZwWnipo 335Q9/MhqcNU3s2TO
|
||||||
X-Empty-Value
|
X-Empty-Value
|
||||||
}
|
}
|
||||||
health_uri /health
|
|
||||||
}
|
}
|
||||||
----------
|
----------
|
||||||
{
|
{
|
||||||
@@ -39,8 +38,7 @@ reverse_proxy 127.0.0.1:65535 {
|
|||||||
"VbG4NZwWnipo",
|
"VbG4NZwWnipo",
|
||||||
"335Q9/MhqcNU3s2TO"
|
"335Q9/MhqcNU3s2TO"
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"uri": "/health"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"upstreams": [
|
"upstreams": [
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
:8884
|
||||||
|
|
||||||
|
reverse_proxy one|http://localhost two|http://localhost {
|
||||||
|
to three|srv+http://localhost four|srv+http://localhost
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":8884"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "reverse_proxy",
|
||||||
|
"upstreams": [
|
||||||
|
{
|
||||||
|
"dial": "localhost:80",
|
||||||
|
"id": "one"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dial": "localhost:80",
|
||||||
|
"id": "two"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "three",
|
||||||
|
"lookup_srv": "localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "four",
|
||||||
|
"lookup_srv": "localhost"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
:8884
|
|
||||||
|
|
||||||
reverse_proxy 127.0.0.1:65535 {
|
|
||||||
lb_policy first
|
|
||||||
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": "first"
|
|
||||||
},
|
|
||||||
"try_duration": 10000000000,
|
|
||||||
"try_interval": 500000000
|
|
||||||
},
|
|
||||||
"upstreams": [
|
|
||||||
{
|
|
||||||
"dial": "127.0.0.1:65535"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
|
|
||||||
https://example.com {
|
https://example.com {
|
||||||
reverse_proxy /path https://localhost:54321 {
|
reverse_proxy /path http://localhost:54321 {
|
||||||
header_up Host {upstream_hostport}
|
header_up Host {host}
|
||||||
header_up Foo bar
|
header_up X-Real-IP {remote}
|
||||||
|
header_up X-Forwarded-For {remote}
|
||||||
method GET
|
header_up X-Forwarded-Port {server_port}
|
||||||
rewrite /rewritten?uri={uri}
|
header_up X-Forwarded-Proto "http"
|
||||||
|
|
||||||
buffer_requests
|
buffer_requests
|
||||||
|
|
||||||
@@ -17,16 +17,11 @@ https://example.com {
|
|||||||
dial_fallback_delay 5s
|
dial_fallback_delay 5s
|
||||||
response_header_timeout 8s
|
response_header_timeout 8s
|
||||||
expect_continue_timeout 9s
|
expect_continue_timeout 9s
|
||||||
resolvers 8.8.8.8 8.8.4.4
|
|
||||||
|
|
||||||
versions h2c 2
|
versions h2c 2
|
||||||
compression off
|
compression off
|
||||||
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
|
|
||||||
|
|
||||||
tls_renegotiation freely
|
|
||||||
tls_except_ports 8181 8182
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,46 +56,37 @@ https://example.com {
|
|||||||
"headers": {
|
"headers": {
|
||||||
"request": {
|
"request": {
|
||||||
"set": {
|
"set": {
|
||||||
"Foo": [
|
|
||||||
"bar"
|
|
||||||
],
|
|
||||||
"Host": [
|
"Host": [
|
||||||
"{http.reverse_proxy.upstream.hostport}"
|
"{http.request.host}"
|
||||||
|
],
|
||||||
|
"X-Forwarded-For": [
|
||||||
|
"{http.request.remote}"
|
||||||
|
],
|
||||||
|
"X-Forwarded-Port": [
|
||||||
|
"{server_port}"
|
||||||
|
],
|
||||||
|
"X-Forwarded-Proto": [
|
||||||
|
"http"
|
||||||
|
],
|
||||||
|
"X-Real-Ip": [
|
||||||
|
"{http.request.remote}"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rewrite": {
|
|
||||||
"method": "GET",
|
|
||||||
"uri": "/rewritten?uri={http.request.uri}"
|
|
||||||
},
|
|
||||||
"transport": {
|
"transport": {
|
||||||
"compression": false,
|
"compression": false,
|
||||||
"dial_fallback_delay": 5000000000,
|
"dial_fallback_delay": 5000000000,
|
||||||
"dial_timeout": 3000000000,
|
"dial_timeout": 3000000000,
|
||||||
"expect_continue_timeout": 9000000000,
|
"expect_continue_timeout": 9000000000,
|
||||||
"keep_alive": {
|
"keep_alive": {
|
||||||
"max_idle_conns_per_host": 2,
|
"max_idle_conns_per_host": 2
|
||||||
"probe_interval": 30000000000
|
|
||||||
},
|
},
|
||||||
"max_conns_per_host": 5,
|
"max_conns_per_host": 5,
|
||||||
"max_response_header_size": 30000000,
|
"max_response_header_size": 30000000,
|
||||||
"protocol": "http",
|
"protocol": "http",
|
||||||
"read_buffer_size": 10000000,
|
"read_buffer_size": 10000000,
|
||||||
"resolver": {
|
|
||||||
"addresses": [
|
|
||||||
"8.8.8.8",
|
|
||||||
"8.8.4.4"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"response_header_timeout": 8000000000,
|
"response_header_timeout": 8000000000,
|
||||||
"tls": {
|
|
||||||
"except_ports": [
|
|
||||||
"8181",
|
|
||||||
"8182"
|
|
||||||
],
|
|
||||||
"renegotiation": "freely"
|
|
||||||
},
|
|
||||||
"versions": [
|
"versions": [
|
||||||
"h2c",
|
"h2c",
|
||||||
"2"
|
"2"
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
:8884
|
|
||||||
|
|
||||||
reverse_proxy 127.0.0.1:65535 {
|
|
||||||
trusted_proxies 127.0.0.1
|
|
||||||
}
|
|
||||||
|
|
||||||
reverse_proxy 127.0.0.1:65535 {
|
|
||||||
trusted_proxies private_ranges
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":8884"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "reverse_proxy",
|
|
||||||
"trusted_proxies": [
|
|
||||||
"127.0.0.1"
|
|
||||||
],
|
|
||||||
"upstreams": [
|
|
||||||
{
|
|
||||||
"dial": "127.0.0.1:65535"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"handler": "reverse_proxy",
|
|
||||||
"trusted_proxies": [
|
|
||||||
"192.168.0.0/16",
|
|
||||||
"172.16.0.0/12",
|
|
||||||
"10.0.0.0/8",
|
|
||||||
"127.0.0.1/8",
|
|
||||||
"fd00::/8",
|
|
||||||
"::1"
|
|
||||||
],
|
|
||||||
"upstreams": [
|
|
||||||
{
|
|
||||||
"dial": "127.0.0.1:65535"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
*.example.com {
|
|
||||||
@foo host foo.example.com
|
|
||||||
handle @foo {
|
|
||||||
handle_path /strip* {
|
|
||||||
respond "this should be first"
|
|
||||||
}
|
|
||||||
handle {
|
|
||||||
respond "this should be second"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handle {
|
|
||||||
respond "this should be last"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"*.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"group": "group5",
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"group": "group2",
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "rewrite",
|
|
||||||
"strip_path_prefix": "/strip"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "this should be first",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"path": [
|
|
||||||
"/strip*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"group": "group2",
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "this should be second",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"foo.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"group": "group5",
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "this should be last",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
:80
|
|
||||||
|
|
||||||
vars /foobar foo last
|
|
||||||
vars /foo foo middle
|
|
||||||
vars * foo first
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":80"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"foo": "first",
|
|
||||||
"handler": "vars"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"path": [
|
|
||||||
"/foo"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"foo": "middle",
|
|
||||||
"handler": "vars"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"path": [
|
|
||||||
"/foobar"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"foo": "last",
|
|
||||||
"handler": "vars"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
# (this Caddyfile is contrived, but based on issues #4176 and #4198)
|
|
||||||
|
|
||||||
http://example.com {
|
|
||||||
}
|
|
||||||
|
|
||||||
https://example.com {
|
|
||||||
tls abc@example.com
|
|
||||||
}
|
|
||||||
|
|
||||||
http://localhost:8081 {
|
|
||||||
}
|
|
||||||
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"srv1": {
|
|
||||||
"listen": [
|
|
||||||
":80"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"srv2": {
|
|
||||||
"listen": [
|
|
||||||
":8081"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"automatic_https": {
|
|
||||||
"skip": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tls": {
|
|
||||||
"automation": {
|
|
||||||
"policies": [
|
|
||||||
{
|
|
||||||
"subjects": [
|
|
||||||
"example.com"
|
|
||||||
],
|
|
||||||
"issuers": [
|
|
||||||
{
|
|
||||||
"email": "abc@example.com",
|
|
||||||
"module": "acme"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"email": "abc@example.com",
|
|
||||||
"module": "zerossl"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
# example from issue #4640
|
|
||||||
http://foo:8447, http://127.0.0.1:8447 {
|
|
||||||
reverse_proxy 127.0.0.1:8080
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":8447"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"foo",
|
|
||||||
"127.0.0.1"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "reverse_proxy",
|
|
||||||
"upstreams": [
|
|
||||||
{
|
|
||||||
"dial": "127.0.0.1:8080"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"automatic_https": {
|
|
||||||
"skip": [
|
|
||||||
"foo",
|
|
||||||
"127.0.0.1"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
a.example.com {
|
|
||||||
tls {
|
|
||||||
issuer internal {
|
|
||||||
ca foo
|
|
||||||
lifetime 24h
|
|
||||||
sign_with_root
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"a.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tls": {
|
|
||||||
"automation": {
|
|
||||||
"policies": [
|
|
||||||
{
|
|
||||||
"subjects": [
|
|
||||||
"a.example.com"
|
|
||||||
],
|
|
||||||
"issuers": [
|
|
||||||
{
|
|
||||||
"ca": "foo",
|
|
||||||
"lifetime": 86400000000000,
|
|
||||||
"module": "internal",
|
|
||||||
"sign_with_root": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,12 +3,7 @@ localhost
|
|||||||
respond "hello from localhost"
|
respond "hello from localhost"
|
||||||
tls {
|
tls {
|
||||||
issuer acme {
|
issuer acme {
|
||||||
propagation_delay 5m10s
|
propagation_timeout "10m0s"
|
||||||
propagation_timeout 10m20s
|
|
||||||
}
|
|
||||||
issuer zerossl {
|
|
||||||
propagation_delay 5m30s
|
|
||||||
propagation_timeout -1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
----------
|
----------
|
||||||
@@ -61,20 +56,10 @@ tls {
|
|||||||
{
|
{
|
||||||
"challenges": {
|
"challenges": {
|
||||||
"dns": {
|
"dns": {
|
||||||
"propagation_delay": 310000000000,
|
"propagation_timeout": 600000000000
|
||||||
"propagation_timeout": 620000000000
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"module": "acme"
|
"module": "acme"
|
||||||
},
|
|
||||||
{
|
|
||||||
"challenges": {
|
|
||||||
"dns": {
|
|
||||||
"propagation_delay": 330000000000,
|
|
||||||
"propagation_timeout": -1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"module": "zerossl"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
:80 {
|
|
||||||
tracing /myhandler {
|
|
||||||
span my-span
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":80"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"path": [
|
|
||||||
"/myhandler"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "tracing",
|
|
||||||
"span": "my-span"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ package integration
|
|||||||
import (
|
import (
|
||||||
jsonMod "encoding/json"
|
jsonMod "encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
func TestCaddyfileAdaptToJSON(t *testing.T) {
|
func TestCaddyfileAdaptToJSON(t *testing.T) {
|
||||||
// load the list of test files from the dir
|
// load the list of test files from the dir
|
||||||
files, err := os.ReadDir("./caddyfile_adapt")
|
files, err := ioutil.ReadDir("./caddyfile_adapt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to read caddyfile_adapt dir: %s", err)
|
t.Errorf("failed to read caddyfile_adapt dir: %s", err)
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ func TestCaddyfileAdaptToJSON(t *testing.T) {
|
|||||||
|
|
||||||
// read the test file
|
// read the test file
|
||||||
filename := f.Name()
|
filename := f.Name()
|
||||||
data, err := os.ReadFile("./caddyfile_adapt/" + filename)
|
data, err := ioutil.ReadFile("./caddyfile_adapt/" + filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to read %s dir: %s", filename, err)
|
t.Errorf("failed to read %s dir: %s", filename, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ func TestDuplicateHosts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
"caddyfile",
|
"caddyfile",
|
||||||
"ambiguous site definition")
|
"duplicate site address not allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadCookie(t *testing.T) {
|
func TestReadCookie(t *testing.T) {
|
||||||
@@ -101,27 +101,3 @@ func TestReadCookie(t *testing.T) {
|
|||||||
// act and assert
|
// act and assert
|
||||||
tester.AssertGetResponse("http://localhost:9080/cookie.html", 200, "<h2>Cookie.ClientName caddytest</h2>")
|
tester.AssertGetResponse("http://localhost:9080/cookie.html", 200, "<h2>Cookie.ClientName caddytest</h2>")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReplIndex(t *testing.T) {
|
|
||||||
|
|
||||||
tester := caddytest.NewTester(t)
|
|
||||||
tester.InitServer(`
|
|
||||||
{
|
|
||||||
http_port 9080
|
|
||||||
https_port 9443
|
|
||||||
}
|
|
||||||
|
|
||||||
localhost:9080 {
|
|
||||||
templates {
|
|
||||||
root testdata
|
|
||||||
}
|
|
||||||
file_server {
|
|
||||||
root testdata
|
|
||||||
index "index.{host}.html"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, "caddyfile")
|
|
||||||
|
|
||||||
// act and assert
|
|
||||||
tester.AssertGetResponse("http://localhost:9080/", 200, "")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func TestMapRespondWithDefault(t *testing.T) {
|
|||||||
tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost unknown")
|
tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost unknown")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMapAsJSON(t *testing.T) {
|
func TestMapAsJson(t *testing.T) {
|
||||||
// arrange
|
// arrange
|
||||||
tester := caddytest.NewTester(t)
|
tester := caddytest.NewTester(t)
|
||||||
tester.InitServer(`
|
tester.InitServer(`
|
||||||
@@ -85,7 +85,7 @@ func TestMapAsJSON(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"handler": "map",
|
"handler": "map",
|
||||||
"source": "{http.request.method}",
|
"source": "{http.request.method}",
|
||||||
"destinations": ["{dest-name}"],
|
"destinations": ["dest-name"],
|
||||||
"defaults": ["unknown"],
|
"defaults": ["unknown"],
|
||||||
"mappings": [
|
"mappings": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -84,7 +85,7 @@ func TestDialWithPlaceholderUnix(t *testing.T) {
|
|||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.CreateTemp("", "*.sock")
|
f, err := ioutil.TempFile("", "*.sock")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to create TempFile: %s", err)
|
t.Errorf("failed to create TempFile: %s", err)
|
||||||
return
|
return
|
||||||
@@ -386,7 +387,7 @@ func TestReverseProxyHealthCheckUnixSocket(t *testing.T) {
|
|||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
tester := caddytest.NewTester(t)
|
tester := caddytest.NewTester(t)
|
||||||
f, err := os.CreateTemp("", "*.sock")
|
f, err := ioutil.TempFile("", "*.sock")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to create TempFile: %s", err)
|
t.Errorf("failed to create TempFile: %s", err)
|
||||||
return
|
return
|
||||||
@@ -441,7 +442,7 @@ func TestReverseProxyHealthCheckUnixSocketWithoutPort(t *testing.T) {
|
|||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
tester := caddytest.NewTester(t)
|
tester := caddytest.NewTester(t)
|
||||||
f, err := os.CreateTemp("", "*.sock")
|
f, err := ioutil.TempFile("", "*.sock")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to create TempFile: %s", err)
|
t.Errorf("failed to create TempFile: %s", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -109,7 +110,7 @@ func TestH2ToH2CStream(t *testing.T) {
|
|||||||
r, w := io.Pipe()
|
r, w := io.Pipe()
|
||||||
req := &http.Request{
|
req := &http.Request{
|
||||||
Method: "PUT",
|
Method: "PUT",
|
||||||
Body: io.NopCloser(r),
|
Body: ioutil.NopCloser(r),
|
||||||
URL: &url.URL{
|
URL: &url.URL{
|
||||||
Scheme: "https",
|
Scheme: "https",
|
||||||
Host: "127.0.0.1:9443",
|
Host: "127.0.0.1:9443",
|
||||||
@@ -123,8 +124,8 @@ func TestH2ToH2CStream(t *testing.T) {
|
|||||||
// Disable any compression method from server.
|
// Disable any compression method from server.
|
||||||
req.Header.Set("Accept-Encoding", "identity")
|
req.Header.Set("Accept-Encoding", "identity")
|
||||||
|
|
||||||
resp := tester.AssertResponseCode(req, http.StatusOK)
|
resp := tester.AssertResponseCode(req, 200)
|
||||||
if resp.StatusCode != http.StatusOK {
|
if 200 != resp.StatusCode {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
@@ -133,7 +134,7 @@ func TestH2ToH2CStream(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
bytes, err := io.ReadAll(resp.Body)
|
bytes, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to read the response body %s", err)
|
t.Fatalf("unable to read the response body %s", err)
|
||||||
}
|
}
|
||||||
@@ -143,6 +144,7 @@ func TestH2ToH2CStream(t *testing.T) {
|
|||||||
if !strings.Contains(body, expectedBody) {
|
if !strings.Contains(body, expectedBody) {
|
||||||
t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
|
t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server {
|
func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server {
|
||||||
@@ -317,7 +319,7 @@ func TestH2ToH1ChunkedResponse(t *testing.T) {
|
|||||||
r, w := io.Pipe()
|
r, w := io.Pipe()
|
||||||
req := &http.Request{
|
req := &http.Request{
|
||||||
Method: "PUT",
|
Method: "PUT",
|
||||||
Body: io.NopCloser(r),
|
Body: ioutil.NopCloser(r),
|
||||||
URL: &url.URL{
|
URL: &url.URL{
|
||||||
Scheme: "https",
|
Scheme: "https",
|
||||||
Host: "127.0.0.1:9443",
|
Host: "127.0.0.1:9443",
|
||||||
@@ -334,13 +336,13 @@ func TestH2ToH1ChunkedResponse(t *testing.T) {
|
|||||||
fmt.Fprint(w, expectedBody)
|
fmt.Fprint(w, expectedBody)
|
||||||
w.Close()
|
w.Close()
|
||||||
}()
|
}()
|
||||||
resp := tester.AssertResponseCode(req, http.StatusOK)
|
resp := tester.AssertResponseCode(req, 200)
|
||||||
if resp.StatusCode != http.StatusOK {
|
if 200 != resp.StatusCode {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
bytes, err := io.ReadAll(resp.Body)
|
bytes, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to read the response body %s", err)
|
t.Fatalf("unable to read the response body %s", err)
|
||||||
}
|
}
|
||||||
@@ -350,6 +352,7 @@ func TestH2ToH1ChunkedResponse(t *testing.T) {
|
|||||||
if body != expectedBody {
|
if body != expectedBody {
|
||||||
t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
|
t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func testH2ToH1ChunkedResponseServeH1(t *testing.T) *http.Server {
|
func testH2ToH1ChunkedResponseServeH1(t *testing.T) *http.Server {
|
||||||
@@ -367,7 +370,7 @@ func testH2ToH1ChunkedResponseServeH1(t *testing.T) *http.Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
bytes, err := io.ReadAll(r.Body)
|
bytes, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to read the response body %s", err)
|
t.Fatalf("unable to read the response body %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,6 @@
|
|||||||
// 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:
|
|
||||||
// https://github.com/caddyserver/xcaddy
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
-120
@@ -1,120 +0,0 @@
|
|||||||
package caddycmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
|
||||||
Use: "caddy",
|
|
||||||
Long: `Caddy is an extensible server platform written in Go.
|
|
||||||
|
|
||||||
At its core, Caddy merely manages configuration. Modules are plugged
|
|
||||||
in statically at compile-time to provide useful functionality. Caddy's
|
|
||||||
standard distribution includes common modules to serve HTTP, TLS,
|
|
||||||
and PKI applications, including the automation of certificates.
|
|
||||||
|
|
||||||
To run Caddy, use:
|
|
||||||
|
|
||||||
- 'caddy run' to run Caddy in the foreground (recommended).
|
|
||||||
- 'caddy start' to start Caddy in the background; only do this
|
|
||||||
if you will be keeping the terminal window open until you run
|
|
||||||
'caddy stop' to close the server.
|
|
||||||
|
|
||||||
When Caddy is started, it opens a locally-bound administrative socket
|
|
||||||
to which configuration can be POSTed via a restful HTTP API (see
|
|
||||||
https://caddyserver.com/docs/api).
|
|
||||||
|
|
||||||
Caddy's native configuration format is JSON. However, config adapters
|
|
||||||
can be used to convert other config formats to JSON when Caddy receives
|
|
||||||
its configuration. The Caddyfile is a built-in config adapter that is
|
|
||||||
popular for hand-written configurations due to its straightforward
|
|
||||||
syntax (see https://caddyserver.com/docs/caddyfile). Many third-party
|
|
||||||
adapters are available (see https://caddyserver.com/docs/config-adapters).
|
|
||||||
Use 'caddy adapt' to see how a config translates to JSON.
|
|
||||||
|
|
||||||
For convenience, the CLI can act as an HTTP client to give Caddy its
|
|
||||||
initial configuration for you. If a file named Caddyfile is in the
|
|
||||||
current working directory, it will do this automatically. Otherwise,
|
|
||||||
you can use the --config flag to specify the path to a config file.
|
|
||||||
|
|
||||||
Some special-purpose subcommands build and load a configuration file
|
|
||||||
for you directly from command line input; for example:
|
|
||||||
|
|
||||||
- caddy file-server
|
|
||||||
- caddy reverse-proxy
|
|
||||||
- caddy respond
|
|
||||||
|
|
||||||
These commands disable the administration endpoint because their
|
|
||||||
configuration is specified solely on the command line.
|
|
||||||
|
|
||||||
In general, the most common way to run Caddy is simply:
|
|
||||||
|
|
||||||
$ caddy run
|
|
||||||
|
|
||||||
Or, with a configuration file:
|
|
||||||
|
|
||||||
$ caddy run --config caddy.json
|
|
||||||
|
|
||||||
If running interactively in a terminal, running Caddy in the
|
|
||||||
background may be more convenient:
|
|
||||||
|
|
||||||
$ caddy start
|
|
||||||
...
|
|
||||||
$ caddy stop
|
|
||||||
|
|
||||||
This allows you to run other commands while Caddy stays running.
|
|
||||||
Be sure to stop Caddy before you close the terminal!
|
|
||||||
|
|
||||||
Depending on the system, Caddy may need permission to bind to low
|
|
||||||
ports. One way to do this on Linux is to use setcap:
|
|
||||||
|
|
||||||
$ sudo setcap cap_net_bind_service=+ep $(which caddy)
|
|
||||||
|
|
||||||
Remember to run that command again after replacing the binary.
|
|
||||||
|
|
||||||
See the Caddy website for tutorials, configuration structure,
|
|
||||||
syntax, and module documentation: https://caddyserver.com/docs/
|
|
||||||
|
|
||||||
Custom Caddy builds are available on the Caddy download page at:
|
|
||||||
https://caddyserver.com/download
|
|
||||||
|
|
||||||
The xcaddy command can be used to build Caddy from source with or
|
|
||||||
without additional plugins: https://github.com/caddyserver/xcaddy
|
|
||||||
|
|
||||||
Where possible, Caddy should be installed using officially-supported
|
|
||||||
package installers: https://caddyserver.com/docs/install
|
|
||||||
|
|
||||||
Instructions for running Caddy in production are also available:
|
|
||||||
https://caddyserver.com/docs/running
|
|
||||||
`,
|
|
||||||
Example: ` $ caddy run
|
|
||||||
$ caddy run --config caddy.json
|
|
||||||
$ caddy reload --config caddy.json
|
|
||||||
$ caddy stop`,
|
|
||||||
|
|
||||||
// kind of annoying to have all the help text printed out if
|
|
||||||
// caddy has an error provisioning its modules, for instance...
|
|
||||||
SilenceUsage: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
const fullDocsFooter = `Full documentation is available at:
|
|
||||||
https://caddyserver.com/docs/command-line`
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func caddyCmdToCoral(caddyCmd Command) *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: caddyCmd.Name,
|
|
||||||
Short: caddyCmd.Short,
|
|
||||||
Long: caddyCmd.Long,
|
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
||||||
fls := cmd.Flags()
|
|
||||||
_, err := caddyCmd.Func(Flags{fls})
|
|
||||||
return err
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.Flags().AddGoFlagSet(caddyCmd.Flags)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
+140
-133
@@ -22,6 +22,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -29,9 +30,9 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/aryann/difflib"
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"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"
|
||||||
@@ -180,7 +181,7 @@ func cmdRun(fl Flags) (int, error) {
|
|||||||
var config []byte
|
var config []byte
|
||||||
var err error
|
var err error
|
||||||
if runCmdResumeFlag {
|
if runCmdResumeFlag {
|
||||||
config, err = os.ReadFile(caddy.ConfigAutosavePath)
|
config, err = ioutil.ReadFile(caddy.ConfigAutosavePath)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
// not a bad error; just can't resume if autosave file doesn't exist
|
// not a bad error; just can't resume if autosave file doesn't exist
|
||||||
caddy.Log().Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath))
|
caddy.Log().Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath))
|
||||||
@@ -202,7 +203,7 @@ func cmdRun(fl Flags) (int, error) {
|
|||||||
// we don't use 'else' here since this value might have been changed in 'if' block; i.e. not mutually exclusive
|
// we don't use 'else' here since this value might have been changed in 'if' block; i.e. not mutually exclusive
|
||||||
var configFile string
|
var configFile string
|
||||||
if !runCmdResumeFlag {
|
if !runCmdResumeFlag {
|
||||||
config, configFile, err = LoadConfig(runCmdConfigFlag, runCmdConfigAdapterFlag)
|
config, configFile, err = loadConfig(runCmdConfigFlag, runCmdConfigAdapterFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return caddy.ExitCodeFailedStartup, err
|
return caddy.ExitCodeFailedStartup, err
|
||||||
}
|
}
|
||||||
@@ -218,7 +219,7 @@ func cmdRun(fl Flags) (int, error) {
|
|||||||
// if we are to report to another process the successful start
|
// if we are to report to another process the successful start
|
||||||
// of the server, do so now by echoing back contents of stdin
|
// of the server, do so now by echoing back contents of stdin
|
||||||
if runCmdPingbackFlag != "" {
|
if runCmdPingbackFlag != "" {
|
||||||
confirmationBytes, err := io.ReadAll(os.Stdin)
|
confirmationBytes, err := ioutil.ReadAll(os.Stdin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return caddy.ExitCodeFailedStartup,
|
return caddy.ExitCodeFailedStartup,
|
||||||
fmt.Errorf("reading confirmation bytes from stdin: %v", err)
|
fmt.Errorf("reading confirmation bytes from stdin: %v", err)
|
||||||
@@ -275,33 +276,25 @@ func cmdRun(fl Flags) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cmdStop(fl Flags) (int, error) {
|
func cmdStop(fl Flags) (int, error) {
|
||||||
addrFlag := fl.String("address")
|
stopCmdAddrFlag := fl.String("address")
|
||||||
configFlag := fl.String("config")
|
|
||||||
configAdapterFlag := fl.String("adapter")
|
|
||||||
|
|
||||||
adminAddr, err := DetermineAdminAPIAddress(addrFlag, nil, configFlag, configAdapterFlag)
|
err := apiRequest(stopCmdAddrFlag, http.MethodPost, "/stop", nil, nil)
|
||||||
if err != nil {
|
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := AdminAPIRequest(adminAddr, http.MethodPost, "/stop", nil, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
caddy.Log().Warn("failed using API to stop instance", zap.Error(err))
|
caddy.Log().Warn("failed using API to stop instance", zap.Error(err))
|
||||||
return caddy.ExitCodeFailedStartup, err
|
return caddy.ExitCodeFailedStartup, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
return caddy.ExitCodeSuccess, nil
|
return caddy.ExitCodeSuccess, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdReload(fl Flags) (int, error) {
|
func cmdReload(fl Flags) (int, error) {
|
||||||
configFlag := fl.String("config")
|
reloadCmdConfigFlag := fl.String("config")
|
||||||
configAdapterFlag := fl.String("adapter")
|
reloadCmdConfigAdapterFlag := fl.String("adapter")
|
||||||
addrFlag := fl.String("address")
|
reloadCmdAddrFlag := fl.String("address")
|
||||||
forceFlag := fl.Bool("force")
|
reloadCmdForceFlag := fl.Bool("force")
|
||||||
|
|
||||||
// get the config in caddy's native format
|
// get the config in caddy's native format
|
||||||
config, configFile, err := LoadConfig(configFlag, configAdapterFlag)
|
config, configFile, err := loadConfig(reloadCmdConfigFlag, reloadCmdConfigAdapterFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return caddy.ExitCodeFailedStartup, err
|
return caddy.ExitCodeFailedStartup, err
|
||||||
}
|
}
|
||||||
@@ -309,45 +302,65 @@ func cmdReload(fl Flags) (int, error) {
|
|||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("no config file to load")
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("no config file to load")
|
||||||
}
|
}
|
||||||
|
|
||||||
adminAddr, err := DetermineAdminAPIAddress(addrFlag, config, configFlag, configAdapterFlag)
|
// get the address of the admin listener; use flag if specified
|
||||||
if err != nil {
|
adminAddr := reloadCmdAddrFlag
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
|
if adminAddr == "" && len(config) > 0 {
|
||||||
|
var tmpStruct struct {
|
||||||
|
Admin caddy.AdminConfig `json:"admin"`
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(config, &tmpStruct)
|
||||||
|
if err != nil {
|
||||||
|
return caddy.ExitCodeFailedStartup,
|
||||||
|
fmt.Errorf("unmarshaling admin listener address from config: %v", err)
|
||||||
|
}
|
||||||
|
adminAddr = tmpStruct.Admin.Listen
|
||||||
}
|
}
|
||||||
|
|
||||||
// optionally force a config reload
|
// optionally force a config reload
|
||||||
headers := make(http.Header)
|
headers := make(http.Header)
|
||||||
if forceFlag {
|
if reloadCmdForceFlag {
|
||||||
headers.Set("Cache-Control", "must-revalidate")
|
headers.Set("Cache-Control", "must-revalidate")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := AdminAPIRequest(adminAddr, http.MethodPost, "/load", headers, bytes.NewReader(config))
|
err = apiRequest(adminAddr, http.MethodPost, "/load", headers, bytes.NewReader(config))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("sending configuration to instance: %v", err)
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("sending configuration to instance: %v", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
return caddy.ExitCodeSuccess, nil
|
return caddy.ExitCodeSuccess, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdVersion(_ Flags) (int, error) {
|
func cmdVersion(_ Flags) (int, error) {
|
||||||
_, full := caddy.Version()
|
fmt.Println(CaddyVersion())
|
||||||
fmt.Println(full)
|
|
||||||
return caddy.ExitCodeSuccess, nil
|
return caddy.ExitCodeSuccess, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdBuildInfo(_ Flags) (int, error) {
|
func cmdBuildInfo(fl Flags) (int, error) {
|
||||||
bi, ok := debug.ReadBuildInfo()
|
bi, ok := debug.ReadBuildInfo()
|
||||||
if !ok {
|
if !ok {
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("no build information")
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("no build information")
|
||||||
}
|
}
|
||||||
fmt.Println(bi)
|
|
||||||
|
fmt.Printf("go_version: %s\n", runtime.Version())
|
||||||
|
fmt.Printf("go_os: %s\n", runtime.GOOS)
|
||||||
|
fmt.Printf("go_arch: %s\n", runtime.GOARCH)
|
||||||
|
fmt.Printf("path: %s\n", bi.Path)
|
||||||
|
fmt.Printf("main: %s %s %s\n", bi.Main.Path, bi.Main.Version, bi.Main.Sum)
|
||||||
|
fmt.Println("dependencies:")
|
||||||
|
|
||||||
|
for _, goMod := range bi.Deps {
|
||||||
|
fmt.Printf("%s %s %s", goMod.Path, goMod.Version, goMod.Sum)
|
||||||
|
if goMod.Replace != nil {
|
||||||
|
fmt.Printf(" => %s %s %s", goMod.Replace.Path, goMod.Replace.Version, goMod.Replace.Sum)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
return caddy.ExitCodeSuccess, nil
|
return caddy.ExitCodeSuccess, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdListModules(fl Flags) (int, error) {
|
func cmdListModules(fl Flags) (int, error) {
|
||||||
packages := fl.Bool("packages")
|
packages := fl.Bool("packages")
|
||||||
versions := fl.Bool("versions")
|
versions := fl.Bool("versions")
|
||||||
skipStandard := fl.Bool("skip-standard")
|
|
||||||
|
|
||||||
printModuleInfo := func(mi moduleInfo) {
|
printModuleInfo := func(mi moduleInfo) {
|
||||||
fmt.Print(mi.caddyModuleID)
|
fmt.Print(mi.caddyModuleID)
|
||||||
@@ -376,19 +389,14 @@ func cmdListModules(fl Flags) (int, error) {
|
|||||||
return caddy.ExitCodeSuccess, nil
|
return caddy.ExitCodeSuccess, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Standard modules (always shipped with Caddy)
|
if len(standard) > 0 {
|
||||||
if !skipStandard {
|
for _, mod := range standard {
|
||||||
if len(standard) > 0 {
|
printModuleInfo(mod)
|
||||||
for _, mod := range standard {
|
|
||||||
printModuleInfo(mod)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fmt.Printf("\n Standard modules: %d\n", len(standard))
|
|
||||||
}
|
}
|
||||||
|
fmt.Printf("\n Standard modules: %d\n", len(standard))
|
||||||
// Non-standard modules (third party plugins)
|
|
||||||
if len(nonstandard) > 0 {
|
if len(nonstandard) > 0 {
|
||||||
if len(standard) > 0 && !skipStandard {
|
if len(standard) > 0 {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
for _, mod := range nonstandard {
|
for _, mod := range nonstandard {
|
||||||
@@ -396,10 +404,8 @@ func cmdListModules(fl Flags) (int, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Printf("\n Non-standard modules: %d\n", len(nonstandard))
|
fmt.Printf("\n Non-standard modules: %d\n", len(nonstandard))
|
||||||
|
|
||||||
// Unknown modules (couldn't get Caddy module info)
|
|
||||||
if len(unknown) > 0 {
|
if len(unknown) > 0 {
|
||||||
if (len(standard) > 0 && !skipStandard) || len(nonstandard) > 0 {
|
if len(standard) > 0 || len(nonstandard) > 0 {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
for _, mod := range unknown {
|
for _, mod := range unknown {
|
||||||
@@ -451,13 +457,13 @@ func cmdAdaptConfig(fl Flags) (int, error) {
|
|||||||
fmt.Errorf("unrecognized config adapter: %s", adaptCmdAdapterFlag)
|
fmt.Errorf("unrecognized config adapter: %s", adaptCmdAdapterFlag)
|
||||||
}
|
}
|
||||||
|
|
||||||
input, err := os.ReadFile(adaptCmdInputFlag)
|
input, err := ioutil.ReadFile(adaptCmdInputFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return caddy.ExitCodeFailedStartup,
|
return caddy.ExitCodeFailedStartup,
|
||||||
fmt.Errorf("reading input file: %v", err)
|
fmt.Errorf("reading input file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := map[string]any{"filename": adaptCmdInputFlag}
|
opts := map[string]interface{}{"filename": adaptCmdInputFlag}
|
||||||
|
|
||||||
adaptedConfig, warnings, err := cfgAdapter.Adapt(input, opts)
|
adaptedConfig, warnings, err := cfgAdapter.Adapt(input, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -482,9 +488,7 @@ func cmdAdaptConfig(fl Flags) (int, error) {
|
|||||||
if warn.Directive != "" {
|
if warn.Directive != "" {
|
||||||
msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message)
|
msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message)
|
||||||
}
|
}
|
||||||
caddy.Log().Named(adaptCmdAdapterFlag).Warn(msg,
|
fmt.Fprintf(os.Stderr, "[WARNING][%s] %s:%d: %s\n", adaptCmdAdapterFlag, warn.File, warn.Line, msg)
|
||||||
zap.String("file", warn.File),
|
|
||||||
zap.Int("line", warn.Line))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate output if requested
|
// validate output if requested
|
||||||
@@ -507,7 +511,7 @@ func cmdValidateConfig(fl Flags) (int, error) {
|
|||||||
validateCmdConfigFlag := fl.String("config")
|
validateCmdConfigFlag := fl.String("config")
|
||||||
validateCmdAdapterFlag := fl.String("adapter")
|
validateCmdAdapterFlag := fl.String("adapter")
|
||||||
|
|
||||||
input, _, err := LoadConfig(validateCmdConfigFlag, validateCmdAdapterFlag)
|
input, _, err := loadConfig(validateCmdConfigFlag, validateCmdAdapterFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return caddy.ExitCodeFailedStartup, err
|
return caddy.ExitCodeFailedStartup, err
|
||||||
}
|
}
|
||||||
@@ -537,7 +541,7 @@ func cmdFmt(fl Flags) (int, error) {
|
|||||||
|
|
||||||
// as a special case, read from stdin if the file name is "-"
|
// as a special case, read from stdin if the file name is "-"
|
||||||
if formatCmdConfigFile == "-" {
|
if formatCmdConfigFile == "-" {
|
||||||
input, err := io.ReadAll(os.Stdin)
|
input, err := ioutil.ReadAll(os.Stdin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return caddy.ExitCodeFailedStartup,
|
return caddy.ExitCodeFailedStartup,
|
||||||
fmt.Errorf("reading stdin: %v", err)
|
fmt.Errorf("reading stdin: %v", err)
|
||||||
@@ -546,7 +550,7 @@ func cmdFmt(fl Flags) (int, error) {
|
|||||||
return caddy.ExitCodeSuccess, nil
|
return caddy.ExitCodeSuccess, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
input, err := os.ReadFile(formatCmdConfigFile)
|
input, err := ioutil.ReadFile(formatCmdConfigFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return caddy.ExitCodeFailedStartup,
|
return caddy.ExitCodeFailedStartup,
|
||||||
fmt.Errorf("reading input file: %v", err)
|
fmt.Errorf("reading input file: %v", err)
|
||||||
@@ -555,22 +559,8 @@ func cmdFmt(fl Flags) (int, error) {
|
|||||||
output := caddyfile.Format(input)
|
output := caddyfile.Format(input)
|
||||||
|
|
||||||
if fl.Bool("overwrite") {
|
if fl.Bool("overwrite") {
|
||||||
if err := os.WriteFile(formatCmdConfigFile, output, 0600); err != nil {
|
if err := ioutil.WriteFile(formatCmdConfigFile, output, 0600); err != nil {
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("overwriting formatted file: %v", err)
|
return caddy.ExitCodeFailedStartup, nil
|
||||||
}
|
|
||||||
} else if fl.Bool("diff") {
|
|
||||||
diff := difflib.Diff(
|
|
||||||
strings.Split(string(input), "\n"),
|
|
||||||
strings.Split(string(output), "\n"))
|
|
||||||
for _, d := range diff {
|
|
||||||
switch d.Delta {
|
|
||||||
case difflib.Common:
|
|
||||||
fmt.Printf(" %s\n", d.Payload)
|
|
||||||
case difflib.LeftOnly:
|
|
||||||
fmt.Printf("- %s\n", d.Payload)
|
|
||||||
case difflib.RightOnly:
|
|
||||||
fmt.Printf("+ %s\n", d.Payload)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Print(string(output))
|
fmt.Print(string(output))
|
||||||
@@ -579,25 +569,91 @@ func cmdFmt(fl Flags) (int, error) {
|
|||||||
return caddy.ExitCodeSuccess, nil
|
return caddy.ExitCodeSuccess, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminAPIRequest makes an API request according to the CLI flags given,
|
func cmdHelp(fl Flags) (int, error) {
|
||||||
// with the given HTTP method and request URI. If body is non-nil, it will
|
const fullDocs = `Full documentation is available at:
|
||||||
// be assumed to be Content-Type application/json. The caller should close
|
https://caddyserver.com/docs/command-line`
|
||||||
// the response body. Should only be used by Caddy CLI commands which
|
|
||||||
// need to interact with a running instance of Caddy via the admin API.
|
args := fl.Args()
|
||||||
func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io.Reader) (*http.Response, error) {
|
if len(args) == 0 {
|
||||||
|
s := `Caddy is an extensible server platform.
|
||||||
|
|
||||||
|
usage:
|
||||||
|
caddy <command> [<args...>]
|
||||||
|
|
||||||
|
commands:
|
||||||
|
`
|
||||||
|
keys := make([]string, 0, len(commands))
|
||||||
|
for k := range commands {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
for _, k := range keys {
|
||||||
|
cmd := commands[k]
|
||||||
|
short := strings.TrimSuffix(cmd.Short, ".")
|
||||||
|
s += fmt.Sprintf(" %-15s %s\n", cmd.Name, short)
|
||||||
|
}
|
||||||
|
|
||||||
|
s += "\nUse 'caddy help <command>' for more information about a command.\n"
|
||||||
|
s += "\n" + fullDocs + "\n"
|
||||||
|
|
||||||
|
fmt.Print(s)
|
||||||
|
|
||||||
|
return caddy.ExitCodeSuccess, nil
|
||||||
|
} else if len(args) > 1 {
|
||||||
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("can only give help with one command")
|
||||||
|
}
|
||||||
|
|
||||||
|
subcommand, ok := commands[args[0]]
|
||||||
|
if !ok {
|
||||||
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("unknown command: %s", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
helpText := strings.TrimSpace(subcommand.Long)
|
||||||
|
if helpText == "" {
|
||||||
|
helpText = subcommand.Short
|
||||||
|
if !strings.HasSuffix(helpText, ".") {
|
||||||
|
helpText += "."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := fmt.Sprintf("%s\n\nusage:\n caddy %s %s\n",
|
||||||
|
helpText,
|
||||||
|
subcommand.Name,
|
||||||
|
strings.TrimSpace(subcommand.Usage),
|
||||||
|
)
|
||||||
|
|
||||||
|
if help := flagHelp(subcommand.Flags); help != "" {
|
||||||
|
result += fmt.Sprintf("\nflags:\n%s", help)
|
||||||
|
}
|
||||||
|
|
||||||
|
result += "\n" + fullDocs + "\n"
|
||||||
|
|
||||||
|
fmt.Print(result)
|
||||||
|
|
||||||
|
return caddy.ExitCodeSuccess, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// apiRequest makes an API request to the endpoint adminAddr with the
|
||||||
|
// given HTTP method and request URI. If body is non-nil, it will be
|
||||||
|
// assumed to be Content-Type application/json.
|
||||||
|
func apiRequest(adminAddr, method, uri string, headers http.Header, body io.Reader) error {
|
||||||
|
// parse the admin address
|
||||||
|
if adminAddr == "" {
|
||||||
|
adminAddr = caddy.DefaultAdminListen
|
||||||
|
}
|
||||||
parsedAddr, err := caddy.ParseNetworkAddress(adminAddr)
|
parsedAddr, err := caddy.ParseNetworkAddress(adminAddr)
|
||||||
if err != nil || parsedAddr.PortRangeSize() > 1 {
|
if err != nil || parsedAddr.PortRangeSize() > 1 {
|
||||||
return nil, fmt.Errorf("invalid admin address %s: %v", adminAddr, err)
|
return fmt.Errorf("invalid admin address %s: %v", adminAddr, err)
|
||||||
}
|
}
|
||||||
origin := "http://" + parsedAddr.JoinHostPort(0)
|
origin := parsedAddr.JoinHostPort(0)
|
||||||
if parsedAddr.IsUnixNetwork() {
|
if parsedAddr.IsUnixNetwork() {
|
||||||
origin = "http://unixsocket" // hack so that http.NewRequest() is happy
|
origin = "unixsocket" // hack so that http.NewRequest() is happy
|
||||||
}
|
}
|
||||||
|
|
||||||
// form the request
|
// form the request
|
||||||
req, err := http.NewRequest(method, origin+uri, body)
|
req, err := http.NewRequest(method, "http://"+origin+uri, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("making request: %v", err)
|
return fmt.Errorf("making request: %v", err)
|
||||||
}
|
}
|
||||||
if parsedAddr.IsUnixNetwork() {
|
if parsedAddr.IsUnixNetwork() {
|
||||||
// When listening on a unix socket, the admin endpoint doesn't
|
// When listening on a unix socket, the admin endpoint doesn't
|
||||||
@@ -637,69 +693,20 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io
|
|||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("performing request: %v", err)
|
return fmt.Errorf("performing request: %v", err)
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// if it didn't work, let the user know
|
// if it didn't work, let the user know
|
||||||
if resp.StatusCode >= 400 {
|
if resp.StatusCode >= 400 {
|
||||||
respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1024*10))
|
respBody, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1024*10))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
|
return fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("caddy responded with error: HTTP %d: %s", resp.StatusCode, respBody)
|
return fmt.Errorf("caddy responded with error: HTTP %d: %s", resp.StatusCode, respBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp, nil
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
// DetermineAdminAPIAddress determines which admin API endpoint address should
|
|
||||||
// be used based on the inputs. By priority: if `address` is specified, then
|
|
||||||
// it is returned; if `config` is specified, then that config will be used for
|
|
||||||
// finding the admin address; if `configFile` (and `configAdapter`) are specified,
|
|
||||||
// then that config will be loaded to find the admin address; otherwise, the
|
|
||||||
// default admin listen address will be returned.
|
|
||||||
func DetermineAdminAPIAddress(address string, config []byte, configFile, configAdapter string) (string, error) {
|
|
||||||
// Prefer the address if specified and non-empty
|
|
||||||
if address != "" {
|
|
||||||
return address, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to load the config from file if specified, with the given adapter name
|
|
||||||
if configFile != "" {
|
|
||||||
var loadedConfigFile string
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// use the provided loaded config if non-empty
|
|
||||||
// otherwise, load it from the specified file/adapter
|
|
||||||
loadedConfig := config
|
|
||||||
if len(loadedConfig) == 0 {
|
|
||||||
// get the config in caddy's native format
|
|
||||||
loadedConfig, loadedConfigFile, err = LoadConfig(configFile, configAdapter)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if loadedConfigFile == "" {
|
|
||||||
return "", fmt.Errorf("no config file to load; either use --config flag or ensure Caddyfile exists in current directory")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the address of the admin listener from the config
|
|
||||||
if len(loadedConfig) > 0 {
|
|
||||||
var tmpStruct struct {
|
|
||||||
Admin caddy.AdminConfig `json:"admin"`
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(loadedConfig, &tmpStruct)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("unmarshaling admin listener address from config: %v", err)
|
|
||||||
}
|
|
||||||
if tmpStruct.Admin.Listen != "" {
|
|
||||||
return tmpStruct.Admin.Listen, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to the default listen address otherwise
|
|
||||||
return caddy.DefaultAdminListen, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type moduleInfo struct {
|
type moduleInfo struct {
|
||||||
|
|||||||
+18
-169
@@ -16,14 +16,7 @@ package caddycmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/cobra/doc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Command represents a subcommand. Name, Func,
|
// Command represents a subcommand. Name, Func,
|
||||||
@@ -77,6 +70,13 @@ func Commands() map[string]Command {
|
|||||||
var commands = make(map[string]Command)
|
var commands = make(map[string]Command)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
RegisterCommand(Command{
|
||||||
|
Name: "help",
|
||||||
|
Func: cmdHelp,
|
||||||
|
Usage: "<command>",
|
||||||
|
Short: "Shows help for a Caddy subcommand",
|
||||||
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "start",
|
Name: "start",
|
||||||
Func: cmdStart,
|
Func: cmdStart,
|
||||||
@@ -137,8 +137,8 @@ The --resume flag will override the --config flag if there is a config auto-
|
|||||||
save file. It is not an error if --resume is used and no autosave file exists.
|
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 is dangerous in production! Only use this option in a local
|
||||||
option in a local development environment.`,
|
development environment.`,
|
||||||
Flags: func() *flag.FlagSet {
|
Flags: func() *flag.FlagSet {
|
||||||
fs := flag.NewFlagSet("run", flag.ExitOnError)
|
fs := flag.NewFlagSet("run", flag.ExitOnError)
|
||||||
fs.String("config", "", "Configuration file")
|
fs.String("config", "", "Configuration file")
|
||||||
@@ -156,19 +156,16 @@ option in a local development environment.`,
|
|||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "stop",
|
Name: "stop",
|
||||||
Func: cmdStop,
|
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
|
||||||
using the --address flag, or from the given --config, if not the default.`,
|
customized using the --address flag if it is not the default.`,
|
||||||
Flags: func() *flag.FlagSet {
|
Flags: func() *flag.FlagSet {
|
||||||
fs := flag.NewFlagSet("stop", flag.ExitOnError)
|
fs := flag.NewFlagSet("stop", flag.ExitOnError)
|
||||||
fs.String("address", "", "The address to use to reach the admin API endpoint, if not the default")
|
fs.String("address", "", "The address to use to reach the admin API endpoint, if not the default")
|
||||||
fs.String("config", "", "Configuration file to use to parse the admin address, if --address is not used")
|
|
||||||
fs.String("adapter", "", "Name of config adapter to apply (when --config is used)")
|
|
||||||
return fs
|
return fs
|
||||||
}(),
|
}(),
|
||||||
})
|
})
|
||||||
@@ -200,19 +197,6 @@ config file; otherwise the default is assumed.`,
|
|||||||
Name: "version",
|
Name: "version",
|
||||||
Func: cmdVersion,
|
Func: cmdVersion,
|
||||||
Short: "Prints the version",
|
Short: "Prints the version",
|
||||||
Long: `
|
|
||||||
Prints the version of this Caddy binary.
|
|
||||||
|
|
||||||
Version information must be embedded into the binary at compile-time in
|
|
||||||
order for Caddy to display anything useful with this command. If Caddy
|
|
||||||
is built from within a version control repository, the Go command will
|
|
||||||
embed the revision hash if available. However, if Caddy is built in the
|
|
||||||
way specified by our online documentation (or by using xcaddy), more
|
|
||||||
detailed version information is printed as given by Go modules.
|
|
||||||
|
|
||||||
For more details about the full version string, see the Go module
|
|
||||||
documentation: https://go.dev/doc/modules/version-numbers
|
|
||||||
`,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
@@ -224,7 +208,6 @@ documentation: https://go.dev/doc/modules/version-numbers
|
|||||||
fs := flag.NewFlagSet("list-modules", flag.ExitOnError)
|
fs := flag.NewFlagSet("list-modules", flag.ExitOnError)
|
||||||
fs.Bool("packages", false, "Print package paths")
|
fs.Bool("packages", false, "Print package paths")
|
||||||
fs.Bool("versions", false, "Print version information")
|
fs.Bool("versions", false, "Print version information")
|
||||||
fs.Bool("skip-standard", false, "Skip printing standard modules")
|
|
||||||
return fs
|
return fs
|
||||||
}(),
|
}(),
|
||||||
})
|
})
|
||||||
@@ -239,24 +222,6 @@ documentation: https://go.dev/doc/modules/version-numbers
|
|||||||
Name: "environ",
|
Name: "environ",
|
||||||
Func: cmdEnviron,
|
Func: cmdEnviron,
|
||||||
Short: "Prints the environment",
|
Short: "Prints the environment",
|
||||||
Long: `
|
|
||||||
Prints the environment as seen by this Caddy process.
|
|
||||||
|
|
||||||
The environment includes variables set in the system. If your Caddy
|
|
||||||
configuration uses environment variables (e.g. "{env.VARIABLE}") then
|
|
||||||
this command can be useful for verifying that the variables will have
|
|
||||||
the values you expect in your config.
|
|
||||||
|
|
||||||
Note that environments may be different depending on how you run Caddy.
|
|
||||||
Environments for Caddy instances started by service managers such as
|
|
||||||
systemd are often different than the environment inherited from your
|
|
||||||
shell or terminal.
|
|
||||||
|
|
||||||
You can also print the environment the same time you use "caddy run"
|
|
||||||
by adding the "--environ" flag.
|
|
||||||
|
|
||||||
Environments may contain sensitive data.
|
|
||||||
`,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
@@ -294,7 +259,7 @@ 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 {
|
Flags: func() *flag.FlagSet {
|
||||||
fs := flag.NewFlagSet("validate", flag.ExitOnError)
|
fs := flag.NewFlagSet("load", flag.ExitOnError)
|
||||||
fs.String("config", "", "Input configuration file")
|
fs.String("config", "", "Input configuration file")
|
||||||
fs.String("adapter", "", "Name of config adapter")
|
fs.String("adapter", "", "Name of config adapter")
|
||||||
return fs
|
return fs
|
||||||
@@ -313,18 +278,12 @@ human readability. It prints the result to stdout.
|
|||||||
If --overwrite is specified, the output will be written to the config file
|
If --overwrite is specified, the output will be written to the config file
|
||||||
directly instead of printing it.
|
directly instead of printing it.
|
||||||
|
|
||||||
If --diff is specified, the output will be compared against the input, and
|
|
||||||
lines will be prefixed with '-' and '+' where they differ. Note that
|
|
||||||
unchanged lines are prefixed with two spaces for alignment, and that this
|
|
||||||
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 {
|
Flags: func() *flag.FlagSet {
|
||||||
fs := flag.NewFlagSet("fmt", flag.ExitOnError)
|
fs := flag.NewFlagSet("format", flag.ExitOnError)
|
||||||
fs.Bool("overwrite", false, "Overwrite the input file with the results")
|
fs.Bool("overwrite", false, "Overwrite the input file with the results")
|
||||||
fs.Bool("diff", false, "Print the differences between the input file and the formatted output")
|
|
||||||
return fs
|
return fs
|
||||||
}(),
|
}(),
|
||||||
})
|
})
|
||||||
@@ -336,11 +295,6 @@ is always printed to stdout.`,
|
|||||||
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 {
|
|
||||||
fs := flag.NewFlagSet("upgrade", flag.ExitOnError)
|
|
||||||
fs.Bool("keep-backup", false, "Keep the backed up binary, instead of deleting it")
|
|
||||||
return fs
|
|
||||||
}(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
@@ -353,11 +307,6 @@ 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.
|
||||||
`,
|
`,
|
||||||
Flags: func() *flag.FlagSet {
|
|
||||||
fs := flag.NewFlagSet("add-package", flag.ExitOnError)
|
|
||||||
fs.Bool("keep-backup", false, "Keep the backed up binary, instead of deleting it")
|
|
||||||
return fs
|
|
||||||
}(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
@@ -370,118 +319,18 @@ 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.
|
||||||
`,
|
`,
|
||||||
Flags: func() *flag.FlagSet {
|
|
||||||
fs := flag.NewFlagSet("remove-package", flag.ExitOnError)
|
|
||||||
fs.Bool("keep-backup", false, "Keep the backed up binary, instead of deleting it")
|
|
||||||
return fs
|
|
||||||
}(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
|
||||||
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>",
|
|
||||||
Short: "Generates the manual pages for Caddy commands",
|
|
||||||
Long: `
|
|
||||||
Generates the manual pages for Caddy commands into the designated directory
|
|
||||||
tagged into section 8 (System Administration).
|
|
||||||
|
|
||||||
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.
|
|
||||||
`,
|
|
||||||
Flags: func() *flag.FlagSet {
|
|
||||||
fs := flag.NewFlagSet("manpage", flag.ExitOnError)
|
|
||||||
fs.String("directory", "", "The output directory where the manpages are generated")
|
|
||||||
return fs
|
|
||||||
}(),
|
|
||||||
})
|
|
||||||
|
|
||||||
// source: https://github.com/spf13/cobra/blob/main/shell_completions.md
|
|
||||||
rootCmd.AddCommand(&cobra.Command{
|
|
||||||
Use: "completion [bash|zsh|fish|powershell]",
|
|
||||||
Short: "Generate completion script",
|
|
||||||
Long: fmt.Sprintf(`To load completions:
|
|
||||||
|
|
||||||
Bash:
|
|
||||||
|
|
||||||
$ source <(%[1]s completion bash)
|
|
||||||
|
|
||||||
# To load completions for each session, execute once:
|
|
||||||
# Linux:
|
|
||||||
$ %[1]s completion bash > /etc/bash_completion.d/%[1]s
|
|
||||||
# macOS:
|
|
||||||
$ %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s
|
|
||||||
|
|
||||||
Zsh:
|
|
||||||
|
|
||||||
# If shell completion is not already enabled in your environment,
|
|
||||||
# you will need to enable it. You can execute the following once:
|
|
||||||
|
|
||||||
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
|
|
||||||
|
|
||||||
# To load completions for each session, execute once:
|
|
||||||
$ %[1]s completion zsh > "${fpath[1]}/_%[1]s"
|
|
||||||
|
|
||||||
# You will need to start a new shell for this setup to take effect.
|
|
||||||
|
|
||||||
fish:
|
|
||||||
|
|
||||||
$ %[1]s completion fish | source
|
|
||||||
|
|
||||||
# To load completions for each session, execute once:
|
|
||||||
$ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish
|
|
||||||
|
|
||||||
PowerShell:
|
|
||||||
|
|
||||||
PS> %[1]s completion powershell | Out-String | Invoke-Expression
|
|
||||||
|
|
||||||
# To load completions for every new session, run:
|
|
||||||
PS> %[1]s completion powershell > %[1]s.ps1
|
|
||||||
# and source this file from your PowerShell profile.
|
|
||||||
`, rootCmd.Root().Name()),
|
|
||||||
DisableFlagsInUseLine: true,
|
|
||||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
|
||||||
Args: cobra.ExactValidArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
switch args[0] {
|
|
||||||
case "bash":
|
|
||||||
return cmd.Root().GenBashCompletion(os.Stdout)
|
|
||||||
case "zsh":
|
|
||||||
return cmd.Root().GenZshCompletion(os.Stdout)
|
|
||||||
case "fish":
|
|
||||||
return cmd.Root().GenFishCompletion(os.Stdout, true)
|
|
||||||
case "powershell":
|
|
||||||
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unrecognized shell: %s", args[0])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterCommand registers the command cmd.
|
// RegisterCommand registers the command cmd.
|
||||||
// cmd.Name must be unique and conform to the
|
// cmd.Name must be unique and conform to the
|
||||||
// following format:
|
// following format:
|
||||||
//
|
//
|
||||||
// - lowercase
|
// - lowercase
|
||||||
// - alphanumeric and hyphen characters only
|
// - alphanumeric and hyphen characters only
|
||||||
// - cannot start or end with a hyphen
|
// - cannot start or end with a hyphen
|
||||||
// - hyphen cannot be adjacent to another hyphen
|
// - hyphen cannot be adjacent to another hyphen
|
||||||
//
|
//
|
||||||
// This function panics if the name is already registered,
|
// This function panics if the name is already registered,
|
||||||
// if the name does not meet the described format, or if
|
// if the name does not meet the described format, or if
|
||||||
@@ -504,7 +353,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(caddyCmdToCoral(cmd))
|
commands[cmd.Name] = 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]$`)
|
||||||
|
|||||||
+99
-77
@@ -20,6 +20,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@@ -33,44 +34,67 @@ import (
|
|||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/spf13/pflag"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// set a fitting User-Agent for ACME requests
|
// set a fitting User-Agent for ACME requests
|
||||||
version, _ := caddy.Version()
|
goModule := caddy.GoModule()
|
||||||
cleanModVersion := strings.TrimPrefix(version, "v")
|
cleanModVersion := strings.TrimPrefix(goModule.Version, "v")
|
||||||
ua := "Caddy/" + cleanModVersion
|
certmagic.UserAgent = "Caddy/" + cleanModVersion
|
||||||
if uaEnv, ok := os.LookupEnv("USERAGENT"); ok {
|
|
||||||
ua = uaEnv + " " + ua
|
|
||||||
}
|
|
||||||
certmagic.UserAgent = ua
|
|
||||||
|
|
||||||
// by using Caddy, user indicates agreement to CA terms
|
// by using Caddy, user indicates agreement to CA terms
|
||||||
// (very important, as Caddy is often non-interactive
|
// (very important, or ACME account creation will fail!)
|
||||||
// and thus ACME account creation will fail!)
|
|
||||||
certmagic.DefaultACME.Agreed = true
|
certmagic.DefaultACME.Agreed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main implements the main function of the caddy command.
|
// Main implements the main function of the caddy command.
|
||||||
// Call this if Caddy is to be the main() of your program.
|
// Call this if Caddy is to be the main() of your program.
|
||||||
func Main() {
|
func Main() {
|
||||||
if len(os.Args) == 0 {
|
switch len(os.Args) {
|
||||||
|
case 0:
|
||||||
fmt.Printf("[FATAL] no arguments provided by OS; args[0] must be command\n")
|
fmt.Printf("[FATAL] no arguments provided by OS; args[0] must be command\n")
|
||||||
os.Exit(caddy.ExitCodeFailedStartup)
|
os.Exit(caddy.ExitCodeFailedStartup)
|
||||||
|
case 1:
|
||||||
|
os.Args = append(os.Args, "help")
|
||||||
|
}
|
||||||
|
|
||||||
|
subcommandName := os.Args[1]
|
||||||
|
subcommand, ok := commands[subcommandName]
|
||||||
|
if !ok {
|
||||||
|
if strings.HasPrefix(os.Args[1], "-") {
|
||||||
|
// user probably forgot to type the subcommand
|
||||||
|
fmt.Println("[ERROR] first argument must be a subcommand; see 'caddy help'")
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[ERROR] '%s' is not a recognized subcommand; see 'caddy help'\n", os.Args[1])
|
||||||
|
}
|
||||||
|
os.Exit(caddy.ExitCodeFailedStartup)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rootCmd.Execute(); err != nil {
|
fs := subcommand.Flags
|
||||||
os.Exit(1)
|
if fs == nil {
|
||||||
|
fs = flag.NewFlagSet(subcommand.Name, flag.ExitOnError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err := fs.Parse(os.Args[2:])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(caddy.ExitCodeFailedStartup)
|
||||||
|
}
|
||||||
|
|
||||||
|
exitCode, err := subcommand.Func(Flags{fs})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s: %v\n", subcommand.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlePingbackConn reads from conn and ensures it matches
|
// handlePingbackConn reads from conn and ensures it matches
|
||||||
// the bytes in expect, or returns an error if it doesn't.
|
// the bytes in expect, or returns an error if it doesn't.
|
||||||
func handlePingbackConn(conn net.Conn, expect []byte) error {
|
func handlePingbackConn(conn net.Conn, expect []byte) error {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
confirmationBytes, err := io.ReadAll(io.LimitReader(conn, 32))
|
confirmationBytes, err := ioutil.ReadAll(io.LimitReader(conn, 32))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -80,15 +104,15 @@ func handlePingbackConn(conn net.Conn, expect []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfig loads the config from configFile and adapts it
|
// loadConfig loads the config from configFile and adapts it
|
||||||
// using adapterName. If adapterName is specified, configFile
|
// using adapterName. If adapterName is specified, configFile
|
||||||
// must be also. If no configFile is specified, it tries
|
// must be also. If no configFile is specified, it tries
|
||||||
// loading a default config file. The lack of a config file is
|
// loading a default config file. The lack of a config file is
|
||||||
// not treated as an error, but false will be returned if
|
// not treated as an error, but false will be returned if
|
||||||
// there is no config available. It prints any warnings to stderr,
|
// there is no config available. It prints any warnings to stderr,
|
||||||
// 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).
|
// whether a config file was loaded or not.
|
||||||
func LoadConfig(configFile, adapterName string) ([]byte, string, error) {
|
func loadConfig(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)")
|
||||||
@@ -100,9 +124,9 @@ func LoadConfig(configFile, adapterName string) ([]byte, string, error) {
|
|||||||
var err error
|
var err error
|
||||||
if configFile != "" {
|
if configFile != "" {
|
||||||
if configFile == "-" {
|
if configFile == "-" {
|
||||||
config, err = io.ReadAll(os.Stdin)
|
config, err = ioutil.ReadAll(os.Stdin)
|
||||||
} else {
|
} else {
|
||||||
config, err = os.ReadFile(configFile)
|
config, err = ioutil.ReadFile(configFile)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("reading config file: %v", err)
|
return nil, "", fmt.Errorf("reading config file: %v", err)
|
||||||
@@ -116,7 +140,7 @@ func LoadConfig(configFile, adapterName string) ([]byte, string, error) {
|
|||||||
// plugged in, and if so, try using a default Caddyfile
|
// plugged in, and if so, try using a default Caddyfile
|
||||||
cfgAdapter = caddyconfig.GetAdapter("caddyfile")
|
cfgAdapter = caddyconfig.GetAdapter("caddyfile")
|
||||||
if cfgAdapter != nil {
|
if cfgAdapter != nil {
|
||||||
config, err = os.ReadFile("Caddyfile")
|
config, err = ioutil.ReadFile("Caddyfile")
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
// okay, no default Caddyfile; pretend like this never happened
|
// okay, no default Caddyfile; pretend like this never happened
|
||||||
cfgAdapter = nil
|
cfgAdapter = nil
|
||||||
@@ -150,7 +174,7 @@ func LoadConfig(configFile, adapterName string) ([]byte, string, error) {
|
|||||||
|
|
||||||
// adapt config
|
// adapt config
|
||||||
if cfgAdapter != nil {
|
if cfgAdapter != nil {
|
||||||
adaptedConfig, warnings, err := cfgAdapter.Adapt(config, map[string]any{
|
adaptedConfig, warnings, err := cfgAdapter.Adapt(config, map[string]interface{}{
|
||||||
"filename": configFile,
|
"filename": configFile,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -239,7 +263,7 @@ func watchConfigFile(filename, adapterName string) {
|
|||||||
lastModified = info.ModTime()
|
lastModified = info.ModTime()
|
||||||
|
|
||||||
// load the contents of the file
|
// load the contents of the file
|
||||||
config, _, err := LoadConfig(filename, adapterName)
|
config, _, err := loadConfig(filename, adapterName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger().Error("unable to load latest config", zap.Error(err))
|
logger().Error("unable to load latest config", zap.Error(err))
|
||||||
continue
|
continue
|
||||||
@@ -257,7 +281,7 @@ func watchConfigFile(filename, adapterName string) {
|
|||||||
// Flags wraps a FlagSet so that typed values
|
// Flags wraps a FlagSet so that typed values
|
||||||
// from flags can be easily retrieved.
|
// from flags can be easily retrieved.
|
||||||
type Flags struct {
|
type Flags struct {
|
||||||
*pflag.FlagSet
|
*flag.FlagSet
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the string representation of the
|
// String returns the string representation of the
|
||||||
@@ -303,6 +327,22 @@ func (f Flags) Duration(name string) time.Duration {
|
|||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flagHelp returns the help text for fs.
|
||||||
|
func flagHelp(fs *flag.FlagSet) string {
|
||||||
|
if fs == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// temporarily redirect output
|
||||||
|
out := fs.Output()
|
||||||
|
defer fs.SetOutput(out)
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
fs.SetOutput(buf)
|
||||||
|
fs.PrintDefaults()
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
func loadEnvFromFile(envFile string) error {
|
func loadEnvFromFile(envFile string) error {
|
||||||
file, err := os.Open(envFile)
|
file, err := os.Open(envFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -329,65 +369,42 @@ func loadEnvFromFile(envFile string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseEnvFile parses an env file from KEY=VALUE format.
|
|
||||||
// It's pretty naive. Limited value quotation is supported,
|
|
||||||
// but variable and command expansions are not supported.
|
|
||||||
func parseEnvFile(envInput io.Reader) (map[string]string, error) {
|
func parseEnvFile(envInput io.Reader) (map[string]string, error) {
|
||||||
envMap := make(map[string]string)
|
envMap := make(map[string]string)
|
||||||
|
|
||||||
scanner := bufio.NewScanner(envInput)
|
scanner := bufio.NewScanner(envInput)
|
||||||
var lineNumber int
|
var line string
|
||||||
|
lineNumber := 0
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := strings.TrimSpace(scanner.Text())
|
line = strings.TrimSpace(scanner.Text())
|
||||||
lineNumber++
|
lineNumber++
|
||||||
|
|
||||||
// skip empty lines and lines starting with comment
|
// skip lines starting with comment
|
||||||
if line == "" || strings.HasPrefix(line, "#") {
|
if strings.HasPrefix(line, "#") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// split line into key and value
|
// skip empty line
|
||||||
before, after, isCut := strings.Cut(line, "=")
|
if len(line) == 0 {
|
||||||
if !isCut {
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := strings.SplitN(line, "=", 2)
|
||||||
|
if len(fields) != 2 {
|
||||||
return nil, fmt.Errorf("can't parse line %d; line should be in KEY=VALUE format", lineNumber)
|
return nil, fmt.Errorf("can't parse line %d; line should be in KEY=VALUE format", lineNumber)
|
||||||
}
|
}
|
||||||
key, val := before, after
|
|
||||||
|
|
||||||
// sometimes keys are prefixed by "export " so file can be sourced in bash; ignore it here
|
if strings.Contains(fields[0], " ") {
|
||||||
key = strings.TrimPrefix(key, "export ")
|
return nil, fmt.Errorf("bad key on line %d: contains whitespace", lineNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
key := fields[0]
|
||||||
|
val := fields[1]
|
||||||
|
|
||||||
// validate key and value
|
|
||||||
if key == "" {
|
if key == "" {
|
||||||
return nil, fmt.Errorf("missing or empty key on line %d", lineNumber)
|
return nil, fmt.Errorf("missing or empty key on line %d", lineNumber)
|
||||||
}
|
}
|
||||||
if strings.Contains(key, " ") {
|
|
||||||
return nil, fmt.Errorf("invalid key on line %d: contains whitespace: %s", lineNumber, key)
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(val, " ") || strings.HasPrefix(val, "\t") {
|
|
||||||
return nil, fmt.Errorf("invalid value on line %d: whitespace before value: '%s'", lineNumber, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove any trailing comment after value
|
|
||||||
if commentStart, _, found := strings.Cut(val, "#"); found {
|
|
||||||
val = strings.TrimRight(commentStart, " \t")
|
|
||||||
}
|
|
||||||
|
|
||||||
// quoted value: support newlines
|
|
||||||
if strings.HasPrefix(val, `"`) {
|
|
||||||
for !(strings.HasSuffix(line, `"`) && !strings.HasSuffix(line, `\"`)) {
|
|
||||||
val = strings.ReplaceAll(val, `\"`, `"`)
|
|
||||||
if !scanner.Scan() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
lineNumber++
|
|
||||||
line = strings.ReplaceAll(scanner.Text(), `\"`, `"`)
|
|
||||||
val += "\n" + line
|
|
||||||
}
|
|
||||||
val = strings.TrimPrefix(val, `"`)
|
|
||||||
val = strings.TrimSuffix(val, `"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
envMap[key] = val
|
envMap[key] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,12 +416,11 @@ func parseEnvFile(envInput io.Reader) (map[string]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func printEnvironment() {
|
func printEnvironment() {
|
||||||
_, version := caddy.Version()
|
|
||||||
fmt.Printf("caddy.HomeDir=%s\n", caddy.HomeDir())
|
fmt.Printf("caddy.HomeDir=%s\n", caddy.HomeDir())
|
||||||
fmt.Printf("caddy.AppDataDir=%s\n", caddy.AppDataDir())
|
fmt.Printf("caddy.AppDataDir=%s\n", caddy.AppDataDir())
|
||||||
fmt.Printf("caddy.AppConfigDir=%s\n", caddy.AppConfigDir())
|
fmt.Printf("caddy.AppConfigDir=%s\n", caddy.AppConfigDir())
|
||||||
fmt.Printf("caddy.ConfigAutosavePath=%s\n", caddy.ConfigAutosavePath)
|
fmt.Printf("caddy.ConfigAutosavePath=%s\n", caddy.ConfigAutosavePath)
|
||||||
fmt.Printf("caddy.Version=%s\n", version)
|
fmt.Printf("caddy.Version=%s\n", CaddyVersion())
|
||||||
fmt.Printf("runtime.GOOS=%s\n", runtime.GOOS)
|
fmt.Printf("runtime.GOOS=%s\n", runtime.GOOS)
|
||||||
fmt.Printf("runtime.GOARCH=%s\n", runtime.GOARCH)
|
fmt.Printf("runtime.GOARCH=%s\n", runtime.GOARCH)
|
||||||
fmt.Printf("runtime.Compiler=%s\n", runtime.Compiler)
|
fmt.Printf("runtime.Compiler=%s\n", runtime.Compiler)
|
||||||
@@ -421,15 +437,21 @@ func printEnvironment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringSlice is a flag.Value that enables repeated use of a string flag.
|
// CaddyVersion returns a detailed version string, if available.
|
||||||
type StringSlice []string
|
func CaddyVersion() string {
|
||||||
|
goModule := caddy.GoModule()
|
||||||
func (ss StringSlice) String() string { return "[" + strings.Join(ss, ", ") + "]" }
|
ver := goModule.Version
|
||||||
|
if goModule.Sum != "" {
|
||||||
func (ss *StringSlice) Set(value string) error {
|
ver += " " + goModule.Sum
|
||||||
*ss = append(*ss, value)
|
}
|
||||||
return nil
|
if goModule.Replace != nil {
|
||||||
|
ver += " => " + goModule.Replace.Path
|
||||||
|
if goModule.Replace.Version != "" {
|
||||||
|
ver += "@" + goModule.Replace.Version
|
||||||
|
}
|
||||||
|
if goModule.Replace.Sum != "" {
|
||||||
|
ver += " " + goModule.Replace.Sum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ver
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface guard
|
|
||||||
var _ flag.Value = (*StringSlice)(nil)
|
|
||||||
|
|||||||
@@ -1,170 +0,0 @@
|
|||||||
package caddycmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseEnvFile(t *testing.T) {
|
|
||||||
for i, tc := range []struct {
|
|
||||||
input string
|
|
||||||
expect map[string]string
|
|
||||||
shouldErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: `KEY=value`,
|
|
||||||
expect: map[string]string{
|
|
||||||
"KEY": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `
|
|
||||||
KEY=value
|
|
||||||
OTHER_KEY=Some Value
|
|
||||||
`,
|
|
||||||
expect: map[string]string{
|
|
||||||
"KEY": "value",
|
|
||||||
"OTHER_KEY": "Some Value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `
|
|
||||||
KEY=value
|
|
||||||
INVALID KEY=asdf
|
|
||||||
OTHER_KEY=Some Value
|
|
||||||
`,
|
|
||||||
shouldErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `
|
|
||||||
KEY=value
|
|
||||||
SIMPLE_QUOTED="quoted value"
|
|
||||||
OTHER_KEY=Some Value
|
|
||||||
`,
|
|
||||||
expect: map[string]string{
|
|
||||||
"KEY": "value",
|
|
||||||
"SIMPLE_QUOTED": "quoted value",
|
|
||||||
"OTHER_KEY": "Some Value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `
|
|
||||||
KEY=value
|
|
||||||
NEWLINES="foo
|
|
||||||
bar"
|
|
||||||
OTHER_KEY=Some Value
|
|
||||||
`,
|
|
||||||
expect: map[string]string{
|
|
||||||
"KEY": "value",
|
|
||||||
"NEWLINES": "foo\n\tbar",
|
|
||||||
"OTHER_KEY": "Some Value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `
|
|
||||||
KEY=value
|
|
||||||
ESCAPED="\"escaped quotes\"
|
|
||||||
here"
|
|
||||||
OTHER_KEY=Some Value
|
|
||||||
`,
|
|
||||||
expect: map[string]string{
|
|
||||||
"KEY": "value",
|
|
||||||
"ESCAPED": "\"escaped quotes\"\nhere",
|
|
||||||
"OTHER_KEY": "Some Value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `
|
|
||||||
export KEY=value
|
|
||||||
OTHER_KEY=Some Value
|
|
||||||
`,
|
|
||||||
expect: map[string]string{
|
|
||||||
"KEY": "value",
|
|
||||||
"OTHER_KEY": "Some Value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `
|
|
||||||
=value
|
|
||||||
OTHER_KEY=Some Value
|
|
||||||
`,
|
|
||||||
shouldErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `
|
|
||||||
EMPTY=
|
|
||||||
OTHER_KEY=Some Value
|
|
||||||
`,
|
|
||||||
expect: map[string]string{
|
|
||||||
"EMPTY": "",
|
|
||||||
"OTHER_KEY": "Some Value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `
|
|
||||||
EMPTY=""
|
|
||||||
OTHER_KEY=Some Value
|
|
||||||
`,
|
|
||||||
expect: map[string]string{
|
|
||||||
"EMPTY": "",
|
|
||||||
"OTHER_KEY": "Some Value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `
|
|
||||||
KEY=value
|
|
||||||
#OTHER_KEY=Some Value
|
|
||||||
`,
|
|
||||||
expect: map[string]string{
|
|
||||||
"KEY": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `
|
|
||||||
KEY=value
|
|
||||||
COMMENT=foo bar # some comment here
|
|
||||||
OTHER_KEY=Some Value
|
|
||||||
`,
|
|
||||||
expect: map[string]string{
|
|
||||||
"KEY": "value",
|
|
||||||
"COMMENT": "foo bar",
|
|
||||||
"OTHER_KEY": "Some Value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `
|
|
||||||
KEY=value
|
|
||||||
WHITESPACE= foo
|
|
||||||
OTHER_KEY=Some Value
|
|
||||||
`,
|
|
||||||
shouldErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `
|
|
||||||
KEY=value
|
|
||||||
WHITESPACE=" foo bar "
|
|
||||||
OTHER_KEY=Some Value
|
|
||||||
`,
|
|
||||||
expect: map[string]string{
|
|
||||||
"KEY": "value",
|
|
||||||
"WHITESPACE": " foo bar ",
|
|
||||||
"OTHER_KEY": "Some Value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
actual, err := parseEnvFile(strings.NewReader(tc.input))
|
|
||||||
if err != nil && !tc.shouldErr {
|
|
||||||
t.Errorf("Test %d: Got error but shouldn't have: %v", i, err)
|
|
||||||
}
|
|
||||||
if err == nil && tc.shouldErr {
|
|
||||||
t.Errorf("Test %d: Did not get error but should have", i)
|
|
||||||
}
|
|
||||||
if tc.shouldErr {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(tc.expect, actual) {
|
|
||||||
t.Errorf("Test %d: Expected %v but got %v", i, tc.expect, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+21
-18
@@ -31,7 +31,7 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cmdUpgrade(fl Flags) (int, error) {
|
func cmdUpgrade(_ Flags) (int, error) {
|
||||||
_, nonstandard, _, err := getModules()
|
_, nonstandard, _, err := getModules()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("unable to enumerate installed plugins: %v", err)
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("unable to enumerate installed plugins: %v", err)
|
||||||
@@ -41,7 +41,7 @@ func cmdUpgrade(fl Flags) (int, error) {
|
|||||||
return caddy.ExitCodeFailedStartup, err
|
return caddy.ExitCodeFailedStartup, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return upgradeBuild(pluginPkgs, fl)
|
return upgradeBuild(pluginPkgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdAddPackage(fl Flags) (int, error) {
|
func cmdAddPackage(fl Flags) (int, error) {
|
||||||
@@ -64,7 +64,7 @@ func cmdAddPackage(fl Flags) (int, error) {
|
|||||||
pluginPkgs[arg] = struct{}{}
|
pluginPkgs[arg] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return upgradeBuild(pluginPkgs, fl)
|
return upgradeBuild(pluginPkgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdRemovePackage(fl Flags) (int, error) {
|
func cmdRemovePackage(fl Flags) (int, error) {
|
||||||
@@ -88,10 +88,10 @@ func cmdRemovePackage(fl Flags) (int, error) {
|
|||||||
delete(pluginPkgs, arg)
|
delete(pluginPkgs, arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
return upgradeBuild(pluginPkgs, fl)
|
return upgradeBuild(pluginPkgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func upgradeBuild(pluginPkgs map[string]struct{}, fl Flags) (int, error) {
|
func upgradeBuild(pluginPkgs map[string]struct{}) (int, error) {
|
||||||
l := caddy.Log()
|
l := caddy.Log()
|
||||||
|
|
||||||
thisExecPath, err := os.Executable()
|
thisExecPath, err := os.Executable()
|
||||||
@@ -152,23 +152,18 @@ func upgradeBuild(pluginPkgs map[string]struct{}, fl Flags) (int, error) {
|
|||||||
// use the new binary to print out version and module info
|
// use the new binary to print out version and module info
|
||||||
fmt.Print("\nModule versions:\n\n")
|
fmt.Print("\nModule versions:\n\n")
|
||||||
if err = listModules(thisExecPath); err != nil {
|
if err = listModules(thisExecPath); err != nil {
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("download succeeded, but unable to execute 'caddy list-modules': %v", err)
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("download succeeded, but unable to execute: %v", err)
|
||||||
}
|
}
|
||||||
fmt.Println("\nVersion:")
|
fmt.Println("\nVersion:")
|
||||||
if err = showVersion(thisExecPath); err != nil {
|
if err = showVersion(thisExecPath); err != nil {
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("download succeeded, but unable to execute 'caddy version': %v", err)
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("download succeeded, but unable to execute: %v", err)
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
// clean up the backup file
|
// clean up the backup file
|
||||||
if !fl.Bool("keep-backup") {
|
if err = os.Remove(backupExecPath); err != nil {
|
||||||
if err = removeCaddyBinary(backupExecPath); err != nil {
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("download succeeded, but unable to clean up backup binary: %v", err)
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("download succeeded, but unable to clean up backup binary: %v", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
l.Info("skipped cleaning up the backup file", zap.String("backup_path", backupExecPath))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Info("upgrade successful; please restart any running Caddy instances", zap.String("executable", thisExecPath))
|
l.Info("upgrade successful; please restart any running Caddy instances", zap.String("executable", thisExecPath))
|
||||||
|
|
||||||
return caddy.ExitCodeSuccess, nil
|
return caddy.ExitCodeSuccess, nil
|
||||||
@@ -194,7 +189,7 @@ func getModules() (standard, nonstandard, unknown []moduleInfo, err error) {
|
|||||||
// can use reflection but we need a non-pointer value (I'm
|
// can use reflection but we need a non-pointer value (I'm
|
||||||
// not sure why), and since New() should return a pointer
|
// not sure why), and since New() should return a pointer
|
||||||
// value, we need to dereference it first
|
// value, we need to dereference it first
|
||||||
iface := any(modInfo.New())
|
iface := interface{}(modInfo.New())
|
||||||
if rv := reflect.ValueOf(iface); rv.Kind() == reflect.Ptr {
|
if rv := reflect.ValueOf(iface); rv.Kind() == reflect.Ptr {
|
||||||
iface = reflect.New(reflect.TypeOf(iface).Elem()).Elem().Interface()
|
iface = reflect.New(reflect.TypeOf(iface).Elem()).Elem().Interface()
|
||||||
}
|
}
|
||||||
@@ -225,17 +220,25 @@ func getModules() (standard, nonstandard, unknown []moduleInfo, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func listModules(path string) error {
|
func listModules(path string) error {
|
||||||
cmd := exec.Command(path, "list-modules", "--versions", "--skip-standard")
|
cmd := exec.Command(path, "list-modules", "--versions")
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
return cmd.Run()
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("download succeeded, but unable to execute: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func showVersion(path string) error {
|
func showVersion(path string) error {
|
||||||
cmd := exec.Command(path, "version")
|
cmd := exec.Command(path, "version")
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
return cmd.Run()
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("download succeeded, but unable to execute: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadBuild(qs url.Values) (*http.Response, error) {
|
func downloadBuild(qs url.Values) (*http.Response, error) {
|
||||||
|
|||||||
@@ -1,39 +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 (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// removeCaddyBinary removes the Caddy binary at the given path.
|
|
||||||
//
|
|
||||||
// On Windows, this uses a syscall to indirectly remove the file,
|
|
||||||
// because otherwise we get an "Access is denied." error when trying
|
|
||||||
// to delete the binary while Caddy is still running and performing
|
|
||||||
// the upgrade. "cmd.exe /C" executes a command specified by the
|
|
||||||
// following arguments, i.e. "del" which will run as a separate process,
|
|
||||||
// which avoids the "Access is denied." error.
|
|
||||||
func removeCaddyBinary(path string) error {
|
|
||||||
var sI syscall.StartupInfo
|
|
||||||
var pI syscall.ProcessInformation
|
|
||||||
argv, err := syscall.UTF16PtrFromString(filepath.Join(os.Getenv("windir"), "system32", "cmd.exe") + " /C del " + path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return syscall.CreateProcess(nil, argv, nil, nil, true, 0, nil, nil, &sI, &pI)
|
|
||||||
}
|
|
||||||
+36
-90
@@ -37,10 +37,9 @@ import (
|
|||||||
// not actually need to do this).
|
// not actually need to do this).
|
||||||
type Context struct {
|
type Context struct {
|
||||||
context.Context
|
context.Context
|
||||||
moduleInstances map[string][]Module
|
moduleInstances map[string][]interface{}
|
||||||
cfg *Config
|
cfg *Config
|
||||||
cleanupFuncs []func()
|
cleanupFuncs []func()
|
||||||
ancestry []Module
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContext provides a new context derived from the given
|
// NewContext provides a new context derived from the given
|
||||||
@@ -52,7 +51,7 @@ type Context struct {
|
|||||||
// modules which are loaded will be properly unloaded.
|
// modules which are loaded will be properly unloaded.
|
||||||
// See standard library context package's documentation.
|
// See standard library context package's documentation.
|
||||||
func NewContext(ctx Context) (Context, context.CancelFunc) {
|
func NewContext(ctx Context) (Context, context.CancelFunc) {
|
||||||
newCtx := Context{moduleInstances: make(map[string][]Module), cfg: ctx.cfg}
|
newCtx := Context{moduleInstances: make(map[string][]interface{}), cfg: ctx.cfg}
|
||||||
c, cancel := context.WithCancel(ctx.Context)
|
c, cancel := context.WithCancel(ctx.Context)
|
||||||
wrappedCancel := func() {
|
wrappedCancel := func() {
|
||||||
cancel()
|
cancel()
|
||||||
@@ -91,15 +90,15 @@ func (ctx *Context) OnCancel(f func()) {
|
|||||||
// ModuleMap may be used in place of map[string]json.RawMessage. The return value's
|
// ModuleMap may be used in place of map[string]json.RawMessage. The return value's
|
||||||
// underlying type mirrors the input field's type:
|
// underlying type mirrors the input field's type:
|
||||||
//
|
//
|
||||||
// json.RawMessage => any
|
// json.RawMessage => interface{}
|
||||||
// []json.RawMessage => []any
|
// []json.RawMessage => []interface{}
|
||||||
// [][]json.RawMessage => [][]any
|
// [][]json.RawMessage => [][]interface{}
|
||||||
// map[string]json.RawMessage => map[string]any
|
// map[string]json.RawMessage => map[string]interface{}
|
||||||
// []map[string]json.RawMessage => []map[string]any
|
// []map[string]json.RawMessage => []map[string]interface{}
|
||||||
//
|
//
|
||||||
// The field must have a "caddy" struct tag in this format:
|
// The field must have a "caddy" struct tag in this format:
|
||||||
//
|
//
|
||||||
// caddy:"key1=val1 key2=val2"
|
// caddy:"key1=val1 key2=val2"
|
||||||
//
|
//
|
||||||
// To load modules, a "namespace" key is required. For example, to load modules
|
// To load modules, a "namespace" key is required. For example, to load modules
|
||||||
// in the "http.handlers" namespace, you'd put: `namespace=http.handlers` in the
|
// in the "http.handlers" namespace, you'd put: `namespace=http.handlers` in the
|
||||||
@@ -116,20 +115,20 @@ func (ctx *Context) OnCancel(f func()) {
|
|||||||
// meaning the key containing the module's name that is defined inline with the module
|
// meaning the key containing the module's name that is defined inline with the module
|
||||||
// itself. You must specify the inline key in a struct tag, along with the namespace:
|
// itself. You must specify the inline key in a struct tag, along with the namespace:
|
||||||
//
|
//
|
||||||
// caddy:"namespace=http.handlers inline_key=handler"
|
// caddy:"namespace=http.handlers inline_key=handler"
|
||||||
//
|
//
|
||||||
// This will look for a key/value pair like `"handler": "..."` in the json.RawMessage
|
// This will look for a key/value pair like `"handler": "..."` in the json.RawMessage
|
||||||
// in order to know the module name.
|
// in order to know the module name.
|
||||||
//
|
//
|
||||||
// To make use of the loaded module(s) (the return value), you will probably want
|
// To make use of the loaded module(s) (the return value), you will probably want
|
||||||
// to type-assert each 'any' value(s) to the types that are useful to you
|
// to type-assert each interface{} value(s) to the types that are useful to you
|
||||||
// and store them on the same struct. Storing them on the same struct makes for
|
// and store them on the same struct. Storing them on the same struct makes for
|
||||||
// easy garbage collection when your host module is no longer needed.
|
// easy garbage collection when your host module is no longer needed.
|
||||||
//
|
//
|
||||||
// Loaded modules have already been provisioned and validated. Upon returning
|
// Loaded modules have already been provisioned and validated. Upon returning
|
||||||
// successfully, this method clears the json.RawMessage(s) in the field since
|
// successfully, this method clears the json.RawMessage(s) in the field since
|
||||||
// the raw JSON is no longer needed, and this allows the GC to free up memory.
|
// the raw JSON is no longer needed, and this allows the GC to free up memory.
|
||||||
func (ctx Context) LoadModule(structPointer any, fieldName string) (any, error) {
|
func (ctx Context) LoadModule(structPointer interface{}, fieldName string) (interface{}, error) {
|
||||||
val := reflect.ValueOf(structPointer).Elem().FieldByName(fieldName)
|
val := reflect.ValueOf(structPointer).Elem().FieldByName(fieldName)
|
||||||
typ := val.Type()
|
typ := val.Type()
|
||||||
|
|
||||||
@@ -149,7 +148,7 @@ func (ctx Context) LoadModule(structPointer any, fieldName string) (any, error)
|
|||||||
}
|
}
|
||||||
inlineModuleKey := opts["inline_key"]
|
inlineModuleKey := opts["inline_key"]
|
||||||
|
|
||||||
var result any
|
var result interface{}
|
||||||
|
|
||||||
switch val.Kind() {
|
switch val.Kind() {
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
@@ -171,7 +170,7 @@ func (ctx Context) LoadModule(structPointer any, fieldName string) (any, error)
|
|||||||
if inlineModuleKey == "" {
|
if inlineModuleKey == "" {
|
||||||
panic("unable to determine module name without inline_key because type is not a ModuleMap")
|
panic("unable to determine module name without inline_key because type is not a ModuleMap")
|
||||||
}
|
}
|
||||||
var all []any
|
var all []interface{}
|
||||||
for i := 0; i < val.Len(); i++ {
|
for i := 0; i < val.Len(); i++ {
|
||||||
val, err := ctx.loadModuleInline(inlineModuleKey, moduleNamespace, val.Index(i).Interface().(json.RawMessage))
|
val, err := ctx.loadModuleInline(inlineModuleKey, moduleNamespace, val.Index(i).Interface().(json.RawMessage))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -187,10 +186,10 @@ func (ctx Context) LoadModule(structPointer any, fieldName string) (any, error)
|
|||||||
if inlineModuleKey == "" {
|
if inlineModuleKey == "" {
|
||||||
panic("unable to determine module name without inline_key because type is not a ModuleMap")
|
panic("unable to determine module name without inline_key because type is not a ModuleMap")
|
||||||
}
|
}
|
||||||
var all [][]any
|
var all [][]interface{}
|
||||||
for i := 0; i < val.Len(); i++ {
|
for i := 0; i < val.Len(); i++ {
|
||||||
innerVal := val.Index(i)
|
innerVal := val.Index(i)
|
||||||
var allInner []any
|
var allInner []interface{}
|
||||||
for j := 0; j < innerVal.Len(); j++ {
|
for j := 0; j < innerVal.Len(); j++ {
|
||||||
innerInnerVal, err := ctx.loadModuleInline(inlineModuleKey, moduleNamespace, innerVal.Index(j).Interface().(json.RawMessage))
|
innerInnerVal, err := ctx.loadModuleInline(inlineModuleKey, moduleNamespace, innerVal.Index(j).Interface().(json.RawMessage))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -205,7 +204,7 @@ func (ctx Context) LoadModule(structPointer any, fieldName string) (any, error)
|
|||||||
} else if isModuleMapType(typ.Elem()) {
|
} else if isModuleMapType(typ.Elem()) {
|
||||||
// val is `[]map[string]json.RawMessage`
|
// val is `[]map[string]json.RawMessage`
|
||||||
|
|
||||||
var all []map[string]any
|
var all []map[string]interface{}
|
||||||
for i := 0; i < val.Len(); i++ {
|
for i := 0; i < val.Len(); i++ {
|
||||||
thisSet, err := ctx.loadModulesFromSomeMap(moduleNamespace, inlineModuleKey, val.Index(i))
|
thisSet, err := ctx.loadModulesFromSomeMap(moduleNamespace, inlineModuleKey, val.Index(i))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -233,10 +232,10 @@ func (ctx Context) LoadModule(structPointer any, fieldName string) (any, error)
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadModulesFromSomeMap loads modules from val, which must be a type of map[string]any.
|
// loadModulesFromSomeMap loads modules from val, which must be a type of map[string]interface{}.
|
||||||
// Depending on inlineModuleKey, it will be interpreted as either a ModuleMap (key is the module
|
// Depending on inlineModuleKey, it will be interpreted as either a ModuleMap (key is the module
|
||||||
// name) or as a regular map (key is not the module name, and module name is defined inline).
|
// name) or as a regular map (key is not the module name, and module name is defined inline).
|
||||||
func (ctx Context) loadModulesFromSomeMap(namespace, inlineModuleKey string, val reflect.Value) (map[string]any, error) {
|
func (ctx Context) loadModulesFromSomeMap(namespace, inlineModuleKey string, val reflect.Value) (map[string]interface{}, error) {
|
||||||
// if no inline_key is specified, then val must be a ModuleMap,
|
// if no inline_key is specified, then val must be a ModuleMap,
|
||||||
// where the key is the module name
|
// where the key is the module name
|
||||||
if inlineModuleKey == "" {
|
if inlineModuleKey == "" {
|
||||||
@@ -254,8 +253,8 @@ func (ctx Context) loadModulesFromSomeMap(namespace, inlineModuleKey string, val
|
|||||||
// loadModulesFromRegularMap loads modules from val, where val is a map[string]json.RawMessage.
|
// loadModulesFromRegularMap loads modules from val, where val is a map[string]json.RawMessage.
|
||||||
// Map keys are NOT interpreted as module names, so module names are still expected to appear
|
// Map keys are NOT interpreted as module names, so module names are still expected to appear
|
||||||
// inline with the objects.
|
// inline with the objects.
|
||||||
func (ctx Context) loadModulesFromRegularMap(namespace, inlineModuleKey string, val reflect.Value) (map[string]any, error) {
|
func (ctx Context) loadModulesFromRegularMap(namespace, inlineModuleKey string, val reflect.Value) (map[string]interface{}, error) {
|
||||||
mods := make(map[string]any)
|
mods := make(map[string]interface{})
|
||||||
iter := val.MapRange()
|
iter := val.MapRange()
|
||||||
for iter.Next() {
|
for iter.Next() {
|
||||||
k := iter.Key()
|
k := iter.Key()
|
||||||
@@ -269,10 +268,10 @@ func (ctx Context) loadModulesFromRegularMap(namespace, inlineModuleKey string,
|
|||||||
return mods, nil
|
return mods, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadModuleMap loads modules from a ModuleMap, i.e. map[string]any, where the key is the
|
// loadModuleMap loads modules from a ModuleMap, i.e. map[string]interface{}, where the key is the
|
||||||
// module name. With a module map, module names do not need to be defined inline with their values.
|
// module name. With a module map, module names do not need to be defined inline with their values.
|
||||||
func (ctx Context) loadModuleMap(namespace string, val reflect.Value) (map[string]any, error) {
|
func (ctx Context) loadModuleMap(namespace string, val reflect.Value) (map[string]interface{}, error) {
|
||||||
all := make(map[string]any)
|
all := make(map[string]interface{})
|
||||||
iter := val.MapRange()
|
iter := val.MapRange()
|
||||||
for iter.Next() {
|
for iter.Next() {
|
||||||
k := iter.Key().Interface().(string)
|
k := iter.Key().Interface().(string)
|
||||||
@@ -300,19 +299,19 @@ func (ctx Context) loadModuleMap(namespace string, val reflect.Value) (map[strin
|
|||||||
// directly by most modules. However, this method is useful when
|
// directly by most modules. However, this method is useful when
|
||||||
// dynamically loading/unloading modules in their own context,
|
// dynamically loading/unloading modules in their own context,
|
||||||
// like from embedded scripts, etc.
|
// like from embedded scripts, etc.
|
||||||
func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error) {
|
func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (interface{}, error) {
|
||||||
modulesMu.RLock()
|
modulesMu.RLock()
|
||||||
modInfo, ok := modules[id]
|
mod, ok := modules[id]
|
||||||
modulesMu.RUnlock()
|
modulesMu.RUnlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("unknown module: %s", id)
|
return nil, fmt.Errorf("unknown module: %s", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if modInfo.New == nil {
|
if mod.New == nil {
|
||||||
return nil, fmt.Errorf("module '%s' has no constructor", modInfo.ID)
|
return nil, fmt.Errorf("module '%s' has no constructor", mod.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
val := modInfo.New()
|
val := mod.New().(interface{})
|
||||||
|
|
||||||
// value must be a pointer for unmarshaling into concrete type, even if
|
// value must be a pointer for unmarshaling into concrete type, even if
|
||||||
// the module's concrete type is a slice or map; New() *should* return
|
// the module's concrete type is a slice or map; New() *should* return
|
||||||
@@ -328,7 +327,7 @@ func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error
|
|||||||
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", mod, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,8 +340,6 @@ func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error
|
|||||||
return nil, fmt.Errorf("module value cannot be null")
|
return nil, fmt.Errorf("module value cannot be null")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.ancestry = append(ctx.ancestry, val)
|
|
||||||
|
|
||||||
if prov, ok := val.(Provisioner); ok {
|
if prov, ok := val.(Provisioner); ok {
|
||||||
err := prov.Provision(ctx)
|
err := prov.Provision(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -354,7 +351,7 @@ func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error
|
|||||||
err = fmt.Errorf("%v; additionally, cleanup: %v", err, err2)
|
err = fmt.Errorf("%v; additionally, cleanup: %v", err, err2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("provision %s: %v", modInfo, err)
|
return nil, fmt.Errorf("provision %s: %v", mod, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,7 +365,7 @@ func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error
|
|||||||
err = fmt.Errorf("%v; additionally, cleanup: %v", err, err2)
|
err = fmt.Errorf("%v; additionally, cleanup: %v", err, err2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("%s: invalid configuration: %v", modInfo, err)
|
return nil, fmt.Errorf("%s: invalid configuration: %v", mod, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,7 +375,7 @@ func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// loadModuleInline loads a module from a JSON raw message which decodes to
|
// loadModuleInline loads a module from a JSON raw message which decodes to
|
||||||
// a map[string]any, where one of the object keys is moduleNameKey
|
// a map[string]interface{}, where one of the object keys is moduleNameKey
|
||||||
// and the corresponding value is the module name (as a string) which can
|
// and the corresponding value is the module name (as a string) which can
|
||||||
// be found in the given scope. In other words, the module name is declared
|
// be found in the given scope. In other words, the module name is declared
|
||||||
// in-line with the module itself.
|
// in-line with the module itself.
|
||||||
@@ -388,7 +385,7 @@ func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error
|
|||||||
// multiple instances in the map or it appears in an array (where there are
|
// multiple instances in the map or it appears in an array (where there are
|
||||||
// no custom keys). In other words, the key containing the module name is
|
// no custom keys). In other words, the key containing the module name is
|
||||||
// treated special/separate from all the other keys in the object.
|
// treated special/separate from all the other keys in the object.
|
||||||
func (ctx Context) loadModuleInline(moduleNameKey, moduleScope string, raw json.RawMessage) (any, error) {
|
func (ctx Context) loadModuleInline(moduleNameKey, moduleScope string, raw json.RawMessage) (interface{}, error) {
|
||||||
moduleName, raw, err := getModuleNameInline(moduleNameKey, raw)
|
moduleName, raw, err := getModuleNameInline(moduleNameKey, raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -410,7 +407,7 @@ 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).
|
||||||
func (ctx Context) App(name string) (any, error) {
|
func (ctx Context) App(name string) (interface{}, error) {
|
||||||
if app, ok := ctx.cfg.apps[name]; ok {
|
if app, ok := ctx.cfg.apps[name]; ok {
|
||||||
return app, nil
|
return app, nil
|
||||||
}
|
}
|
||||||
@@ -426,43 +423,13 @@ func (ctx Context) App(name string) (any, error) {
|
|||||||
return modVal, nil
|
return modVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppIsConfigured returns whether an app named name has been
|
|
||||||
// configured. Can be called before calling App() to avoid
|
|
||||||
// instantiating an empty app when that's not desirable.
|
|
||||||
func (ctx Context) AppIsConfigured(name string) bool {
|
|
||||||
if _, ok := ctx.cfg.apps[name]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
appRaw := ctx.cfg.AppsRaw[name]
|
|
||||||
return appRaw != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Storage returns the configured Caddy storage implementation.
|
// Storage returns the configured Caddy storage implementation.
|
||||||
func (ctx Context) Storage() certmagic.Storage {
|
func (ctx Context) Storage() certmagic.Storage {
|
||||||
return ctx.cfg.storage
|
return ctx.cfg.storage
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger returns a logger that is intended for use by the most
|
// Logger returns a logger that can be used by mod.
|
||||||
// recent module associated with the context. Callers should not
|
func (ctx Context) Logger(mod Module) *zap.Logger {
|
||||||
// pass in any arguments unless they want to associate with a
|
|
||||||
// different module; it panics if more than 1 value is passed in.
|
|
||||||
//
|
|
||||||
// Originally, this method's signature was `Logger(mod Module)`,
|
|
||||||
// requiring that an instance of a Caddy module be passsed in.
|
|
||||||
// However, that is no longer necessary, as the closest module
|
|
||||||
// most recently associated with the context will be automatically
|
|
||||||
// assumed. To prevent a sudden breaking change, this method's
|
|
||||||
// signature has been changed to be variadic, but we may remove
|
|
||||||
// the parameter altogether in the future. Callers should not
|
|
||||||
// pass in any argument. If there is valid need to specify a
|
|
||||||
// different module, please open an issue to discuss.
|
|
||||||
//
|
|
||||||
// PARTIALLY DEPRECATED: The Logger(module) form is deprecated and
|
|
||||||
// may be removed in the future. Do not pass in any arguments.
|
|
||||||
func (ctx Context) Logger(module ...Module) *zap.Logger {
|
|
||||||
if len(module) > 1 {
|
|
||||||
panic("more than 1 module passed in")
|
|
||||||
}
|
|
||||||
if ctx.cfg == nil {
|
if ctx.cfg == nil {
|
||||||
// often the case in tests; just use a dev logger
|
// often the case in tests; just use a dev logger
|
||||||
l, err := zap.NewDevelopment()
|
l, err := zap.NewDevelopment()
|
||||||
@@ -471,26 +438,5 @@ func (ctx Context) Logger(module ...Module) *zap.Logger {
|
|||||||
}
|
}
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
mod := ctx.Module()
|
|
||||||
if len(module) > 0 {
|
|
||||||
mod = module[0]
|
|
||||||
}
|
|
||||||
return ctx.cfg.Logging.Logger(mod)
|
return ctx.cfg.Logging.Logger(mod)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modules returns the lineage of modules that this context provisioned,
|
|
||||||
// with the most recent/current module being last in the list.
|
|
||||||
func (ctx Context) Modules() []Module {
|
|
||||||
mods := make([]Module, len(ctx.ancestry))
|
|
||||||
copy(mods, ctx.ancestry)
|
|
||||||
return mods
|
|
||||||
}
|
|
||||||
|
|
||||||
// Module returns the current module, or the most recent one
|
|
||||||
// provisioned by the context.
|
|
||||||
func (ctx Context) Module() Module {
|
|
||||||
if len(ctx.ancestry) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ctx.ancestry[len(ctx.ancestry)-1]
|
|
||||||
}
|
|
||||||
|
|||||||
+4
-4
@@ -71,13 +71,13 @@ func ExampleContext_LoadModule_array() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// since our input is []json.RawMessage, the output will be []any
|
// since our input is []json.RawMessage, the output will be []interface{}
|
||||||
mods, err := ctx.LoadModule(myStruct, "GuestModulesRaw")
|
mods, err := ctx.LoadModule(myStruct, "GuestModulesRaw")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// you'd want to actually handle the error here
|
// you'd want to actually handle the error here
|
||||||
// return fmt.Errorf("loading guest modules: %v", err)
|
// return fmt.Errorf("loading guest modules: %v", err)
|
||||||
}
|
}
|
||||||
for _, mod := range mods.([]any) {
|
for _, mod := range mods.([]interface{}) {
|
||||||
myStruct.guestModules = append(myStruct.guestModules, mod.(io.Writer))
|
myStruct.guestModules = append(myStruct.guestModules, mod.(io.Writer))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,13 +104,13 @@ func ExampleContext_LoadModule_map() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// since our input is map[string]json.RawMessage, the output will be map[string]any
|
// since our input is map[string]json.RawMessage, the output will be map[string]interface{}
|
||||||
mods, err := ctx.LoadModule(myStruct, "GuestModulesRaw")
|
mods, err := ctx.LoadModule(myStruct, "GuestModulesRaw")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// you'd want to actually handle the error here
|
// you'd want to actually handle the error here
|
||||||
// return fmt.Errorf("loading guest modules: %v", err)
|
// return fmt.Errorf("loading guest modules: %v", err)
|
||||||
}
|
}
|
||||||
for modName, mod := range mods.(map[string]any) {
|
for modName, mod := range mods.(map[string]interface{}) {
|
||||||
myStruct.guestModules[modName] = mod.(io.Writer)
|
myStruct.guestModules[modName] = mod.(io.Writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@
|
|||||||
// 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 gofuzz
|
// +build gofuzz
|
||||||
|
|
||||||
package caddy
|
package caddy
|
||||||
|
|
||||||
|
|||||||
@@ -1,138 +1,35 @@
|
|||||||
module github.com/caddyserver/caddy/v2
|
module github.com/caddyserver/caddy/v2
|
||||||
|
|
||||||
go 1.18
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.2.0
|
|
||||||
github.com/Masterminds/sprig/v3 v3.2.2
|
github.com/Masterminds/sprig/v3 v3.2.2
|
||||||
github.com/alecthomas/chroma v0.10.0
|
github.com/alecthomas/chroma v0.9.2
|
||||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
|
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
|
||||||
github.com/caddyserver/certmagic v0.17.1
|
github.com/caddyserver/certmagic v0.14.5
|
||||||
github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac
|
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.12.4
|
github.com/google/cel-go v0.7.3
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/klauspost/compress v1.15.9
|
github.com/klauspost/compress v1.13.4
|
||||||
github.com/klauspost/cpuid/v2 v2.1.0
|
github.com/klauspost/cpuid/v2 v2.0.9
|
||||||
github.com/lucas-clemente/quic-go v0.28.2-0.20220813150001-9957668d4301
|
github.com/lucas-clemente/quic-go v0.23.0
|
||||||
github.com/mholt/acmez v1.0.4
|
github.com/mholt/acmez v1.0.0
|
||||||
github.com/prometheus/client_golang v1.12.2
|
github.com/naoina/go-stringutil v0.1.0 // indirect
|
||||||
github.com/smallstep/certificates v0.21.0
|
github.com/naoina/toml v0.1.1
|
||||||
github.com/smallstep/cli v0.21.0
|
github.com/prometheus/client_golang v1.11.0
|
||||||
github.com/smallstep/nosql v0.4.0
|
github.com/smallstep/certificates v0.16.4
|
||||||
github.com/smallstep/truststore v0.12.0
|
github.com/smallstep/cli v0.16.1
|
||||||
github.com/spf13/cobra v1.1.3
|
github.com/smallstep/nosql v0.3.8
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/smallstep/truststore v0.9.6
|
||||||
github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2
|
github.com/yuin/goldmark v1.4.0
|
||||||
github.com/yuin/goldmark v1.4.13
|
github.com/yuin/goldmark-highlighting v0.0.0-20210516132338-9216f9c5aa01
|
||||||
github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594
|
go.uber.org/zap v1.19.0
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.34.0
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
||||||
go.opentelemetry.io/otel v1.9.0
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.0
|
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
|
||||||
go.opentelemetry.io/otel/sdk v1.4.0
|
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08
|
||||||
go.uber.org/zap v1.21.0
|
google.golang.org/protobuf v1.27.1
|
||||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
|
|
||||||
golang.org/x/net v0.0.0-20220812165438-1d4ff48094d1
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
|
||||||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21
|
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/golang/mock v1.6.0 // indirect
|
|
||||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
|
|
||||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
|
|
||||||
github.com/Masterminds/goutils v1.1.1 // 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/cenkalti/backoff/v4 v4.1.2 // indirect
|
|
||||||
github.com/cespare/xxhash v1.1.0 // indirect
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
|
||||||
github.com/dgraph-io/badger v1.6.2 // indirect
|
|
||||||
github.com/dgraph-io/badger/v2 v2.2007.4 // 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/dlclark/regexp2 v1.4.0 // 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-logfmt/logfmt v0.5.0 // indirect
|
|
||||||
github.com/go-logr/logr v1.2.3 // indirect
|
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
|
||||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
|
||||||
github.com/golang/snappy v0.0.4 // 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/inconshreveable/mousetrap v1.0.0 // indirect
|
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
|
||||||
github.com/jackc/pgconn v1.10.1 // indirect
|
|
||||||
github.com/jackc/pgio v1.0.0 // indirect
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
|
||||||
github.com/jackc/pgproto3/v2 v2.2.0 // indirect
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
|
||||||
github.com/jackc/pgtype v1.9.0 // indirect
|
|
||||||
github.com/jackc/pgx/v4 v4.14.0 // indirect
|
|
||||||
github.com/libdns/libdns v0.2.1 // 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.2 // indirect
|
|
||||||
github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
|
||||||
github.com/micromdm/scep/v2 v2.1.0 // indirect
|
|
||||||
github.com/miekg/dns v1.1.50 // indirect
|
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
|
||||||
github.com/mitchellh/go-ps v1.0.0 // 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/prometheus/client_model v0.2.0 // indirect
|
|
||||||
github.com/prometheus/common v0.32.1 // indirect
|
|
||||||
github.com/prometheus/procfs v0.7.3 // indirect
|
|
||||||
github.com/rs/xid v1.2.1 // indirect
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
|
||||||
github.com/shopspring/decimal v1.2.0 // indirect
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
|
||||||
github.com/slackhq/nebula v1.5.2 // indirect
|
|
||||||
github.com/spf13/cast v1.4.1 // indirect
|
|
||||||
github.com/stoewer/go-strcase v1.2.0 // indirect
|
|
||||||
github.com/urfave/cli v1.22.5 // indirect
|
|
||||||
go.etcd.io/bbolt v1.3.6 // indirect
|
|
||||||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/metric v0.31.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/trace v1.9.0 // indirect
|
|
||||||
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
|
|
||||||
go.step.sm/cli-utils v0.7.3 // indirect
|
|
||||||
go.step.sm/crypto v0.16.2 // indirect
|
|
||||||
go.step.sm/linkedca v0.16.1 // indirect
|
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
|
||||||
go.uber.org/multierr v1.6.0 // indirect
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10
|
|
||||||
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b // indirect
|
|
||||||
golang.org/x/tools v0.1.10 // indirect
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
|
||||||
google.golang.org/grpc v1.46.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.28.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
|
|
||||||
)
|
)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user