mirror of
https://github.com/caddyserver/caddy.git
synced 2026-05-26 16:52:40 -04:00
Compare commits
185 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d57ab215a2 | |||
| f4432a306a | |||
| 220cd1c2bc | |||
| 1975408d89 | |||
| 4ebcfed9c9 | |||
| d2a2311bfd | |||
| adbe7f87e6 | |||
| 19876208c7 | |||
| a686f7c346 | |||
| 84364ffcd0 | |||
| 1641e76fd7 | |||
| bc3d497739 | |||
| a807fe0659 | |||
| 3207769232 | |||
| 481bc80d6e | |||
| 3644ee31ca | |||
| d7764dfdbb | |||
| eacd7720e9 | |||
| 02e348f911 | |||
| ca37c0b05f | |||
| 8861eae223 | |||
| fd4de7e0ae | |||
| 0d7c63920d | |||
| 6a8d4f1d60 | |||
| d7621fdbe6 | |||
| 172136a0a0 | |||
| 22563a70eb | |||
| 9b74a53e51 | |||
| 932dac157a | |||
| 96c5c554c1 | |||
| 9283770f68 | |||
| 9996d6a70b | |||
| cfc3af6749 | |||
| 904a0fa368 | |||
| d7872c3bfa | |||
| 066d770409 | |||
| 1115158616 | |||
| 7b8f3505e3 | |||
| 30743c361a | |||
| 8d748bee71 | |||
| 99073eaa33 | |||
| e7da3b267b | |||
| 9e0e5a4b4c | |||
| 2c4295ee48 | |||
| 1f35a8a402 | |||
| 0e570e0cc7 | |||
| e48b75843b | |||
| 1f927d6b07 | |||
| 50778b5542 | |||
| 1bd567d7ad | |||
| 34cff4af7d | |||
| 3f3f8b3d52 | |||
| f2c17d1f3f | |||
| afa778ae05 | |||
| 5ba1e06fd6 | |||
| c216cf551d | |||
| ed1c594cdb | |||
| 66c80caf23 | |||
| 47391e4ec7 | |||
| 6790c0e38a | |||
| c864b82ae1 | |||
| e76405d550 | |||
| 328fb614f0 | |||
| bcaa8aaf11 | |||
| d0e209e1da | |||
| 290cfea08f | |||
| 5c2617ebf9 | |||
| 57ae9c3107 | |||
| 9c0c71e577 | |||
| a1751adb40 | |||
| d0123bd760 | |||
| fb72793269 | |||
| efd9251ad3 | |||
| b116dcea3d | |||
| 16d5b22349 | |||
| 22b9d51268 | |||
| 1d106fa14d | |||
| 8c3dd3de70 | |||
| eddbccd298 | |||
| 197c564f20 | |||
| b3ce260389 | |||
| 6028ff27fa | |||
| 37f0c4bfae | |||
| 315715e90f | |||
| 238f1108e6 | |||
| b183aec83c | |||
| 825fe48e06 | |||
| b285763969 | |||
| da88ec152c | |||
| 5c8dc34418 | |||
| 5823eccf99 | |||
| cc23ad6402 | |||
| 91e34139a1 | |||
| 09b2cbcf4d | |||
| a3481f871b | |||
| 05cfb121ec | |||
| 00f948c605 | |||
| 1d156527ea | |||
| 350ad38f63 | |||
| b129ed6be8 | |||
| d398898b35 | |||
| eaaa2e5872 | |||
| 4457afc170 | |||
| fbf0f4c425 | |||
| 5e6024c48d | |||
| 669fc41e63 | |||
| 0182fb87fa | |||
| 1391e8ed9a | |||
| 9753c44510 | |||
| b443190b66 | |||
| 388c7e898c | |||
| c6f2979986 | |||
| a211c656f1 | |||
| 48ce47f1d4 | |||
| ef4e0224a8 | |||
| c8a76d003f | |||
| dd5decabe7 | |||
| d7564d632f | |||
| 88fd5f3491 | |||
| 2ae58ac13e | |||
| 01be1b54a8 | |||
| 41f5dd56e1 | |||
| 16724842d9 | |||
| 792f1c7ed7 | |||
| c8adb1b553 | |||
| 9b4acc2449 | |||
| f3aead0e4d | |||
| 571f88d86f | |||
| 0e829bc418 | |||
| 4b1a9b6cc1 | |||
| 1a345b4fa6 | |||
| 22c98ea165 | |||
| 2faeac0a10 | |||
| 9dda8fbf84 | |||
| ff67b97126 | |||
| 5b44d6cea8 | |||
| 6ab9fb6f74 | |||
| f4bf4e0097 | |||
| 21f9c20a04 | |||
| 2d12fb7ac6 | |||
| 91e62db666 | |||
| c050a37e1c | |||
| 5c47c2f147 | |||
| ffd28be90a | |||
| 141c785420 | |||
| dcbf38d0b3 | |||
| 2028da4e74 | |||
| 4ade967005 | |||
| 8af646730b | |||
| 098897bdea | |||
| 8ccfedf2bb | |||
| 2bb2ecc549 | |||
| 54a0c8f948 | |||
| 043fe41ab8 | |||
| 9ddb78fadc | |||
| 3a48b03369 | |||
| 7cf8376e63 | |||
| 21af88fefc | |||
| 52bad45181 | |||
| b85b6c6469 | |||
| 59cbb2c83a | |||
| a8b0dfa8da | |||
| b198678174 | |||
| 840094ac65 | |||
| 976469ca0d | |||
| 9cc26ee7bf | |||
| 3579815a6c | |||
| 61fe152c60 | |||
| 3afa02ba4e | |||
| 806f5b1117 | |||
| b2492f8567 | |||
| 07c863637d | |||
| dc2a5d5c52 | |||
| 4943a4fc52 | |||
| 630c62b313 | |||
| 9338741ca7 | |||
| 88c7e53da5 | |||
| 4ef360745d | |||
| 7142d7c1e4 | |||
| c3fb5f4d3f | |||
| 15d986e1c9 | |||
| f350e001b6 | |||
| 0287009ee5 | |||
| f8861ca16b | |||
| c2ccf8690f |
+2
-2
@@ -6,8 +6,8 @@ The Caddy project would like to make sure that it stays on top of all practicall
|
|||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | ------------------ |
|
| -------- | ----------|
|
||||||
| 2.x | ✔️ |
|
| 2.latest | ✔️ |
|
||||||
| 1.x | :x: |
|
| 1.x | :x: |
|
||||||
| < 1.x | :x: |
|
| < 1.x | :x: |
|
||||||
|
|
||||||
|
|||||||
+52
-13
@@ -12,6 +12,10 @@ on:
|
|||||||
- master
|
- master
|
||||||
- 2.*
|
- 2.*
|
||||||
|
|
||||||
|
env:
|
||||||
|
# https://github.com/actions/setup-go/issues/491
|
||||||
|
GOTOOLCHAIN: local
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
strategy:
|
strategy:
|
||||||
@@ -23,17 +27,13 @@ jobs:
|
|||||||
- mac
|
- mac
|
||||||
- windows
|
- windows
|
||||||
go:
|
go:
|
||||||
- '1.21'
|
- '1.24'
|
||||||
- '1.22'
|
|
||||||
|
|
||||||
include:
|
include:
|
||||||
# Set the minimum Go patch version for the given Go minor
|
# Set the minimum Go patch version for the given Go minor
|
||||||
# Usable via ${{ matrix.GO_SEMVER }}
|
# Usable via ${{ matrix.GO_SEMVER }}
|
||||||
- go: '1.21'
|
- go: '1.24'
|
||||||
GO_SEMVER: '~1.21.0'
|
GO_SEMVER: '~1.24.1'
|
||||||
|
|
||||||
- go: '1.22'
|
|
||||||
GO_SEMVER: '~1.22.3'
|
|
||||||
|
|
||||||
# Set some variables per OS, usable via ${{ matrix.VAR }}
|
# Set some variables per OS, usable via ${{ matrix.VAR }}
|
||||||
# OS_LABEL: the VM label from GitHub Actions (see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories)
|
# OS_LABEL: the VM label from GitHub Actions (see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories)
|
||||||
@@ -99,7 +99,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
run: |
|
run: |
|
||||||
go build -tags nobdger -trimpath -ldflags="-w -s" -v
|
go build -tags nobadger,nomysql,nopgx -trimpath -ldflags="-w -s" -v
|
||||||
|
|
||||||
- name: Smoke test Caddy
|
- name: Smoke test Caddy
|
||||||
working-directory: ./cmd/caddy
|
working-directory: ./cmd/caddy
|
||||||
@@ -122,7 +122,7 @@ jobs:
|
|||||||
# continue-on-error: true
|
# continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
# (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out
|
# (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out
|
||||||
go test -tags nobadger -v -coverprofile="cover-profile.out" -short -race ./...
|
go test -tags nobadger,nomysql,nopgx -v -coverprofile="cover-profile.out" -short -race ./...
|
||||||
# echo "status=$?" >> $GITHUB_OUTPUT
|
# echo "status=$?" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
# Relevant step if we reinvestigate publishing test/coverage reports
|
# Relevant step if we reinvestigate publishing test/coverage reports
|
||||||
@@ -143,25 +143,48 @@ jobs:
|
|||||||
s390x-test:
|
s390x-test:
|
||||||
name: test (s390x on IBM Z)
|
name: test (s390x on IBM Z)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]'
|
if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]'
|
||||||
continue-on-error: true # August 2020: s390x VM is down due to weather and power issues
|
continue-on-error: true # August 2020: s390x VM is down due to weather and power issues
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: |
|
run: |
|
||||||
|
set +e
|
||||||
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
|
||||||
|
|
||||||
# short sha is enough?
|
# short sha is enough?
|
||||||
short_sha=$(git rev-parse --short HEAD)
|
short_sha=$(git rev-parse --short HEAD)
|
||||||
|
|
||||||
|
# To shorten the following lines
|
||||||
|
ssh_opts="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||||
|
ssh_host="$CI_USER@ci-s390x.caddyserver.com"
|
||||||
|
|
||||||
# The environment is fresh, so there's no point in keeping accepting and adding the key.
|
# The environment is fresh, so there's no point in keeping accepting and adding the key.
|
||||||
rsync -arz -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" --progress --delete --exclude '.git' . "$CI_USER"@ci-s390x.caddyserver.com:/var/tmp/"$short_sha"
|
rsync -arz -e "ssh $ssh_opts" --progress --delete --exclude '.git' . "$ssh_host":/var/tmp/"$short_sha"
|
||||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -t "$CI_USER"@ci-s390x.caddyserver.com "cd /var/tmp/$short_sha; go version; go env; printf "\n\n";CGO_ENABLED=0 go test -tags nobadger -v ./..."
|
ssh $ssh_opts -t "$ssh_host" bash <<EOF
|
||||||
|
cd /var/tmp/$short_sha
|
||||||
|
go version
|
||||||
|
go env
|
||||||
|
printf "\n\n"
|
||||||
|
retries=3
|
||||||
|
exit_code=0
|
||||||
|
while ((retries > 0)); do
|
||||||
|
CGO_ENABLED=0 go test -p 1 -tags nobadger,nomysql,nopgx -v ./...
|
||||||
|
exit_code=$?
|
||||||
|
if ((exit_code == 0)); then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "\n\nTest failed: \$exit_code, retrying..."
|
||||||
|
((retries--))
|
||||||
|
done
|
||||||
|
echo "Remote exit code: \$exit_code"
|
||||||
|
exit \$exit_code
|
||||||
|
EOF
|
||||||
test_result=$?
|
test_result=$?
|
||||||
|
|
||||||
# There's no need leaving the files around
|
# There's no need leaving the files around
|
||||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "$CI_USER"@ci-s390x.caddyserver.com "rm -rf /var/tmp/'$short_sha'"
|
ssh $ssh_opts "$ssh_host" "rm -rf /var/tmp/'$short_sha'"
|
||||||
|
|
||||||
echo "Test exit code: $test_result"
|
echo "Test exit code: $test_result"
|
||||||
exit $test_result
|
exit $test_result
|
||||||
@@ -171,6 +194,7 @@ jobs:
|
|||||||
|
|
||||||
goreleaser-check:
|
goreleaser-check:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -179,3 +203,18 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: check
|
args: check
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: "~1.24"
|
||||||
|
check-latest: true
|
||||||
|
- name: Install xcaddy
|
||||||
|
run: |
|
||||||
|
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
|
||||||
|
xcaddy version
|
||||||
|
- uses: goreleaser/goreleaser-action@v6
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
args: build --single-target --snapshot
|
||||||
|
env:
|
||||||
|
TAG: ${{ github.head_ref || github.ref_name }}
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ on:
|
|||||||
- master
|
- master
|
||||||
- 2.*
|
- 2.*
|
||||||
|
|
||||||
|
env:
|
||||||
|
# https://github.com/actions/setup-go/issues/491
|
||||||
|
GOTOOLCHAIN: local
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
@@ -27,13 +31,13 @@ jobs:
|
|||||||
- 'darwin'
|
- 'darwin'
|
||||||
- 'netbsd'
|
- 'netbsd'
|
||||||
go:
|
go:
|
||||||
- '1.22'
|
- '1.24'
|
||||||
|
|
||||||
include:
|
include:
|
||||||
# Set the minimum Go patch version for the given Go minor
|
# Set the minimum Go patch version for the given Go minor
|
||||||
# Usable via ${{ matrix.GO_SEMVER }}
|
# Usable via ${{ matrix.GO_SEMVER }}
|
||||||
- go: '1.22'
|
- go: '1.24'
|
||||||
GO_SEMVER: '~1.22.3'
|
GO_SEMVER: '~1.24.1'
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
@@ -66,4 +70,4 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
working-directory: ./cmd/caddy
|
working-directory: ./cmd/caddy
|
||||||
run: |
|
run: |
|
||||||
GOOS=$GOOS GOARCH=$GOARCH go build -tags nobadger -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
|
GOOS=$GOOS GOARCH=$GOARCH go build -tags=nobadger,nomysql,nopgx -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ on:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
# https://github.com/actions/setup-go/issues/491
|
||||||
|
GOTOOLCHAIN: local
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# From https://github.com/golangci/golangci-lint-action
|
# From https://github.com/golangci/golangci-lint-action
|
||||||
golangci:
|
golangci:
|
||||||
@@ -43,13 +47,13 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '~1.22.3'
|
go-version: '~1.24'
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
version: v1.55
|
version: latest
|
||||||
|
|
||||||
# Windows times out frequently after about 5m50s if we don't set a longer timeout.
|
# Windows times out frequently after about 5m50s if we don't set a longer timeout.
|
||||||
args: --timeout 10m
|
args: --timeout 10m
|
||||||
@@ -63,5 +67,5 @@ jobs:
|
|||||||
- name: govulncheck
|
- name: govulncheck
|
||||||
uses: golang/govulncheck-action@v1
|
uses: golang/govulncheck-action@v1
|
||||||
with:
|
with:
|
||||||
go-version-input: '~1.22.3'
|
go-version-input: '~1.24.1'
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- 'v*.*.*'
|
- 'v*.*.*'
|
||||||
|
|
||||||
|
env:
|
||||||
|
# https://github.com/actions/setup-go/issues/491
|
||||||
|
GOTOOLCHAIN: local
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
@@ -13,13 +17,13 @@ jobs:
|
|||||||
os:
|
os:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
go:
|
go:
|
||||||
- '1.22'
|
- '1.24'
|
||||||
|
|
||||||
include:
|
include:
|
||||||
# Set the minimum Go patch version for the given Go minor
|
# Set the minimum Go patch version for the given Go minor
|
||||||
# Usable via ${{ matrix.GO_SEMVER }}
|
# Usable via ${{ matrix.GO_SEMVER }}
|
||||||
- go: '1.22'
|
- go: '1.24'
|
||||||
GO_SEMVER: '~1.22.3'
|
GO_SEMVER: '~1.24.1'
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
|
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
|
||||||
@@ -104,6 +108,10 @@ jobs:
|
|||||||
uses: anchore/sbom-action/download-syft@main
|
uses: anchore/sbom-action/download-syft@main
|
||||||
- name: Syft version
|
- name: Syft version
|
||||||
run: syft version
|
run: syft version
|
||||||
|
- name: Install xcaddy
|
||||||
|
run: |
|
||||||
|
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
|
||||||
|
xcaddy 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@v6
|
uses: goreleaser/goreleaser-action@v6
|
||||||
|
|||||||
+19
-5
@@ -1,7 +1,9 @@
|
|||||||
linters-settings:
|
linters-settings:
|
||||||
errcheck:
|
errcheck:
|
||||||
ignore: fmt:.*,go.uber.org/zap/zapcore:^Add.*
|
exclude-functions:
|
||||||
ignoretests: true
|
- fmt.*
|
||||||
|
- (go.uber.org/zap/zapcore.ObjectEncoder).AddObject
|
||||||
|
- (go.uber.org/zap/zapcore.ObjectEncoder).AddArray
|
||||||
gci:
|
gci:
|
||||||
sections:
|
sections:
|
||||||
- standard # Standard section: captures all standard packages.
|
- standard # Standard section: captures all standard packages.
|
||||||
@@ -33,7 +35,6 @@ linters:
|
|||||||
- errcheck
|
- errcheck
|
||||||
- errname
|
- errname
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- exportloopref
|
|
||||||
- gci
|
- gci
|
||||||
- gofmt
|
- gofmt
|
||||||
- goimports
|
- goimports
|
||||||
@@ -130,18 +131,22 @@ linters:
|
|||||||
run:
|
run:
|
||||||
# default concurrency is a available CPU number.
|
# default concurrency is a available CPU number.
|
||||||
# concurrency: 4 # explicitly omit this value to fully utilize available resources.
|
# concurrency: 4 # explicitly omit this value to fully utilize available resources.
|
||||||
deadline: 5m
|
timeout: 5m
|
||||||
issues-exit-code: 1
|
issues-exit-code: 1
|
||||||
tests: false
|
tests: false
|
||||||
|
|
||||||
# output configuration options
|
# output configuration options
|
||||||
output:
|
output:
|
||||||
format: 'colored-line-number'
|
formats:
|
||||||
|
- format: 'colored-line-number'
|
||||||
print-issued-lines: true
|
print-issued-lines: true
|
||||||
print-linter-name: true
|
print-linter-name: true
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
|
- text: 'G115' # TODO: Either we should fix the issues or nuke the linter if it's bad
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
# we aren't calling unknown URL
|
# we aren't calling unknown URL
|
||||||
- text: 'G107' # G107: Url provided to HTTP request as taint input
|
- text: 'G107' # G107: Url provided to HTTP request as taint input
|
||||||
linters:
|
linters:
|
||||||
@@ -166,3 +171,12 @@ issues:
|
|||||||
- path: modules/logging/filters.go
|
- path: modules/logging/filters.go
|
||||||
linters:
|
linters:
|
||||||
- dupl
|
- dupl
|
||||||
|
- path: modules/caddyhttp/matchers.go
|
||||||
|
linters:
|
||||||
|
- dupl
|
||||||
|
- path: modules/caddyhttp/vars.go
|
||||||
|
linters:
|
||||||
|
- dupl
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- errcheck
|
||||||
|
|||||||
+9
-2
@@ -12,6 +12,9 @@ before:
|
|||||||
- mkdir -p caddy-build
|
- mkdir -p caddy-build
|
||||||
- cp cmd/caddy/main.go caddy-build/main.go
|
- cp cmd/caddy/main.go caddy-build/main.go
|
||||||
- /bin/sh -c 'cd ./caddy-build && go mod init caddy'
|
- /bin/sh -c 'cd ./caddy-build && go mod init caddy'
|
||||||
|
# prepare syso files for windows embedding
|
||||||
|
- /bin/sh -c 'for a in amd64 arm arm64; do XCADDY_SKIP_BUILD=1 GOOS=windows GOARCH=$a xcaddy build {{.Env.TAG}}; done'
|
||||||
|
- /bin/sh -c 'mv /tmp/buildenv_*/*.syso caddy-build'
|
||||||
# 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
|
||||||
@@ -31,7 +34,6 @@ builds:
|
|||||||
- env:
|
- env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
- GO111MODULE=on
|
- GO111MODULE=on
|
||||||
main: main.go
|
|
||||||
dir: ./caddy-build
|
dir: ./caddy-build
|
||||||
binary: caddy
|
binary: caddy
|
||||||
goos:
|
goos:
|
||||||
@@ -81,6 +83,8 @@ builds:
|
|||||||
- -s -w
|
- -s -w
|
||||||
tags:
|
tags:
|
||||||
- nobadger
|
- nobadger
|
||||||
|
- nomysql
|
||||||
|
- nopgx
|
||||||
|
|
||||||
signs:
|
signs:
|
||||||
- cmd: cosign
|
- cmd: cosign
|
||||||
@@ -107,7 +111,7 @@ archives:
|
|||||||
- id: default
|
- id: default
|
||||||
format_overrides:
|
format_overrides:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
format: zip
|
formats: zip
|
||||||
name_template: >-
|
name_template: >-
|
||||||
{{ .ProjectName }}_
|
{{ .ProjectName }}_
|
||||||
{{- .Version }}_
|
{{- .Version }}_
|
||||||
@@ -188,6 +192,9 @@ nfpms:
|
|||||||
preremove: ./caddy-dist/scripts/preremove.sh
|
preremove: ./caddy-dist/scripts/preremove.sh
|
||||||
postremove: ./caddy-dist/scripts/postremove.sh
|
postremove: ./caddy-dist/scripts/postremove.sh
|
||||||
|
|
||||||
|
provides:
|
||||||
|
- httpd
|
||||||
|
|
||||||
release:
|
release:
|
||||||
github:
|
github:
|
||||||
owner: caddyserver
|
owner: caddyserver
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<a href="https://github.com/caddyserver/caddy/actions/workflows/ci.yml"><img src="https://github.com/caddyserver/caddy/actions/workflows/ci.yml/badge.svg"></a>
|
<a href="https://github.com/caddyserver/caddy/actions/workflows/ci.yml"><img src="https://github.com/caddyserver/caddy/actions/workflows/ci.yml/badge.svg"></a>
|
||||||
<a href="https://pkg.go.dev/github.com/caddyserver/caddy/v2"><img src="https://img.shields.io/badge/godoc-reference-%23007d9c.svg"></a>
|
<a href="https://pkg.go.dev/github.com/caddyserver/caddy/v2"><img src="https://img.shields.io/badge/godoc-reference-%23007d9c.svg"></a>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://twitter.com/caddyserver" title="@caddyserver on Twitter"><img src="https://img.shields.io/badge/twitter-@caddyserver-55acee.svg" alt="@caddyserver on Twitter"></a>
|
<a href="https://x.com/caddyserver" title="@caddyserver on Twitter"><img src="https://img.shields.io/twitter/follow/caddyserver" alt="@caddyserver on Twitter"></a>
|
||||||
<a href="https://caddy.community" title="Caddy Forum"><img src="https://img.shields.io/badge/community-forum-ff69b4.svg" alt="Caddy Forum"></a>
|
<a href="https://caddy.community" title="Caddy Forum"><img src="https://img.shields.io/badge/community-forum-ff69b4.svg" alt="Caddy Forum"></a>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://sourcegraph.com/github.com/caddyserver/caddy?badge" title="Caddy on Sourcegraph"><img src="https://sourcegraph.com/github.com/caddyserver/caddy/-/badge.svg" alt="Caddy on Sourcegraph"></a>
|
<a href="https://sourcegraph.com/github.com/caddyserver/caddy?badge" title="Caddy on Sourcegraph"><img src="https://sourcegraph.com/github.com/caddyserver/caddy/-/badge.svg" alt="Caddy on Sourcegraph"></a>
|
||||||
@@ -67,6 +67,7 @@
|
|||||||
- Fully-managed local CA for internal names & IPs
|
- Fully-managed local CA for internal names & IPs
|
||||||
- Can coordinate with other Caddy instances in a cluster
|
- Can coordinate with other Caddy instances in a cluster
|
||||||
- Multi-issuer fallback
|
- Multi-issuer fallback
|
||||||
|
- Encrypted ClientHello (ECH) support
|
||||||
- **Stays up when other servers go down** due to TLS/OCSP/certificate-related issues
|
- **Stays up when other servers go down** due to TLS/OCSP/certificate-related issues
|
||||||
- **Production-ready** after serving trillions of requests and managing millions of TLS certificates
|
- **Production-ready** after serving trillions of requests and managing millions of TLS certificates
|
||||||
- **Scales to hundreds of thousands of sites** as proven in production
|
- **Scales to hundreds of thousands of sites** as proven in production
|
||||||
@@ -87,7 +88,7 @@ See [our online documentation](https://caddyserver.com/docs/install) for other i
|
|||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
|
|
||||||
- [Go 1.21 or newer](https://golang.org/dl/)
|
- [Go 1.24.0 or newer](https://golang.org/dl/)
|
||||||
|
|
||||||
### For development
|
### For development
|
||||||
|
|
||||||
@@ -131,7 +132,7 @@ $ xcaddy build
|
|||||||
4. Initialize a Go module: `go mod init caddy`
|
4. Initialize a Go module: `go mod init caddy`
|
||||||
5. (Optional) Pin Caddy version: `go get github.com/caddyserver/caddy/v2@version` replacing `version` with a git tag, commit, or branch name.
|
5. (Optional) Pin Caddy version: `go get github.com/caddyserver/caddy/v2@version` replacing `version` with a git tag, commit, or branch name.
|
||||||
6. (Optional) Add plugins by adding their import: `_ "import/path/here"`
|
6. (Optional) Add plugins by adding their import: `_ "import/path/here"`
|
||||||
7. Compile: `go build`
|
7. Compile: `go build -tags=nobadger,nomysql,nopgx`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -176,7 +177,7 @@ 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 advise companies using Caddy to secure a support contract through [Ardan Labs](https://www.ardanlabs.com) 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! 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!
|
||||||
|
|
||||||
@@ -192,8 +193,8 @@ Matthew Holt began developing Caddy in 2014 while studying computer science at B
|
|||||||
|
|
||||||
**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 X: [@caddyserver](https://x.com/caddyserver)_
|
||||||
- _Author on Twitter: [@mholt6](https://twitter.com/mholt6)_
|
- _Author on X: [@mholt6](https://x.com/mholt6)_
|
||||||
|
|
||||||
Caddy is a project of [ZeroSSL](https://zerossl.com), a Stack Holdings company.
|
Caddy is a project of [ZeroSSL](https://zerossl.com), a Stack Holdings company.
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -213,7 +214,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, _ Context) 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
|
||||||
@@ -269,7 +270,6 @@ func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool) admi
|
|||||||
// register third-party module endpoints
|
// register third-party module endpoints
|
||||||
for _, m := range GetModules("admin.api") {
|
for _, m := range GetModules("admin.api") {
|
||||||
router := m.New().(AdminRouter)
|
router := m.New().(AdminRouter)
|
||||||
handlerLabel := m.ID.Name()
|
|
||||||
for _, route := range router.Routes() {
|
for _, route := range router.Routes() {
|
||||||
addRoute(route.Pattern, handlerLabel, route.Handler)
|
addRoute(route.Pattern, handlerLabel, route.Handler)
|
||||||
}
|
}
|
||||||
@@ -312,7 +312,7 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
|
|||||||
}
|
}
|
||||||
if admin.Origins == nil {
|
if admin.Origins == nil {
|
||||||
if addr.isLoopback() {
|
if addr.isLoopback() {
|
||||||
if addr.IsUnixNetwork() {
|
if addr.IsUnixNetwork() || addr.IsFdNetwork() {
|
||||||
// RFC 2616, Section 14.26:
|
// RFC 2616, Section 14.26:
|
||||||
// "A client MUST include a Host header field in all HTTP/1.1 request
|
// "A client MUST include a Host header field in all HTTP/1.1 request
|
||||||
// messages. If the requested URI does not include an Internet host
|
// messages. If the requested URI does not include an Internet host
|
||||||
@@ -350,7 +350,7 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
|
|||||||
uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
|
uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !addr.IsUnixNetwork() {
|
if !addr.IsUnixNetwork() && !addr.IsFdNetwork() {
|
||||||
uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
|
uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -381,7 +381,9 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
|
|||||||
// for the admin endpoint exists in cfg, a default one is used, so
|
// for the admin endpoint exists in cfg, a default one is used, so
|
||||||
// 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 {
|
// Critically note that some elements and functionality of the context
|
||||||
|
// may not be ready, e.g. storage. Tread carefully.
|
||||||
|
func replaceLocalAdminServer(cfg *Config, ctx Context) 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
|
||||||
@@ -423,7 +425,7 @@ func replaceLocalAdminServer(cfg *Config) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
handler := cfg.Admin.newAdminHandler(addr, false)
|
handler := cfg.Admin.newAdminHandler(addr, false, ctx)
|
||||||
|
|
||||||
ln, err := addr.Listen(context.TODO(), 0, net.ListenConfig{})
|
ln, err := addr.Listen(context.TODO(), 0, net.ListenConfig{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -544,7 +546,7 @@ func replaceRemoteAdminServer(ctx Context, cfg *Config) error {
|
|||||||
|
|
||||||
// make the HTTP handler but disable Host/Origin enforcement
|
// make the HTTP handler but disable Host/Origin enforcement
|
||||||
// because we are using TLS authentication instead
|
// because we are using TLS authentication instead
|
||||||
handler := cfg.Admin.newAdminHandler(addr, true)
|
handler := cfg.Admin.newAdminHandler(addr, true, ctx)
|
||||||
|
|
||||||
// create client certificate pool for TLS mutual auth, and extract public keys
|
// create client certificate pool for TLS mutual auth, and extract public keys
|
||||||
// so that we can enforce access controls at the application layer
|
// so that we can enforce access controls at the application layer
|
||||||
@@ -675,13 +677,7 @@ func (remote RemoteAdmin) enforceAccessControls(r *http.Request) error {
|
|||||||
// key recognized; make sure its HTTP request is permitted
|
// key recognized; make sure its HTTP request is permitted
|
||||||
for _, accessPerm := range adminAccess.Permissions {
|
for _, accessPerm := range adminAccess.Permissions {
|
||||||
// verify method
|
// verify method
|
||||||
methodFound := accessPerm.Methods == nil
|
methodFound := accessPerm.Methods == nil || slices.Contains(accessPerm.Methods, r.Method)
|
||||||
for _, method := range accessPerm.Methods {
|
|
||||||
if method == r.Method {
|
|
||||||
methodFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !methodFound {
|
if !methodFound {
|
||||||
return APIError{
|
return APIError{
|
||||||
HTTPStatus: http.StatusForbidden,
|
HTTPStatus: http.StatusForbidden,
|
||||||
@@ -877,13 +873,9 @@ func (h adminHandler) handleError(w http.ResponseWriter, r *http.Request, err er
|
|||||||
// a trustworthy/expected value. This helps to mitigate DNS
|
// a trustworthy/expected value. This helps to mitigate DNS
|
||||||
// rebinding attacks.
|
// rebinding attacks.
|
||||||
func (h adminHandler) checkHost(r *http.Request) error {
|
func (h adminHandler) checkHost(r *http.Request) error {
|
||||||
var allowed bool
|
allowed := slices.ContainsFunc(h.allowedOrigins, func(u *url.URL) bool {
|
||||||
for _, allowedOrigin := range h.allowedOrigins {
|
return r.Host == u.Host
|
||||||
if r.Host == allowedOrigin.Host {
|
})
|
||||||
allowed = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !allowed {
|
if !allowed {
|
||||||
return APIError{
|
return APIError{
|
||||||
HTTPStatus: http.StatusForbidden,
|
HTTPStatus: http.StatusForbidden,
|
||||||
@@ -1147,7 +1139,7 @@ traverseLoop:
|
|||||||
return fmt.Errorf("[%s] invalid array index '%s': %v",
|
return fmt.Errorf("[%s] invalid array index '%s': %v",
|
||||||
path, idxStr, err)
|
path, idxStr, err)
|
||||||
}
|
}
|
||||||
if idx < 0 || idx >= len(arr) {
|
if idx < 0 || (method != http.MethodPut && idx >= len(arr)) || idx > len(arr) {
|
||||||
return fmt.Errorf("[%s] array index out of bounds: %s", path, idxStr)
|
return fmt.Errorf("[%s] array index out of bounds: %s", path, idxStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+734
@@ -15,12 +15,19 @@
|
|||||||
package caddy
|
package caddy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/caddyserver/certmagic"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testCfg = []byte(`{
|
var testCfg = []byte(`{
|
||||||
@@ -203,3 +210,730 @@ func BenchmarkLoad(b *testing.B) {
|
|||||||
Load(testCfg, true)
|
Load(testCfg, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAdminHandlerErrorHandling(t *testing.T) {
|
||||||
|
initAdminMetrics()
|
||||||
|
|
||||||
|
handler := adminHandler{
|
||||||
|
mux: http.NewServeMux(),
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.mux.Handle("/error", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
err := fmt.Errorf("test error")
|
||||||
|
handler.handleError(w, r, err)
|
||||||
|
}))
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/error", nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
if rr.Code == http.StatusOK {
|
||||||
|
t.Error("expected error response, got success")
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiErr APIError
|
||||||
|
if err := json.NewDecoder(rr.Body).Decode(&apiErr); err != nil {
|
||||||
|
t.Fatalf("decoding response: %v", err)
|
||||||
|
}
|
||||||
|
if apiErr.Message != "test error" {
|
||||||
|
t.Errorf("expected error message 'test error', got '%s'", apiErr.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initAdminMetrics() {
|
||||||
|
if adminMetrics.requestErrors != nil {
|
||||||
|
prometheus.Unregister(adminMetrics.requestErrors)
|
||||||
|
}
|
||||||
|
if adminMetrics.requestCount != nil {
|
||||||
|
prometheus.Unregister(adminMetrics.requestCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
adminMetrics.requestErrors = prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Namespace: "caddy",
|
||||||
|
Subsystem: "admin_http",
|
||||||
|
Name: "request_errors_total",
|
||||||
|
Help: "Number of errors that occurred handling admin endpoint requests",
|
||||||
|
}, []string{"handler", "path", "method"})
|
||||||
|
|
||||||
|
adminMetrics.requestCount = prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Namespace: "caddy",
|
||||||
|
Subsystem: "admin_http",
|
||||||
|
Name: "requests_total",
|
||||||
|
Help: "Count of requests to the admin endpoint",
|
||||||
|
}, []string{"handler", "path", "code", "method"}) // Added code and method labels
|
||||||
|
|
||||||
|
prometheus.MustRegister(adminMetrics.requestErrors)
|
||||||
|
prometheus.MustRegister(adminMetrics.requestCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminHandlerBuiltinRouteErrors(t *testing.T) {
|
||||||
|
initAdminMetrics()
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
Admin: &AdminConfig{
|
||||||
|
Listen: "localhost:2019",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := replaceLocalAdminServer(cfg, Context{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("setting up admin server: %v", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
stopAdminServer(localAdminServer)
|
||||||
|
}()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
method string
|
||||||
|
expectedStatus int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "stop endpoint wrong method",
|
||||||
|
path: "/stop",
|
||||||
|
method: http.MethodGet,
|
||||||
|
expectedStatus: http.StatusMethodNotAllowed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config endpoint wrong content-type",
|
||||||
|
path: "/config/",
|
||||||
|
method: http.MethodPost,
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config ID missing ID",
|
||||||
|
path: "/id/",
|
||||||
|
method: http.MethodGet,
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(test.method, fmt.Sprintf("http://localhost:2019%s", test.path), nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
localAdminServer.Handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
if rr.Code != test.expectedStatus {
|
||||||
|
t.Errorf("expected status %d but got %d", test.expectedStatus, rr.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
metricValue := testGetMetricValue(map[string]string{
|
||||||
|
"path": test.path,
|
||||||
|
"handler": "admin",
|
||||||
|
"method": test.method,
|
||||||
|
})
|
||||||
|
if metricValue != 1 {
|
||||||
|
t.Errorf("expected error metric to be incremented once, got %v", metricValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGetMetricValue(labels map[string]string) float64 {
|
||||||
|
promLabels := prometheus.Labels{}
|
||||||
|
for k, v := range labels {
|
||||||
|
promLabels[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
metric, err := adminMetrics.requestErrors.GetMetricWith(promLabels)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pb := &dto.Metric{}
|
||||||
|
metric.Write(pb)
|
||||||
|
return pb.GetCounter().GetValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockRouter struct {
|
||||||
|
routes []AdminRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockRouter) Routes() []AdminRoute {
|
||||||
|
return m.routes
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockModule struct {
|
||||||
|
mockRouter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockModule) CaddyModule() ModuleInfo {
|
||||||
|
return ModuleInfo{
|
||||||
|
ID: "admin.api.mock",
|
||||||
|
New: func() Module {
|
||||||
|
mm := &mockModule{
|
||||||
|
mockRouter: mockRouter{
|
||||||
|
routes: m.routes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return mm
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewAdminHandlerRouterRegistration(t *testing.T) {
|
||||||
|
originalModules := make(map[string]ModuleInfo)
|
||||||
|
for k, v := range modules {
|
||||||
|
originalModules[k] = v
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
modules = originalModules
|
||||||
|
}()
|
||||||
|
|
||||||
|
mockRoute := AdminRoute{
|
||||||
|
Pattern: "/mock",
|
||||||
|
Handler: AdminHandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
mock := &mockModule{
|
||||||
|
mockRouter: mockRouter{
|
||||||
|
routes: []AdminRoute{mockRoute},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
RegisterModule(mock)
|
||||||
|
|
||||||
|
addr, err := ParseNetworkAddress("localhost:2019")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse address: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
admin := &AdminConfig{
|
||||||
|
EnforceOrigin: false,
|
||||||
|
}
|
||||||
|
handler := admin.newAdminHandler(addr, false, Context{})
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "/mock", nil)
|
||||||
|
req.Host = "localhost:2019"
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
if rr.Code != http.StatusOK {
|
||||||
|
t.Errorf("Expected status code %d but got %d", http.StatusOK, rr.Code)
|
||||||
|
t.Logf("Response body: %s", rr.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(admin.routers) != 1 {
|
||||||
|
t.Errorf("Expected 1 router to be stored, got %d", len(admin.routers))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockProvisionableRouter struct {
|
||||||
|
mockRouter
|
||||||
|
provisionErr error
|
||||||
|
provisioned bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockProvisionableRouter) Provision(Context) error {
|
||||||
|
m.provisioned = true
|
||||||
|
return m.provisionErr
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockProvisionableModule struct {
|
||||||
|
*mockProvisionableRouter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockProvisionableModule) CaddyModule() ModuleInfo {
|
||||||
|
return ModuleInfo{
|
||||||
|
ID: "admin.api.mock_provision",
|
||||||
|
New: func() Module {
|
||||||
|
mm := &mockProvisionableModule{
|
||||||
|
mockProvisionableRouter: &mockProvisionableRouter{
|
||||||
|
mockRouter: m.mockRouter,
|
||||||
|
provisionErr: m.provisionErr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return mm
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminRouterProvisioning(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
provisionErr error
|
||||||
|
wantErr bool
|
||||||
|
routersAfter int // expected number of routers after provisioning
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "successful provisioning",
|
||||||
|
provisionErr: nil,
|
||||||
|
wantErr: false,
|
||||||
|
routersAfter: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "provisioning error",
|
||||||
|
provisionErr: fmt.Errorf("provision failed"),
|
||||||
|
wantErr: true,
|
||||||
|
routersAfter: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
originalModules := make(map[string]ModuleInfo)
|
||||||
|
for k, v := range modules {
|
||||||
|
originalModules[k] = v
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
modules = originalModules
|
||||||
|
}()
|
||||||
|
|
||||||
|
mockRoute := AdminRoute{
|
||||||
|
Pattern: "/mock",
|
||||||
|
Handler: AdminHandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create provisionable module
|
||||||
|
mock := &mockProvisionableModule{
|
||||||
|
mockProvisionableRouter: &mockProvisionableRouter{
|
||||||
|
mockRouter: mockRouter{
|
||||||
|
routes: []AdminRoute{mockRoute},
|
||||||
|
},
|
||||||
|
provisionErr: test.provisionErr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
RegisterModule(mock)
|
||||||
|
|
||||||
|
admin := &AdminConfig{}
|
||||||
|
addr, err := ParseNetworkAddress("localhost:2019")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse address: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = admin.newAdminHandler(addr, false, Context{})
|
||||||
|
err = admin.provisionAdminRouters(Context{})
|
||||||
|
|
||||||
|
if test.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error but got nil")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(admin.routers) != test.routersAfter {
|
||||||
|
t.Errorf("Expected %d routers after provisioning, got %d", test.routersAfter, len(admin.routers))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllowedOriginsUnixSocket(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
addr NetworkAddress
|
||||||
|
origins []string
|
||||||
|
expectOrigins []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "unix socket with default origins",
|
||||||
|
addr: NetworkAddress{
|
||||||
|
Network: "unix",
|
||||||
|
Host: "/tmp/caddy.sock",
|
||||||
|
},
|
||||||
|
origins: nil, // default origins
|
||||||
|
expectOrigins: []string{
|
||||||
|
"", // empty host as per RFC 2616
|
||||||
|
"127.0.0.1",
|
||||||
|
"::1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unix socket with custom origins",
|
||||||
|
addr: NetworkAddress{
|
||||||
|
Network: "unix",
|
||||||
|
Host: "/tmp/caddy.sock",
|
||||||
|
},
|
||||||
|
origins: []string{"example.com"},
|
||||||
|
expectOrigins: []string{
|
||||||
|
"example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tcp socket on localhost gets all loopback addresses",
|
||||||
|
addr: NetworkAddress{
|
||||||
|
Network: "tcp",
|
||||||
|
Host: "localhost",
|
||||||
|
StartPort: 2019,
|
||||||
|
EndPort: 2019,
|
||||||
|
},
|
||||||
|
origins: nil,
|
||||||
|
expectOrigins: []string{
|
||||||
|
"localhost:2019",
|
||||||
|
"[::1]:2019",
|
||||||
|
"127.0.0.1:2019",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
admin := AdminConfig{
|
||||||
|
Origins: test.origins,
|
||||||
|
}
|
||||||
|
|
||||||
|
got := admin.allowedOrigins(test.addr)
|
||||||
|
|
||||||
|
var gotOrigins []string
|
||||||
|
for _, u := range got {
|
||||||
|
gotOrigins = append(gotOrigins, u.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(gotOrigins) != len(test.expectOrigins) {
|
||||||
|
t.Errorf("Expected %d origins but got %d", len(test.expectOrigins), len(gotOrigins))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expectMap := make(map[string]struct{})
|
||||||
|
for _, origin := range test.expectOrigins {
|
||||||
|
expectMap[origin] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
gotMap := make(map[string]struct{})
|
||||||
|
for _, origin := range gotOrigins {
|
||||||
|
gotMap[origin] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expectMap, gotMap) {
|
||||||
|
t.Errorf("Origins mismatch.\nExpected: %v\nGot: %v", test.expectOrigins, gotOrigins)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReplaceRemoteAdminServer(t *testing.T) {
|
||||||
|
const testCert = `MIIDCTCCAfGgAwIBAgIUXsqJ1mY8pKlHQtI3HJ23x2eZPqwwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIzMDEwMTAwMDAwMFoXDTI0MDEw
|
||||||
|
MTAwMDAwMFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
|
||||||
|
AAOCAQ8AMIIBCgKCAQEA4O4S6BSoYcoxvRqI+h7yPOjF6KjntjzVVm9M+uHK4lzX
|
||||||
|
F1L3pSxJ2nDD4wZEV3FJ5yFOHVFqkG2vXG3BIczOlYG7UeNmKbQnKc5kZj3HGUrS
|
||||||
|
VGEktA4OJbeZhhWP15gcXN5eDM2eH3g9BFXVX6AURxLiUXzhNBUEZuj/OEyH9yEF
|
||||||
|
/qPCE+EjzVvWxvBXwgz/io4r4yok/Vq/bxJ6FlV6R7DX5oJSXyO0VEHZPi9DIyNU
|
||||||
|
kK3F/r4U1sWiJGWOs8i3YQWZ2ejh1C0aLFZpPcCGGgMNpoF31gyYP6ZuPDUyCXsE
|
||||||
|
g36UUw1JHNtIXYcLhnXuqj4A8TybTDpgXLqvwA9DBQIDAQABo1MwUTAdBgNVHQ4E
|
||||||
|
FgQUc13z30pFC63rr/HGKOE7E82vjXwwHwYDVR0jBBgwFoAUc13z30pFC63rr/HG
|
||||||
|
KOE7E82vjXwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAHO3j
|
||||||
|
oeiUXXJ7xD4P8Wj5t9d+E8lE1Xv1Dk3Z+EdG5+dan+RcToE42JJp9zB7FIh5Qz8g
|
||||||
|
W77LAjqh5oyqz3A2VJcyVgfE3uJP1R1mJM7JfGHf84QH4TZF2Q1RZY4SZs0VQ6+q
|
||||||
|
5wSlIZ4NXDy4Q4XkIJBGS61wT8IzYFXYBpx4PCP1Qj0PIE4sevEGwjsBIgxK307o
|
||||||
|
BxF8AWe6N6e4YZmQLGjQ+SeH0iwZb6vpkHyAY8Kj2hvK+cq2P7vU3VGi0t3r1F8L
|
||||||
|
IvrXHCvO2BMNJ/1UK1M4YNX8LYJqQhg9hEsIROe1OE/m3VhxIYMJI+qZXk9yHfgJ
|
||||||
|
vq+SH04xKhtFudVBAQ==`
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cfg *Config
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil config",
|
||||||
|
cfg: nil,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil admin config",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: nil,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil remote config",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: &AdminConfig{},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid listen address",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: &AdminConfig{
|
||||||
|
Remote: &RemoteAdmin{
|
||||||
|
Listen: "invalid:address",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid config",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: &AdminConfig{
|
||||||
|
Identity: &IdentityConfig{},
|
||||||
|
Remote: &RemoteAdmin{
|
||||||
|
Listen: "localhost:2021",
|
||||||
|
AccessControl: []*AdminAccess{
|
||||||
|
{
|
||||||
|
PublicKeys: []string{testCert},
|
||||||
|
Permissions: []AdminPermissions{{Methods: []string{"GET"}, Paths: []string{"/test"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid certificate",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: &AdminConfig{
|
||||||
|
Identity: &IdentityConfig{},
|
||||||
|
Remote: &RemoteAdmin{
|
||||||
|
Listen: "localhost:2021",
|
||||||
|
AccessControl: []*AdminAccess{
|
||||||
|
{
|
||||||
|
PublicKeys: []string{"invalid-cert-data"},
|
||||||
|
Permissions: []AdminPermissions{{Methods: []string{"GET"}, Paths: []string{"/test"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
ctx := Context{
|
||||||
|
Context: context.Background(),
|
||||||
|
cfg: test.cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.cfg != nil {
|
||||||
|
test.cfg.storage = &certmagic.FileStorage{Path: t.TempDir()}
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.cfg != nil && test.cfg.Admin != nil && test.cfg.Admin.Identity != nil {
|
||||||
|
identityCertCache = certmagic.NewCache(certmagic.CacheOptions{
|
||||||
|
GetConfigForCert: func(certmagic.Certificate) (*certmagic.Config, error) {
|
||||||
|
return &certmagic.Config{}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
err := replaceRemoteAdminServer(ctx, test.cfg)
|
||||||
|
|
||||||
|
if test.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error but got nil")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
if remoteAdminServer != nil {
|
||||||
|
_ = stopAdminServer(remoteAdminServer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockIssuer struct {
|
||||||
|
configSet *certmagic.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) {
|
||||||
|
return &certmagic.IssuedCertificate{
|
||||||
|
Certificate: []byte(csr.Raw),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockIssuer) SetConfig(cfg *certmagic.Config) {
|
||||||
|
m.configSet = cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockIssuer) IssuerKey() string {
|
||||||
|
return "mock"
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockIssuerModule struct {
|
||||||
|
*mockIssuer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockIssuerModule) CaddyModule() ModuleInfo {
|
||||||
|
return ModuleInfo{
|
||||||
|
ID: "tls.issuance.acme",
|
||||||
|
New: func() Module {
|
||||||
|
return &mockIssuerModule{mockIssuer: new(mockIssuer)}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManageIdentity(t *testing.T) {
|
||||||
|
originalModules := make(map[string]ModuleInfo)
|
||||||
|
for k, v := range modules {
|
||||||
|
originalModules[k] = v
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
modules = originalModules
|
||||||
|
}()
|
||||||
|
|
||||||
|
RegisterModule(&mockIssuerModule{})
|
||||||
|
|
||||||
|
certPEM := []byte(`-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDujCCAqKgAwIBAgIIE31FZVaPXTUwDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE
|
||||||
|
BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl
|
||||||
|
cm5ldCBBdXRob3JpdHkgRzIwHhcNMTQwMTI5MTMyNzQzWhcNMTQwNTI5MDAwMDAw
|
||||||
|
WjBpMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN
|
||||||
|
TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEYMBYGA1UEAwwPbWFp
|
||||||
|
bC5nb29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3lcub2pUwkjC
|
||||||
|
5GJQA2ZZfJJi6d1QHhEmkX9VxKYGp6gagZuRqJWy9TXP6++1ZzQQxqZLD0TkuxZ9
|
||||||
|
8i9Nz00000CCBjCCAQQwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMGgG
|
||||||
|
CCsGAQUFBwEBBFwwWjArBggrBgEFBQcwAoYfaHR0cDovL3BraS5nb29nbGUuY29t
|
||||||
|
L0dJQUcyLmNydDArBggrBgEFBQcwAYYfaHR0cDovL2NsaWVudHMxLmdvb2dsZS5j
|
||||||
|
b20vb2NzcDAdBgNVHQ4EFgQUiJxtimAuTfwb+aUtBn5UYKreKvMwDAYDVR0TAQH/
|
||||||
|
BAIwADAfBgNVHSMEGDAWgBRK3QYWG7z2aLV29YG2u2IaulqBLzAXBgNVHREEEDAO
|
||||||
|
ggxtYWlsLmdvb2dsZTANBgkqhkiG9w0BAQUFAAOCAQEAMP6IWgNGZE8wP9TjFjSZ
|
||||||
|
3mmW3A1eIr0CuPwNZ2LJ5ZD1i70ojzcj4I9IdP5yPg9CAEV4hNASbM1LzfC7GmJE
|
||||||
|
tPzW5tRmpKVWZGRgTgZI8Hp/xZXMwLh9ZmXV4kESFAGj5G5FNvJyUV7R5Eh+7OZX
|
||||||
|
7G4jJ4ZGJh+5jzN9HdJJHQHGYNIYOzC7+HH9UMwCjX9vhQ4RjwFZJThS2Yb+y7pb
|
||||||
|
9yxTJZoXC6J0H5JpnZb7kZEJ+Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
-----END CERTIFICATE-----`)
|
||||||
|
|
||||||
|
keyPEM := []byte(`-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRS0LmTwUT0iwP
|
||||||
|
...
|
||||||
|
-----END PRIVATE KEY-----`)
|
||||||
|
|
||||||
|
testStorage := certmagic.FileStorage{Path: t.TempDir()}
|
||||||
|
err := testStorage.Store(context.Background(), "localhost/localhost.crt", certPEM)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = testStorage.Store(context.Background(), "localhost/localhost.key", keyPEM)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cfg *Config
|
||||||
|
wantErr bool
|
||||||
|
checkState func(*testing.T, *Config)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil config",
|
||||||
|
cfg: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil admin config",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil identity config",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: &AdminConfig{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default issuer when none specified",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: &AdminConfig{
|
||||||
|
Identity: &IdentityConfig{
|
||||||
|
Identifiers: []string{"localhost"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
storage: &testStorage,
|
||||||
|
},
|
||||||
|
checkState: func(t *testing.T, cfg *Config) {
|
||||||
|
if len(cfg.Admin.Identity.issuers) == 0 {
|
||||||
|
t.Error("Expected at least 1 issuer to be configured")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := cfg.Admin.Identity.issuers[0].(*mockIssuerModule); !ok {
|
||||||
|
t.Error("Expected mock issuer to be configured")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom issuer",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: &AdminConfig{
|
||||||
|
Identity: &IdentityConfig{
|
||||||
|
Identifiers: []string{"localhost"},
|
||||||
|
IssuersRaw: []json.RawMessage{
|
||||||
|
json.RawMessage(`{"module": "acme"}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
storage: &certmagic.FileStorage{Path: "testdata"},
|
||||||
|
},
|
||||||
|
checkState: func(t *testing.T, cfg *Config) {
|
||||||
|
if len(cfg.Admin.Identity.issuers) != 1 {
|
||||||
|
t.Fatalf("Expected 1 issuer, got %d", len(cfg.Admin.Identity.issuers))
|
||||||
|
}
|
||||||
|
mockIss, ok := cfg.Admin.Identity.issuers[0].(*mockIssuerModule)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Expected mock issuer")
|
||||||
|
}
|
||||||
|
if mockIss.configSet == nil {
|
||||||
|
t.Error("Issuer config was not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid issuer module",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: &AdminConfig{
|
||||||
|
Identity: &IdentityConfig{
|
||||||
|
Identifiers: []string{"localhost"},
|
||||||
|
IssuersRaw: []json.RawMessage{
|
||||||
|
json.RawMessage(`{"module": "doesnt_exist"}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
if identityCertCache != nil {
|
||||||
|
// Reset the cert cache before each test
|
||||||
|
identityCertCache.Stop()
|
||||||
|
identityCertCache = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := Context{
|
||||||
|
Context: context.Background(),
|
||||||
|
cfg: test.cfg,
|
||||||
|
moduleInstances: make(map[string][]Module),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := manageIdentity(ctx, test.cfg)
|
||||||
|
|
||||||
|
if test.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error but got nil")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.checkState != nil {
|
||||||
|
test.checkState(t, test.cfg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -399,6 +399,7 @@ func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
|||||||
func run(newCfg *Config, start bool) (Context, error) {
|
func run(newCfg *Config, start bool) (Context, error) {
|
||||||
ctx, err := provisionContext(newCfg, start)
|
ctx, err := provisionContext(newCfg, start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
globalMetrics.configSuccess.Set(0)
|
||||||
return ctx, err
|
return ctx, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,6 +411,7 @@ func run(newCfg *Config, start bool) (Context, error) {
|
|||||||
// some of the other apps at runtime
|
// some of the other apps at runtime
|
||||||
err = ctx.cfg.Admin.provisionAdminRouters(ctx)
|
err = ctx.cfg.Admin.provisionAdminRouters(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
globalMetrics.configSuccess.Set(0)
|
||||||
return ctx, err
|
return ctx, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,9 +437,11 @@ func run(newCfg *Config, start bool) (Context, error) {
|
|||||||
return nil
|
return nil
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
globalMetrics.configSuccess.Set(0)
|
||||||
return ctx, err
|
return ctx, err
|
||||||
}
|
}
|
||||||
|
globalMetrics.configSuccess.Set(1)
|
||||||
|
globalMetrics.configSuccessTime.SetToCurrentTime()
|
||||||
// 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, ctx.cfg)
|
return ctx, finishSettingUp(ctx, ctx.cfg)
|
||||||
@@ -471,6 +475,7 @@ func provisionContext(newCfg *Config, replaceAdminServer bool) (Context, error)
|
|||||||
ctx, cancel := NewContext(Context{Context: context.Background(), cfg: newCfg})
|
ctx, cancel := NewContext(Context{Context: context.Background(), cfg: newCfg})
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
globalMetrics.configSuccess.Set(0)
|
||||||
// if there were any errors during startup,
|
// if there were any errors during startup,
|
||||||
// we should cancel the new context we created
|
// we should cancel the new context we created
|
||||||
// since the associated config won't be used;
|
// since the associated config won't be used;
|
||||||
@@ -497,7 +502,7 @@ func provisionContext(newCfg *Config, replaceAdminServer bool) (Context, error)
|
|||||||
|
|
||||||
// start the admin endpoint (and stop any prior one)
|
// start the admin endpoint (and stop any prior one)
|
||||||
if replaceAdminServer {
|
if replaceAdminServer {
|
||||||
err = replaceLocalAdminServer(newCfg)
|
err = replaceLocalAdminServer(newCfg, ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx, fmt.Errorf("starting caddy administration endpoint: %v", err)
|
return ctx, fmt.Errorf("starting caddy administration endpoint: %v", err)
|
||||||
}
|
}
|
||||||
@@ -720,8 +725,10 @@ func Validate(cfg *Config) error {
|
|||||||
// 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(ctx context.Context, logger *zap.Logger) {
|
||||||
// let the rest of the program know we're quitting
|
// let the rest of the program know we're quitting; only do it once
|
||||||
atomic.StoreInt32(exiting, 1)
|
if !atomic.CompareAndSwapInt32(exiting, 0, 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// give the OS or service/process manager our 2 weeks' notice: we quit
|
// give the OS or service/process manager our 2 weeks' notice: we quit
|
||||||
if err := notify.Stopping(); err != nil {
|
if err := notify.Stopping(); err != nil {
|
||||||
|
|||||||
@@ -415,7 +415,7 @@ func (d *Dispenser) EOFErr() error {
|
|||||||
|
|
||||||
// Err generates a custom parse-time error with a message of msg.
|
// Err generates a custom parse-time error with a message of msg.
|
||||||
func (d *Dispenser) Err(msg string) error {
|
func (d *Dispenser) Err(msg string) error {
|
||||||
return d.Errf(msg)
|
return d.WrapErr(errors.New(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errf is like Err, but for formatted error messages
|
// Errf is like Err, but for formatted error messages
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package caddyfile
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
type adjacency map[string][]string
|
type adjacency map[string][]string
|
||||||
@@ -91,12 +92,7 @@ func (i *importGraph) areConnected(from, to string) bool {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, v := range al {
|
return slices.Contains(al, to)
|
||||||
if v == to {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *importGraph) willCycle(from, to string) bool {
|
func (i *importGraph) willCycle(from, to string) bool {
|
||||||
|
|||||||
@@ -264,9 +264,14 @@ func (p *parser) addresses() error {
|
|||||||
return p.Errf("Site addresses cannot contain a comma ',': '%s' - put a space after the comma to separate site addresses", value)
|
return p.Errf("Site addresses cannot contain a comma ',': '%s' - put a space after the comma to separate site addresses", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// After the above, a comma surrounded by spaces would result
|
||||||
|
// in an empty token which we should ignore
|
||||||
|
if value != "" {
|
||||||
|
// Add the token as a site address
|
||||||
token.Text = value
|
token.Text = value
|
||||||
p.block.Keys = append(p.block.Keys, token)
|
p.block.Keys = append(p.block.Keys, token)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Advance token and possibly break out of loop or return error
|
// Advance token and possibly break out of loop or return error
|
||||||
hasNext := p.Next()
|
hasNext := p.Next()
|
||||||
@@ -418,7 +423,7 @@ func (p *parser) doImport(nesting int) error {
|
|||||||
// make path relative to the file of the _token_ being processed rather
|
// make path relative to the file of the _token_ being processed rather
|
||||||
// than current working directory (issue #867) and then use glob to get
|
// than current working directory (issue #867) and then use glob to get
|
||||||
// list of matching filenames
|
// list of matching filenames
|
||||||
absFile, err := filepath.Abs(p.Dispenser.File())
|
absFile, err := caddy.FastAbs(p.Dispenser.File())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return p.Errf("Failed to get absolute path of file: %s: %v", p.Dispenser.File(), err)
|
return p.Errf("Failed to get absolute path of file: %s: %v", p.Dispenser.File(), err)
|
||||||
}
|
}
|
||||||
@@ -617,7 +622,7 @@ func (p *parser) doSingleImport(importFile string) ([]Token, error) {
|
|||||||
|
|
||||||
// Tack the file path onto these tokens so errors show the imported file's name
|
// Tack the file path onto these tokens so errors show the imported file's name
|
||||||
// (we use full, absolute path to avoid bugs: issue #1892)
|
// (we use full, absolute path to avoid bugs: issue #1892)
|
||||||
filename, err := filepath.Abs(importFile)
|
filename, err := caddy.FastAbs(importFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, p.Errf("Failed to get absolute path of file: %s: %v", importFile, err)
|
return nil, p.Errf("Failed to get absolute path of file: %s: %v", importFile, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -555,6 +555,10 @@ func TestParseAll(t *testing.T) {
|
|||||||
{"localhost:1234", "http://host2"},
|
{"localhost:1234", "http://host2"},
|
||||||
}},
|
}},
|
||||||
|
|
||||||
|
{`foo.example.com , example.com`, false, [][]string{
|
||||||
|
{"foo.example.com", "example.com"},
|
||||||
|
}},
|
||||||
|
|
||||||
{`localhost:1234, http://host2,`, true, [][]string{}},
|
{`localhost:1234, http://host2,`, true, [][]string{}},
|
||||||
|
|
||||||
{`http://host1.com, http://host2.com {
|
{`http://host1.com, http://host2.com {
|
||||||
@@ -614,8 +618,8 @@ func TestParseAll(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for j, block := range blocks {
|
for j, block := range blocks {
|
||||||
if len(block.Keys) != len(test.keys[j]) {
|
if len(block.Keys) != len(test.keys[j]) {
|
||||||
t.Errorf("Test %d: Expected %d keys in block %d, got %d",
|
t.Errorf("Test %d: Expected %d keys in block %d, got %d: %v",
|
||||||
i, len(test.keys[j]), j, len(block.Keys))
|
i, len(test.keys[j]), j, len(block.Keys), block.Keys)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for k, addr := range block.GetKeysText() {
|
for k, addr := range block.GetKeysText() {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import (
|
|||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// mapAddressToServerBlocks returns a map of listener address to list of server
|
// mapAddressToProtocolToServerBlocks returns a map of listener address to list of server
|
||||||
// blocks that will be served on that address. To do this, each server block is
|
// blocks that will be served on that address. To do this, each server block is
|
||||||
// expanded so that each one is considered individually, although keys of a
|
// expanded so that each one is considered individually, although keys of a
|
||||||
// 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
|
||||||
@@ -77,10 +77,15 @@ import (
|
|||||||
// repetition may be undesirable, so call consolidateAddrMappings() to map
|
// repetition may be undesirable, so call consolidateAddrMappings() to map
|
||||||
// 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) mapAddressToProtocolToServerBlocks(originalServerBlocks []serverBlock,
|
||||||
options map[string]any,
|
options map[string]any,
|
||||||
) (map[string][]serverBlock, error) {
|
) (map[string]map[string][]serverBlock, error) {
|
||||||
sbmap := make(map[string][]serverBlock)
|
addrToProtocolToServerBlocks := map[string]map[string][]serverBlock{}
|
||||||
|
|
||||||
|
type keyWithParsedKey struct {
|
||||||
|
key caddyfile.Token
|
||||||
|
parsedKey Address
|
||||||
|
}
|
||||||
|
|
||||||
for i, sblock := range originalServerBlocks {
|
for i, sblock := range originalServerBlocks {
|
||||||
// within a server block, we need to map all the listener addresses
|
// within a server block, we need to map all the listener addresses
|
||||||
@@ -88,27 +93,48 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
|
|||||||
// will be served by them; this has the effect of treating each
|
// will be served by them; this has the effect of treating each
|
||||||
// key of a server block as its own, but without having to repeat its
|
// key of a server block as its own, but without having to repeat its
|
||||||
// contents in cases where multiple keys really can be served together
|
// contents in cases where multiple keys really can be served together
|
||||||
addrToKeys := make(map[string][]caddyfile.Token)
|
addrToProtocolToKeyWithParsedKeys := map[string]map[string][]keyWithParsedKey{}
|
||||||
for j, key := range sblock.block.Keys {
|
for j, key := range sblock.block.Keys {
|
||||||
|
parsedKey, err := ParseAddress(key.Text)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing key: %v", err)
|
||||||
|
}
|
||||||
|
parsedKey = parsedKey.Normalize()
|
||||||
|
|
||||||
// a key can have multiple listener addresses if there are multiple
|
// a key can have multiple listener addresses if there are multiple
|
||||||
// arguments to the 'bind' directive (although they will all have
|
// arguments to the 'bind' directive (although they will all have
|
||||||
// the same port, since the port is defined by the key or is implicit
|
// the same port, since the port is defined by the key or is implicit
|
||||||
// through automatic HTTPS)
|
// through automatic HTTPS)
|
||||||
addrs, err := st.listenerAddrsForServerBlockKey(sblock, key.Text, options)
|
listeners, err := st.listenersForServerBlockAddress(sblock, parsedKey, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("server block %d, key %d (%s): determining listener address: %v", i, j, key.Text, err)
|
return nil, fmt.Errorf("server block %d, key %d (%s): determining listener address: %v", i, j, key.Text, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// associate this key with each listener address it is served on
|
// associate this key with its protocols and each listener address served with them
|
||||||
for _, addr := range addrs {
|
kwpk := keyWithParsedKey{key, parsedKey}
|
||||||
addrToKeys[addr] = append(addrToKeys[addr], key)
|
for addr, protocols := range listeners {
|
||||||
|
protocolToKeyWithParsedKeys, ok := addrToProtocolToKeyWithParsedKeys[addr]
|
||||||
|
if !ok {
|
||||||
|
protocolToKeyWithParsedKeys = map[string][]keyWithParsedKey{}
|
||||||
|
addrToProtocolToKeyWithParsedKeys[addr] = protocolToKeyWithParsedKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
// an empty protocol indicates the default, a nil or empty value in the ListenProtocols array
|
||||||
|
if len(protocols) == 0 {
|
||||||
|
protocols[""] = struct{}{}
|
||||||
|
}
|
||||||
|
for prot := range protocols {
|
||||||
|
protocolToKeyWithParsedKeys[prot] = append(
|
||||||
|
protocolToKeyWithParsedKeys[prot],
|
||||||
|
kwpk)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// make a slice of the map keys so we can iterate in sorted order
|
// make a slice of the map keys so we can iterate in sorted order
|
||||||
addrs := make([]string, 0, len(addrToKeys))
|
addrs := make([]string, 0, len(addrToProtocolToKeyWithParsedKeys))
|
||||||
for k := range addrToKeys {
|
for addr := range addrToProtocolToKeyWithParsedKeys {
|
||||||
addrs = append(addrs, k)
|
addrs = append(addrs, addr)
|
||||||
}
|
}
|
||||||
sort.Strings(addrs)
|
sort.Strings(addrs)
|
||||||
|
|
||||||
@@ -118,85 +144,132 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
|
|||||||
// 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 := range addrs {
|
||||||
keys := addrToKeys[addr]
|
protocolToKeyWithParsedKeys := addrToProtocolToKeyWithParsedKeys[addr]
|
||||||
// parse keys so that we only have to do it once
|
|
||||||
parsedKeys := make([]Address, 0, len(keys))
|
prots := make([]string, 0, len(protocolToKeyWithParsedKeys))
|
||||||
for _, key := range keys {
|
for prot := range protocolToKeyWithParsedKeys {
|
||||||
addr, err := ParseAddress(key.Text)
|
prots = append(prots, prot)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing key '%s': %v", key.Text, err)
|
|
||||||
}
|
}
|
||||||
parsedKeys = append(parsedKeys, addr.Normalize())
|
sort.Strings(prots)
|
||||||
|
|
||||||
|
protocolToServerBlocks, ok := addrToProtocolToServerBlocks[addr]
|
||||||
|
if !ok {
|
||||||
|
protocolToServerBlocks = map[string][]serverBlock{}
|
||||||
|
addrToProtocolToServerBlocks[addr] = protocolToServerBlocks
|
||||||
}
|
}
|
||||||
sbmap[addr] = append(sbmap[addr], serverBlock{
|
|
||||||
|
for _, prot := range prots {
|
||||||
|
keyWithParsedKeys := protocolToKeyWithParsedKeys[prot]
|
||||||
|
|
||||||
|
keys := make([]caddyfile.Token, len(keyWithParsedKeys))
|
||||||
|
parsedKeys := make([]Address, len(keyWithParsedKeys))
|
||||||
|
|
||||||
|
for k, keyWithParsedKey := range keyWithParsedKeys {
|
||||||
|
keys[k] = keyWithParsedKey.key
|
||||||
|
parsedKeys[k] = keyWithParsedKey.parsedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
protocolToServerBlocks[prot] = append(protocolToServerBlocks[prot], serverBlock{
|
||||||
block: caddyfile.ServerBlock{
|
block: caddyfile.ServerBlock{
|
||||||
Keys: keys,
|
Keys: keys,
|
||||||
Segments: sblock.block.Segments,
|
Segments: sblock.block.Segments,
|
||||||
},
|
},
|
||||||
pile: sblock.pile,
|
pile: sblock.pile,
|
||||||
keys: parsedKeys,
|
parsedKeys: parsedKeys,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return sbmap, nil
|
return addrToProtocolToServerBlocks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// consolidateAddrMappings eliminates repetition of identical server blocks in a mapping of
|
// consolidateAddrMappings eliminates repetition of identical server blocks in a mapping of
|
||||||
// single listener addresses to lists of server blocks. Since multiple addresses may serve
|
// single listener addresses to protocols to lists of server blocks. Since multiple addresses
|
||||||
// identical sites (server block contents), this function turns a 1:many mapping into a
|
// may serve multiple protocols to identical sites (server block contents), this function turns
|
||||||
// many:many mapping. Server block contents (tokens) must be exactly identical so that
|
// a 1:many mapping into a many:many mapping. Server block contents (tokens) must be
|
||||||
// reflect.DeepEqual returns true in order for the addresses to be combined. Identical
|
// exactly identical so that reflect.DeepEqual returns true in order for the addresses to be combined.
|
||||||
// entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each
|
// Identical entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each
|
||||||
// association from multiple addresses to multiple server blocks; i.e. each element of
|
// association from multiple addresses to multiple server blocks; i.e. each element of
|
||||||
// the returned slice) becomes a server definition in the output JSON.
|
// the returned slice) becomes a server definition in the output JSON.
|
||||||
func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]serverBlock) []sbAddrAssociation {
|
func (st *ServerType) consolidateAddrMappings(addrToProtocolToServerBlocks map[string]map[string][]serverBlock) []sbAddrAssociation {
|
||||||
sbaddrs := make([]sbAddrAssociation, 0, len(addrToServerBlocks))
|
sbaddrs := make([]sbAddrAssociation, 0, len(addrToProtocolToServerBlocks))
|
||||||
for addr, sblocks := range addrToServerBlocks {
|
|
||||||
// we start with knowing that at least this address
|
addrs := make([]string, 0, len(addrToProtocolToServerBlocks))
|
||||||
// maps to these server blocks
|
for addr := range addrToProtocolToServerBlocks {
|
||||||
a := sbAddrAssociation{
|
addrs = append(addrs, addr)
|
||||||
addresses: []string{addr},
|
|
||||||
serverBlocks: sblocks,
|
|
||||||
}
|
}
|
||||||
|
sort.Strings(addrs)
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
protocolToServerBlocks := addrToProtocolToServerBlocks[addr]
|
||||||
|
|
||||||
|
prots := make([]string, 0, len(protocolToServerBlocks))
|
||||||
|
for prot := range protocolToServerBlocks {
|
||||||
|
prots = append(prots, prot)
|
||||||
|
}
|
||||||
|
sort.Strings(prots)
|
||||||
|
|
||||||
|
for _, prot := range prots {
|
||||||
|
serverBlocks := protocolToServerBlocks[prot]
|
||||||
|
|
||||||
// now find other addresses that map to identical
|
// now find other addresses that map to identical
|
||||||
// server blocks and add them to our list of
|
// server blocks and add them to our map of listener
|
||||||
// addresses, while removing them from the map
|
// addresses and protocols, while removing them from
|
||||||
for otherAddr, otherSblocks := range addrToServerBlocks {
|
// the original map
|
||||||
if addr == otherAddr {
|
listeners := map[string]map[string]struct{}{}
|
||||||
continue
|
|
||||||
}
|
|
||||||
if reflect.DeepEqual(sblocks, otherSblocks) {
|
|
||||||
a.addresses = append(a.addresses, otherAddr)
|
|
||||||
delete(addrToServerBlocks, otherAddr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(a.addresses)
|
|
||||||
|
|
||||||
sbaddrs = append(sbaddrs, a)
|
for otherAddr, otherProtocolToServerBlocks := range addrToProtocolToServerBlocks {
|
||||||
|
for otherProt, otherServerBlocks := range otherProtocolToServerBlocks {
|
||||||
|
if addr == otherAddr && prot == otherProt || reflect.DeepEqual(serverBlocks, otherServerBlocks) {
|
||||||
|
listener, ok := listeners[otherAddr]
|
||||||
|
if !ok {
|
||||||
|
listener = map[string]struct{}{}
|
||||||
|
listeners[otherAddr] = listener
|
||||||
|
}
|
||||||
|
listener[otherProt] = struct{}{}
|
||||||
|
delete(otherProtocolToServerBlocks, otherProt)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort them by their first address (we know there will always be at least one)
|
addresses := make([]string, 0, len(listeners))
|
||||||
// to avoid problems with non-deterministic ordering (makes tests flaky)
|
for lnAddr := range listeners {
|
||||||
sort.Slice(sbaddrs, func(i, j int) bool {
|
addresses = append(addresses, lnAddr)
|
||||||
return sbaddrs[i].addresses[0] < sbaddrs[j].addresses[0]
|
}
|
||||||
|
sort.Strings(addresses)
|
||||||
|
|
||||||
|
addressesWithProtocols := make([]addressWithProtocols, 0, len(listeners))
|
||||||
|
|
||||||
|
for _, lnAddr := range addresses {
|
||||||
|
lnProts := listeners[lnAddr]
|
||||||
|
prots := make([]string, 0, len(lnProts))
|
||||||
|
for prot := range lnProts {
|
||||||
|
prots = append(prots, prot)
|
||||||
|
}
|
||||||
|
sort.Strings(prots)
|
||||||
|
|
||||||
|
addressesWithProtocols = append(addressesWithProtocols, addressWithProtocols{
|
||||||
|
address: lnAddr,
|
||||||
|
protocols: prots,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sbaddrs = append(sbaddrs, sbAddrAssociation{
|
||||||
|
addressesWithProtocols: addressesWithProtocols,
|
||||||
|
serverBlocks: serverBlocks,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return sbaddrs
|
return sbaddrs
|
||||||
}
|
}
|
||||||
|
|
||||||
// listenerAddrsForServerBlockKey essentially converts the Caddyfile
|
// listenersForServerBlockAddress essentially converts the Caddyfile site addresses to a map from
|
||||||
// site addresses to Caddy listener addresses for each server block.
|
// Caddy listener addresses and the protocols to serve them with to the parsed address for each server block.
|
||||||
func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string,
|
func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Address,
|
||||||
options map[string]any,
|
options map[string]any,
|
||||||
) ([]string, error) {
|
) (map[string]map[string]struct{}, error) {
|
||||||
addr, err := ParseAddress(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing key: %v", err)
|
|
||||||
}
|
|
||||||
addr = addr.Normalize()
|
|
||||||
|
|
||||||
switch addr.Scheme {
|
switch addr.Scheme {
|
||||||
case "wss":
|
case "wss":
|
||||||
return nil, fmt.Errorf("the scheme wss:// is only supported in browsers; use https:// instead")
|
return nil, fmt.Errorf("the scheme wss:// is only supported in browsers; use https:// instead")
|
||||||
@@ -230,55 +303,58 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
|
|||||||
|
|
||||||
// error if scheme and port combination violate convention
|
// error if scheme and port combination violate convention
|
||||||
if (addr.Scheme == "http" && lnPort == httpsPort) || (addr.Scheme == "https" && lnPort == httpPort) {
|
if (addr.Scheme == "http" && lnPort == httpsPort) || (addr.Scheme == "https" && lnPort == httpPort) {
|
||||||
return nil, fmt.Errorf("[%s] scheme and port violate convention", key)
|
return nil, fmt.Errorf("[%s] scheme and port violate convention", addr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// the bind directive specifies hosts (and potentially network), but is optional
|
// the bind directive specifies hosts (and potentially network), and the protocols to serve them with, but is optional
|
||||||
lnHosts := make([]string, 0, len(sblock.pile["bind"]))
|
lnCfgVals := make([]addressesWithProtocols, 0, len(sblock.pile["bind"]))
|
||||||
for _, cfgVal := range sblock.pile["bind"] {
|
for _, cfgVal := range sblock.pile["bind"] {
|
||||||
lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
|
if val, ok := cfgVal.Value.(addressesWithProtocols); ok {
|
||||||
|
lnCfgVals = append(lnCfgVals, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(lnCfgVals) == 0 {
|
||||||
|
if defaultBindValues, ok := options["default_bind"].([]ConfigValue); ok {
|
||||||
|
for _, defaultBindValue := range defaultBindValues {
|
||||||
|
lnCfgVals = append(lnCfgVals, defaultBindValue.Value.(addressesWithProtocols))
|
||||||
}
|
}
|
||||||
if len(lnHosts) == 0 {
|
|
||||||
if defaultBind, ok := options["default_bind"].([]string); ok {
|
|
||||||
lnHosts = defaultBind
|
|
||||||
} else {
|
} else {
|
||||||
lnHosts = []string{""}
|
lnCfgVals = []addressesWithProtocols{{
|
||||||
|
addresses: []string{""},
|
||||||
|
protocols: nil,
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// use a map to prevent duplication
|
// use a map to prevent duplication
|
||||||
listeners := make(map[string]struct{})
|
listeners := map[string]map[string]struct{}{}
|
||||||
for _, lnHost := range lnHosts {
|
for _, lnCfgVal := range lnCfgVals {
|
||||||
// normally we would simply append the port,
|
for _, lnAddr := range lnCfgVal.addresses {
|
||||||
// but if lnHost is IPv6, we need to ensure it
|
lnNetw, lnHost, _, err := caddy.SplitNetworkAddress(lnAddr)
|
||||||
// is enclosed in [ ]; net.JoinHostPort does
|
if err != nil {
|
||||||
// this for us, but lnHost might also have a
|
return nil, fmt.Errorf("splitting listener address: %v", err)
|
||||||
// network type in front (e.g. "tcp/") leading
|
|
||||||
// to "[tcp/::1]" which causes parsing failures
|
|
||||||
// later; what we need is "tcp/[::1]", so we have
|
|
||||||
// to split the network and host, then re-combine
|
|
||||||
network, host, ok := strings.Cut(lnHost, "/")
|
|
||||||
if !ok {
|
|
||||||
host = network
|
|
||||||
network = ""
|
|
||||||
}
|
}
|
||||||
host = strings.Trim(host, "[]") // IPv6
|
networkAddr, err := caddy.ParseNetworkAddress(caddy.JoinNetworkAddress(lnNetw, lnHost, lnPort))
|
||||||
networkAddr := caddy.JoinNetworkAddress(network, host, lnPort)
|
|
||||||
addr, err := caddy.ParseNetworkAddress(networkAddr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parsing network address: %v", err)
|
return nil, fmt.Errorf("parsing network address: %v", err)
|
||||||
}
|
}
|
||||||
listeners[addr.String()] = struct{}{}
|
if _, ok := listeners[addr.String()]; !ok {
|
||||||
|
listeners[networkAddr.String()] = map[string]struct{}{}
|
||||||
|
}
|
||||||
|
for _, protocol := range lnCfgVal.protocols {
|
||||||
|
listeners[networkAddr.String()][protocol] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// now turn map into list
|
return listeners, nil
|
||||||
listenersList := make([]string, 0, len(listeners))
|
}
|
||||||
for lnStr := range listeners {
|
|
||||||
listenersList = append(listenersList, lnStr)
|
|
||||||
}
|
|
||||||
sort.Strings(listenersList)
|
|
||||||
|
|
||||||
return listenersList, nil
|
// addressesWithProtocols associates a list of listen addresses
|
||||||
|
// with a list of protocols to serve them with
|
||||||
|
type addressesWithProtocols struct {
|
||||||
|
addresses []string
|
||||||
|
protocols []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address represents a site address. It contains
|
// Address represents a site address. It contains
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/mholt/acmez/v2/acme"
|
"github.com/mholt/acmez/v3/acme"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
@@ -56,15 +56,35 @@ func init() {
|
|||||||
|
|
||||||
// parseBind parses the bind directive. Syntax:
|
// parseBind parses the bind directive. Syntax:
|
||||||
//
|
//
|
||||||
// bind <addresses...>
|
// bind <addresses...> [{
|
||||||
|
// protocols [h1|h2|h2c|h3] [...]
|
||||||
|
// }]
|
||||||
func parseBind(h Helper) ([]ConfigValue, error) {
|
func parseBind(h Helper) ([]ConfigValue, error) {
|
||||||
h.Next() // consume directive name
|
h.Next() // consume directive name
|
||||||
return []ConfigValue{{Class: "bind", Value: h.RemainingArgs()}}, nil
|
var addresses, protocols []string
|
||||||
|
addresses = h.RemainingArgs()
|
||||||
|
|
||||||
|
for h.NextBlock(0) {
|
||||||
|
switch h.Val() {
|
||||||
|
case "protocols":
|
||||||
|
protocols = h.RemainingArgs()
|
||||||
|
if len(protocols) == 0 {
|
||||||
|
return nil, h.Errf("protocols requires one or more arguments")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, h.Errf("unknown subdirective: %s", h.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []ConfigValue{{Class: "bind", Value: addressesWithProtocols{
|
||||||
|
addresses: addresses,
|
||||||
|
protocols: protocols,
|
||||||
|
}}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseTLS parses the tls directive. Syntax:
|
// parseTLS parses the tls directive. Syntax:
|
||||||
//
|
//
|
||||||
// tls [<email>|internal]|[<cert_file> <key_file>] {
|
// tls [<email>|internal|force_automate]|[<cert_file> <key_file>] {
|
||||||
// protocols <min> [<max>]
|
// protocols <min> [<max>]
|
||||||
// ciphers <cipher_suites...>
|
// ciphers <cipher_suites...>
|
||||||
// curves <curves...>
|
// curves <curves...>
|
||||||
@@ -79,7 +99,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
|
|||||||
// ca <acme_ca_endpoint>
|
// ca <acme_ca_endpoint>
|
||||||
// ca_root <pem_file>
|
// ca_root <pem_file>
|
||||||
// key_type [ed25519|p256|p384|rsa2048|rsa4096]
|
// key_type [ed25519|p256|p384|rsa2048|rsa4096]
|
||||||
// dns <provider_name> [...]
|
// dns [<provider_name> [...]] (required, though, if DNS is not configured as global option)
|
||||||
// propagation_delay <duration>
|
// propagation_delay <duration>
|
||||||
// propagation_timeout <duration>
|
// propagation_timeout <duration>
|
||||||
// resolvers <dns_servers...>
|
// resolvers <dns_servers...>
|
||||||
@@ -87,6 +107,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
|
|||||||
// dns_challenge_override_domain <domain>
|
// dns_challenge_override_domain <domain>
|
||||||
// on_demand
|
// on_demand
|
||||||
// reuse_private_keys
|
// reuse_private_keys
|
||||||
|
// force_automate
|
||||||
// eab <key_id> <mac_key>
|
// eab <key_id> <mac_key>
|
||||||
// issuer <module_name> [...]
|
// issuer <module_name> [...]
|
||||||
// get_certificate <module_name> [...]
|
// get_certificate <module_name> [...]
|
||||||
@@ -106,6 +127,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||||||
var certManagers []certmagic.Manager
|
var certManagers []certmagic.Manager
|
||||||
var onDemand bool
|
var onDemand bool
|
||||||
var reusePrivateKeys bool
|
var reusePrivateKeys bool
|
||||||
|
var forceAutomate bool
|
||||||
|
|
||||||
firstLine := h.RemainingArgs()
|
firstLine := h.RemainingArgs()
|
||||||
switch len(firstLine) {
|
switch len(firstLine) {
|
||||||
@@ -113,8 +135,10 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||||||
case 1:
|
case 1:
|
||||||
if firstLine[0] == "internal" {
|
if firstLine[0] == "internal" {
|
||||||
internalIssuer = new(caddytls.InternalIssuer)
|
internalIssuer = new(caddytls.InternalIssuer)
|
||||||
|
} else if firstLine[0] == "force_automate" {
|
||||||
|
forceAutomate = true
|
||||||
} else if !strings.Contains(firstLine[0], "@") {
|
} else if !strings.Contains(firstLine[0], "@") {
|
||||||
return nil, h.Err("single argument must either be 'internal' or an email address")
|
return nil, h.Err("single argument must either be 'internal', 'force_automate', or an email address")
|
||||||
} else {
|
} else {
|
||||||
acmeIssuer = &caddytls.ACMEIssuer{
|
acmeIssuer = &caddytls.ACMEIssuer{
|
||||||
Email: firstLine[0],
|
Email: firstLine[0],
|
||||||
@@ -288,10 +312,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||||||
certManagers = append(certManagers, certManager)
|
certManagers = append(certManagers, certManager)
|
||||||
|
|
||||||
case "dns":
|
case "dns":
|
||||||
if !h.NextArg() {
|
|
||||||
return nil, h.ArgErr()
|
|
||||||
}
|
|
||||||
provName := h.Val()
|
|
||||||
if acmeIssuer == nil {
|
if acmeIssuer == nil {
|
||||||
acmeIssuer = new(caddytls.ACMEIssuer)
|
acmeIssuer = new(caddytls.ACMEIssuer)
|
||||||
}
|
}
|
||||||
@@ -301,12 +321,19 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||||||
if acmeIssuer.Challenges.DNS == nil {
|
if acmeIssuer.Challenges.DNS == nil {
|
||||||
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
||||||
}
|
}
|
||||||
|
// DNS provider configuration optional, since it may be configured globally via the TLS app with global options
|
||||||
|
if h.NextArg() {
|
||||||
|
provName := h.Val()
|
||||||
modID := "dns.providers." + provName
|
modID := "dns.providers." + provName
|
||||||
unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID)
|
unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
acmeIssuer.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(unm, "name", provName, h.warnings)
|
acmeIssuer.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(unm, "name", provName, h.warnings)
|
||||||
|
} else if h.Option("dns") == nil {
|
||||||
|
// if DNS is omitted locally, it needs to be configured globally
|
||||||
|
return nil, h.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
case "resolvers":
|
case "resolvers":
|
||||||
args := h.RemainingArgs()
|
args := h.RemainingArgs()
|
||||||
@@ -549,6 +576,15 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if enabled, the names in the site addresses will be
|
||||||
|
// added to the automation policies
|
||||||
|
if forceAutomate {
|
||||||
|
configVals = append(configVals, ConfigValue{
|
||||||
|
Class: "tls.force_automate",
|
||||||
|
Value: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// custom certificate selection
|
// custom certificate selection
|
||||||
if len(certSelector.AnyTag) > 0 {
|
if len(certSelector.AnyTag) > 0 {
|
||||||
cp.CertSelection = &certSelector
|
cp.CertSelection = &certSelector
|
||||||
@@ -961,6 +997,50 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
|||||||
}
|
}
|
||||||
cl.WriterRaw = caddyconfig.JSONModuleObject(wo, "output", moduleName, h.warnings)
|
cl.WriterRaw = caddyconfig.JSONModuleObject(wo, "output", moduleName, h.warnings)
|
||||||
|
|
||||||
|
case "sampling":
|
||||||
|
d := h.Dispenser.NewFromNextSegment()
|
||||||
|
for d.NextArg() {
|
||||||
|
// consume any tokens on the same line, if any.
|
||||||
|
}
|
||||||
|
|
||||||
|
sampling := &caddy.LogSampling{}
|
||||||
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||||
|
subdir := d.Val()
|
||||||
|
switch subdir {
|
||||||
|
case "interval":
|
||||||
|
if !d.NextArg() {
|
||||||
|
return nil, d.ArgErr()
|
||||||
|
}
|
||||||
|
interval, err := time.ParseDuration(d.Val() + "ns")
|
||||||
|
if err != nil {
|
||||||
|
return nil, d.Errf("failed to parse interval: %v", err)
|
||||||
|
}
|
||||||
|
sampling.Interval = interval
|
||||||
|
case "first":
|
||||||
|
if !d.NextArg() {
|
||||||
|
return nil, d.ArgErr()
|
||||||
|
}
|
||||||
|
first, err := strconv.Atoi(d.Val())
|
||||||
|
if err != nil {
|
||||||
|
return nil, d.Errf("failed to parse first: %v", err)
|
||||||
|
}
|
||||||
|
sampling.First = first
|
||||||
|
case "thereafter":
|
||||||
|
if !d.NextArg() {
|
||||||
|
return nil, d.ArgErr()
|
||||||
|
}
|
||||||
|
thereafter, err := strconv.Atoi(d.Val())
|
||||||
|
if err != nil {
|
||||||
|
return nil, d.Errf("failed to parse thereafter: %v", err)
|
||||||
|
}
|
||||||
|
sampling.Thereafter = thereafter
|
||||||
|
default:
|
||||||
|
return nil, d.Errf("unrecognized subdirective: %s", subdir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cl.Sampling = sampling
|
||||||
|
|
||||||
case "core":
|
case "core":
|
||||||
if !h.NextArg() {
|
if !h.NextArg() {
|
||||||
return nil, h.ArgErr()
|
return nil, h.ArgErr()
|
||||||
|
|||||||
@@ -62,6 +62,20 @@ func TestLogDirectiveSyntax(t *testing.T) {
|
|||||||
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.name-override"]},"name-override":{"writer":{"filename":"foo.log","output":"file"},"core":{"module":"mock"},"include":["http.log.access.name-override"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"name-override"}}}}}}`,
|
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.name-override"]},"name-override":{"writer":{"filename":"foo.log","output":"file"},"core":{"module":"mock"},"include":["http.log.access.name-override"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"name-override"}}}}}}`,
|
||||||
expectError: false,
|
expectError: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: `:8080 {
|
||||||
|
log {
|
||||||
|
sampling {
|
||||||
|
interval 2
|
||||||
|
first 3
|
||||||
|
thereafter 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.log0"]},"log0":{"sampling":{"interval":2,"first":3,"thereafter":4},"include":["http.log.access.log0"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"log0"}}}}}}`,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
|
|
||||||
adapter := caddyfile.Adapter{
|
adapter := caddyfile.Adapter{
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package httpcaddyfile
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net"
|
"net"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -100,17 +101,6 @@ var defaultDirectiveOrder = []string{
|
|||||||
// plugins or by the user via the "order" global option.
|
// plugins or by the user via the "order" global option.
|
||||||
var directiveOrder = defaultDirectiveOrder
|
var directiveOrder = defaultDirectiveOrder
|
||||||
|
|
||||||
// directiveIsOrdered returns true if dir is
|
|
||||||
// a known, ordered (sorted) directive.
|
|
||||||
func directiveIsOrdered(dir string) bool {
|
|
||||||
for _, d := range directiveOrder {
|
|
||||||
if d == dir {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterDirective registers a unique directive dir with an
|
// RegisterDirective registers a unique directive dir with an
|
||||||
// associated unmarshaling (setup) function. When directive dir
|
// associated unmarshaling (setup) function. When directive dir
|
||||||
// is encountered in a Caddyfile, setupFunc will be called to
|
// is encountered in a Caddyfile, setupFunc will be called to
|
||||||
@@ -161,7 +151,7 @@ func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) {
|
|||||||
// EXPERIMENTAL: This API may change or be removed.
|
// EXPERIMENTAL: This API may change or be removed.
|
||||||
func RegisterDirectiveOrder(dir string, position Positional, standardDir string) {
|
func RegisterDirectiveOrder(dir string, position Positional, standardDir string) {
|
||||||
// check if directive was already ordered
|
// check if directive was already ordered
|
||||||
if directiveIsOrdered(dir) {
|
if slices.Contains(directiveOrder, dir) {
|
||||||
panic("directive '" + dir + "' already ordered")
|
panic("directive '" + dir + "' already ordered")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,12 +162,7 @@ func RegisterDirectiveOrder(dir string, position Positional, standardDir string)
|
|||||||
// check if directive exists in standard distribution, since
|
// check if directive exists in standard distribution, since
|
||||||
// we can't allow plugins to depend on one another; we can't
|
// we can't allow plugins to depend on one another; we can't
|
||||||
// guarantee the order that plugins are loaded in.
|
// guarantee the order that plugins are loaded in.
|
||||||
foundStandardDir := false
|
foundStandardDir := slices.Contains(defaultDirectiveOrder, standardDir)
|
||||||
for _, d := range defaultDirectiveOrder {
|
|
||||||
if d == standardDir {
|
|
||||||
foundStandardDir = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !foundStandardDir {
|
if !foundStandardDir {
|
||||||
panic("the 3rd argument '" + standardDir + "' must be a directive that exists in the standard distribution of Caddy")
|
panic("the 3rd argument '" + standardDir + "' must be a directive that exists in the standard distribution of Caddy")
|
||||||
}
|
}
|
||||||
@@ -533,7 +518,7 @@ func sortRoutes(routes []ConfigValue) {
|
|||||||
type serverBlock struct {
|
type serverBlock struct {
|
||||||
block caddyfile.ServerBlock
|
block caddyfile.ServerBlock
|
||||||
pile map[string][]ConfigValue // config values obtained from directives
|
pile map[string][]ConfigValue // config values obtained from directives
|
||||||
keys []Address
|
parsedKeys []Address
|
||||||
}
|
}
|
||||||
|
|
||||||
// hostsFromKeys returns a list of all the non-empty hostnames found in
|
// hostsFromKeys returns a list of all the non-empty hostnames found in
|
||||||
@@ -550,7 +535,7 @@ type serverBlock struct {
|
|||||||
func (sb serverBlock) hostsFromKeys(loggerMode bool) []string {
|
func (sb serverBlock) hostsFromKeys(loggerMode bool) []string {
|
||||||
// ensure each entry in our list is unique
|
// ensure each entry in our list is unique
|
||||||
hostMap := make(map[string]struct{})
|
hostMap := make(map[string]struct{})
|
||||||
for _, addr := range sb.keys {
|
for _, addr := range sb.parsedKeys {
|
||||||
if addr.Host == "" {
|
if addr.Host == "" {
|
||||||
if !loggerMode {
|
if !loggerMode {
|
||||||
// server block contains a key like ":443", i.e. the host portion
|
// server block contains a key like ":443", i.e. the host portion
|
||||||
@@ -582,7 +567,7 @@ func (sb serverBlock) hostsFromKeys(loggerMode bool) []string {
|
|||||||
func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string {
|
func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string {
|
||||||
// ensure each entry in our list is unique
|
// ensure each entry in our list is unique
|
||||||
hostMap := make(map[string]struct{})
|
hostMap := make(map[string]struct{})
|
||||||
for _, addr := range sb.keys {
|
for _, addr := range sb.parsedKeys {
|
||||||
if addr.Host == "" {
|
if addr.Host == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -603,23 +588,17 @@ func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string {
|
|||||||
// hasHostCatchAllKey returns true if sb has a key that
|
// hasHostCatchAllKey returns true if sb has a key that
|
||||||
// omits a host portion, i.e. it "catches all" hosts.
|
// omits a host portion, i.e. it "catches all" hosts.
|
||||||
func (sb serverBlock) hasHostCatchAllKey() bool {
|
func (sb serverBlock) hasHostCatchAllKey() bool {
|
||||||
for _, addr := range sb.keys {
|
return slices.ContainsFunc(sb.parsedKeys, func(addr Address) bool {
|
||||||
if addr.Host == "" {
|
return addr.Host == ""
|
||||||
return true
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// isAllHTTP returns true if all sb keys explicitly specify
|
// isAllHTTP returns true if all sb keys explicitly specify
|
||||||
// the http:// scheme
|
// the http:// scheme
|
||||||
func (sb serverBlock) isAllHTTP() bool {
|
func (sb serverBlock) isAllHTTP() bool {
|
||||||
for _, addr := range sb.keys {
|
return !slices.ContainsFunc(sb.parsedKeys, func(addr Address) bool {
|
||||||
if addr.Scheme != "http" {
|
return addr.Scheme != "http"
|
||||||
return false
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Positional are the supported modes for ordering directives.
|
// Positional are the supported modes for ordering directives.
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ func TestHostsFromKeys(t *testing.T) {
|
|||||||
[]string{"example.com:2015"},
|
[]string{"example.com:2015"},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
sb := serverBlock{keys: tc.keys}
|
sb := serverBlock{parsedKeys: tc.keys}
|
||||||
|
|
||||||
// test in normal mode
|
// test in normal mode
|
||||||
actual := sb.hostsFromKeys(false)
|
actual := sb.hostsFromKeys(false)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
package httpcaddyfile
|
package httpcaddyfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@@ -171,7 +172,7 @@ func (st ServerType) Setup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// map
|
// map
|
||||||
sbmap, err := st.mapAddressToServerBlocks(originalServerBlocks, options)
|
sbmap, err := st.mapAddressToProtocolToServerBlocks(originalServerBlocks, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, warnings, err
|
return nil, warnings, err
|
||||||
}
|
}
|
||||||
@@ -186,12 +187,25 @@ func (st ServerType) Setup(
|
|||||||
return nil, warnings, err
|
return nil, warnings, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hoist the metrics config from per-server to global
|
||||||
|
metrics, _ := options["metrics"].(*caddyhttp.Metrics)
|
||||||
|
for _, s := range servers {
|
||||||
|
if s.Metrics != nil {
|
||||||
|
metrics = cmp.Or(metrics, &caddyhttp.Metrics{})
|
||||||
|
metrics = &caddyhttp.Metrics{
|
||||||
|
PerHost: metrics.PerHost || s.Metrics.PerHost,
|
||||||
|
}
|
||||||
|
s.Metrics = nil // we don't need it anymore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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),
|
ShutdownDelay: tryDuration(options["shutdown_delay"], &warnings),
|
||||||
|
Metrics: metrics,
|
||||||
Servers: servers,
|
Servers: servers,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +350,7 @@ func (st ServerType) Setup(
|
|||||||
|
|
||||||
// avoid duplicates by sorting + compacting
|
// avoid duplicates by sorting + compacting
|
||||||
sort.Strings(defaultLog.Exclude)
|
sort.Strings(defaultLog.Exclude)
|
||||||
defaultLog.Exclude = slices.Compact[[]string, string](defaultLog.Exclude)
|
defaultLog.Exclude = slices.Compact(defaultLog.Exclude)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// we may have not actually added anything, so remove if empty
|
// we may have not actually added anything, so remove if empty
|
||||||
@@ -402,6 +416,20 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options
|
|||||||
options[opt] = append(existingOpts, logOpts...)
|
options[opt] = append(existingOpts, logOpts...)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Also fold multiple "default_bind" options together into an
|
||||||
|
// array so that server blocks can have multiple binds by default.
|
||||||
|
if opt == "default_bind" {
|
||||||
|
existingOpts, ok := options[opt].([]ConfigValue)
|
||||||
|
if !ok {
|
||||||
|
existingOpts = []ConfigValue{}
|
||||||
|
}
|
||||||
|
defaultBindOpts, ok := val.([]ConfigValue)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected type from 'default_bind' global options: %T", val)
|
||||||
|
}
|
||||||
|
options[opt] = append(existingOpts, defaultBindOpts...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
options[opt] = val
|
options[opt] = val
|
||||||
}
|
}
|
||||||
@@ -520,8 +548,8 @@ func (st *ServerType) serversFromPairings(
|
|||||||
if hsp, ok := options["https_port"].(int); ok {
|
if hsp, ok := options["https_port"].(int); ok {
|
||||||
httpsPort = strconv.Itoa(hsp)
|
httpsPort = strconv.Itoa(hsp)
|
||||||
}
|
}
|
||||||
autoHTTPS := "on"
|
autoHTTPS := []string{}
|
||||||
if ah, ok := options["auto_https"].(string); ok {
|
if ah, ok := options["auto_https"].([]string); ok {
|
||||||
autoHTTPS = ah
|
autoHTTPS = ah
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,29 +564,81 @@ func (st *ServerType) serversFromPairings(
|
|||||||
if k == j {
|
if k == j {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if sliceContains(sblock2.block.GetKeysText(), key) {
|
if slices.Contains(sblock2.block.GetKeysText(), key) {
|
||||||
return nil, fmt.Errorf("ambiguous site definition: %s", key)
|
return nil, fmt.Errorf("ambiguous site definition: %s", key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
addresses []string
|
||||||
|
protocols [][]string
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, addressWithProtocols := range p.addressesWithProtocols {
|
||||||
|
addresses = append(addresses, addressWithProtocols.address)
|
||||||
|
protocols = append(protocols, addressWithProtocols.protocols)
|
||||||
|
}
|
||||||
|
|
||||||
srv := &caddyhttp.Server{
|
srv := &caddyhttp.Server{
|
||||||
Listen: p.addresses,
|
Listen: addresses,
|
||||||
|
ListenProtocols: protocols,
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove srv.ListenProtocols[j] if it only contains the default protocols
|
||||||
|
for j, lnProtocols := range srv.ListenProtocols {
|
||||||
|
srv.ListenProtocols[j] = nil
|
||||||
|
for _, lnProtocol := range lnProtocols {
|
||||||
|
if lnProtocol != "" {
|
||||||
|
srv.ListenProtocols[j] = lnProtocols
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove srv.ListenProtocols if it only contains the default protocols for all listen addresses
|
||||||
|
listenProtocols := srv.ListenProtocols
|
||||||
|
srv.ListenProtocols = nil
|
||||||
|
for _, lnProtocols := range listenProtocols {
|
||||||
|
if lnProtocols != nil {
|
||||||
|
srv.ListenProtocols = listenProtocols
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle the auto_https global option
|
// handle the auto_https global option
|
||||||
if autoHTTPS != "on" {
|
for _, val := range autoHTTPS {
|
||||||
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
switch val {
|
||||||
switch autoHTTPS {
|
|
||||||
case "off":
|
case "off":
|
||||||
|
if srv.AutoHTTPS == nil {
|
||||||
|
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
||||||
|
}
|
||||||
srv.AutoHTTPS.Disabled = true
|
srv.AutoHTTPS.Disabled = true
|
||||||
|
|
||||||
case "disable_redirects":
|
case "disable_redirects":
|
||||||
|
if srv.AutoHTTPS == nil {
|
||||||
|
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
||||||
|
}
|
||||||
srv.AutoHTTPS.DisableRedir = true
|
srv.AutoHTTPS.DisableRedir = true
|
||||||
|
|
||||||
case "disable_certs":
|
case "disable_certs":
|
||||||
|
if srv.AutoHTTPS == nil {
|
||||||
|
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
||||||
|
}
|
||||||
srv.AutoHTTPS.DisableCerts = true
|
srv.AutoHTTPS.DisableCerts = true
|
||||||
|
|
||||||
case "ignore_loaded_certs":
|
case "ignore_loaded_certs":
|
||||||
|
if srv.AutoHTTPS == nil {
|
||||||
|
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
||||||
|
}
|
||||||
srv.AutoHTTPS.IgnoreLoadedCerts = true
|
srv.AutoHTTPS.IgnoreLoadedCerts = true
|
||||||
|
|
||||||
|
case "prefer_wildcard":
|
||||||
|
if srv.AutoHTTPS == nil {
|
||||||
|
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
||||||
|
}
|
||||||
|
srv.AutoHTTPS.PreferWildcard = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,7 +646,7 @@ func (st *ServerType) serversFromPairings(
|
|||||||
// See ParseAddress() where parsing should later reject paths
|
// See ParseAddress() where parsing should later reject paths
|
||||||
// See https://github.com/caddyserver/caddy/pull/4728 for a full explanation
|
// See https://github.com/caddyserver/caddy/pull/4728 for a full explanation
|
||||||
for _, sblock := range p.serverBlocks {
|
for _, sblock := range p.serverBlocks {
|
||||||
for _, addr := range sblock.keys {
|
for _, addr := range sblock.parsedKeys {
|
||||||
if addr.Path != "" {
|
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()))
|
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()))
|
||||||
}
|
}
|
||||||
@@ -584,7 +664,7 @@ func (st *ServerType) serversFromPairings(
|
|||||||
var iLongestPath, jLongestPath string
|
var iLongestPath, jLongestPath string
|
||||||
var iLongestHost, jLongestHost string
|
var iLongestHost, jLongestHost string
|
||||||
var iWildcardHost, jWildcardHost bool
|
var iWildcardHost, jWildcardHost bool
|
||||||
for _, addr := range p.serverBlocks[i].keys {
|
for _, addr := range p.serverBlocks[i].parsedKeys {
|
||||||
if strings.Contains(addr.Host, "*") || addr.Host == "" {
|
if strings.Contains(addr.Host, "*") || addr.Host == "" {
|
||||||
iWildcardHost = true
|
iWildcardHost = true
|
||||||
}
|
}
|
||||||
@@ -595,7 +675,7 @@ func (st *ServerType) serversFromPairings(
|
|||||||
iLongestPath = addr.Path
|
iLongestPath = addr.Path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, addr := range p.serverBlocks[j].keys {
|
for _, addr := range p.serverBlocks[j].parsedKeys {
|
||||||
if strings.Contains(addr.Host, "*") || addr.Host == "" {
|
if strings.Contains(addr.Host, "*") || addr.Host == "" {
|
||||||
jWildcardHost = true
|
jWildcardHost = true
|
||||||
}
|
}
|
||||||
@@ -626,8 +706,18 @@ func (st *ServerType) serversFromPairings(
|
|||||||
return specificity(iLongestHost) > specificity(jLongestHost)
|
return specificity(iLongestHost) > specificity(jLongestHost)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// collect all hosts that have a wildcard in them
|
||||||
|
wildcardHosts := []string{}
|
||||||
|
for _, sblock := range p.serverBlocks {
|
||||||
|
for _, addr := range sblock.parsedKeys {
|
||||||
|
if strings.HasPrefix(addr.Host, "*.") {
|
||||||
|
wildcardHosts = append(wildcardHosts, addr.Host[2:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool
|
var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool
|
||||||
autoHTTPSWillAddConnPolicy := autoHTTPS != "off"
|
autoHTTPSWillAddConnPolicy := srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled
|
||||||
|
|
||||||
// if needed, the ServerLogConfig is initialized beforehand so
|
// if needed, the ServerLogConfig is initialized beforehand so
|
||||||
// that all server blocks can populate it with data, even when not
|
// that all server blocks can populate it with data, even when not
|
||||||
@@ -673,6 +763,14 @@ func (st *ServerType) serversFromPairings(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// collect hosts that are forced to be automated
|
||||||
|
forceAutomatedNames := make(map[string]struct{})
|
||||||
|
if _, ok := sblock.pile["tls.force_automate"]; ok {
|
||||||
|
for _, host := range hosts {
|
||||||
|
forceAutomatedNames[host] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// tls: connection policies
|
// tls: connection policies
|
||||||
if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
|
if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
|
||||||
// tls connection policies
|
// tls connection policies
|
||||||
@@ -704,14 +802,14 @@ func (st *ServerType) serversFromPairings(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// only append this policy if it actually changes something
|
// only append this policy if it actually changes something
|
||||||
if !cp.SettingsEmpty() {
|
if !cp.SettingsEmpty() || mapContains(forceAutomatedNames, hosts) {
|
||||||
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
|
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
|
||||||
hasCatchAllTLSConnPolicy = len(hosts) == 0
|
hasCatchAllTLSConnPolicy = len(hosts) == 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, addr := range sblock.keys {
|
for _, addr := range sblock.parsedKeys {
|
||||||
// if server only uses HTTP port, auto-HTTPS will not apply
|
// if server only uses HTTP 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://"
|
||||||
@@ -720,7 +818,7 @@ func (st *ServerType) serversFromPairings(
|
|||||||
if srv.AutoHTTPS == nil {
|
if srv.AutoHTTPS == nil {
|
||||||
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
||||||
}
|
}
|
||||||
if !sliceContains(srv.AutoHTTPS.Skip, addr.Host) {
|
if !slices.Contains(srv.AutoHTTPS.Skip, addr.Host) {
|
||||||
srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, addr.Host)
|
srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, addr.Host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -734,7 +832,7 @@ func (st *ServerType) serversFromPairings(
|
|||||||
// https://caddy.community/t/making-sense-of-auto-https-and-why-disabling-it-still-serves-https-instead-of-http/9761
|
// https://caddy.community/t/making-sense-of-auto-https-and-why-disabling-it-still-serves-https-instead-of-http/9761
|
||||||
createdTLSConnPolicies, ok := sblock.pile["tls.connection_policy"]
|
createdTLSConnPolicies, ok := sblock.pile["tls.connection_policy"]
|
||||||
hasTLSEnabled := (ok && len(createdTLSConnPolicies) > 0) ||
|
hasTLSEnabled := (ok && len(createdTLSConnPolicies) > 0) ||
|
||||||
(addr.Host != "" && srv.AutoHTTPS != nil && !sliceContains(srv.AutoHTTPS.Skip, addr.Host))
|
(addr.Host != "" && srv.AutoHTTPS != nil && !slices.Contains(srv.AutoHTTPS.Skip, addr.Host))
|
||||||
|
|
||||||
// we'll need to remember if the address qualifies for auto-HTTPS, so we
|
// we'll need to remember if the address qualifies for auto-HTTPS, so we
|
||||||
// can add a TLS conn policy if necessary
|
// can add a TLS conn policy if necessary
|
||||||
@@ -742,6 +840,19 @@ func (st *ServerType) serversFromPairings(
|
|||||||
(addr.Scheme != "http" && addr.Port != httpPort && hasTLSEnabled) {
|
(addr.Scheme != "http" && addr.Port != httpPort && hasTLSEnabled) {
|
||||||
addressQualifiesForTLS = true
|
addressQualifiesForTLS = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If prefer wildcard is enabled, then we add hosts that are
|
||||||
|
// already covered by the wildcard to the skip list
|
||||||
|
if addressQualifiesForTLS && srv.AutoHTTPS != nil && srv.AutoHTTPS.PreferWildcard {
|
||||||
|
baseDomain := addr.Host
|
||||||
|
if idx := strings.Index(baseDomain, "."); idx != -1 {
|
||||||
|
baseDomain = baseDomain[idx+1:]
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(addr.Host, "*.") && slices.Contains(wildcardHosts, baseDomain) {
|
||||||
|
srv.AutoHTTPS.SkipCerts = append(srv.AutoHTTPS.SkipCerts, addr.Host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// predict whether auto-HTTPS will add the conn policy for us; if so, we
|
// predict whether auto-HTTPS will add the conn policy for us; if so, we
|
||||||
// may not need to add one for this server
|
// may not need to add one for this server
|
||||||
autoHTTPSWillAddConnPolicy = autoHTTPSWillAddConnPolicy &&
|
autoHTTPSWillAddConnPolicy = autoHTTPSWillAddConnPolicy &&
|
||||||
@@ -873,7 +984,10 @@ func (st *ServerType) serversFromPairings(
|
|||||||
if addressQualifiesForTLS &&
|
if addressQualifiesForTLS &&
|
||||||
!hasCatchAllTLSConnPolicy &&
|
!hasCatchAllTLSConnPolicy &&
|
||||||
(len(srv.TLSConnPolicies) > 0 || !autoHTTPSWillAddConnPolicy || defaultSNI != "" || fallbackSNI != "") {
|
(len(srv.TLSConnPolicies) > 0 || !autoHTTPSWillAddConnPolicy || defaultSNI != "" || fallbackSNI != "") {
|
||||||
srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{DefaultSNI: defaultSNI, FallbackSNI: fallbackSNI})
|
srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{
|
||||||
|
DefaultSNI: defaultSNI,
|
||||||
|
FallbackSNI: fallbackSNI,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// tidy things up a bit
|
// tidy things up a bit
|
||||||
@@ -886,8 +1000,7 @@ func (st *ServerType) serversFromPairings(
|
|||||||
servers[fmt.Sprintf("srv%d", i)] = srv
|
servers[fmt.Sprintf("srv%d", i)] = srv
|
||||||
}
|
}
|
||||||
|
|
||||||
err := applyServerOptions(servers, options, warnings)
|
if err := applyServerOptions(servers, options, warnings); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("applying global server options: %v", err)
|
return nil, fmt.Errorf("applying global server options: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -932,7 +1045,7 @@ func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []serverBlock,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, sblock := range serverBlocks {
|
for _, sblock := range serverBlocks {
|
||||||
for _, addr := range sblock.keys {
|
for _, addr := range sblock.parsedKeys {
|
||||||
if addr.Scheme == "http" || addr.Port == httpPort {
|
if addr.Scheme == "http" || addr.Port == httpPort {
|
||||||
if err := checkAndSetHTTP(addr); err != nil {
|
if err := checkAndSetHTTP(addr); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1008,6 +1121,12 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti
|
|||||||
return nil, fmt.Errorf("two policies with same match criteria have conflicting default SNI: %s vs. %s",
|
return nil, fmt.Errorf("two policies with same match criteria have conflicting default SNI: %s vs. %s",
|
||||||
cps[i].DefaultSNI, cps[j].DefaultSNI)
|
cps[i].DefaultSNI, cps[j].DefaultSNI)
|
||||||
}
|
}
|
||||||
|
if cps[i].FallbackSNI != "" &&
|
||||||
|
cps[j].FallbackSNI != "" &&
|
||||||
|
cps[i].FallbackSNI != cps[j].FallbackSNI {
|
||||||
|
return nil, fmt.Errorf("two policies with same match criteria have conflicting fallback SNI: %s vs. %s",
|
||||||
|
cps[i].FallbackSNI, cps[j].FallbackSNI)
|
||||||
|
}
|
||||||
if cps[i].ProtocolMin != "" &&
|
if cps[i].ProtocolMin != "" &&
|
||||||
cps[j].ProtocolMin != "" &&
|
cps[j].ProtocolMin != "" &&
|
||||||
cps[i].ProtocolMin != cps[j].ProtocolMin {
|
cps[i].ProtocolMin != cps[j].ProtocolMin {
|
||||||
@@ -1048,6 +1167,9 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti
|
|||||||
if cps[i].DefaultSNI == "" && cps[j].DefaultSNI != "" {
|
if cps[i].DefaultSNI == "" && cps[j].DefaultSNI != "" {
|
||||||
cps[i].DefaultSNI = cps[j].DefaultSNI
|
cps[i].DefaultSNI = cps[j].DefaultSNI
|
||||||
}
|
}
|
||||||
|
if cps[i].FallbackSNI == "" && cps[j].FallbackSNI != "" {
|
||||||
|
cps[i].FallbackSNI = cps[j].FallbackSNI
|
||||||
|
}
|
||||||
if cps[i].ProtocolMin == "" && cps[j].ProtocolMin != "" {
|
if cps[i].ProtocolMin == "" && cps[j].ProtocolMin != "" {
|
||||||
cps[i].ProtocolMin = cps[j].ProtocolMin
|
cps[i].ProtocolMin = cps[j].ProtocolMin
|
||||||
}
|
}
|
||||||
@@ -1061,7 +1183,7 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti
|
|||||||
} else if cps[i].CertSelection != nil && cps[j].CertSelection != nil {
|
} else if cps[i].CertSelection != nil && cps[j].CertSelection != nil {
|
||||||
// if both have one, then combine AnyTag
|
// if both have one, then combine AnyTag
|
||||||
for _, tag := range cps[j].CertSelection.AnyTag {
|
for _, tag := range cps[j].CertSelection.AnyTag {
|
||||||
if !sliceContains(cps[i].CertSelection.AnyTag, tag) {
|
if !slices.Contains(cps[i].CertSelection.AnyTag, tag) {
|
||||||
cps[i].CertSelection.AnyTag = append(cps[i].CertSelection.AnyTag, tag)
|
cps[i].CertSelection.AnyTag = append(cps[i].CertSelection.AnyTag, tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1144,7 +1266,7 @@ func appendSubrouteToRouteList(routeList caddyhttp.RouteList,
|
|||||||
func buildSubroute(routes []ConfigValue, groupCounter counter, needsSorting bool) (*caddyhttp.Subroute, error) {
|
func buildSubroute(routes []ConfigValue, groupCounter counter, needsSorting bool) (*caddyhttp.Subroute, error) {
|
||||||
if needsSorting {
|
if needsSorting {
|
||||||
for _, val := range routes {
|
for _, val := range routes {
|
||||||
if !directiveIsOrdered(val.directive) {
|
if !slices.Contains(directiveOrder, val.directive) {
|
||||||
return nil, fmt.Errorf("directive '%s' is not an ordered HTTP handler, so it cannot be used here - try placing within a route block or using the order global option", val.directive)
|
return nil, fmt.Errorf("directive '%s' is not an ordered HTTP handler, so it cannot be used here - try placing within a route block or using the order global option", val.directive)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1322,7 +1444,7 @@ func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.Mod
|
|||||||
var matcherPairs []*hostPathPair
|
var matcherPairs []*hostPathPair
|
||||||
|
|
||||||
var catchAllHosts bool
|
var catchAllHosts bool
|
||||||
for _, addr := range sblock.keys {
|
for _, addr := range sblock.parsedKeys {
|
||||||
// choose a matcher pair that should be shared by this
|
// choose a matcher pair that should be shared by this
|
||||||
// server block; if none exists yet, create one
|
// server block; if none exists yet, create one
|
||||||
var chosenMatcherPair *hostPathPair
|
var chosenMatcherPair *hostPathPair
|
||||||
@@ -1354,25 +1476,16 @@ func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.Mod
|
|||||||
|
|
||||||
// add this server block's keys to the matcher
|
// add this server block's keys to the matcher
|
||||||
// pair if it doesn't already exist
|
// pair if it doesn't already exist
|
||||||
if addr.Host != "" {
|
if addr.Host != "" && !slices.Contains(chosenMatcherPair.hostm, addr.Host) {
|
||||||
var found bool
|
|
||||||
for _, h := range chosenMatcherPair.hostm {
|
|
||||||
if h == addr.Host {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
chosenMatcherPair.hostm = append(chosenMatcherPair.hostm, addr.Host)
|
chosenMatcherPair.hostm = append(chosenMatcherPair.hostm, addr.Host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// iterate each pairing of host and path matchers and
|
// iterate each pairing of host and path matchers and
|
||||||
// put them into a map for JSON encoding
|
// put them into a map for JSON encoding
|
||||||
var matcherSets []map[string]caddyhttp.RequestMatcher
|
var matcherSets []map[string]caddyhttp.RequestMatcherWithError
|
||||||
for _, mp := range matcherPairs {
|
for _, mp := range matcherPairs {
|
||||||
matcherSet := make(map[string]caddyhttp.RequestMatcher)
|
matcherSet := make(map[string]caddyhttp.RequestMatcherWithError)
|
||||||
if len(mp.hostm) > 0 {
|
if len(mp.hostm) > 0 {
|
||||||
matcherSet["host"] = mp.hostm
|
matcherSet["host"] = mp.hostm
|
||||||
}
|
}
|
||||||
@@ -1431,13 +1544,18 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rm, ok := unm.(caddyhttp.RequestMatcher)
|
|
||||||
if !ok {
|
if rm, ok := unm.(caddyhttp.RequestMatcherWithError); ok {
|
||||||
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
|
return nil
|
||||||
}
|
}
|
||||||
|
// nolint:staticcheck
|
||||||
|
if rm, ok := unm.(caddyhttp.RequestMatcher); ok {
|
||||||
|
matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
|
||||||
|
}
|
||||||
|
|
||||||
// if the next token is quoted, we can assume it's not a matcher name
|
// if the next token is quoted, we can assume it's not a matcher name
|
||||||
// and that it's probably an 'expression' matcher
|
// and that it's probably an 'expression' matcher
|
||||||
@@ -1480,7 +1598,7 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (caddy.ModuleMap, error) {
|
func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcherWithError) (caddy.ModuleMap, error) {
|
||||||
msEncoded := make(caddy.ModuleMap)
|
msEncoded := make(caddy.ModuleMap)
|
||||||
for matcherName, val := range matchers {
|
for matcherName, val := range matchers {
|
||||||
jsonBytes, err := json.Marshal(val)
|
jsonBytes, err := json.Marshal(val)
|
||||||
@@ -1540,16 +1658,6 @@ func tryDuration(val any, warnings *[]caddyconfig.Warning) caddy.Duration {
|
|||||||
return durationVal
|
return durationVal
|
||||||
}
|
}
|
||||||
|
|
||||||
// sliceContains returns true if needle is in haystack.
|
|
||||||
func sliceContains(haystack []string, needle string) bool {
|
|
||||||
for _, s := range haystack {
|
|
||||||
if s == needle {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// listenersUseAnyPortOtherThan returns true if there are any
|
// listenersUseAnyPortOtherThan returns true if there are any
|
||||||
// listeners in addresses that use a port which is not otherPort.
|
// listeners in addresses that use a port which is not otherPort.
|
||||||
// Mostly borrowed from unexported method in caddyhttp package.
|
// Mostly borrowed from unexported method in caddyhttp package.
|
||||||
@@ -1570,6 +1678,18 @@ func listenersUseAnyPortOtherThan(addresses []string, otherPort string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mapContains[K comparable, V any](m map[K]V, keys []K) bool {
|
||||||
|
if len(m) == 0 || len(keys) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, key := range keys {
|
||||||
|
if _, ok := m[key]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// specificity returns len(s) minus any wildcards (*) and
|
// specificity returns len(s) minus any wildcards (*) and
|
||||||
// placeholders ({...}). Basically, it's a length count
|
// placeholders ({...}). Basically, it's a length count
|
||||||
// that penalizes the use of wildcards and placeholders.
|
// that penalizes the use of wildcards and placeholders.
|
||||||
@@ -1613,11 +1733,18 @@ type namedCustomLog struct {
|
|||||||
noHostname bool
|
noHostname bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addressWithProtocols associates a listen address with
|
||||||
|
// the protocols to serve it with
|
||||||
|
type addressWithProtocols struct {
|
||||||
|
address string
|
||||||
|
protocols []string
|
||||||
|
}
|
||||||
|
|
||||||
// sbAddrAssociation is a mapping from a list of
|
// sbAddrAssociation is a mapping from a list of
|
||||||
// addresses to a list of server blocks that are
|
// addresses with protocols, and a list of server
|
||||||
// served on those addresses.
|
// blocks that are served on those addresses.
|
||||||
type sbAddrAssociation struct {
|
type sbAddrAssociation struct {
|
||||||
addresses []string
|
addressesWithProtocols []addressWithProtocols
|
||||||
serverBlocks []serverBlock
|
serverBlocks []serverBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,14 +15,17 @@
|
|||||||
package httpcaddyfile
|
package httpcaddyfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/mholt/acmez/v2/acme"
|
"github.com/libdns/libdns"
|
||||||
|
"github.com/mholt/acmez/v3/acme"
|
||||||
|
|
||||||
"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"
|
||||||
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,19 +33,20 @@ 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("default_bind", parseOptDefaultBind)
|
||||||
RegisterGlobalOption("grace_period", parseOptDuration)
|
RegisterGlobalOption("grace_period", parseOptDuration)
|
||||||
RegisterGlobalOption("shutdown_delay", parseOptDuration)
|
RegisterGlobalOption("shutdown_delay", parseOptDuration)
|
||||||
RegisterGlobalOption("default_sni", parseOptSingleString)
|
RegisterGlobalOption("default_sni", parseOptSingleString)
|
||||||
RegisterGlobalOption("fallback_sni", parseOptSingleString)
|
RegisterGlobalOption("fallback_sni", parseOptSingleString)
|
||||||
RegisterGlobalOption("order", parseOptOrder)
|
RegisterGlobalOption("order", parseOptOrder)
|
||||||
RegisterGlobalOption("storage", parseOptStorage)
|
RegisterGlobalOption("storage", parseOptStorage)
|
||||||
RegisterGlobalOption("storage_clean_interval", parseOptDuration)
|
RegisterGlobalOption("storage_check", parseStorageCheck)
|
||||||
|
RegisterGlobalOption("storage_clean_interval", parseStorageCleanInterval)
|
||||||
RegisterGlobalOption("renew_interval", parseOptDuration)
|
RegisterGlobalOption("renew_interval", parseOptDuration)
|
||||||
RegisterGlobalOption("ocsp_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", parseOptDNS)
|
||||||
RegisterGlobalOption("acme_eab", parseOptACMEEAB)
|
RegisterGlobalOption("acme_eab", parseOptACMEEAB)
|
||||||
RegisterGlobalOption("cert_issuer", parseOptCertIssuer)
|
RegisterGlobalOption("cert_issuer", parseOptCertIssuer)
|
||||||
RegisterGlobalOption("skip_install_trust", parseOptTrue)
|
RegisterGlobalOption("skip_install_trust", parseOptTrue)
|
||||||
@@ -52,12 +56,15 @@ func init() {
|
|||||||
RegisterGlobalOption("local_certs", parseOptTrue)
|
RegisterGlobalOption("local_certs", parseOptTrue)
|
||||||
RegisterGlobalOption("key_type", parseOptSingleString)
|
RegisterGlobalOption("key_type", parseOptSingleString)
|
||||||
RegisterGlobalOption("auto_https", parseOptAutoHTTPS)
|
RegisterGlobalOption("auto_https", parseOptAutoHTTPS)
|
||||||
|
RegisterGlobalOption("metrics", parseMetricsOptions)
|
||||||
RegisterGlobalOption("servers", parseServerOptions)
|
RegisterGlobalOption("servers", parseServerOptions)
|
||||||
RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions)
|
RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions)
|
||||||
RegisterGlobalOption("cert_lifetime", parseOptDuration)
|
RegisterGlobalOption("cert_lifetime", parseOptDuration)
|
||||||
RegisterGlobalOption("log", parseLogOptions)
|
RegisterGlobalOption("log", parseLogOptions)
|
||||||
RegisterGlobalOption("preferred_chains", parseOptPreferredChains)
|
RegisterGlobalOption("preferred_chains", parseOptPreferredChains)
|
||||||
RegisterGlobalOption("persist_config", parseOptPersistConfig)
|
RegisterGlobalOption("persist_config", parseOptPersistConfig)
|
||||||
|
RegisterGlobalOption("dns", parseOptDNS)
|
||||||
|
RegisterGlobalOption("ech", parseOptECH)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil }
|
func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil }
|
||||||
@@ -110,17 +117,12 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
}
|
}
|
||||||
pos := Positional(d.Val())
|
pos := Positional(d.Val())
|
||||||
|
|
||||||
newOrder := directiveOrder
|
// if directive already had an order, drop it
|
||||||
|
newOrder := slices.DeleteFunc(directiveOrder, func(d string) bool {
|
||||||
|
return d == dirName
|
||||||
|
})
|
||||||
|
|
||||||
// if directive exists, first remove it
|
// act on the positional; if it's First or Last, we're done right away
|
||||||
for i, d := range newOrder {
|
|
||||||
if d == dirName {
|
|
||||||
newOrder = append(newOrder[:i], newOrder[i+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// act on the positional
|
|
||||||
switch pos {
|
switch pos {
|
||||||
case First:
|
case First:
|
||||||
newOrder = append([]string{dirName}, newOrder...)
|
newOrder = append([]string{dirName}, newOrder...)
|
||||||
@@ -129,6 +131,7 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
}
|
}
|
||||||
directiveOrder = newOrder
|
directiveOrder = newOrder
|
||||||
return newOrder, nil
|
return newOrder, nil
|
||||||
|
|
||||||
case Last:
|
case Last:
|
||||||
newOrder = append(newOrder, dirName)
|
newOrder = append(newOrder, dirName)
|
||||||
if d.NextArg() {
|
if d.NextArg() {
|
||||||
@@ -136,8 +139,11 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
}
|
}
|
||||||
directiveOrder = newOrder
|
directiveOrder = newOrder
|
||||||
return newOrder, nil
|
return newOrder, nil
|
||||||
|
|
||||||
|
// if it's Before or After, continue
|
||||||
case Before:
|
case Before:
|
||||||
case After:
|
case After:
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, d.Errf("unknown positional '%s'", pos)
|
return nil, d.Errf("unknown positional '%s'", pos)
|
||||||
}
|
}
|
||||||
@@ -151,17 +157,17 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
return nil, d.ArgErr()
|
return nil, d.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert directive into proper position
|
// get the position of the target directive
|
||||||
for i, d := range newOrder {
|
targetIndex := slices.Index(newOrder, otherDir)
|
||||||
if d == otherDir {
|
if targetIndex == -1 {
|
||||||
if pos == Before {
|
return nil, d.Errf("directive '%s' not found", otherDir)
|
||||||
newOrder = append(newOrder[:i], append([]string{dirName}, newOrder[i:]...)...)
|
|
||||||
} else if pos == After {
|
|
||||||
newOrder = append(newOrder[:i+1], append([]string{dirName}, newOrder[i+1:]...)...)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
// if we're inserting after, we need to increment the index to go after
|
||||||
|
if pos == After {
|
||||||
|
targetIndex++
|
||||||
}
|
}
|
||||||
|
// insert the directive into the new order
|
||||||
|
newOrder = slices.Insert(newOrder, targetIndex, dirName)
|
||||||
|
|
||||||
directiveOrder = newOrder
|
directiveOrder = newOrder
|
||||||
|
|
||||||
@@ -187,6 +193,40 @@ func parseOptStorage(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
return storage, nil
|
return storage, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseStorageCheck(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
|
d.Next() // consume option name
|
||||||
|
if !d.Next() {
|
||||||
|
return "", d.ArgErr()
|
||||||
|
}
|
||||||
|
val := d.Val()
|
||||||
|
if d.Next() {
|
||||||
|
return "", d.ArgErr()
|
||||||
|
}
|
||||||
|
if val != "off" {
|
||||||
|
return "", d.Errf("storage_check must be 'off'")
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseStorageCleanInterval(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
|
d.Next() // consume option name
|
||||||
|
if !d.Next() {
|
||||||
|
return "", d.ArgErr()
|
||||||
|
}
|
||||||
|
val := d.Val()
|
||||||
|
if d.Next() {
|
||||||
|
return "", d.ArgErr()
|
||||||
|
}
|
||||||
|
if val == "off" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
dur, err := caddy.ParseDuration(d.Val())
|
||||||
|
if err != nil {
|
||||||
|
return nil, d.Errf("failed to parse storage_clean_interval, must be a duration or 'off' %w", err)
|
||||||
|
}
|
||||||
|
return caddy.Duration(dur), nil
|
||||||
|
}
|
||||||
|
|
||||||
func parseOptDuration(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptDuration(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
if !d.Next() { // consume option name
|
if !d.Next() { // consume option name
|
||||||
return nil, d.ArgErr()
|
return nil, d.ArgErr()
|
||||||
@@ -201,25 +241,6 @@ 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) {
|
|
||||||
if !d.Next() { // consume option name
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
if !d.Next() { // get DNS module name
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
modID := "dns.providers." + d.Val()
|
|
||||||
unm, err := caddyfile.UnmarshalModule(d, modID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
prov, ok := unm.(certmagic.DNSProvider)
|
|
||||||
if !ok {
|
|
||||||
return nil, d.Errf("module %s (%T) is not a certmagic.DNSProvider", modID, unm)
|
|
||||||
}
|
|
||||||
return prov, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseOptACMEEAB(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptACMEEAB(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
eab := new(acme.EAB)
|
eab := new(acme.EAB)
|
||||||
d.Next() // consume option name
|
d.Next() // consume option name
|
||||||
@@ -284,13 +305,32 @@ func parseOptSingleString(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptStringList(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptDefaultBind(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
d.Next() // consume option name
|
d.Next() // consume option name
|
||||||
val := d.RemainingArgs()
|
|
||||||
if len(val) == 0 {
|
var addresses, protocols []string
|
||||||
return "", d.ArgErr()
|
addresses = d.RemainingArgs()
|
||||||
|
|
||||||
|
if len(addresses) == 0 {
|
||||||
|
addresses = append(addresses, "")
|
||||||
}
|
}
|
||||||
return val, nil
|
|
||||||
|
for d.NextBlock(0) {
|
||||||
|
switch d.Val() {
|
||||||
|
case "protocols":
|
||||||
|
protocols = d.RemainingArgs()
|
||||||
|
if len(protocols) == 0 {
|
||||||
|
return nil, d.Errf("protocols requires one or more arguments")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, d.Errf("unknown subdirective: %s", d.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []ConfigValue{{Class: "bind", Value: addressesWithProtocols{
|
||||||
|
addresses: addresses,
|
||||||
|
protocols: protocols,
|
||||||
|
}}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptAdmin(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptAdmin(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
@@ -375,36 +415,10 @@ func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
ond.PermissionRaw = caddyconfig.JSONModuleObject(perm, "module", modName, nil)
|
ond.PermissionRaw = caddyconfig.JSONModuleObject(perm, "module", modName, nil)
|
||||||
|
|
||||||
case "interval":
|
case "interval":
|
||||||
if !d.NextArg() {
|
return nil, d.Errf("the on_demand_tls 'interval' option is no longer supported, remove it from your config")
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
dur, err := caddy.ParseDuration(d.Val())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if ond == nil {
|
|
||||||
ond = new(caddytls.OnDemandConfig)
|
|
||||||
}
|
|
||||||
if ond.RateLimit == nil {
|
|
||||||
ond.RateLimit = new(caddytls.RateLimit)
|
|
||||||
}
|
|
||||||
ond.RateLimit.Interval = caddy.Duration(dur)
|
|
||||||
|
|
||||||
case "burst":
|
case "burst":
|
||||||
if !d.NextArg() {
|
return nil, d.Errf("the on_demand_tls 'burst' option is no longer supported, remove it from your config")
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
burst, err := strconv.Atoi(d.Val())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if ond == nil {
|
|
||||||
ond = new(caddytls.OnDemandConfig)
|
|
||||||
}
|
|
||||||
if ond.RateLimit == nil {
|
|
||||||
ond.RateLimit = new(caddytls.RateLimit)
|
|
||||||
}
|
|
||||||
ond.RateLimit.Burst = burst
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
|
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
|
||||||
@@ -433,19 +447,44 @@ func parseOptPersistConfig(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
|
|
||||||
func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
d.Next() // consume option name
|
d.Next() // consume option name
|
||||||
if !d.Next() {
|
val := d.RemainingArgs()
|
||||||
|
if len(val) == 0 {
|
||||||
return "", d.ArgErr()
|
return "", d.ArgErr()
|
||||||
}
|
}
|
||||||
val := d.Val()
|
for _, v := range val {
|
||||||
if d.Next() {
|
switch v {
|
||||||
return "", d.ArgErr()
|
case "off":
|
||||||
|
case "disable_redirects":
|
||||||
|
case "disable_certs":
|
||||||
|
case "ignore_loaded_certs":
|
||||||
|
case "prefer_wildcard":
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', 'ignore_loaded_certs', or 'prefer_wildcard'")
|
||||||
}
|
}
|
||||||
if val != "off" && val != "disable_redirects" && val != "disable_certs" && val != "ignore_loaded_certs" {
|
|
||||||
return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', or 'ignore_loaded_certs'")
|
|
||||||
}
|
}
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func unmarshalCaddyfileMetricsOptions(d *caddyfile.Dispenser) (any, error) {
|
||||||
|
d.Next() // consume option name
|
||||||
|
metrics := new(caddyhttp.Metrics)
|
||||||
|
for d.NextBlock(0) {
|
||||||
|
switch d.Val() {
|
||||||
|
case "per_host":
|
||||||
|
metrics.PerHost = true
|
||||||
|
default:
|
||||||
|
return nil, d.Errf("unrecognized servers option '%s'", d.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return metrics, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMetricsOptions(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
|
return unmarshalCaddyfileMetricsOptions(d)
|
||||||
|
}
|
||||||
|
|
||||||
func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
return unmarshalCaddyfileServerOptions(d)
|
return unmarshalCaddyfileServerOptions(d)
|
||||||
}
|
}
|
||||||
@@ -515,3 +554,68 @@ func parseOptPreferredChains(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
d.Next()
|
d.Next()
|
||||||
return caddytls.ParseCaddyfilePreferredChainsOptions(d)
|
return caddytls.ParseCaddyfilePreferredChainsOptions(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseOptDNS(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
|
d.Next() // consume option name
|
||||||
|
|
||||||
|
if !d.Next() { // get DNS module name
|
||||||
|
return nil, d.ArgErr()
|
||||||
|
}
|
||||||
|
modID := "dns.providers." + d.Val()
|
||||||
|
unm, err := caddyfile.UnmarshalModule(d, modID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch unm.(type) {
|
||||||
|
case libdns.RecordGetter,
|
||||||
|
libdns.RecordSetter,
|
||||||
|
libdns.RecordAppender,
|
||||||
|
libdns.RecordDeleter:
|
||||||
|
default:
|
||||||
|
return nil, d.Errf("module %s (%T) is not a libdns provider", modID, unm)
|
||||||
|
}
|
||||||
|
return unm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOptECH(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
|
d.Next() // consume option name
|
||||||
|
|
||||||
|
ech := new(caddytls.ECH)
|
||||||
|
|
||||||
|
publicNames := d.RemainingArgs()
|
||||||
|
for _, publicName := range publicNames {
|
||||||
|
ech.Configs = append(ech.Configs, caddytls.ECHConfiguration{
|
||||||
|
PublicName: publicName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(ech.Configs) == 0 {
|
||||||
|
return nil, d.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||||
|
switch d.Val() {
|
||||||
|
case "dns":
|
||||||
|
if !d.Next() {
|
||||||
|
return nil, d.ArgErr()
|
||||||
|
}
|
||||||
|
providerName := d.Val()
|
||||||
|
modID := "dns.providers." + providerName
|
||||||
|
unm, err := caddyfile.UnmarshalModule(d, modID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ech.Publication = append(ech.Publication, &caddytls.ECHPublication{
|
||||||
|
Configs: publicNames,
|
||||||
|
PublishersRaw: caddy.ModuleMap{
|
||||||
|
"dns": caddyconfig.JSON(caddytls.ECHDNSPublisher{
|
||||||
|
ProviderRaw: caddyconfig.JSONModuleObject(unm, "name", providerName, nil),
|
||||||
|
}, nil),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
return nil, d.Errf("ech: unrecognized subdirective '%s'", d.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ech, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package httpcaddyfile
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
|
|
||||||
@@ -180,7 +181,7 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
|||||||
if proto != "h1" && proto != "h2" && proto != "h2c" && proto != "h3" {
|
if proto != "h1" && proto != "h2" && proto != "h2c" && proto != "h3" {
|
||||||
return nil, d.Errf("unknown protocol '%s': expected h1, h2, h2c, or h3", proto)
|
return nil, d.Errf("unknown protocol '%s': expected h1, h2, h2c, or h3", proto)
|
||||||
}
|
}
|
||||||
if sliceContains(serverOpts.Protocols, proto) {
|
if slices.Contains(serverOpts.Protocols, proto) {
|
||||||
return nil, d.Errf("protocol %s specified more than once", proto)
|
return nil, d.Errf("protocol %s specified more than once", proto)
|
||||||
}
|
}
|
||||||
serverOpts.Protocols = append(serverOpts.Protocols, proto)
|
serverOpts.Protocols = append(serverOpts.Protocols, proto)
|
||||||
@@ -229,7 +230,7 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
|||||||
case "client_ip_headers":
|
case "client_ip_headers":
|
||||||
headers := d.RemainingArgs()
|
headers := d.RemainingArgs()
|
||||||
for _, header := range headers {
|
for _, header := range headers {
|
||||||
if sliceContains(serverOpts.ClientIPHeaders, header) {
|
if slices.Contains(serverOpts.ClientIPHeaders, header) {
|
||||||
return nil, d.Errf("client IP header %s specified more than once", header)
|
return nil, d.Errf("client IP header %s specified more than once", header)
|
||||||
}
|
}
|
||||||
serverOpts.ClientIPHeaders = append(serverOpts.ClientIPHeaders, header)
|
serverOpts.ClientIPHeaders = append(serverOpts.ClientIPHeaders, header)
|
||||||
@@ -239,13 +240,16 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "metrics":
|
case "metrics":
|
||||||
if d.NextArg() {
|
caddy.Log().Warn("The nested 'metrics' option inside `servers` is deprecated and will be removed in the next major version. Use the global 'metrics' option instead.")
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
if nesting := d.Nesting(); d.NextBlock(nesting) {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
serverOpts.Metrics = new(caddyhttp.Metrics)
|
serverOpts.Metrics = new(caddyhttp.Metrics)
|
||||||
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||||
|
switch d.Val() {
|
||||||
|
case "per_host":
|
||||||
|
serverOpts.Metrics.PerHost = true
|
||||||
|
default:
|
||||||
|
return nil, d.Errf("unrecognized metrics option '%s'", d.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case "trace":
|
case "trace":
|
||||||
if d.NextArg() {
|
if d.NextArg() {
|
||||||
@@ -288,24 +292,15 @@ func applyServerOptions(
|
|||||||
|
|
||||||
for key, server := range servers {
|
for key, server := range servers {
|
||||||
// find the options that apply to this server
|
// find the options that apply to this server
|
||||||
opts := func() *serverOptions {
|
optsIndex := slices.IndexFunc(serverOpts, func(s serverOptions) bool {
|
||||||
for _, entry := range serverOpts {
|
return s.ListenerAddress == "" || slices.Contains(server.Listen, s.ListenerAddress)
|
||||||
if entry.ListenerAddress == "" {
|
})
|
||||||
return &entry
|
|
||||||
}
|
|
||||||
for _, listener := range server.Listen {
|
|
||||||
if entry.ListenerAddress == listener {
|
|
||||||
return &entry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
// if none apply, then move to the next server
|
// if none apply, then move to the next server
|
||||||
if opts == nil {
|
if optsIndex == -1 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
opts := serverOpts[optsIndex]
|
||||||
|
|
||||||
// set all the options
|
// set all the options
|
||||||
server.ListenerWrappersRaw = opts.ListenerWrappersRaw
|
server.ListenerWrappersRaw = opts.ListenerWrappersRaw
|
||||||
|
|||||||
@@ -52,19 +52,27 @@ func NewShorthandReplacer() ShorthandReplacer {
|
|||||||
// be used in the Caddyfile, and the right is the replacement.
|
// be used in the Caddyfile, and the right is the replacement.
|
||||||
func placeholderShorthands() []string {
|
func placeholderShorthands() []string {
|
||||||
return []string{
|
return []string{
|
||||||
"{dir}", "{http.request.uri.path.dir}",
|
|
||||||
"{file}", "{http.request.uri.path.file}",
|
|
||||||
"{host}", "{http.request.host}",
|
"{host}", "{http.request.host}",
|
||||||
"{hostport}", "{http.request.hostport}",
|
"{hostport}", "{http.request.hostport}",
|
||||||
"{port}", "{http.request.port}",
|
"{port}", "{http.request.port}",
|
||||||
|
"{orig_method}", "{http.request.orig_method}",
|
||||||
|
"{orig_uri}", "{http.request.orig_uri}",
|
||||||
|
"{orig_path}", "{http.request.orig_uri.path}",
|
||||||
|
"{orig_dir}", "{http.request.orig_uri.path.dir}",
|
||||||
|
"{orig_file}", "{http.request.orig_uri.path.file}",
|
||||||
|
"{orig_query}", "{http.request.orig_uri.query}",
|
||||||
|
"{orig_?query}", "{http.request.orig_uri.prefixed_query}",
|
||||||
"{method}", "{http.request.method}",
|
"{method}", "{http.request.method}",
|
||||||
|
"{uri}", "{http.request.uri}",
|
||||||
"{path}", "{http.request.uri.path}",
|
"{path}", "{http.request.uri.path}",
|
||||||
|
"{dir}", "{http.request.uri.path.dir}",
|
||||||
|
"{file}", "{http.request.uri.path.file}",
|
||||||
"{query}", "{http.request.uri.query}",
|
"{query}", "{http.request.uri.query}",
|
||||||
|
"{?query}", "{http.request.uri.prefixed_query}",
|
||||||
"{remote}", "{http.request.remote}",
|
"{remote}", "{http.request.remote}",
|
||||||
"{remote_host}", "{http.request.remote.host}",
|
"{remote_host}", "{http.request.remote.host}",
|
||||||
"{remote_port}", "{http.request.remote.port}",
|
"{remote_port}", "{http.request.remote.port}",
|
||||||
"{scheme}", "{http.request.scheme}",
|
"{scheme}", "{http.request.scheme}",
|
||||||
"{uri}", "{http.request.uri}",
|
|
||||||
"{uuid}", "{http.request.uuid}",
|
"{uuid}", "{http.request.uuid}",
|
||||||
"{tls_cipher}", "{http.request.tls.cipher_suite}",
|
"{tls_cipher}", "{http.request.tls.cipher_suite}",
|
||||||
"{tls_version}", "{http.request.tls.version}",
|
"{tls_version}", "{http.request.tls.version}",
|
||||||
|
|||||||
@@ -19,12 +19,13 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/mholt/acmez/v2/acme"
|
"github.com/mholt/acmez/v3/acme"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
@@ -44,8 +45,8 @@ func (st ServerType) buildTLSApp(
|
|||||||
if hp, ok := options["http_port"].(int); ok {
|
if hp, ok := options["http_port"].(int); ok {
|
||||||
httpPort = strconv.Itoa(hp)
|
httpPort = strconv.Itoa(hp)
|
||||||
}
|
}
|
||||||
autoHTTPS := "on"
|
autoHTTPS := []string{}
|
||||||
if ah, ok := options["auto_https"].(string); ok {
|
if ah, ok := options["auto_https"].([]string); ok {
|
||||||
autoHTTPS = ah
|
autoHTTPS = ah
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,14 +54,17 @@ func (st ServerType) buildTLSApp(
|
|||||||
// key, so that they don't get forgotten/omitted by auto-HTTPS
|
// key, so that they don't get forgotten/omitted by auto-HTTPS
|
||||||
// (since they won't appear in route matchers)
|
// (since they won't appear in route matchers)
|
||||||
httpsHostsSharedWithHostlessKey := make(map[string]struct{})
|
httpsHostsSharedWithHostlessKey := make(map[string]struct{})
|
||||||
if autoHTTPS != "off" {
|
if !slices.Contains(autoHTTPS, "off") {
|
||||||
for _, pair := range pairings {
|
for _, pair := range pairings {
|
||||||
for _, sb := range pair.serverBlocks {
|
for _, sb := range pair.serverBlocks {
|
||||||
for _, addr := range sb.keys {
|
for _, addr := range sb.parsedKeys {
|
||||||
if addr.Host == "" {
|
if addr.Host != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// this server block has a hostless key, now
|
// this server block has a hostless key, now
|
||||||
// go through and add all the hosts to the set
|
// go through and add all the hosts to the set
|
||||||
for _, otherAddr := range sb.keys {
|
for _, otherAddr := range sb.parsedKeys {
|
||||||
if otherAddr.Original == addr.Original {
|
if otherAddr.Original == addr.Original {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -73,7 +77,6 @@ func (st ServerType) buildTLSApp(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// a catch-all automation policy is used as a "default" for all subjects that
|
// a catch-all automation policy is used as a "default" for all subjects that
|
||||||
// don't have custom configuration explicitly associated with them; this
|
// don't have custom configuration explicitly associated with them; this
|
||||||
@@ -89,9 +92,35 @@ func (st ServerType) buildTLSApp(
|
|||||||
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
|
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// collect all hosts that have a wildcard in them, and arent HTTP
|
||||||
|
wildcardHosts := []string{}
|
||||||
|
// hosts that have been explicitly marked to be automated,
|
||||||
|
// even if covered by another wildcard
|
||||||
|
forcedAutomatedNames := make(map[string]struct{})
|
||||||
|
for _, p := range pairings {
|
||||||
|
var addresses []string
|
||||||
|
for _, addressWithProtocols := range p.addressesWithProtocols {
|
||||||
|
addresses = append(addresses, addressWithProtocols.address)
|
||||||
|
}
|
||||||
|
if !listenersUseAnyPortOtherThan(addresses, httpPort) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, sblock := range p.serverBlocks {
|
||||||
|
for _, addr := range sblock.parsedKeys {
|
||||||
|
if strings.HasPrefix(addr.Host, "*.") {
|
||||||
|
wildcardHosts = append(wildcardHosts, addr.Host[2:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, p := range pairings {
|
for _, p := range pairings {
|
||||||
// avoid setting up TLS automation policies for a server that is HTTP-only
|
// avoid setting up TLS automation policies for a server that is HTTP-only
|
||||||
if !listenersUseAnyPortOtherThan(p.addresses, httpPort) {
|
var addresses []string
|
||||||
|
for _, addressWithProtocols := range p.addressesWithProtocols {
|
||||||
|
addresses = append(addresses, addressWithProtocols.address)
|
||||||
|
}
|
||||||
|
if !listenersUseAnyPortOtherThan(addresses, httpPort) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +137,12 @@ func (st ServerType) buildTLSApp(
|
|||||||
return nil, warnings, err
|
return nil, warnings, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make a plain copy so we can compare whether we made any changes
|
||||||
|
apCopy, err := newBaseAutomationPolicy(options, warnings, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, warnings, err
|
||||||
|
}
|
||||||
|
|
||||||
sblockHosts := sblock.hostsFromKeys(false)
|
sblockHosts := sblock.hostsFromKeys(false)
|
||||||
if len(sblockHosts) == 0 && catchAllAP != nil {
|
if len(sblockHosts) == 0 && catchAllAP != nil {
|
||||||
ap = catchAllAP
|
ap = catchAllAP
|
||||||
@@ -118,6 +153,13 @@ func (st ServerType) buildTLSApp(
|
|||||||
ap.OnDemand = true
|
ap.OnDemand = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// collect hosts that are forced to be automated
|
||||||
|
if _, ok := sblock.pile["tls.force_automate"]; ok {
|
||||||
|
for _, host := range sblockHosts {
|
||||||
|
forcedAutomatedNames[host] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// reuse private keys tls
|
// reuse private keys tls
|
||||||
if _, ok := sblock.pile["tls.reuse_private_keys"]; ok {
|
if _, ok := sblock.pile["tls.reuse_private_keys"]; ok {
|
||||||
ap.ReusePrivateKeys = true
|
ap.ReusePrivateKeys = true
|
||||||
@@ -181,8 +223,8 @@ func (st ServerType) buildTLSApp(
|
|||||||
if acmeIssuer.Challenges.BindHost == "" {
|
if acmeIssuer.Challenges.BindHost == "" {
|
||||||
// only binding to one host is supported
|
// only binding to one host is supported
|
||||||
var bindHost string
|
var bindHost string
|
||||||
if bindHosts, ok := cfgVal.Value.([]string); ok && len(bindHosts) > 0 {
|
if asserted, ok := cfgVal.Value.(addressesWithProtocols); ok && len(asserted.addresses) > 0 {
|
||||||
bindHost = bindHosts[0]
|
bindHost = asserted.addresses[0]
|
||||||
}
|
}
|
||||||
acmeIssuer.Challenges.BindHost = bindHost
|
acmeIssuer.Challenges.BindHost = bindHost
|
||||||
}
|
}
|
||||||
@@ -210,9 +252,21 @@ func (st ServerType) buildTLSApp(
|
|||||||
catchAllAP = ap
|
catchAllAP = ap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hostsNotHTTP := sblock.hostsFromKeysNotHTTP(httpPort)
|
||||||
|
sort.Strings(hostsNotHTTP) // solely for deterministic test results
|
||||||
|
|
||||||
|
// if the we prefer wildcards and the AP is unchanged,
|
||||||
|
// then we can skip this AP because it should be covered
|
||||||
|
// by an AP with a wildcard
|
||||||
|
if slices.Contains(autoHTTPS, "prefer_wildcard") {
|
||||||
|
if hostsCoveredByWildcard(hostsNotHTTP, wildcardHosts) &&
|
||||||
|
reflect.DeepEqual(ap, apCopy) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// associate our new automation policy with this server block's hosts
|
// associate our new automation policy with this server block's hosts
|
||||||
ap.SubjectsRaw = sblock.hostsFromKeysNotHTTP(httpPort)
|
ap.SubjectsRaw = hostsNotHTTP
|
||||||
sort.Strings(ap.SubjectsRaw) // solely for deterministic test results
|
|
||||||
|
|
||||||
// if a combination of public and internal names were given
|
// if a combination of public and internal names were given
|
||||||
// for this same server block and no issuer was specified, we
|
// for this same server block and no issuer was specified, we
|
||||||
@@ -251,6 +305,7 @@ func (st ServerType) buildTLSApp(
|
|||||||
ap2.IssuersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)}
|
ap2.IssuersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tlsApp.Automation == nil {
|
if tlsApp.Automation == nil {
|
||||||
tlsApp.Automation = new(caddytls.AutomationConfig)
|
tlsApp.Automation = new(caddytls.AutomationConfig)
|
||||||
}
|
}
|
||||||
@@ -304,6 +359,40 @@ func (st ServerType) buildTLSApp(
|
|||||||
tlsApp.Automation.OnDemand = onDemand
|
tlsApp.Automation.OnDemand = onDemand
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set up "global" (to the TLS app) DNS provider config
|
||||||
|
if globalDNS, ok := options["dns"]; ok && globalDNS != nil {
|
||||||
|
tlsApp.DNSRaw = caddyconfig.JSONModuleObject(globalDNS, "name", globalDNS.(caddy.Module).CaddyModule().ID.Name(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up ECH from Caddyfile options
|
||||||
|
if ech, ok := options["ech"].(*caddytls.ECH); ok {
|
||||||
|
tlsApp.EncryptedClientHello = ech
|
||||||
|
|
||||||
|
// outer server names will need certificates, so make sure they're included
|
||||||
|
// in an automation policy for them that applies any global options
|
||||||
|
ap, err := newBaseAutomationPolicy(options, warnings, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, warnings, err
|
||||||
|
}
|
||||||
|
for _, cfg := range ech.Configs {
|
||||||
|
ap.SubjectsRaw = append(ap.SubjectsRaw, cfg.PublicName)
|
||||||
|
}
|
||||||
|
if tlsApp.Automation == nil {
|
||||||
|
tlsApp.Automation = new(caddytls.AutomationConfig)
|
||||||
|
}
|
||||||
|
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, ap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the storage clean interval is a boolean, then it's "off" to disable cleaning
|
||||||
|
if sc, ok := options["storage_check"].(string); ok && sc == "off" {
|
||||||
|
tlsApp.DisableStorageCheck = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the storage clean interval is a boolean, then it's "off" to disable cleaning
|
||||||
|
if sci, ok := options["storage_clean_interval"].(bool); ok && !sci {
|
||||||
|
tlsApp.DisableStorageClean = true
|
||||||
|
}
|
||||||
|
|
||||||
// set the storage clean interval if configured
|
// set the storage clean interval if configured
|
||||||
if storageCleanInterval, ok := options["storage_clean_interval"].(caddy.Duration); ok {
|
if storageCleanInterval, ok := options["storage_clean_interval"].(caddy.Duration); ok {
|
||||||
if tlsApp.Automation == nil {
|
if tlsApp.Automation == nil {
|
||||||
@@ -344,7 +433,7 @@ func (st ServerType) buildTLSApp(
|
|||||||
internalAP := &caddytls.AutomationPolicy{
|
internalAP := &caddytls.AutomationPolicy{
|
||||||
IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
|
IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
|
||||||
}
|
}
|
||||||
if autoHTTPS != "off" && autoHTTPS != "disable_certs" {
|
if !slices.Contains(autoHTTPS, "off") && !slices.Contains(autoHTTPS, "disable_certs") {
|
||||||
for h := range httpsHostsSharedWithHostlessKey {
|
for h := range httpsHostsSharedWithHostlessKey {
|
||||||
al = append(al, h)
|
al = append(al, h)
|
||||||
if !certmagic.SubjectQualifiesForPublicCert(h) {
|
if !certmagic.SubjectQualifiesForPublicCert(h) {
|
||||||
@@ -352,6 +441,13 @@ func (st ServerType) buildTLSApp(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for name := range forcedAutomatedNames {
|
||||||
|
if slices.Contains(al, name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
al = append(al, name)
|
||||||
|
}
|
||||||
|
slices.Sort(al) // to stabilize the adapt output
|
||||||
if len(al) > 0 {
|
if len(al) > 0 {
|
||||||
tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings)
|
tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings)
|
||||||
}
|
}
|
||||||
@@ -465,7 +561,7 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
|
|||||||
if globalACMECA != nil && acmeIssuer.CA == "" {
|
if globalACMECA != nil && acmeIssuer.CA == "" {
|
||||||
acmeIssuer.CA = globalACMECA.(string)
|
acmeIssuer.CA = globalACMECA.(string)
|
||||||
}
|
}
|
||||||
if globalACMECARoot != nil && !sliceContains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) {
|
if globalACMECARoot != nil && !slices.Contains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) {
|
||||||
acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string))
|
acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string))
|
||||||
}
|
}
|
||||||
if globalACMEDNS != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) {
|
if globalACMEDNS != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) {
|
||||||
@@ -481,7 +577,8 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
|
|||||||
if globalPreferredChains != nil && acmeIssuer.PreferredChains == nil {
|
if globalPreferredChains != nil && acmeIssuer.PreferredChains == nil {
|
||||||
acmeIssuer.PreferredChains = globalPreferredChains.(*caddytls.ChainPreference)
|
acmeIssuer.PreferredChains = globalPreferredChains.(*caddytls.ChainPreference)
|
||||||
}
|
}
|
||||||
if globalHTTPPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.HTTP == nil || acmeIssuer.Challenges.HTTP.AlternatePort == 0) {
|
// only configure alt HTTP and TLS-ALPN ports if the DNS challenge is not enabled (wouldn't hurt, but isn't necessary since the DNS challenge is exclusive of others)
|
||||||
|
if globalHTTPPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.HTTP == nil || acmeIssuer.Challenges.HTTP.AlternatePort == 0) {
|
||||||
if acmeIssuer.Challenges == nil {
|
if acmeIssuer.Challenges == nil {
|
||||||
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
||||||
}
|
}
|
||||||
@@ -490,7 +587,7 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
|
|||||||
}
|
}
|
||||||
acmeIssuer.Challenges.HTTP.AlternatePort = globalHTTPPort.(int)
|
acmeIssuer.Challenges.HTTP.AlternatePort = globalHTTPPort.(int)
|
||||||
}
|
}
|
||||||
if globalHTTPSPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.TLSALPN == nil || acmeIssuer.Challenges.TLSALPN.AlternatePort == 0) {
|
if globalHTTPSPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.TLSALPN == nil || acmeIssuer.Challenges.TLSALPN.AlternatePort == 0) {
|
||||||
if acmeIssuer.Challenges == nil {
|
if acmeIssuer.Challenges == nil {
|
||||||
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
||||||
}
|
}
|
||||||
@@ -580,7 +677,7 @@ func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls
|
|||||||
if !automationPolicyHasAllPublicNames(aps[i]) {
|
if !automationPolicyHasAllPublicNames(aps[i]) {
|
||||||
// if this automation policy has internal names, we might as well remove it
|
// if this automation policy has internal names, we might as well remove it
|
||||||
// so auto-https can implicitly use the internal issuer
|
// so auto-https can implicitly use the internal issuer
|
||||||
aps = append(aps[:i], aps[i+1:]...)
|
aps = slices.Delete(aps, i, i+1)
|
||||||
i--
|
i--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -597,7 +694,7 @@ outer:
|
|||||||
for j := i + 1; j < len(aps); j++ {
|
for j := i + 1; j < len(aps); j++ {
|
||||||
// if they're exactly equal in every way, just keep one of them
|
// if they're exactly equal in every way, just keep one of them
|
||||||
if reflect.DeepEqual(aps[i], aps[j]) {
|
if reflect.DeepEqual(aps[i], aps[j]) {
|
||||||
aps = append(aps[:j], aps[j+1:]...)
|
aps = slices.Delete(aps, j, j+1)
|
||||||
// must re-evaluate current i against next j; can't skip it!
|
// must re-evaluate current i against next j; can't skip it!
|
||||||
// even if i decrements to -1, will be incremented to 0 immediately
|
// even if i decrements to -1, will be incremented to 0 immediately
|
||||||
i--
|
i--
|
||||||
@@ -627,18 +724,18 @@ outer:
|
|||||||
// cause example.com to be served by the less specific policy for
|
// cause example.com to be served by the less specific policy for
|
||||||
// '*.com', which might be different (yes we've seen this happen)
|
// '*.com', which might be different (yes we've seen this happen)
|
||||||
if automationPolicyShadows(i, aps) >= j {
|
if automationPolicyShadows(i, aps) >= j {
|
||||||
aps = append(aps[:i], aps[i+1:]...)
|
aps = slices.Delete(aps, i, i+1)
|
||||||
i--
|
i--
|
||||||
continue outer
|
continue outer
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// avoid repeated subjects
|
// avoid repeated subjects
|
||||||
for _, subj := range aps[j].SubjectsRaw {
|
for _, subj := range aps[j].SubjectsRaw {
|
||||||
if !sliceContains(aps[i].SubjectsRaw, subj) {
|
if !slices.Contains(aps[i].SubjectsRaw, subj) {
|
||||||
aps[i].SubjectsRaw = append(aps[i].SubjectsRaw, subj)
|
aps[i].SubjectsRaw = append(aps[i].SubjectsRaw, subj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
aps = append(aps[:j], aps[j+1:]...)
|
aps = slices.Delete(aps, j, j+1)
|
||||||
j--
|
j--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -658,13 +755,9 @@ func automationPolicyIsSubset(a, b *caddytls.AutomationPolicy) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, aSubj := range a.SubjectsRaw {
|
for _, aSubj := range a.SubjectsRaw {
|
||||||
var inSuperset bool
|
inSuperset := slices.ContainsFunc(b.SubjectsRaw, func(bSubj string) bool {
|
||||||
for _, bSubj := range b.SubjectsRaw {
|
return certmagic.MatchWildcard(aSubj, bSubj)
|
||||||
if certmagic.MatchWildcard(aSubj, bSubj) {
|
})
|
||||||
inSuperset = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !inSuperset {
|
if !inSuperset {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -709,14 +802,28 @@ func subjectQualifiesForPublicCert(ap *caddytls.AutomationPolicy, subj string) b
|
|||||||
// automationPolicyHasAllPublicNames returns true if all the names on the policy
|
// automationPolicyHasAllPublicNames returns true if all the names on the policy
|
||||||
// do NOT qualify for public certs OR are tailscale domains.
|
// do NOT qualify for public certs OR are tailscale domains.
|
||||||
func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
|
func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
|
||||||
for _, subj := range ap.SubjectsRaw {
|
return !slices.ContainsFunc(ap.SubjectsRaw, func(i string) bool {
|
||||||
if !subjectQualifiesForPublicCert(ap, subj) || isTailscaleDomain(subj) {
|
return !subjectQualifiesForPublicCert(ap, i) || isTailscaleDomain(i)
|
||||||
return false
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isTailscaleDomain(name string) bool {
|
func isTailscaleDomain(name string) bool {
|
||||||
return strings.HasSuffix(strings.ToLower(name), ".ts.net")
|
return strings.HasSuffix(strings.ToLower(name), ".ts.net")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hostsCoveredByWildcard(hosts []string, wildcards []string) bool {
|
||||||
|
if len(hosts) == 0 || len(wildcards) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, host := range hosts {
|
||||||
|
for _, wildcard := range wildcards {
|
||||||
|
if strings.HasPrefix(host, "*.") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if certmagic.MatchWildcard(host, "*."+wildcard) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func init() {
|
|||||||
// If the response is not a JSON config, a config adapter must be specified
|
// If the response is not a JSON config, a config adapter must be specified
|
||||||
// either in the loader config (`adapter`), or in the Content-Type HTTP header
|
// either in the loader config (`adapter`), or in the Content-Type HTTP header
|
||||||
// returned in the HTTP response from the server. The Content-Type header is
|
// returned in the HTTP response from the server. The Content-Type header is
|
||||||
// read just like the admin API's `/load` endpoint. Uf you don't have control
|
// read just like the admin API's `/load` endpoint. If you don't have control
|
||||||
// over the HTTP server (but can still trust its response), you can override
|
// over the HTTP server (but can still trust its response), you can override
|
||||||
// the Content-Type header by setting the `adapter` property in this config.
|
// the Content-Type header by setting the `adapter` property in this config.
|
||||||
type HTTPLoader struct {
|
type HTTPLoader struct {
|
||||||
|
|||||||
+42
-21
@@ -31,8 +31,8 @@ import (
|
|||||||
_ "github.com/caddyserver/caddy/v2/modules/standard"
|
_ "github.com/caddyserver/caddy/v2/modules/standard"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Defaults store any configuration required to make the tests run
|
// Config store any configuration required to make the tests run
|
||||||
type Defaults struct {
|
type Config struct {
|
||||||
// Port we expect caddy to listening on
|
// Port we expect caddy to listening on
|
||||||
AdminPort int
|
AdminPort int
|
||||||
// Certificates we expect to be loaded before attempting to run the tests
|
// Certificates we expect to be loaded before attempting to run the tests
|
||||||
@@ -44,7 +44,7 @@ type Defaults struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Default testing values
|
// Default testing values
|
||||||
var Default = Defaults{
|
var Default = Config{
|
||||||
AdminPort: 2999, // different from what a real server also running on a developer's machine might be
|
AdminPort: 2999, // different from what a real server also running on a developer's machine might be
|
||||||
Certificates: []string{"/caddy.localhost.crt", "/caddy.localhost.key"},
|
Certificates: []string{"/caddy.localhost.crt", "/caddy.localhost.key"},
|
||||||
TestRequestTimeout: 5 * time.Second,
|
TestRequestTimeout: 5 * time.Second,
|
||||||
@@ -61,6 +61,7 @@ type Tester struct {
|
|||||||
Client *http.Client
|
Client *http.Client
|
||||||
configLoaded bool
|
configLoaded bool
|
||||||
t testing.TB
|
t testing.TB
|
||||||
|
config Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTester will create a new testing client with an attached cookie jar
|
// NewTester will create a new testing client with an attached cookie jar
|
||||||
@@ -78,9 +79,29 @@ func NewTester(t testing.TB) *Tester {
|
|||||||
},
|
},
|
||||||
configLoaded: false,
|
configLoaded: false,
|
||||||
t: t,
|
t: t,
|
||||||
|
config: Default,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithDefaultOverrides this will override the default test configuration with the provided values.
|
||||||
|
func (tc *Tester) WithDefaultOverrides(overrides Config) *Tester {
|
||||||
|
if overrides.AdminPort != 0 {
|
||||||
|
tc.config.AdminPort = overrides.AdminPort
|
||||||
|
}
|
||||||
|
if len(overrides.Certificates) > 0 {
|
||||||
|
tc.config.Certificates = overrides.Certificates
|
||||||
|
}
|
||||||
|
if overrides.TestRequestTimeout != 0 {
|
||||||
|
tc.config.TestRequestTimeout = overrides.TestRequestTimeout
|
||||||
|
tc.Client.Timeout = overrides.TestRequestTimeout
|
||||||
|
}
|
||||||
|
if overrides.LoadRequestTimeout != 0 {
|
||||||
|
tc.config.LoadRequestTimeout = overrides.LoadRequestTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
return tc
|
||||||
|
}
|
||||||
|
|
||||||
type configLoadError struct {
|
type configLoadError struct {
|
||||||
Response string
|
Response string
|
||||||
}
|
}
|
||||||
@@ -113,7 +134,7 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := validateTestPrerequisites(tc.t)
|
err := validateTestPrerequisites(tc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err)
|
tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err)
|
||||||
return nil
|
return nil
|
||||||
@@ -121,7 +142,7 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
|
|||||||
|
|
||||||
tc.t.Cleanup(func() {
|
tc.t.Cleanup(func() {
|
||||||
if tc.t.Failed() && tc.configLoaded {
|
if tc.t.Failed() && tc.configLoaded {
|
||||||
res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
|
res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Log("unable to read the current config")
|
tc.t.Log("unable to read the current config")
|
||||||
return
|
return
|
||||||
@@ -151,10 +172,10 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
|
|||||||
tc.t.Logf("After: %s", rawConfig)
|
tc.t.Logf("After: %s", rawConfig)
|
||||||
}
|
}
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: Default.LoadRequestTimeout,
|
Timeout: tc.config.LoadRequestTimeout,
|
||||||
}
|
}
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", Default.AdminPort), strings.NewReader(rawConfig))
|
req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", tc.config.AdminPort), strings.NewReader(rawConfig))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Errorf("failed to create request. %s", err)
|
tc.t.Errorf("failed to create request. %s", err)
|
||||||
return err
|
return err
|
||||||
@@ -205,11 +226,11 @@ func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: Default.LoadRequestTimeout,
|
Timeout: tc.config.LoadRequestTimeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchConfig := func(client *http.Client) any {
|
fetchConfig := func(client *http.Client) any {
|
||||||
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
|
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -237,30 +258,30 @@ func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
const initConfig = `{
|
const initConfig = `{
|
||||||
admin localhost:2999
|
admin localhost:%d
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
// validateTestPrerequisites ensures the certificates are available in the
|
// validateTestPrerequisites ensures the certificates are available in the
|
||||||
// designated path and Caddy sub-process is running.
|
// designated path and Caddy sub-process is running.
|
||||||
func validateTestPrerequisites(t testing.TB) error {
|
func validateTestPrerequisites(tc *Tester) error {
|
||||||
// check certificates are found
|
// check certificates are found
|
||||||
for _, certName := range Default.Certificates {
|
for _, certName := range tc.config.Certificates {
|
||||||
if _, err := os.Stat(getIntegrationDir() + certName); errors.Is(err, fs.ErrNotExist) {
|
if _, err := os.Stat(getIntegrationDir() + certName); errors.Is(err, fs.ErrNotExist) {
|
||||||
return fmt.Errorf("caddy integration test certificates (%s) not found", certName)
|
return fmt.Errorf("caddy integration test certificates (%s) not found", certName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isCaddyAdminRunning() != nil {
|
if isCaddyAdminRunning(tc) != nil {
|
||||||
// setup the init config file, and set the cleanup afterwards
|
// setup the init config file, and set the cleanup afterwards
|
||||||
f, err := os.CreateTemp("", "")
|
f, err := os.CreateTemp("", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
t.Cleanup(func() {
|
tc.t.Cleanup(func() {
|
||||||
os.Remove(f.Name())
|
os.Remove(f.Name())
|
||||||
})
|
})
|
||||||
if _, err := f.WriteString(initConfig); err != nil {
|
if _, err := f.WriteString(fmt.Sprintf(initConfig, tc.config.AdminPort)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,23 +292,23 @@ func validateTestPrerequisites(t testing.TB) error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// wait for caddy to start serving the initial config
|
// wait for caddy to start serving the initial config
|
||||||
for retries := 10; retries > 0 && isCaddyAdminRunning() != nil; retries-- {
|
for retries := 10; retries > 0 && isCaddyAdminRunning(tc) != nil; retries-- {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// one more time to return the error
|
// one more time to return the error
|
||||||
return isCaddyAdminRunning()
|
return isCaddyAdminRunning(tc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isCaddyAdminRunning() error {
|
func isCaddyAdminRunning(tc *Tester) error {
|
||||||
// assert that caddy is running
|
// assert that caddy is running
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: Default.LoadRequestTimeout,
|
Timeout: tc.config.LoadRequestTimeout,
|
||||||
}
|
}
|
||||||
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
|
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("caddy integration test caddy server not running. Expected to be listening on localhost:%d", Default.AdminPort)
|
return fmt.Errorf("caddy integration test caddy server not running. Expected to be listening on localhost:%d", tc.config.AdminPort)
|
||||||
}
|
}
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ func TestLoadUnorderedJSON(t *testing.T) {
|
|||||||
"servers": {
|
"servers": {
|
||||||
"s_server": {
|
"s_server": {
|
||||||
"listen": [
|
"listen": [
|
||||||
":9443",
|
|
||||||
":9080"
|
":9080"
|
||||||
],
|
],
|
||||||
"routes": [
|
"routes": [
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -13,10 +14,11 @@ import (
|
|||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddytest"
|
"github.com/caddyserver/caddy/v2/caddytest"
|
||||||
"github.com/mholt/acmez/v2"
|
"github.com/mholt/acmez/v3"
|
||||||
"github.com/mholt/acmez/v2/acme"
|
"github.com/mholt/acmez/v3/acme"
|
||||||
smallstepacme "github.com/smallstep/certificates/acme"
|
smallstepacme "github.com/smallstep/certificates/acme"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/exp/zapslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
const acmeChallengePort = 9081
|
const acmeChallengePort = 9081
|
||||||
@@ -48,7 +50,7 @@ func TestACMEServerWithDefaults(t *testing.T) {
|
|||||||
Client: &acme.Client{
|
Client: &acme.Client{
|
||||||
Directory: "https://acme.localhost:9443/acme/local/directory",
|
Directory: "https://acme.localhost:9443/acme/local/directory",
|
||||||
HTTPClient: tester.Client,
|
HTTPClient: tester.Client,
|
||||||
Logger: logger,
|
Logger: slog.New(zapslog.NewHandler(logger.Core())),
|
||||||
},
|
},
|
||||||
ChallengeSolvers: map[string]acmez.Solver{
|
ChallengeSolvers: map[string]acmez.Solver{
|
||||||
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
|
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
|
||||||
@@ -117,7 +119,7 @@ func TestACMEServerWithMismatchedChallenges(t *testing.T) {
|
|||||||
Client: &acme.Client{
|
Client: &acme.Client{
|
||||||
Directory: "https://acme.localhost:9443/acme/local/directory",
|
Directory: "https://acme.localhost:9443/acme/local/directory",
|
||||||
HTTPClient: tester.Client,
|
HTTPClient: tester.Client,
|
||||||
Logger: logger,
|
Logger: slog.New(zapslog.NewHandler(logger.Core())),
|
||||||
},
|
},
|
||||||
ChallengeSolvers: map[string]acmez.Solver{
|
ChallengeSolvers: map[string]acmez.Solver{
|
||||||
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
|
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
|
||||||
|
|||||||
@@ -5,13 +5,15 @@ import (
|
|||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"log/slog"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2/caddytest"
|
"github.com/caddyserver/caddy/v2/caddytest"
|
||||||
"github.com/mholt/acmez/v2"
|
"github.com/mholt/acmez/v3"
|
||||||
"github.com/mholt/acmez/v2/acme"
|
"github.com/mholt/acmez/v3/acme"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/exp/zapslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestACMEServerDirectory(t *testing.T) {
|
func TestACMEServerDirectory(t *testing.T) {
|
||||||
@@ -76,7 +78,7 @@ func TestACMEServerAllowPolicy(t *testing.T) {
|
|||||||
Client: &acme.Client{
|
Client: &acme.Client{
|
||||||
Directory: "https://acme.localhost:9443/acme/local/directory",
|
Directory: "https://acme.localhost:9443/acme/local/directory",
|
||||||
HTTPClient: tester.Client,
|
HTTPClient: tester.Client,
|
||||||
Logger: logger,
|
Logger: slog.New(zapslog.NewHandler(logger.Core())),
|
||||||
},
|
},
|
||||||
ChallengeSolvers: map[string]acmez.Solver{
|
ChallengeSolvers: map[string]acmez.Solver{
|
||||||
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
|
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
|
||||||
@@ -165,7 +167,7 @@ func TestACMEServerDenyPolicy(t *testing.T) {
|
|||||||
Client: &acme.Client{
|
Client: &acme.Client{
|
||||||
Directory: "https://acme.localhost:9443/acme/local/directory",
|
Directory: "https://acme.localhost:9443/acme/local/directory",
|
||||||
HTTPClient: tester.Client,
|
HTTPClient: tester.Client,
|
||||||
Logger: logger,
|
Logger: slog.New(zapslog.NewHandler(logger.Core())),
|
||||||
},
|
},
|
||||||
ChallengeSolvers: map[string]acmez.Solver{
|
ChallengeSolvers: map[string]acmez.Solver{
|
||||||
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
|
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
acme.example.com {
|
acme.example.com {
|
||||||
acme_server {
|
acme_server {
|
||||||
ca internal
|
ca internal
|
||||||
sign_with_root
|
sign_with_root
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
----------
|
----------
|
||||||
{
|
{
|
||||||
"apps": {
|
"apps": {
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
{
|
||||||
|
auto_https prefer_wildcard
|
||||||
|
}
|
||||||
|
|
||||||
|
*.example.com {
|
||||||
|
tls {
|
||||||
|
dns mock
|
||||||
|
}
|
||||||
|
respond "fallback"
|
||||||
|
}
|
||||||
|
|
||||||
|
foo.example.com {
|
||||||
|
respond "foo"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"foo.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "foo",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"*.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "fallback",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"automatic_https": {
|
||||||
|
"skip_certificates": [
|
||||||
|
"foo.example.com"
|
||||||
|
],
|
||||||
|
"prefer_wildcard": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"automation": {
|
||||||
|
"policies": [
|
||||||
|
{
|
||||||
|
"subjects": [
|
||||||
|
"*.example.com"
|
||||||
|
],
|
||||||
|
"issuers": [
|
||||||
|
{
|
||||||
|
"challenges": {
|
||||||
|
"dns": {
|
||||||
|
"provider": {
|
||||||
|
"name": "mock"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"module": "acme"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
{
|
||||||
|
auto_https prefer_wildcard
|
||||||
|
}
|
||||||
|
|
||||||
|
# Covers two domains
|
||||||
|
*.one.example.com {
|
||||||
|
tls {
|
||||||
|
dns mock
|
||||||
|
}
|
||||||
|
respond "one fallback"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Is covered, should not get its own AP
|
||||||
|
foo.one.example.com {
|
||||||
|
respond "foo one"
|
||||||
|
}
|
||||||
|
|
||||||
|
# This one has its own tls config so it doesn't get covered (escape hatch)
|
||||||
|
bar.one.example.com {
|
||||||
|
respond "bar one"
|
||||||
|
tls bar@bar.com
|
||||||
|
}
|
||||||
|
|
||||||
|
# Covers nothing but AP gets consolidated with the first
|
||||||
|
*.two.example.com {
|
||||||
|
tls {
|
||||||
|
dns mock
|
||||||
|
}
|
||||||
|
respond "two fallback"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Is HTTP so it should not cover
|
||||||
|
http://*.three.example.com {
|
||||||
|
respond "three fallback"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Has no wildcard coverage so it gets an AP
|
||||||
|
foo.three.example.com {
|
||||||
|
respond "foo three"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"foo.three.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "foo three",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"foo.one.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "foo one",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"bar.one.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "bar one",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"*.one.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "one fallback",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"*.two.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "two fallback",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"automatic_https": {
|
||||||
|
"skip_certificates": [
|
||||||
|
"foo.one.example.com",
|
||||||
|
"bar.one.example.com"
|
||||||
|
],
|
||||||
|
"prefer_wildcard": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"srv1": {
|
||||||
|
"listen": [
|
||||||
|
":80"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"*.three.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "three fallback",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"automatic_https": {
|
||||||
|
"prefer_wildcard": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"automation": {
|
||||||
|
"policies": [
|
||||||
|
{
|
||||||
|
"subjects": [
|
||||||
|
"foo.three.example.com"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"subjects": [
|
||||||
|
"bar.one.example.com"
|
||||||
|
],
|
||||||
|
"issuers": [
|
||||||
|
{
|
||||||
|
"email": "bar@bar.com",
|
||||||
|
"module": "acme"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ca": "https://acme.zerossl.com/v2/DV90",
|
||||||
|
"email": "bar@bar.com",
|
||||||
|
"module": "acme"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"subjects": [
|
||||||
|
"*.one.example.com",
|
||||||
|
"*.two.example.com"
|
||||||
|
],
|
||||||
|
"issuers": [
|
||||||
|
{
|
||||||
|
"challenges": {
|
||||||
|
"dns": {
|
||||||
|
"provider": {
|
||||||
|
"name": "mock"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"module": "acme"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
{
|
||||||
|
auto_https disable_redirects
|
||||||
|
admin off
|
||||||
|
}
|
||||||
|
|
||||||
|
http://localhost {
|
||||||
|
bind fd/{env.CADDY_HTTP_FD} {
|
||||||
|
protocols h1
|
||||||
|
}
|
||||||
|
log
|
||||||
|
respond "Hello, HTTP!"
|
||||||
|
}
|
||||||
|
|
||||||
|
https://localhost {
|
||||||
|
bind fd/{env.CADDY_HTTPS_FD} {
|
||||||
|
protocols h1 h2
|
||||||
|
}
|
||||||
|
bind fdgram/{env.CADDY_HTTP3_FD} {
|
||||||
|
protocols h3
|
||||||
|
}
|
||||||
|
log
|
||||||
|
respond "Hello, HTTPS!"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"admin": {
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
"fd/{env.CADDY_HTTPS_FD}",
|
||||||
|
"fdgram/{env.CADDY_HTTP3_FD}"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Hello, HTTPS!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"automatic_https": {
|
||||||
|
"disable_redirects": true
|
||||||
|
},
|
||||||
|
"logs": {
|
||||||
|
"logger_names": {
|
||||||
|
"localhost": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"listen_protocols": [
|
||||||
|
[
|
||||||
|
"h1",
|
||||||
|
"h2"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"h3"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"srv1": {
|
||||||
|
"automatic_https": {
|
||||||
|
"disable_redirects": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"srv2": {
|
||||||
|
"listen": [
|
||||||
|
"fd/{env.CADDY_HTTP_FD}"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Hello, HTTP!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"automatic_https": {
|
||||||
|
"disable_redirects": true,
|
||||||
|
"skip": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"logs": {
|
||||||
|
"logger_names": {
|
||||||
|
"localhost": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"listen_protocols": [
|
||||||
|
[
|
||||||
|
"h1"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,8 @@ encode {
|
|||||||
zstd
|
zstd
|
||||||
gzip 5
|
gzip 5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encode
|
||||||
----------
|
----------
|
||||||
{
|
{
|
||||||
"apps": {
|
"apps": {
|
||||||
@@ -76,6 +78,17 @@ encode {
|
|||||||
"zstd",
|
"zstd",
|
||||||
"gzip"
|
"gzip"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"encodings": {
|
||||||
|
"gzip": {},
|
||||||
|
"zstd": {}
|
||||||
|
},
|
||||||
|
"handler": "encode",
|
||||||
|
"prefer": [
|
||||||
|
"zstd",
|
||||||
|
"gzip"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
:80
|
||||||
|
|
||||||
|
file_server {
|
||||||
|
browse {
|
||||||
|
file_limit 4000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":80"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"browse": {
|
||||||
|
"file_limit": 4000
|
||||||
|
},
|
||||||
|
"handler": "file_server",
|
||||||
|
"hide": [
|
||||||
|
"./Caddyfile"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,10 @@
|
|||||||
file_server {
|
file_server {
|
||||||
precompressed zstd br gzip
|
precompressed zstd br gzip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
file_server {
|
||||||
|
precompressed
|
||||||
|
}
|
||||||
----------
|
----------
|
||||||
{
|
{
|
||||||
"apps": {
|
"apps": {
|
||||||
@@ -30,6 +34,22 @@ file_server {
|
|||||||
"br",
|
"br",
|
||||||
"gzip"
|
"gzip"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"handler": "file_server",
|
||||||
|
"hide": [
|
||||||
|
"./Caddyfile"
|
||||||
|
],
|
||||||
|
"precompressed": {
|
||||||
|
"br": {},
|
||||||
|
"gzip": {},
|
||||||
|
"zstd": {}
|
||||||
|
},
|
||||||
|
"precompressed_order": [
|
||||||
|
"br",
|
||||||
|
"zstd",
|
||||||
|
"gzip"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
:80
|
||||||
|
|
||||||
|
file_server {
|
||||||
|
browse {
|
||||||
|
sort size desc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":80"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"browse": {
|
||||||
|
"sort": [
|
||||||
|
"size",
|
||||||
|
"desc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"handler": "file_server",
|
||||||
|
"hide": [
|
||||||
|
"./Caddyfile"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
app.example.com {
|
app.example.com {
|
||||||
forward_auth authelia:9091 {
|
forward_auth authelia:9091 {
|
||||||
uri /api/verify?rd=https://authelia.example.com
|
uri /api/authz/forward-auth
|
||||||
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
|
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +39,13 @@ app.example.com {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"routes": [
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "vars"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
@@ -47,19 +54,104 @@ app.example.com {
|
|||||||
"set": {
|
"set": {
|
||||||
"Remote-Email": [
|
"Remote-Email": [
|
||||||
"{http.reverse_proxy.header.Remote-Email}"
|
"{http.reverse_proxy.header.Remote-Email}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"not": [
|
||||||
|
{
|
||||||
|
"vars": {
|
||||||
|
"{http.reverse_proxy.header.Remote-Email}": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "headers",
|
||||||
|
"request": {
|
||||||
|
"set": {
|
||||||
"Remote-Groups": [
|
"Remote-Groups": [
|
||||||
"{http.reverse_proxy.header.Remote-Groups}"
|
"{http.reverse_proxy.header.Remote-Groups}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"not": [
|
||||||
|
{
|
||||||
|
"vars": {
|
||||||
|
"{http.reverse_proxy.header.Remote-Groups}": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "headers",
|
||||||
|
"request": {
|
||||||
|
"set": {
|
||||||
"Remote-Name": [
|
"Remote-Name": [
|
||||||
"{http.reverse_proxy.header.Remote-Name}"
|
"{http.reverse_proxy.header.Remote-Name}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"not": [
|
||||||
|
{
|
||||||
|
"vars": {
|
||||||
|
"{http.reverse_proxy.header.Remote-Name}": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "headers",
|
||||||
|
"request": {
|
||||||
|
"set": {
|
||||||
"Remote-User": [
|
"Remote-User": [
|
||||||
"{http.reverse_proxy.header.Remote-User}"
|
"{http.reverse_proxy.header.Remote-User}"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"not": [
|
||||||
|
{
|
||||||
|
"vars": {
|
||||||
|
"{http.reverse_proxy.header.Remote-User}": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -80,7 +172,7 @@ app.example.com {
|
|||||||
},
|
},
|
||||||
"rewrite": {
|
"rewrite": {
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"uri": "/api/verify?rd=https://authelia.example.com"
|
"uri": "/api/authz/forward-auth"
|
||||||
},
|
},
|
||||||
"upstreams": [
|
"upstreams": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,6 +28,13 @@ forward_auth localhost:9000 {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"routes": [
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "vars"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
@@ -36,22 +43,131 @@ forward_auth localhost:9000 {
|
|||||||
"set": {
|
"set": {
|
||||||
"1": [
|
"1": [
|
||||||
"{http.reverse_proxy.header.A}"
|
"{http.reverse_proxy.header.A}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"3": [
|
"match": [
|
||||||
"{http.reverse_proxy.header.C}"
|
{
|
||||||
],
|
"not": [
|
||||||
"5": [
|
{
|
||||||
"{http.reverse_proxy.header.E}"
|
"vars": {
|
||||||
],
|
"{http.reverse_proxy.header.A}": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "headers",
|
||||||
|
"request": {
|
||||||
|
"set": {
|
||||||
"B": [
|
"B": [
|
||||||
"{http.reverse_proxy.header.B}"
|
"{http.reverse_proxy.header.B}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"not": [
|
||||||
|
{
|
||||||
|
"vars": {
|
||||||
|
"{http.reverse_proxy.header.B}": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "headers",
|
||||||
|
"request": {
|
||||||
|
"set": {
|
||||||
|
"3": [
|
||||||
|
"{http.reverse_proxy.header.C}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"not": [
|
||||||
|
{
|
||||||
|
"vars": {
|
||||||
|
"{http.reverse_proxy.header.C}": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "headers",
|
||||||
|
"request": {
|
||||||
|
"set": {
|
||||||
"D": [
|
"D": [
|
||||||
"{http.reverse_proxy.header.D}"
|
"{http.reverse_proxy.header.D}"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"not": [
|
||||||
|
{
|
||||||
|
"vars": {
|
||||||
|
"{http.reverse_proxy.header.D}": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "headers",
|
||||||
|
"request": {
|
||||||
|
"set": {
|
||||||
|
"5": [
|
||||||
|
"{http.reverse_proxy.header.E}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"not": [
|
||||||
|
{
|
||||||
|
"vars": {
|
||||||
|
"{http.reverse_proxy.header.E}": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
storage file_system {
|
storage file_system {
|
||||||
root /data
|
root /data
|
||||||
}
|
}
|
||||||
|
storage_check off
|
||||||
|
storage_clean_interval off
|
||||||
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
|
ocsp_stapling off
|
||||||
@@ -17,8 +19,6 @@
|
|||||||
admin off
|
admin off
|
||||||
on_demand_tls {
|
on_demand_tls {
|
||||||
ask https://example.com
|
ask https://example.com
|
||||||
interval 30s
|
|
||||||
burst 20
|
|
||||||
}
|
}
|
||||||
local_certs
|
local_certs
|
||||||
key_type ed25519
|
key_type ed25519
|
||||||
@@ -72,14 +72,12 @@
|
|||||||
"permission": {
|
"permission": {
|
||||||
"endpoint": "https://example.com",
|
"endpoint": "https://example.com",
|
||||||
"module": "http"
|
"module": "http"
|
||||||
},
|
|
||||||
"rate_limit": {
|
|
||||||
"interval": 30000000000,
|
|
||||||
"burst": 20
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"disable_ocsp_stapling": true
|
"disable_ocsp_stapling": true,
|
||||||
|
"disable_storage_check": true,
|
||||||
|
"disable_storage_clean": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,6 @@
|
|||||||
admin off
|
admin off
|
||||||
on_demand_tls {
|
on_demand_tls {
|
||||||
ask https://example.com
|
ask https://example.com
|
||||||
interval 30s
|
|
||||||
burst 20
|
|
||||||
}
|
}
|
||||||
storage_clean_interval 7d
|
storage_clean_interval 7d
|
||||||
renew_interval 1d
|
renew_interval 1d
|
||||||
@@ -89,10 +87,6 @@
|
|||||||
"permission": {
|
"permission": {
|
||||||
"endpoint": "https://example.com",
|
"endpoint": "https://example.com",
|
||||||
"module": "http"
|
"module": "http"
|
||||||
},
|
|
||||||
"rate_limit": {
|
|
||||||
"interval": 30000000000,
|
|
||||||
"burst": 20
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ocsp_interval": 172800000000000,
|
"ocsp_interval": 172800000000000,
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
}
|
}
|
||||||
on_demand_tls {
|
on_demand_tls {
|
||||||
ask https://example.com
|
ask https://example.com
|
||||||
interval 30s
|
|
||||||
burst 20
|
|
||||||
}
|
}
|
||||||
local_certs
|
local_certs
|
||||||
key_type ed25519
|
key_type ed25519
|
||||||
@@ -74,10 +72,6 @@
|
|||||||
"permission": {
|
"permission": {
|
||||||
"endpoint": "https://example.com",
|
"endpoint": "https://example.com",
|
||||||
"module": "http"
|
"module": "http"
|
||||||
},
|
|
||||||
"rate_limit": {
|
|
||||||
"interval": 30000000000,
|
|
||||||
"burst": 20
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
log {
|
||||||
|
sampling {
|
||||||
|
interval 300
|
||||||
|
first 50
|
||||||
|
thereafter 40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"logging": {
|
||||||
|
"logs": {
|
||||||
|
"default": {
|
||||||
|
"sampling": {
|
||||||
|
"interval": 300,
|
||||||
|
"first": 50,
|
||||||
|
"thereafter": 40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,10 +12,14 @@
|
|||||||
@images path /images/*
|
@images path /images/*
|
||||||
header @images {
|
header @images {
|
||||||
Cache-Control "public, max-age=3600, stale-while-revalidate=86400"
|
Cache-Control "public, max-age=3600, stale-while-revalidate=86400"
|
||||||
|
match {
|
||||||
|
status 200
|
||||||
|
}
|
||||||
}
|
}
|
||||||
header {
|
header {
|
||||||
+Link "Foo"
|
+Link "Foo"
|
||||||
+Link "Bar"
|
+Link "Bar"
|
||||||
|
match status 200
|
||||||
}
|
}
|
||||||
header >Set Defer
|
header >Set Defer
|
||||||
header >Replace Deferred Replacement
|
header >Replace Deferred Replacement
|
||||||
@@ -42,6 +46,11 @@
|
|||||||
{
|
{
|
||||||
"handler": "headers",
|
"handler": "headers",
|
||||||
"response": {
|
"response": {
|
||||||
|
"require": {
|
||||||
|
"status_code": [
|
||||||
|
200
|
||||||
|
]
|
||||||
|
},
|
||||||
"set": {
|
"set": {
|
||||||
"Cache-Control": [
|
"Cache-Control": [
|
||||||
"public, max-age=3600, stale-while-revalidate=86400"
|
"public, max-age=3600, stale-while-revalidate=86400"
|
||||||
@@ -136,6 +145,11 @@
|
|||||||
"Foo",
|
"Foo",
|
||||||
"Bar"
|
"Bar"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"status_code": [
|
||||||
|
200
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ example.com {
|
|||||||
</html>
|
</html>
|
||||||
EOF 200
|
EOF 200
|
||||||
}
|
}
|
||||||
|
|
||||||
----------
|
----------
|
||||||
{
|
{
|
||||||
"apps": {
|
"apps": {
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
:80 {
|
||||||
|
log {
|
||||||
|
sampling {
|
||||||
|
interval 300
|
||||||
|
first 50
|
||||||
|
thereafter 40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"logging": {
|
||||||
|
"logs": {
|
||||||
|
"default": {
|
||||||
|
"exclude": [
|
||||||
|
"http.log.access.log0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"log0": {
|
||||||
|
"sampling": {
|
||||||
|
"interval": 300,
|
||||||
|
"first": 50,
|
||||||
|
"thereafter": 40
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"http.log.access.log0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":80"
|
||||||
|
],
|
||||||
|
"logs": {
|
||||||
|
"default_logger_name": "log0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ vars {
|
|||||||
ghi 2.3
|
ghi 2.3
|
||||||
jkl "mn op"
|
jkl "mn op"
|
||||||
}
|
}
|
||||||
|
|
||||||
----------
|
----------
|
||||||
{
|
{
|
||||||
"apps": {
|
"apps": {
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
metrics
|
||||||
|
servers :80 {
|
||||||
|
metrics {
|
||||||
|
per_host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:80 {
|
||||||
|
respond "Hello"
|
||||||
|
}
|
||||||
|
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":80"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Hello",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metrics": {
|
||||||
|
"per_host": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
servers :80 {
|
||||||
|
metrics {
|
||||||
|
per_host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:80 {
|
||||||
|
respond "Hello"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":80"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Hello",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metrics": {
|
||||||
|
"per_host": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ route {
|
|||||||
}
|
}
|
||||||
not path */
|
not path */
|
||||||
}
|
}
|
||||||
redir @canonicalPath {http.request.orig_uri.path}/ 308
|
redir @canonicalPath {orig_path}/{orig_?query} 308
|
||||||
|
|
||||||
# If the requested file does not exist, try index files
|
# If the requested file does not exist, try index files
|
||||||
@indexFiles {
|
@indexFiles {
|
||||||
@@ -17,7 +17,7 @@ route {
|
|||||||
split_path .php
|
split_path .php
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rewrite @indexFiles {http.matchers.file.relative}
|
rewrite @indexFiles {file_match.relative}
|
||||||
|
|
||||||
# Proxy PHP files to the FastCGI responder
|
# Proxy PHP files to the FastCGI responder
|
||||||
@phpFiles {
|
@phpFiles {
|
||||||
@@ -50,7 +50,7 @@ route {
|
|||||||
"handler": "static_response",
|
"handler": "static_response",
|
||||||
"headers": {
|
"headers": {
|
||||||
"Location": [
|
"Location": [
|
||||||
"{http.request.orig_uri.path}/"
|
"{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"status_code": 308
|
"status_code": 308
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"handler": "static_response",
|
"handler": "static_response",
|
||||||
"headers": {
|
"headers": {
|
||||||
"Location": [
|
"Location": [
|
||||||
"{http.request.orig_uri.path}/"
|
"{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"status_code": 308
|
"status_code": 308
|
||||||
@@ -58,6 +58,7 @@
|
|||||||
"{http.request.uri.path}/index.php",
|
"{http.request.uri.path}/index.php",
|
||||||
"index.php"
|
"index.php"
|
||||||
],
|
],
|
||||||
|
"try_policy": "first_exist_fallback",
|
||||||
"split_path": [
|
"split_path": [
|
||||||
".php"
|
".php"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ php_fastcgi @test localhost:9000
|
|||||||
"handler": "static_response",
|
"handler": "static_response",
|
||||||
"headers": {
|
"headers": {
|
||||||
"Location": [
|
"Location": [
|
||||||
"{http.request.orig_uri.path}/"
|
"{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"status_code": 308
|
"status_code": 308
|
||||||
@@ -73,7 +73,8 @@ php_fastcgi @test localhost:9000
|
|||||||
"{http.request.uri.path}",
|
"{http.request.uri.path}",
|
||||||
"{http.request.uri.path}/index.php",
|
"{http.request.uri.path}/index.php",
|
||||||
"index.php"
|
"index.php"
|
||||||
]
|
],
|
||||||
|
"try_policy": "first_exist_fallback"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ php_fastcgi localhost:9000 {
|
|||||||
"handler": "static_response",
|
"handler": "static_response",
|
||||||
"headers": {
|
"headers": {
|
||||||
"Location": [
|
"Location": [
|
||||||
"{http.request.orig_uri.path}/"
|
"{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"status_code": 308
|
"status_code": 308
|
||||||
@@ -59,6 +59,7 @@ php_fastcgi localhost:9000 {
|
|||||||
"{http.request.uri.path}/index.php5",
|
"{http.request.uri.path}/index.php5",
|
||||||
"index.php5"
|
"index.php5"
|
||||||
],
|
],
|
||||||
|
"try_policy": "first_exist_fallback",
|
||||||
"split_path": [
|
"split_path": [
|
||||||
".php",
|
".php",
|
||||||
".php5"
|
".php5"
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ php_fastcgi localhost:9000 {
|
|||||||
"handler": "static_response",
|
"handler": "static_response",
|
||||||
"headers": {
|
"headers": {
|
||||||
"Location": [
|
"Location": [
|
||||||
"{http.request.orig_uri.path}/"
|
"{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"status_code": 308
|
"status_code": 308
|
||||||
|
|||||||
+95
@@ -0,0 +1,95 @@
|
|||||||
|
: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} index.php
|
||||||
|
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"
|
||||||
|
],
|
||||||
|
"try_policy": "first_exist_fallback",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
:8884
|
||||||
|
|
||||||
|
reverse_proxy 127.0.0.1:65535 {
|
||||||
|
health_uri /health
|
||||||
|
health_method HEAD
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":8884"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "reverse_proxy",
|
||||||
|
"health_checks": {
|
||||||
|
"active": {
|
||||||
|
"method": "HEAD",
|
||||||
|
"uri": "/health"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"upstreams": [
|
||||||
|
{
|
||||||
|
"dial": "127.0.0.1:65535"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
:8884
|
||||||
|
|
||||||
|
reverse_proxy 127.0.0.1:65535 {
|
||||||
|
health_uri /health
|
||||||
|
health_request_body "test body"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":8884"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "reverse_proxy",
|
||||||
|
"health_checks": {
|
||||||
|
"active": {
|
||||||
|
"body": "test body",
|
||||||
|
"uri": "/health"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"upstreams": [
|
||||||
|
{
|
||||||
|
"dial": "127.0.0.1:65535"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
https://example.com {
|
||||||
|
reverse_proxy http://localhost:54321 {
|
||||||
|
transport http {
|
||||||
|
local_address 192.168.0.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "reverse_proxy",
|
||||||
|
"transport": {
|
||||||
|
"local_address": "192.168.0.1",
|
||||||
|
"protocol": "http"
|
||||||
|
},
|
||||||
|
"upstreams": [
|
||||||
|
{
|
||||||
|
"dial": "localhost:54321"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+180
@@ -0,0 +1,180 @@
|
|||||||
|
automated1.example.com {
|
||||||
|
tls force_automate
|
||||||
|
respond "Automated!"
|
||||||
|
}
|
||||||
|
|
||||||
|
automated2.example.com {
|
||||||
|
tls force_automate
|
||||||
|
respond "Automated!"
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowed.example.com {
|
||||||
|
respond "Shadowed!"
|
||||||
|
}
|
||||||
|
|
||||||
|
*.example.com {
|
||||||
|
tls cert.pem key.pem
|
||||||
|
respond "Wildcard!"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"automated1.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Automated!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"automated2.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Automated!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"shadowed.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Shadowed!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"*.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Wildcard!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tls_connection_policies": [
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"sni": [
|
||||||
|
"automated1.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"sni": [
|
||||||
|
"automated2.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"sni": [
|
||||||
|
"*.example.com"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"certificate_selection": {
|
||||||
|
"any_tag": [
|
||||||
|
"cert0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"certificates": {
|
||||||
|
"automate": [
|
||||||
|
"automated1.example.com",
|
||||||
|
"automated2.example.com"
|
||||||
|
],
|
||||||
|
"load_files": [
|
||||||
|
{
|
||||||
|
"certificate": "cert.pem",
|
||||||
|
"key": "key.pem",
|
||||||
|
"tags": [
|
||||||
|
"cert0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+102
@@ -0,0 +1,102 @@
|
|||||||
|
subdomain.example.com {
|
||||||
|
respond "Subdomain!"
|
||||||
|
}
|
||||||
|
|
||||||
|
*.example.com {
|
||||||
|
tls cert.pem key.pem
|
||||||
|
respond "Wildcard!"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"subdomain.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Subdomain!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"*.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Wildcard!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tls_connection_policies": [
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"sni": [
|
||||||
|
"*.example.com"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"certificate_selection": {
|
||||||
|
"any_tag": [
|
||||||
|
"cert0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"certificates": {
|
||||||
|
"load_files": [
|
||||||
|
{
|
||||||
|
"certificate": "cert.pem",
|
||||||
|
"key": "key.pem",
|
||||||
|
"tags": [
|
||||||
|
"cert0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
*.example.com {
|
||||||
|
tls foo@example.com {
|
||||||
|
dns mock
|
||||||
|
}
|
||||||
|
|
||||||
|
@foo host foo.example.com
|
||||||
|
handle @foo {
|
||||||
|
respond "Foo!"
|
||||||
|
}
|
||||||
|
|
||||||
|
@bar host bar.example.com
|
||||||
|
handle @bar {
|
||||||
|
respond "Bar!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fallback for otherwise unhandled domains
|
||||||
|
handle {
|
||||||
|
abort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"*.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"group": "group3",
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Foo!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"foo.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "group3",
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Bar!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"bar.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "group3",
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"abort": true,
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"automation": {
|
||||||
|
"policies": [
|
||||||
|
{
|
||||||
|
"subjects": [
|
||||||
|
"*.example.com"
|
||||||
|
],
|
||||||
|
"issuers": [
|
||||||
|
{
|
||||||
|
"challenges": {
|
||||||
|
"dns": {
|
||||||
|
"provider": {
|
||||||
|
"name": "mock"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": "foo@example.com",
|
||||||
|
"module": "acme"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ca": "https://acme.zerossl.com/v2/DV90",
|
||||||
|
"challenges": {
|
||||||
|
"dns": {
|
||||||
|
"provider": {
|
||||||
|
"name": "mock"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": "foo@example.com",
|
||||||
|
"module": "acme"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,17 +18,23 @@ func TestIntercept(t *testing.T) {
|
|||||||
|
|
||||||
localhost:9080 {
|
localhost:9080 {
|
||||||
respond /intercept "I'm a teapot" 408
|
respond /intercept "I'm a teapot" 408
|
||||||
|
header /intercept To-Intercept ok
|
||||||
respond /no-intercept "I'm not a teapot"
|
respond /no-intercept "I'm not a teapot"
|
||||||
|
|
||||||
intercept {
|
intercept {
|
||||||
@teapot status 408
|
@teapot status 408
|
||||||
handle_response @teapot {
|
handle_response @teapot {
|
||||||
|
header /intercept intercepted {resp.header.To-Intercept}
|
||||||
respond /intercept "I'm a combined coffee/tea pot that is temporarily out of coffee" 503
|
respond /intercept "I'm a combined coffee/tea pot that is temporarily out of coffee" 503
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, "caddyfile")
|
`, "caddyfile")
|
||||||
|
|
||||||
tester.AssertGetResponse("http://localhost:9080/intercept", 503, "I'm a combined coffee/tea pot that is temporarily out of coffee")
|
r, _ := tester.AssertGetResponse("http://localhost:9080/intercept", 503, "I'm a combined coffee/tea pot that is temporarily out of coffee")
|
||||||
|
if r.Header.Get("intercepted") != "ok" {
|
||||||
|
t.Fatalf(`header "intercepted" value is not "ok": %s`, r.Header.Get("intercepted"))
|
||||||
|
}
|
||||||
|
|
||||||
tester.AssertGetResponse("http://localhost:9080/no-intercept", 200, "I'm not a teapot")
|
tester.AssertGetResponse("http://localhost:9080/no-intercept", 200, "I'm not a teapot")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
|
"github.com/caddyserver/certmagic"
|
||||||
|
"github.com/libdns/libdns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
caddy.RegisterModule(MockDNSProvider{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockDNSProvider is a mock DNS provider, for testing config with DNS modules.
|
||||||
|
type MockDNSProvider struct{}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (MockDNSProvider) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
ID: "dns.providers.mock",
|
||||||
|
New: func() caddy.Module { return new(MockDNSProvider) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision sets up the module.
|
||||||
|
func (MockDNSProvider) Provision(ctx caddy.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
|
||||||
|
func (MockDNSProvider) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendRecords appends DNS records to the zone.
|
||||||
|
func (MockDNSProvider) AppendRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRecords deletes DNS records from the zone.
|
||||||
|
func (MockDNSProvider) DeleteRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecords gets DNS records from the zone.
|
||||||
|
func (MockDNSProvider) GetRecords(ctx context.Context, zone string) ([]libdns.Record, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRecords sets DNS records in the zone.
|
||||||
|
func (MockDNSProvider) SetRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface guard
|
||||||
|
var _ caddyfile.Unmarshaler = (*MockDNSProvider)(nil)
|
||||||
|
var _ certmagic.DNSProvider = (*MockDNSProvider)(nil)
|
||||||
|
var _ caddy.Provisioner = (*MockDNSProvider)(nil)
|
||||||
|
var _ caddy.Module = (*MockDNSProvider)(nil)
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
foo
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
foo
|
||||||
+6
-2
@@ -8,7 +8,8 @@ import (
|
|||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var defaultFactory = newRootCommandFactory(func() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
Use: "caddy",
|
Use: "caddy",
|
||||||
Long: `Caddy is an extensible server platform written in Go.
|
Long: `Caddy is an extensible server platform written in Go.
|
||||||
|
|
||||||
@@ -100,14 +101,17 @@ https://caddyserver.com/docs/running
|
|||||||
// caddy has an error provisioning its modules, for instance...
|
// caddy has an error provisioning its modules, for instance...
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
Version: onlyVersionText(),
|
Version: onlyVersionText(),
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const fullDocsFooter = `Full documentation is available at:
|
const fullDocsFooter = `Full documentation is available at:
|
||||||
https://caddyserver.com/docs/command-line`
|
https://caddyserver.com/docs/command-line`
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
defaultFactory.Use(func(rootCmd *cobra.Command) {
|
||||||
rootCmd.SetVersionTemplate("{{.Version}}\n")
|
rootCmd.SetVersionTemplate("{{.Version}}\n")
|
||||||
rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n")
|
rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func onlyVersionText() string {
|
func onlyVersionText() string {
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package caddycmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rootCommandFactory struct {
|
||||||
|
constructor func() *cobra.Command
|
||||||
|
options []func(*cobra.Command)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRootCommandFactory(fn func() *cobra.Command) *rootCommandFactory {
|
||||||
|
return &rootCommandFactory{
|
||||||
|
constructor: fn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *rootCommandFactory) Use(fn func(cmd *cobra.Command)) {
|
||||||
|
f.options = append(f.options, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *rootCommandFactory) Build() *cobra.Command {
|
||||||
|
o := f.constructor()
|
||||||
|
for _, v := range f.options {
|
||||||
|
v(o)
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
+32
-17
@@ -74,6 +74,10 @@ func cmdStart(fl Flags) (int, error) {
|
|||||||
// sure by giving it some random bytes and having it echo
|
// sure by giving it some random bytes and having it echo
|
||||||
// them back to us)
|
// them back to us)
|
||||||
cmd := exec.Command(os.Args[0], "run", "--pingback", ln.Addr().String())
|
cmd := exec.Command(os.Args[0], "run", "--pingback", ln.Addr().String())
|
||||||
|
// we should be able to run caddy in relative paths
|
||||||
|
if errors.Is(cmd.Err, exec.ErrDot) {
|
||||||
|
cmd.Err = nil
|
||||||
|
}
|
||||||
if configFlag != "" {
|
if configFlag != "" {
|
||||||
cmd.Args = append(cmd.Args, "--config", configFlag)
|
cmd.Args = append(cmd.Args, "--config", configFlag)
|
||||||
}
|
}
|
||||||
@@ -167,6 +171,10 @@ func cmdStart(fl Flags) (int, error) {
|
|||||||
func cmdRun(fl Flags) (int, error) {
|
func cmdRun(fl Flags) (int, error) {
|
||||||
caddy.TrapSignals()
|
caddy.TrapSignals()
|
||||||
|
|
||||||
|
logger := caddy.Log()
|
||||||
|
undoMaxProcs := setResourceLimits(logger)
|
||||||
|
defer undoMaxProcs()
|
||||||
|
|
||||||
configFlag := fl.String("config")
|
configFlag := fl.String("config")
|
||||||
configAdapterFlag := fl.String("adapter")
|
configAdapterFlag := fl.String("adapter")
|
||||||
resumeFlag := fl.Bool("resume")
|
resumeFlag := fl.Bool("resume")
|
||||||
@@ -192,18 +200,18 @@ func cmdRun(fl Flags) (int, error) {
|
|||||||
config, err = os.ReadFile(caddy.ConfigAutosavePath)
|
config, err = os.ReadFile(caddy.ConfigAutosavePath)
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
// 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))
|
logger.Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath))
|
||||||
resumeFlag = false
|
resumeFlag = false
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return caddy.ExitCodeFailedStartup, err
|
return caddy.ExitCodeFailedStartup, err
|
||||||
} else {
|
} else {
|
||||||
if configFlag == "" {
|
if configFlag == "" {
|
||||||
caddy.Log().Info("resuming from last configuration",
|
logger.Info("resuming from last configuration",
|
||||||
zap.String("autosave_file", caddy.ConfigAutosavePath))
|
zap.String("autosave_file", caddy.ConfigAutosavePath))
|
||||||
} else {
|
} else {
|
||||||
// if they also specified a config file, user should be aware that we're not
|
// if they also specified a config file, user should be aware that we're not
|
||||||
// using it (doing so could lead to data/config loss by overwriting!)
|
// using it (doing so could lead to data/config loss by overwriting!)
|
||||||
caddy.Log().Warn("--config and --resume flags were used together; ignoring --config and resuming from last configuration",
|
logger.Warn("--config and --resume flags were used together; ignoring --config and resuming from last configuration",
|
||||||
zap.String("autosave_file", caddy.ConfigAutosavePath))
|
zap.String("autosave_file", caddy.ConfigAutosavePath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,7 +229,7 @@ func cmdRun(fl Flags) (int, error) {
|
|||||||
if pidfileFlag != "" {
|
if pidfileFlag != "" {
|
||||||
err := caddy.PIDFile(pidfileFlag)
|
err := caddy.PIDFile(pidfileFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
caddy.Log().Error("unable to write PID file",
|
logger.Error("unable to write PID file",
|
||||||
zap.String("pidfile", pidfileFlag),
|
zap.String("pidfile", pidfileFlag),
|
||||||
zap.Error(err))
|
zap.Error(err))
|
||||||
}
|
}
|
||||||
@@ -232,7 +240,7 @@ func cmdRun(fl Flags) (int, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("loading initial config: %v", err)
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("loading initial config: %v", err)
|
||||||
}
|
}
|
||||||
caddy.Log().Info("serving initial configuration")
|
logger.Info("serving initial configuration")
|
||||||
|
|
||||||
// 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
|
||||||
@@ -268,15 +276,15 @@ func cmdRun(fl Flags) (int, error) {
|
|||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "windows":
|
case "windows":
|
||||||
if os.Getenv("HOME") == "" && os.Getenv("USERPROFILE") == "" && !hasXDG {
|
if os.Getenv("HOME") == "" && os.Getenv("USERPROFILE") == "" && !hasXDG {
|
||||||
caddy.Log().Warn("neither HOME nor USERPROFILE environment variables are set - please fix; some assets might be stored in ./caddy")
|
logger.Warn("neither HOME nor USERPROFILE environment variables are set - please fix; some assets might be stored in ./caddy")
|
||||||
}
|
}
|
||||||
case "plan9":
|
case "plan9":
|
||||||
if os.Getenv("home") == "" && !hasXDG {
|
if os.Getenv("home") == "" && !hasXDG {
|
||||||
caddy.Log().Warn("$home environment variable is empty - please fix; some assets might be stored in ./caddy")
|
logger.Warn("$home environment variable is empty - please fix; some assets might be stored in ./caddy")
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if os.Getenv("HOME") == "" && !hasXDG {
|
if os.Getenv("HOME") == "" && !hasXDG {
|
||||||
caddy.Log().Warn("$HOME environment variable is empty - please fix; some assets might be stored in ./caddy")
|
logger.Warn("$HOME environment variable is empty - please fix; some assets might be stored in ./caddy")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,10 +564,15 @@ func cmdValidateConfig(fl Flags) (int, error) {
|
|||||||
|
|
||||||
func cmdFmt(fl Flags) (int, error) {
|
func cmdFmt(fl Flags) (int, error) {
|
||||||
configFile := fl.Arg(0)
|
configFile := fl.Arg(0)
|
||||||
if configFile == "" {
|
configFlag := fl.String("config")
|
||||||
configFile = "Caddyfile"
|
if (len(fl.Args()) > 1) || (configFlag != "" && configFile != "") {
|
||||||
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("fmt does not support multiple files %s %s", configFlag, strings.Join(fl.Args(), " "))
|
||||||
|
}
|
||||||
|
if configFile == "" && configFlag == "" {
|
||||||
|
configFile = "Caddyfile"
|
||||||
|
} else if configFile == "" {
|
||||||
|
configFile = configFlag
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 configFile == "-" {
|
if configFile == "-" {
|
||||||
input, err := io.ReadAll(os.Stdin)
|
input, err := io.ReadAll(os.Stdin)
|
||||||
@@ -656,6 +669,8 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
parsedAddr.Host = addr
|
parsedAddr.Host = addr
|
||||||
|
} else if parsedAddr.IsFdNetwork() {
|
||||||
|
origin = "http://127.0.0.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
// form the request
|
// form the request
|
||||||
@@ -663,13 +678,13 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("making request: %v", err)
|
return nil, fmt.Errorf("making request: %v", err)
|
||||||
}
|
}
|
||||||
if parsedAddr.IsUnixNetwork() {
|
if parsedAddr.IsUnixNetwork() || parsedAddr.IsFdNetwork() {
|
||||||
// We used to conform to RFC 2616 Section 14.26 which requires
|
// We used to conform to RFC 2616 Section 14.26 which requires
|
||||||
// an empty host header when there is no host, as is the case
|
// an empty host header when there is no host, as is the case
|
||||||
// with unix sockets. However, Go required a Host value so we
|
// with unix sockets and socket fds. However, Go required a
|
||||||
// used a hack of a space character as the host (it would see
|
// Host value so we used a hack of a space character as the host
|
||||||
// the Host was non-empty, then trim the space later). As of
|
// (it would see the Host was non-empty, then trim the space later).
|
||||||
// Go 1.20.6 (July 2023), this hack no longer works. See:
|
// As of Go 1.20.6 (July 2023), this hack no longer works. See:
|
||||||
// https://github.com/golang/go/issues/60374
|
// https://github.com/golang/go/issues/60374
|
||||||
// See also the discussion here:
|
// See also the discussion here:
|
||||||
// https://github.com/golang/go/issues/61431
|
// https://github.com/golang/go/issues/61431
|
||||||
@@ -710,7 +725,7 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io
|
|||||||
|
|
||||||
// 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 := io.ReadAll(io.LimitReader(resp.Body, 1024*1024*2))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
|
return nil, fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-5
@@ -388,6 +388,7 @@ When reading from stdin, the --overwrite flag has no effect: the result
|
|||||||
is always printed to stdout.
|
is always printed to stdout.
|
||||||
`,
|
`,
|
||||||
CobraFunc: func(cmd *cobra.Command) {
|
CobraFunc: func(cmd *cobra.Command) {
|
||||||
|
cmd.Flags().StringP("config", "c", "", "Configuration file")
|
||||||
cmd.Flags().BoolP("overwrite", "w", false, "Overwrite the input file with the results")
|
cmd.Flags().BoolP("overwrite", "w", false, "Overwrite the input file with the results")
|
||||||
cmd.Flags().BoolP("diff", "d", false, "Print the differences between the input file and the formatted output")
|
cmd.Flags().BoolP("diff", "d", false, "Print the differences between the input file and the formatted output")
|
||||||
cmd.RunE = WrapCommandFuncForCobra(cmdFmt)
|
cmd.RunE = WrapCommandFuncForCobra(cmdFmt)
|
||||||
@@ -409,12 +410,13 @@ latest versions. EXPERIMENTAL: May be changed or removed.
|
|||||||
|
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "add-package",
|
Name: "add-package",
|
||||||
Usage: "<packages...>",
|
Usage: "<package[@version]...>",
|
||||||
Short: "Adds Caddy packages (EXPERIMENTAL)",
|
Short: "Adds Caddy packages (EXPERIMENTAL)",
|
||||||
Long: `
|
Long: `
|
||||||
Downloads an updated Caddy binary with the specified packages (module/plugin)
|
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, with an optional version specified (e.g., "package@version"). Retains
|
||||||
already included. EXPERIMENTAL: May be changed or removed.
|
existing packages. Returns an error if any of the specified packages are already
|
||||||
|
included. EXPERIMENTAL: May be changed or removed.
|
||||||
`,
|
`,
|
||||||
CobraFunc: func(cmd *cobra.Command) {
|
CobraFunc: func(cmd *cobra.Command) {
|
||||||
cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it")
|
cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it")
|
||||||
@@ -438,7 +440,8 @@ EXPERIMENTAL: May be changed or removed.
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterCommand(Command{
|
defaultFactory.Use(func(rootCmd *cobra.Command) {
|
||||||
|
rootCmd.AddCommand(caddyCmdToCobra(Command{
|
||||||
Name: "manpage",
|
Name: "manpage",
|
||||||
Usage: "--directory <path>",
|
Usage: "--directory <path>",
|
||||||
Short: "Generates the manual pages for Caddy commands",
|
Short: "Generates the manual pages for Caddy commands",
|
||||||
@@ -468,7 +471,7 @@ argument of --directory. If the directory does not exist, it will be created.
|
|||||||
return caddy.ExitCodeSuccess, nil
|
return caddy.ExitCodeSuccess, nil
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
}))
|
||||||
|
|
||||||
// source: https://github.com/spf13/cobra/blob/main/shell_completions.md
|
// source: https://github.com/spf13/cobra/blob/main/shell_completions.md
|
||||||
rootCmd.AddCommand(&cobra.Command{
|
rootCmd.AddCommand(&cobra.Command{
|
||||||
@@ -531,6 +534,7 @@ argument of --directory. If the directory does not exist, it will be created.
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterCommand registers the command cmd.
|
// RegisterCommand registers the command cmd.
|
||||||
@@ -563,7 +567,9 @@ func RegisterCommand(cmd Command) {
|
|||||||
if !commandNameRegex.MatchString(cmd.Name) {
|
if !commandNameRegex.MatchString(cmd.Name) {
|
||||||
panic("invalid command name")
|
panic("invalid command name")
|
||||||
}
|
}
|
||||||
|
defaultFactory.Use(func(rootCmd *cobra.Command) {
|
||||||
rootCmd.AddCommand(caddyCmdToCobra(cmd))
|
rootCmd.AddCommand(caddyCmdToCobra(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]$`)
|
||||||
|
|||||||
+29
-7
@@ -24,6 +24,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -33,10 +34,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/KimMachineGun/automemlimit/memlimit"
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"go.uber.org/automaxprocs/maxprocs"
|
"go.uber.org/automaxprocs/maxprocs"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/exp/zapslog"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
@@ -66,13 +69,7 @@ func Main() {
|
|||||||
os.Exit(caddy.ExitCodeFailedStartup)
|
os.Exit(caddy.ExitCodeFailedStartup)
|
||||||
}
|
}
|
||||||
|
|
||||||
undo, err := maxprocs.Set()
|
if err := defaultFactory.Build().Execute(); err != nil {
|
||||||
defer undo()
|
|
||||||
if err != nil {
|
|
||||||
caddy.Log().Warn("failed to set GOMAXPROCS", zap.Error(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := rootCmd.Execute(); err != nil {
|
|
||||||
var exitError *exitError
|
var exitError *exitError
|
||||||
if errors.As(err, &exitError) {
|
if errors.As(err, &exitError) {
|
||||||
os.Exit(exitError.ExitCode)
|
os.Exit(exitError.ExitCode)
|
||||||
@@ -467,6 +464,31 @@ func printEnvironment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setResourceLimits(logger *zap.Logger) func() {
|
||||||
|
// Configure the maximum number of CPUs to use to match the Linux container quota (if any)
|
||||||
|
// See https://pkg.go.dev/runtime#GOMAXPROCS
|
||||||
|
undo, err := maxprocs.Set(maxprocs.Logger(logger.Sugar().Infof))
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("failed to set GOMAXPROCS", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure the maximum memory to use to match the Linux container quota (if any) or system memory
|
||||||
|
// See https://pkg.go.dev/runtime/debug#SetMemoryLimit
|
||||||
|
_, _ = memlimit.SetGoMemLimitWithOpts(
|
||||||
|
memlimit.WithLogger(
|
||||||
|
slog.New(zapslog.NewHandler(logger.Core())),
|
||||||
|
),
|
||||||
|
memlimit.WithProvider(
|
||||||
|
memlimit.ApplyFallback(
|
||||||
|
memlimit.FromCgroup,
|
||||||
|
memlimit.FromSystem,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return undo
|
||||||
|
}
|
||||||
|
|
||||||
// StringSlice is a flag.Value that enables repeated use of a string flag.
|
// StringSlice is a flag.Value that enables repeated use of a string flag.
|
||||||
type StringSlice []string
|
type StringSlice []string
|
||||||
|
|
||||||
|
|||||||
+49
-9
@@ -46,6 +46,25 @@ func cmdUpgrade(fl Flags) (int, error) {
|
|||||||
return upgradeBuild(pluginPkgs, fl)
|
return upgradeBuild(pluginPkgs, fl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func splitModule(arg string) (module, version string, err error) {
|
||||||
|
const versionSplit = "@"
|
||||||
|
|
||||||
|
// accommodate module paths that have @ in them, but we can only tolerate that if there's also
|
||||||
|
// a version, otherwise we don't know if it's a version separator or part of the file path
|
||||||
|
lastVersionSplit := strings.LastIndex(arg, versionSplit)
|
||||||
|
if lastVersionSplit < 0 {
|
||||||
|
module = arg
|
||||||
|
} else {
|
||||||
|
module, version = arg[:lastVersionSplit], arg[lastVersionSplit+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if module == "" {
|
||||||
|
err = fmt.Errorf("module name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func cmdAddPackage(fl Flags) (int, error) {
|
func cmdAddPackage(fl Flags) (int, error) {
|
||||||
if len(fl.Args()) == 0 {
|
if len(fl.Args()) == 0 {
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("at least one package name must be specified")
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("at least one package name must be specified")
|
||||||
@@ -60,10 +79,15 @@ func cmdAddPackage(fl Flags) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, arg := range fl.Args() {
|
for _, arg := range fl.Args() {
|
||||||
if _, ok := pluginPkgs[arg]; ok {
|
module, version, err := splitModule(arg)
|
||||||
|
if err != nil {
|
||||||
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err)
|
||||||
|
}
|
||||||
|
// only allow a version to be specified if it's different from the existing version
|
||||||
|
if _, ok := pluginPkgs[module]; ok && !(version != "" && pluginPkgs[module].Version != version) {
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("package is already added")
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("package is already added")
|
||||||
}
|
}
|
||||||
pluginPkgs[arg] = struct{}{}
|
pluginPkgs[module] = pluginPackage{Version: version, Path: module}
|
||||||
}
|
}
|
||||||
|
|
||||||
return upgradeBuild(pluginPkgs, fl)
|
return upgradeBuild(pluginPkgs, fl)
|
||||||
@@ -83,7 +107,11 @@ func cmdRemovePackage(fl Flags) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, arg := range fl.Args() {
|
for _, arg := range fl.Args() {
|
||||||
if _, ok := pluginPkgs[arg]; !ok {
|
module, _, err := splitModule(arg)
|
||||||
|
if err != nil {
|
||||||
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err)
|
||||||
|
}
|
||||||
|
if _, ok := pluginPkgs[module]; !ok {
|
||||||
// package does not exist
|
// package does not exist
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("package is not added")
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("package is not added")
|
||||||
}
|
}
|
||||||
@@ -93,7 +121,7 @@ func cmdRemovePackage(fl Flags) (int, error) {
|
|||||||
return upgradeBuild(pluginPkgs, fl)
|
return upgradeBuild(pluginPkgs, fl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func upgradeBuild(pluginPkgs map[string]struct{}, fl Flags) (int, error) {
|
func upgradeBuild(pluginPkgs map[string]pluginPackage, fl Flags) (int, error) {
|
||||||
l := caddy.Log()
|
l := caddy.Log()
|
||||||
|
|
||||||
thisExecPath, err := os.Executable()
|
thisExecPath, err := os.Executable()
|
||||||
@@ -120,8 +148,8 @@ func upgradeBuild(pluginPkgs map[string]struct{}, fl Flags) (int, error) {
|
|||||||
"os": {runtime.GOOS},
|
"os": {runtime.GOOS},
|
||||||
"arch": {runtime.GOARCH},
|
"arch": {runtime.GOARCH},
|
||||||
}
|
}
|
||||||
for pkg := range pluginPkgs {
|
for _, pkgInfo := range pluginPkgs {
|
||||||
qs.Add("p", pkg)
|
qs.Add("p", pkgInfo.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// initiate the build
|
// initiate the build
|
||||||
@@ -276,14 +304,14 @@ func downloadBuild(qs url.Values) (*http.Response, error) {
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPluginPackages(modules []moduleInfo) (map[string]struct{}, error) {
|
func getPluginPackages(modules []moduleInfo) (map[string]pluginPackage, error) {
|
||||||
pluginPkgs := make(map[string]struct{})
|
pluginPkgs := make(map[string]pluginPackage)
|
||||||
for _, mod := range modules {
|
for _, mod := range modules {
|
||||||
if mod.goModule.Replace != nil {
|
if mod.goModule.Replace != nil {
|
||||||
return nil, fmt.Errorf("cannot auto-upgrade when Go module has been replaced: %s => %s",
|
return nil, fmt.Errorf("cannot auto-upgrade when Go module has been replaced: %s => %s",
|
||||||
mod.goModule.Path, mod.goModule.Replace.Path)
|
mod.goModule.Path, mod.goModule.Replace.Path)
|
||||||
}
|
}
|
||||||
pluginPkgs[mod.goModule.Path] = struct{}{}
|
pluginPkgs[mod.goModule.Path] = pluginPackage{Version: mod.goModule.Version, Path: mod.goModule.Path}
|
||||||
}
|
}
|
||||||
return pluginPkgs, nil
|
return pluginPkgs, nil
|
||||||
}
|
}
|
||||||
@@ -312,3 +340,15 @@ func writeCaddyBinary(path string, body *io.ReadCloser, fileInfo os.FileInfo) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
const downloadPath = "https://caddyserver.com/api/download"
|
const downloadPath = "https://caddyserver.com/api/download"
|
||||||
|
|
||||||
|
type pluginPackage struct {
|
||||||
|
Version string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p pluginPackage) String() string {
|
||||||
|
if p.Version == "" {
|
||||||
|
return p.Path
|
||||||
|
}
|
||||||
|
return p.Path + "@" + p.Version
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
@@ -190,12 +191,20 @@ func cmdExportStorage(fl Flags) (int, error) {
|
|||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
info, err := stor.Stat(ctx, k)
|
info, err := stor.Stat(ctx, k)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
caddy.Log().Warn(fmt.Sprintf("key: %s removed while export is in-progress", k))
|
||||||
|
continue
|
||||||
|
}
|
||||||
return caddy.ExitCodeFailedQuit, err
|
return caddy.ExitCodeFailedQuit, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.IsTerminal {
|
if info.IsTerminal {
|
||||||
v, err := stor.Load(ctx, k)
|
v, err := stor.Load(ctx, k)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
caddy.Log().Warn(fmt.Sprintf("key: %s removed while export is in-progress", k))
|
||||||
|
continue
|
||||||
|
}
|
||||||
return caddy.ExitCodeFailedQuit, err
|
return caddy.ExitCodeFailedQuit, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+36
-8
@@ -23,6 +23,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/collectors"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/exp/zapslog"
|
"go.uber.org/zap/exp/zapslog"
|
||||||
|
|
||||||
@@ -47,6 +49,7 @@ type Context struct {
|
|||||||
ancestry []Module
|
ancestry []Module
|
||||||
cleanupFuncs []func() // invoked at every config unload
|
cleanupFuncs []func() // invoked at every config unload
|
||||||
exitFuncs []func(context.Context) // invoked at config unload ONLY IF the process is exiting (EXPERIMENTAL)
|
exitFuncs []func(context.Context) // invoked at config unload ONLY IF the process is exiting (EXPERIMENTAL)
|
||||||
|
metricsRegistry *prometheus.Registry
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContext provides a new context derived from the given
|
// NewContext provides a new context derived from the given
|
||||||
@@ -58,7 +61,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][]Module), cfg: ctx.cfg, metricsRegistry: prometheus.NewPedanticRegistry()}
|
||||||
c, cancel := context.WithCancel(ctx.Context)
|
c, cancel := context.WithCancel(ctx.Context)
|
||||||
wrappedCancel := func() {
|
wrappedCancel := func() {
|
||||||
cancel()
|
cancel()
|
||||||
@@ -79,6 +82,7 @@ func NewContext(ctx Context) (Context, context.CancelFunc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
newCtx.Context = c
|
newCtx.Context = c
|
||||||
|
newCtx.initMetrics()
|
||||||
return newCtx, wrappedCancel
|
return newCtx, wrappedCancel
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +101,24 @@ func (ctx *Context) Filesystems() FileSystems {
|
|||||||
return ctx.cfg.filesystems
|
return ctx.cfg.filesystems
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the active metrics registry for the context
|
||||||
|
// EXPERIMENTAL: This API is subject to change.
|
||||||
|
func (ctx *Context) GetMetricsRegistry() *prometheus.Registry {
|
||||||
|
return ctx.metricsRegistry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) initMetrics() {
|
||||||
|
ctx.metricsRegistry.MustRegister(
|
||||||
|
collectors.NewBuildInfoCollector(),
|
||||||
|
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
|
||||||
|
collectors.NewGoCollector(),
|
||||||
|
adminMetrics.requestCount,
|
||||||
|
adminMetrics.requestErrors,
|
||||||
|
globalMetrics.configSuccess,
|
||||||
|
globalMetrics.configSuccessTime,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// OnExit executes f when the process exits gracefully.
|
// OnExit executes f when the process exits gracefully.
|
||||||
// The function is only executed if the process is gracefully
|
// The function is only executed if the process is gracefully
|
||||||
// shut down while this context is active.
|
// shut down while this context is active.
|
||||||
@@ -363,6 +385,17 @@ 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if this is an app module, keep a reference to it,
|
||||||
|
// since submodules may need to reference it during
|
||||||
|
// provisioning (even though the parent app module
|
||||||
|
// may not be fully provisioned yet; this is the case
|
||||||
|
// with the tls app's automation policies, which may
|
||||||
|
// refer to the tls app to check if a global DNS
|
||||||
|
// module has been configured for DNS challenges)
|
||||||
|
if appModule, ok := val.(App); ok {
|
||||||
|
ctx.cfg.apps[id] = appModule
|
||||||
|
}
|
||||||
|
|
||||||
ctx.ancestry = append(ctx.ancestry, val)
|
ctx.ancestry = append(ctx.ancestry, val)
|
||||||
|
|
||||||
if prov, ok := val.(Provisioner); ok {
|
if prov, ok := val.(Provisioner); ok {
|
||||||
@@ -449,7 +482,6 @@ func (ctx Context) App(name string) (any, error) {
|
|||||||
if appRaw != nil {
|
if appRaw != nil {
|
||||||
ctx.cfg.AppsRaw[name] = nil // allow GC to deallocate
|
ctx.cfg.AppsRaw[name] = nil // allow GC to deallocate
|
||||||
}
|
}
|
||||||
ctx.cfg.apps[name] = modVal.(App)
|
|
||||||
return modVal, nil
|
return modVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -535,12 +567,8 @@ func (ctx Context) Slogger() *slog.Logger {
|
|||||||
if mod == nil {
|
if mod == nil {
|
||||||
return slog.New(zapslog.NewHandler(Log().Core(), nil))
|
return slog.New(zapslog.NewHandler(Log().Core(), nil))
|
||||||
}
|
}
|
||||||
|
return slog.New(zapslog.NewHandler(ctx.cfg.Logging.Logger(mod).Core(),
|
||||||
return slog.New(zapslog.NewHandler(
|
zapslog.WithName(string(mod.CaddyModule().ID)),
|
||||||
ctx.cfg.Logging.Logger(mod).Core(),
|
|
||||||
&zapslog.HandlerOptions{
|
|
||||||
LoggerName: string(mod.CaddyModule().ID),
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package caddy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FastAbs is an optimized version of filepath.Abs for Unix systems,
|
||||||
|
// since we don't expect the working directory to ever change once
|
||||||
|
// Caddy is running. Avoid the os.Getwd() syscall overhead.
|
||||||
|
// It's overall the same as stdlib's implementation, the difference
|
||||||
|
// being cached working directory.
|
||||||
|
func FastAbs(path string) (string, error) {
|
||||||
|
if filepath.IsAbs(path) {
|
||||||
|
return filepath.Clean(path), nil
|
||||||
|
}
|
||||||
|
if wderr != nil {
|
||||||
|
return "", wderr
|
||||||
|
}
|
||||||
|
return filepath.Join(wd, path), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var wd, wderr = os.Getwd()
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// 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 caddy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FastAbs can't be optimized on Windows because there
|
||||||
|
// are special file paths that require the use of syscall.FullPath
|
||||||
|
// to handle correctly.
|
||||||
|
// Just call stdlib's implementation which uses that function.
|
||||||
|
func FastAbs(path string) (string, error) {
|
||||||
|
return filepath.Abs(path)
|
||||||
|
}
|
||||||
@@ -1,3 +1,17 @@
|
|||||||
|
// 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 caddy
|
package caddy
|
||||||
|
|
||||||
import "io/fs"
|
import "io/fs"
|
||||||
|
|||||||
@@ -1,110 +1,112 @@
|
|||||||
module github.com/caddyserver/caddy/v2
|
module github.com/caddyserver/caddy/v2
|
||||||
|
|
||||||
go 1.21.0
|
go 1.24
|
||||||
|
|
||||||
toolchain go1.22.2
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.3.2
|
github.com/BurntSushi/toml v1.4.0
|
||||||
github.com/Masterminds/sprig/v3 v3.2.3
|
github.com/KimMachineGun/automemlimit v0.7.1
|
||||||
github.com/alecthomas/chroma/v2 v2.13.0
|
github.com/Masterminds/sprig/v3 v3.3.0
|
||||||
|
github.com/alecthomas/chroma/v2 v2.15.0
|
||||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
|
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
|
||||||
github.com/caddyserver/certmagic v0.21.3
|
github.com/caddyserver/certmagic v0.22.0
|
||||||
github.com/caddyserver/zerossl v0.1.3
|
github.com/caddyserver/zerossl v0.1.3
|
||||||
|
github.com/cloudflare/circl v1.6.0
|
||||||
github.com/dustin/go-humanize v1.0.1
|
github.com/dustin/go-humanize v1.0.1
|
||||||
github.com/go-chi/chi/v5 v5.0.12
|
github.com/go-chi/chi/v5 v5.2.1
|
||||||
github.com/google/cel-go v0.20.1
|
github.com/google/cel-go v0.24.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/klauspost/compress v1.17.8
|
github.com/klauspost/compress v1.18.0
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7
|
github.com/klauspost/cpuid/v2 v2.2.10
|
||||||
github.com/mholt/acmez/v2 v2.0.1
|
github.com/mholt/acmez/v3 v3.1.0
|
||||||
github.com/prometheus/client_golang v1.19.1
|
github.com/prometheus/client_golang v1.19.1
|
||||||
github.com/quic-go/quic-go v0.44.0
|
github.com/quic-go/quic-go v0.50.0
|
||||||
github.com/smallstep/certificates v0.26.1
|
github.com/smallstep/certificates v0.26.1
|
||||||
github.com/smallstep/nosql v0.6.1
|
github.com/smallstep/nosql v0.6.1
|
||||||
github.com/smallstep/truststore v0.13.0
|
github.com/smallstep/truststore v0.13.0
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.6
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53
|
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53
|
||||||
github.com/yuin/goldmark v1.7.1
|
github.com/yuin/goldmark v1.7.8
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0
|
||||||
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0
|
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0
|
||||||
go.opentelemetry.io/otel v1.24.0
|
go.opentelemetry.io/otel v1.31.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0
|
||||||
go.opentelemetry.io/otel/sdk v1.21.0
|
go.opentelemetry.io/otel/sdk v1.31.0
|
||||||
go.uber.org/automaxprocs v1.5.3
|
go.uber.org/automaxprocs v1.6.0
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
go.uber.org/zap/exp v0.2.0
|
go.uber.org/zap/exp v0.3.0
|
||||||
golang.org/x/crypto v0.23.0
|
golang.org/x/crypto v0.36.0
|
||||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810
|
||||||
golang.org/x/net v0.25.0
|
golang.org/x/net v0.37.0
|
||||||
golang.org/x/sync v0.7.0
|
golang.org/x/sync v0.12.0
|
||||||
golang.org/x/term v0.20.0
|
golang.org/x/term v0.30.0
|
||||||
golang.org/x/time v0.5.0
|
golang.org/x/time v0.11.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
cel.dev/expr v0.19.1 // indirect
|
||||||
|
dario.cat/mergo v1.0.1 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
|
||||||
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
|
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
|
||||||
github.com/go-kit/log v0.2.1 // indirect
|
github.com/go-kit/log v0.2.1 // indirect
|
||||||
github.com/golang/glog v1.2.0 // indirect
|
|
||||||
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 // indirect
|
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 // indirect
|
||||||
github.com/google/go-tpm v0.9.0 // indirect
|
github.com/google/go-tpm v0.9.0 // indirect
|
||||||
github.com/google/go-tspi v0.3.0 // indirect
|
github.com/google/go-tspi v0.3.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e // indirect
|
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.13.2 // indirect
|
github.com/onsi/ginkgo/v2 v2.13.2 // indirect
|
||||||
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 // indirect
|
github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 // indirect
|
||||||
github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect
|
github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect
|
||||||
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect
|
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||||
go.opentelemetry.io/contrib/propagators/aws v1.17.0 // indirect
|
go.opentelemetry.io/contrib/propagators/aws v1.17.0 // indirect
|
||||||
go.opentelemetry.io/contrib/propagators/b3 v1.17.0 // indirect
|
go.opentelemetry.io/contrib/propagators/b3 v1.17.0 // indirect
|
||||||
go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 // indirect
|
go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 // indirect
|
||||||
go.opentelemetry.io/contrib/propagators/ot v1.17.0 // indirect
|
go.opentelemetry.io/contrib/propagators/ot v1.17.0 // indirect
|
||||||
go.uber.org/mock v0.4.0 // indirect
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
|
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
|
||||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash v1.1.0 // indirect
|
github.com/cespare/xxhash v1.1.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0
|
github.com/cespare/xxhash/v2 v2.3.0
|
||||||
github.com/chzyer/readline v1.5.1 // indirect
|
github.com/chzyer/readline v1.5.1 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||||
github.com/dgraph-io/badger v1.6.2 // indirect
|
github.com/dgraph-io/badger v1.6.2 // indirect
|
||||||
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
|
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
|
||||||
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
github.com/dgraph-io/ristretto v0.2.0 // indirect
|
||||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/go-kit/kit v0.13.0 // indirect
|
github.com/go-kit/kit v0.13.0 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.1 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/huandu/xstrings v1.3.3 // indirect
|
github.com/huandu/xstrings v1.5.0 // indirect
|
||||||
github.com/imdario/mergo v0.3.12 // indirect
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||||
github.com/jackc/pgconn v1.14.3 // indirect
|
github.com/jackc/pgconn v1.14.3 // indirect
|
||||||
@@ -114,43 +116,43 @@ require (
|
|||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
github.com/jackc/pgtype v1.14.0 // indirect
|
github.com/jackc/pgtype v1.14.0 // indirect
|
||||||
github.com/jackc/pgx/v4 v4.18.3 // indirect
|
github.com/jackc/pgx/v4 v4.18.3 // indirect
|
||||||
github.com/libdns/libdns v0.2.2 // indirect
|
github.com/libdns/libdns v0.2.3
|
||||||
github.com/manifoldco/promptui v0.9.0 // indirect
|
github.com/manifoldco/promptui v0.9.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
github.com/miekg/dns v1.1.59 // indirect
|
github.com/miekg/dns v1.1.63 // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/pires/go-proxyproto v0.7.0
|
github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/prometheus/client_model v0.5.0 // indirect
|
github.com/prometheus/client_model v0.5.0
|
||||||
github.com/prometheus/common v0.48.0 // indirect
|
github.com/prometheus/common v0.48.0 // indirect
|
||||||
github.com/prometheus/procfs v0.12.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
github.com/rs/xid v1.5.0 // indirect
|
github.com/rs/xid v1.5.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/shopspring/decimal v1.2.0 // indirect
|
github.com/shopspring/decimal v1.4.0 // indirect
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/slackhq/nebula v1.6.1 // indirect
|
github.com/slackhq/nebula v1.6.1 // indirect
|
||||||
github.com/spf13/cast v1.4.1 // indirect
|
github.com/spf13/cast v1.7.0 // indirect
|
||||||
github.com/stoewer/go-strcase v1.2.0 // indirect
|
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||||
github.com/urfave/cli v1.22.14 // indirect
|
github.com/urfave/cli v1.22.14 // indirect
|
||||||
go.etcd.io/bbolt v1.3.9 // indirect
|
go.etcd.io/bbolt v1.3.9 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
go.opentelemetry.io/otel/metric v1.31.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.24.0
|
go.opentelemetry.io/otel/trace v1.31.0
|
||||||
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||||
go.step.sm/cli-utils v0.9.0 // indirect
|
go.step.sm/cli-utils v0.9.0 // indirect
|
||||||
go.step.sm/crypto v0.45.0
|
go.step.sm/crypto v0.45.0
|
||||||
go.step.sm/linkedca v0.20.1 // indirect
|
go.step.sm/linkedca v0.20.1 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/mod v0.17.0 // indirect
|
golang.org/x/mod v0.24.0 // indirect
|
||||||
golang.org/x/sys v0.20.0
|
golang.org/x/sys v0.31.0
|
||||||
golang.org/x/text v0.15.0 // indirect
|
golang.org/x/text v0.23.0 // indirect
|
||||||
golang.org/x/tools v0.21.0 // indirect
|
golang.org/x/tools v0.31.0 // indirect
|
||||||
google.golang.org/grpc v1.63.2 // indirect
|
google.golang.org/grpc v1.67.1 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
google.golang.org/protobuf v1.35.1 // indirect
|
||||||
howett.net/plist v1.0.0 // indirect
|
howett.net/plist v1.0.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,43 +1,60 @@
|
|||||||
|
cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4=
|
||||||
|
cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||||
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
|
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
|
||||||
cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg=
|
cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg=
|
||||||
cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro=
|
cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro=
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
|
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
|
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
|
||||||
cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
|
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
|
||||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
||||||
cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
|
cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
|
||||||
cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
|
cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
|
||||||
cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY=
|
cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY=
|
||||||
cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc=
|
cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc=
|
||||||
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
|
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
|
||||||
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
|
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
|
||||||
|
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||||
|
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
|
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||||
|
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||||
|
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||||
|
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
|
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
|
||||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
|
||||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
|
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||||
|
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
|
github.com/KimMachineGun/automemlimit v0.7.1 h1:QcG/0iCOLChjfUweIMC3YL5Xy9C3VBeNmCZHrZfJMBw=
|
||||||
|
github.com/KimMachineGun/automemlimit v0.7.1/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM=
|
||||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||||
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
|
||||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||||
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||||
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
||||||
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
||||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
|
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||||
github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||||
github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI=
|
github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
|
||||||
github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk=
|
github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
|
||||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
||||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||||
@@ -71,19 +88,21 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 h1:et3Ta53gotFR4ERLXXHIHl/Uuk1q
|
|||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw=
|
||||||
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
||||||
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0=
|
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||||
github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI=
|
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||||
|
github.com/caddyserver/certmagic v0.22.0 h1:hi2skv2jouUw9uQUEyYSTTmqPZPHgf61dOANSIVCLOw=
|
||||||
|
github.com/caddyserver/certmagic v0.22.0/go.mod h1:Vc0msarAPhOagbDc/SU6M2zbzdwVuZ0lkTh2EqtH4vs=
|
||||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
||||||
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
||||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||||
@@ -93,17 +112,21 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk
|
|||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
||||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
||||||
|
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -114,27 +137,35 @@ github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdw
|
|||||||
github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
|
github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
|
||||||
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||||
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||||
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
|
github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
|
||||||
github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
|
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
|
||||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
|
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
|
||||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
|
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||||
|
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
|
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
|
||||||
github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
|
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||||
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
|
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
|
||||||
|
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||||
github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
|
github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
|
||||||
github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
|
github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
|
||||||
@@ -146,8 +177,8 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE
|
|||||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||||
@@ -158,55 +189,66 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe
|
|||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
|
|
||||||
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
|
github.com/google/cel-go v0.24.1 h1:jsBCtxG8mM5wiUJDSGUqU0K7Mtr3w7Eyv00rw4DiZxI=
|
||||||
github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
|
github.com/google/cel-go v0.24.1/go.mod h1:Hdf9TqOaTNSFQA1ybQaRqATVoK7m/zcf7IMhGXP5zI8=
|
||||||
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||||
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo=
|
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo=
|
||||||
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k=
|
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||||
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
|
github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
|
||||||
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
|
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
|
||||||
github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98=
|
github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98=
|
||||||
github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY=
|
github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY=
|
||||||
github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
|
github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
|
||||||
github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
|
github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
|
||||||
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e h1:bwOy7hAFd0C91URzMIEBfr6BAz29yk7Qj0cy6S7DJlU=
|
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e h1:bwOy7hAFd0C91URzMIEBfr6BAz29yk7Qj0cy6S7DJlU=
|
||||||
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||||
|
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
|
||||||
|
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
|
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
|
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk=
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
|
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
|
||||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
|
||||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
@@ -257,14 +299,16 @@ github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx
|
|||||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
@@ -272,6 +316,7 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
|
|||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@@ -282,9 +327,11 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
|||||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
github.com/libdns/libdns v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8=
|
||||||
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||||
|
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
||||||
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
||||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||||
@@ -297,31 +344,38 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
|||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k=
|
github.com/mholt/acmez/v3 v3.1.0 h1:RlOx2SSZ8dIAM5GfkMe8TdaxjjkiHTGorlMUt8GeMzg=
|
||||||
github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U=
|
github.com/mholt/acmez/v3 v3.1.0/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
||||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||||
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||||
|
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||||
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||||
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
|
github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
|
||||||
github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o=
|
github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o=
|
||||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964 h1:ct/vxNBgHpASQ4sT8NaBX9LtsEtluZqaUJydLG50U3E=
|
||||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
@@ -329,21 +383,25 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
|
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||||
|
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0=
|
github.com/quic-go/quic-go v0.50.0 h1:3H/ld1pa3CYhkcc20TPIyG1bNsdhn9qZBGN3b9/UyUo=
|
||||||
github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek=
|
github.com/quic-go/quic-go v0.50.0/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
@@ -355,11 +413,35 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
|||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E=
|
github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E=
|
||||||
github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg=
|
github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg=
|
||||||
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
|
||||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
|
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||||
|
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||||
|
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||||
|
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||||
|
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||||
|
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
||||||
|
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
||||||
|
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||||
|
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||||
|
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
||||||
|
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
||||||
|
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
||||||
|
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||||
|
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
||||||
|
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
||||||
|
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
||||||
|
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
||||||
|
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||||
|
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||||
|
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
@@ -381,21 +463,22 @@ github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjI
|
|||||||
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU=
|
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU=
|
||||||
github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4=
|
github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4=
|
||||||
github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A=
|
github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A=
|
||||||
|
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||||
|
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||||
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
|
||||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||||
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
|
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
|
||||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||||
@@ -413,37 +496,41 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 h1:uxMgm0C+EjytfAqyfBG55ZONKQ7mvd7x4YYCWsf8QHQ=
|
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 h1:uxMgm0C+EjytfAqyfBG55ZONKQ7mvd7x4YYCWsf8QHQ=
|
||||||
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU=
|
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU=
|
||||||
|
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||||
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
|
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
|
||||||
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
|
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
|
||||||
|
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||||
|
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
|
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||||
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
||||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||||
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||||
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
|
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
|
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
|
||||||
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
|
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
|
||||||
|
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
|
||||||
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 h1:s2RzYOAqHVgG23q8fPWYChobUoZM6rJZ98EnylJr66w=
|
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 h1:s2RzYOAqHVgG23q8fPWYChobUoZM6rJZ98EnylJr66w=
|
||||||
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0/go.mod h1:Mv/tWNtZn+NbALDb2XcItP0OM3lWWZjAfSroINxfW+Y=
|
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0/go.mod h1:Mv/tWNtZn+NbALDb2XcItP0OM3lWWZjAfSroINxfW+Y=
|
||||||
go.opentelemetry.io/contrib/propagators/aws v1.17.0 h1:IX8d7l2uRw61BlmZBOTQFaK+y22j6vytMVTs9wFrO+c=
|
go.opentelemetry.io/contrib/propagators/aws v1.17.0 h1:IX8d7l2uRw61BlmZBOTQFaK+y22j6vytMVTs9wFrO+c=
|
||||||
@@ -454,20 +541,20 @@ go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 h1:Zbpbmwav32Ea5jSotpmkWE
|
|||||||
go.opentelemetry.io/contrib/propagators/jaeger v1.17.0/go.mod h1:tcTUAlmO8nuInPDSBVfG+CP6Mzjy5+gNV4mPxMbL0IA=
|
go.opentelemetry.io/contrib/propagators/jaeger v1.17.0/go.mod h1:tcTUAlmO8nuInPDSBVfG+CP6Mzjy5+gNV4mPxMbL0IA=
|
||||||
go.opentelemetry.io/contrib/propagators/ot v1.17.0 h1:ufo2Vsz8l76eI47jFjuVyjyB3Ae2DmfiCV/o6Vc8ii0=
|
go.opentelemetry.io/contrib/propagators/ot v1.17.0 h1:ufo2Vsz8l76eI47jFjuVyjyB3Ae2DmfiCV/o6Vc8ii0=
|
||||||
go.opentelemetry.io/contrib/propagators/ot v1.17.0/go.mod h1:SbKPj5XGp8K/sGm05XblaIABgMgw2jDczP8gGeuaVLk=
|
go.opentelemetry.io/contrib/propagators/ot v1.17.0/go.mod h1:SbKPj5XGp8K/sGm05XblaIABgMgw2jDczP8gGeuaVLk=
|
||||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
|
||||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o=
|
||||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
|
||||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
|
||||||
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
|
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
|
||||||
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
|
||||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
|
||||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
|
||||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||||
go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ=
|
go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ=
|
||||||
go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8=
|
go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8=
|
||||||
go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc=
|
go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc=
|
||||||
@@ -478,12 +565,12 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
|||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||||
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||||
@@ -495,10 +582,14 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
|||||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs=
|
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
|
||||||
go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ=
|
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
|
||||||
|
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||||
|
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||||
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
@@ -508,44 +599,67 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP
|
|||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw=
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810 h1:V5+zy0jmgNYmK1uW/sPpBw8ioFvalrhaUrYWmu1Fpe4=
|
||||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810/go.mod h1:lxN5T34bK4Z/i6cMaU7frUU57VkDXFD4Kamfl/cp9oU=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||||
|
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||||
|
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||||
|
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -554,7 +668,6 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -562,37 +675,41 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||||
|
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
@@ -603,25 +720,41 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
|
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
|
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||||
google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4=
|
google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4=
|
||||||
google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE=
|
google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||||
|
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw=
|
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw=
|
||||||
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw=
|
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
||||||
|
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||||
|
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||||
|
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -629,16 +762,22 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
|||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||||
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||||
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||||
|
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||||
|
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
// PrivateRangesCIDR returns a list of private CIDR range
|
||||||
|
// strings, which can be used as a configuration shortcut.
|
||||||
|
func PrivateRangesCIDR() []string {
|
||||||
|
return []string{
|
||||||
|
"192.168.0.0/16",
|
||||||
|
"172.16.0.0/12",
|
||||||
|
"10.0.0.0/8",
|
||||||
|
"127.0.0.1/8",
|
||||||
|
"fd00::/8",
|
||||||
|
"::1",
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,11 @@ package caddy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -26,15 +30,54 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func reuseUnixSocket(network, addr string) (any, error) {
|
func reuseUnixSocket(_, _ string) (any, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) {
|
func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) {
|
||||||
switch network {
|
var socketFile *os.File
|
||||||
case "udp", "udp4", "udp6", "unixgram":
|
|
||||||
|
fd := slices.Contains([]string{"fd", "fdgram"}, network)
|
||||||
|
if fd {
|
||||||
|
socketFd, err := strconv.ParseUint(address, 0, strconv.IntSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid file descriptor: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func() {
|
||||||
|
socketFilesMu.Lock()
|
||||||
|
defer socketFilesMu.Unlock()
|
||||||
|
|
||||||
|
socketFdWide := uintptr(socketFd)
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
socketFile, ok = socketFiles[socketFdWide]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
socketFile = os.NewFile(socketFdWide, lnKey)
|
||||||
|
if socketFile != nil {
|
||||||
|
socketFiles[socketFdWide] = socketFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if socketFile == nil {
|
||||||
|
return nil, fmt.Errorf("invalid socket file descriptor: %d", socketFd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
datagram := slices.Contains([]string{"udp", "udp4", "udp6", "unixgram", "fdgram"}, network)
|
||||||
|
if datagram {
|
||||||
sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
||||||
pc, err := config.ListenPacket(ctx, network, address)
|
var (
|
||||||
|
pc net.PacketConn
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if fd {
|
||||||
|
pc, err = net.FilePacketConn(socketFile)
|
||||||
|
} else {
|
||||||
|
pc, err = config.ListenPacket(ctx, network, address)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -44,10 +87,18 @@ func listenReusable(ctx context.Context, lnKey string, network, address string,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &fakeClosePacketConn{sharedPacketConn: sharedPc.(*sharedPacketConn)}, nil
|
return &fakeClosePacketConn{sharedPacketConn: sharedPc.(*sharedPacketConn)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
|
||||||
sharedLn, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
sharedLn, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
||||||
ln, err := config.Listen(ctx, network, address)
|
var (
|
||||||
|
ln net.Listener
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if fd {
|
||||||
|
ln, err = net.FileListener(socketFile)
|
||||||
|
} else {
|
||||||
|
ln, err = config.Listen(ctx, network, address)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -57,7 +108,6 @@ func listenReusable(ctx context.Context, lnKey string, network, address string,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &fakeCloseListener{sharedListener: sharedLn.(*sharedListener), keepAlivePeriod: config.KeepAlive}, nil
|
return &fakeCloseListener{sharedListener: sharedLn.(*sharedListener), keepAlivePeriod: config.KeepAlive}, nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fakeCloseListener is a private wrapper over a listener that
|
// fakeCloseListener is a private wrapper over a listener that
|
||||||
@@ -260,3 +310,9 @@ var (
|
|||||||
Unwrap() net.PacketConn
|
Unwrap() net.PacketConn
|
||||||
}) = (*fakeClosePacketConn)(nil)
|
}) = (*fakeClosePacketConn)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// socketFiles is a fd -> *os.File map used to make a FileListener/FilePacketConn from a socket file descriptor.
|
||||||
|
var socketFiles = map[uintptr]*os.File{}
|
||||||
|
|
||||||
|
// socketFilesMu synchronizes socketFiles insertions
|
||||||
|
var socketFilesMu sync.Mutex
|
||||||
|
|||||||
+101
-31
@@ -22,10 +22,14 @@ package caddy
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
@@ -34,12 +38,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// reuseUnixSocket copies and reuses the unix domain socket (UDS) if we already
|
// reuseUnixSocket copies and reuses the unix domain socket (UDS) if we already
|
||||||
// have it open; if not, unlink it so we can have it. No-op if not a unix network.
|
// have it open; if not, unlink it so we can have it.
|
||||||
|
// No-op if not a unix network.
|
||||||
func reuseUnixSocket(network, addr string) (any, error) {
|
func reuseUnixSocket(network, addr string) (any, error) {
|
||||||
if !IsUnixNetwork(network) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
socketKey := listenerKey(network, addr)
|
socketKey := listenerKey(network, addr)
|
||||||
|
|
||||||
socket, exists := unixSockets[socketKey]
|
socket, exists := unixSockets[socketKey]
|
||||||
@@ -71,7 +72,7 @@ func reuseUnixSocket(network, addr string) (any, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
atomic.AddInt32(unixSocket.count, 1)
|
atomic.AddInt32(unixSocket.count, 1)
|
||||||
unixSockets[socketKey] = &unixConn{pc.(*net.UnixConn), addr, socketKey, unixSocket.count}
|
unixSockets[socketKey] = &unixConn{pc.(*net.UnixConn), socketKey, unixSocket.count}
|
||||||
}
|
}
|
||||||
|
|
||||||
return unixSockets[socketKey], nil
|
return unixSockets[socketKey], nil
|
||||||
@@ -89,7 +90,46 @@ func reuseUnixSocket(network, addr string) (any, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// listenReusable creates a new listener for the given network and address, and adds it to listenerPool.
|
||||||
func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) {
|
func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) {
|
||||||
|
// even though SO_REUSEPORT lets us bind the socket multiple times,
|
||||||
|
// we still put it in the listenerPool so we can count how many
|
||||||
|
// configs are using this socket; necessary to ensure we can know
|
||||||
|
// whether to enforce shutdown delays, for example (see #5393).
|
||||||
|
var (
|
||||||
|
ln io.Closer
|
||||||
|
err error
|
||||||
|
socketFile *os.File
|
||||||
|
)
|
||||||
|
|
||||||
|
fd := slices.Contains([]string{"fd", "fdgram"}, network)
|
||||||
|
if fd {
|
||||||
|
socketFd, err := strconv.ParseUint(address, 0, strconv.IntSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid file descriptor: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func() {
|
||||||
|
socketFilesMu.Lock()
|
||||||
|
defer socketFilesMu.Unlock()
|
||||||
|
|
||||||
|
socketFdWide := uintptr(socketFd)
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
socketFile, ok = socketFiles[socketFdWide]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
socketFile = os.NewFile(socketFdWide, lnKey)
|
||||||
|
if socketFile != nil {
|
||||||
|
socketFiles[socketFdWide] = socketFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if socketFile == nil {
|
||||||
|
return nil, fmt.Errorf("invalid socket file descriptor: %d", socketFd)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// wrap any Control function set by the user so we can also add our reusePort control without clobbering theirs
|
// wrap any Control function set by the user so we can also add our reusePort control without clobbering theirs
|
||||||
oldControl := config.Control
|
oldControl := config.Control
|
||||||
config.Control = func(network, address string, c syscall.RawConn) error {
|
config.Control = func(network, address string, c syscall.RawConn) error {
|
||||||
@@ -100,45 +140,57 @@ func listenReusable(ctx context.Context, lnKey string, network, address string,
|
|||||||
}
|
}
|
||||||
return reusePort(network, address, c)
|
return reusePort(network, address, c)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// even though SO_REUSEPORT lets us bind the socket multiple times,
|
datagram := slices.Contains([]string{"udp", "udp4", "udp6", "unixgram", "fdgram"}, network)
|
||||||
// we still put it in the listenerPool so we can count how many
|
if datagram {
|
||||||
// configs are using this socket; necessary to ensure we can know
|
if fd {
|
||||||
// whether to enforce shutdown delays, for example (see #5393).
|
ln, err = net.FilePacketConn(socketFile)
|
||||||
var ln io.Closer
|
} else {
|
||||||
var err error
|
|
||||||
switch network {
|
|
||||||
case "udp", "udp4", "udp6", "unixgram":
|
|
||||||
ln, err = config.ListenPacket(ctx, network, address)
|
ln, err = config.ListenPacket(ctx, network, address)
|
||||||
default:
|
}
|
||||||
|
} else {
|
||||||
|
if fd {
|
||||||
|
ln, err = net.FileListener(socketFile)
|
||||||
|
} else {
|
||||||
ln, err = config.Listen(ctx, network, address)
|
ln, err = config.Listen(ctx, network, address)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
listenerPool.LoadOrStore(lnKey, nil)
|
listenerPool.LoadOrStore(lnKey, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if datagram {
|
||||||
|
if !fd {
|
||||||
|
// TODO: Not 100% sure this is necessary, but we do this for net.UnixListener, so...
|
||||||
|
if unix, ok := ln.(*net.UnixConn); ok {
|
||||||
|
one := int32(1)
|
||||||
|
ln = &unixConn{unix, lnKey, &one}
|
||||||
|
unixSockets[lnKey] = ln.(*unixConn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// lightly wrap the connection so that when it is closed,
|
||||||
|
// we can decrement the usage pool counter
|
||||||
|
if specificLn, ok := ln.(net.PacketConn); ok {
|
||||||
|
ln = deletePacketConn{specificLn, lnKey}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !fd {
|
||||||
// if new listener is a unix socket, make sure we can reuse it later
|
// if new listener is a unix socket, make sure we can reuse it later
|
||||||
// (we do our own "unlink on close" -- not required, but more tidy)
|
// (we do our own "unlink on close" -- not required, but more tidy)
|
||||||
one := int32(1)
|
|
||||||
if unix, ok := ln.(*net.UnixListener); ok {
|
if unix, ok := ln.(*net.UnixListener); ok {
|
||||||
unix.SetUnlinkOnClose(false)
|
unix.SetUnlinkOnClose(false)
|
||||||
|
one := int32(1)
|
||||||
ln = &unixListener{unix, lnKey, &one}
|
ln = &unixListener{unix, lnKey, &one}
|
||||||
unixSockets[lnKey] = ln.(*unixListener)
|
unixSockets[lnKey] = ln.(*unixListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Not 100% sure this is necessary, but we do this for net.UnixListener in listen_unix.go, so...
|
|
||||||
if unix, ok := ln.(*net.UnixConn); ok {
|
|
||||||
ln = &unixConn{unix, address, lnKey, &one}
|
|
||||||
unixSockets[lnKey] = ln.(*unixConn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// lightly wrap the listener so that when it is closed,
|
// lightly wrap the listener so that when it is closed,
|
||||||
// we can decrement the usage pool counter
|
// we can decrement the usage pool counter
|
||||||
switch specificLn := ln.(type) {
|
if specificLn, ok := ln.(net.Listener); ok {
|
||||||
case net.Listener:
|
ln = deleteListener{specificLn, lnKey}
|
||||||
return deleteListener{specificLn, lnKey}, err
|
}
|
||||||
case net.PacketConn:
|
|
||||||
return deletePacketConn{specificLn, lnKey}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// other types, I guess we just return them directly
|
// other types, I guess we just return them directly
|
||||||
@@ -170,12 +222,18 @@ type unixListener struct {
|
|||||||
func (uln *unixListener) Close() error {
|
func (uln *unixListener) Close() error {
|
||||||
newCount := atomic.AddInt32(uln.count, -1)
|
newCount := atomic.AddInt32(uln.count, -1)
|
||||||
if newCount == 0 {
|
if newCount == 0 {
|
||||||
|
file, err := uln.File()
|
||||||
|
var name string
|
||||||
|
if err == nil {
|
||||||
|
name = file.Name()
|
||||||
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
addr := uln.Addr().String()
|
|
||||||
unixSocketsMu.Lock()
|
unixSocketsMu.Lock()
|
||||||
delete(unixSockets, uln.mapKey)
|
delete(unixSockets, uln.mapKey)
|
||||||
unixSocketsMu.Unlock()
|
unixSocketsMu.Unlock()
|
||||||
_ = syscall.Unlink(addr)
|
if err == nil {
|
||||||
|
_ = syscall.Unlink(name)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
return uln.UnixListener.Close()
|
return uln.UnixListener.Close()
|
||||||
@@ -183,7 +241,6 @@ func (uln *unixListener) Close() error {
|
|||||||
|
|
||||||
type unixConn struct {
|
type unixConn struct {
|
||||||
*net.UnixConn
|
*net.UnixConn
|
||||||
filename string
|
|
||||||
mapKey string
|
mapKey string
|
||||||
count *int32 // accessed atomically
|
count *int32 // accessed atomically
|
||||||
}
|
}
|
||||||
@@ -191,11 +248,18 @@ type unixConn struct {
|
|||||||
func (uc *unixConn) Close() error {
|
func (uc *unixConn) Close() error {
|
||||||
newCount := atomic.AddInt32(uc.count, -1)
|
newCount := atomic.AddInt32(uc.count, -1)
|
||||||
if newCount == 0 {
|
if newCount == 0 {
|
||||||
|
file, err := uc.File()
|
||||||
|
var name string
|
||||||
|
if err == nil {
|
||||||
|
name = file.Name()
|
||||||
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
unixSocketsMu.Lock()
|
unixSocketsMu.Lock()
|
||||||
delete(unixSockets, uc.mapKey)
|
delete(unixSockets, uc.mapKey)
|
||||||
unixSocketsMu.Unlock()
|
unixSocketsMu.Unlock()
|
||||||
_ = syscall.Unlink(uc.filename)
|
if err == nil {
|
||||||
|
_ = syscall.Unlink(name)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
return uc.UnixConn.Close()
|
return uc.UnixConn.Close()
|
||||||
@@ -211,6 +275,12 @@ var unixSockets = make(map[string]interface {
|
|||||||
File() (*os.File, error)
|
File() (*os.File, error)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// socketFiles is a fd -> *os.File map used to make a FileListener/FilePacketConn from a socket file descriptor.
|
||||||
|
var socketFiles = map[uintptr]*os.File{}
|
||||||
|
|
||||||
|
// socketFilesMu synchronizes socketFiles insertions
|
||||||
|
var socketFilesMu sync.Mutex
|
||||||
|
|
||||||
// deleteListener is a type that simply deletes itself
|
// deleteListener is a type that simply deletes itself
|
||||||
// from the listenerPool when it closes. It is used
|
// from the listenerPool when it closes. It is used
|
||||||
// solely for the purpose of reference counting (i.e.
|
// solely for the purpose of reference counting (i.e.
|
||||||
|
|||||||
+76
-50
@@ -31,6 +31,7 @@ import (
|
|||||||
|
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
"github.com/quic-go/quic-go/http3"
|
"github.com/quic-go/quic-go/http3"
|
||||||
|
"github.com/quic-go/quic-go/qlog"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
@@ -57,11 +58,9 @@ type NetworkAddress struct {
|
|||||||
EndPort uint
|
EndPort uint
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenAll calls Listen() for all addresses represented by this struct, i.e. all ports in the range.
|
// ListenAll calls Listen for all addresses represented by this struct, i.e. all ports in the range.
|
||||||
// (If the address doesn't use ports or has 1 port only, then only 1 listener will be created.)
|
// (If the address doesn't use ports or has 1 port only, then only 1 listener will be created.)
|
||||||
// It returns an error if any listener failed to bind, and closes any listeners opened up to that point.
|
// It returns an error if any listener failed to bind, and closes any listeners opened up to that point.
|
||||||
//
|
|
||||||
// TODO: Experimental API: subject to change or removal.
|
|
||||||
func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig) ([]any, error) {
|
func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig) ([]any, error) {
|
||||||
var listeners []any
|
var listeners []any
|
||||||
var err error
|
var err error
|
||||||
@@ -107,7 +106,8 @@ func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig)
|
|||||||
// portOffset to the start port. (For network types that do not use ports, the
|
// portOffset to the start port. (For network types that do not use ports, the
|
||||||
// portOffset is ignored.)
|
// portOffset is ignored.)
|
||||||
//
|
//
|
||||||
// The provided ListenConfig is used to create the listener. Its Control function,
|
// First Listen checks if a plugin can provide a listener from this address. Otherwise,
|
||||||
|
// the provided ListenConfig is used to create the listener. Its Control function,
|
||||||
// if set, may be wrapped by an internally-used Control function. The provided
|
// if set, may be wrapped by an internally-used Control function. The provided
|
||||||
// context may be used to cancel long operations early. The context is not used
|
// context may be used to cancel long operations early. The context is not used
|
||||||
// to close the listener after it has been created.
|
// to close the listener after it has been created.
|
||||||
@@ -130,8 +130,8 @@ func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig)
|
|||||||
// Unix sockets will be unlinked before being created, to ensure we can bind to
|
// Unix sockets will be unlinked before being created, to ensure we can bind to
|
||||||
// it even if the previous program using it exited uncleanly; it will also be
|
// it even if the previous program using it exited uncleanly; it will also be
|
||||||
// unlinked upon a graceful exit (or when a new config does not use that socket).
|
// unlinked upon a graceful exit (or when a new config does not use that socket).
|
||||||
//
|
// Listen synchronizes binds to unix domain sockets to avoid race conditions
|
||||||
// TODO: Experimental API: subject to change or removal.
|
// while an existing socket is unlinked.
|
||||||
func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
|
func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
|
||||||
if na.IsUnixNetwork() {
|
if na.IsUnixNetwork() {
|
||||||
unixSocketsMu.Lock()
|
unixSocketsMu.Lock()
|
||||||
@@ -139,7 +139,7 @@ func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check to see if plugin provides listener
|
// check to see if plugin provides listener
|
||||||
if ln, err := getListenerFromPlugin(ctx, na.Network, na.JoinHostPort(portOffset), config); ln != nil || err != nil {
|
if ln, err := getListenerFromPlugin(ctx, na.Network, na.Host, na.port(), portOffset, config); ln != nil || err != nil {
|
||||||
return ln, err
|
return ln, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,50 +153,49 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net
|
|||||||
err error
|
err error
|
||||||
address string
|
address string
|
||||||
unixFileMode fs.FileMode
|
unixFileMode fs.FileMode
|
||||||
isAbstractUnixSocket bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// split unix socket addr early so lnKey
|
// split unix socket addr early so lnKey
|
||||||
// is independent of permissions bits
|
// is independent of permissions bits
|
||||||
if na.IsUnixNetwork() {
|
if na.IsUnixNetwork() {
|
||||||
var err error
|
|
||||||
address, unixFileMode, err = internal.SplitUnixSocketPermissionsBits(na.Host)
|
address, unixFileMode, err = internal.SplitUnixSocketPermissionsBits(na.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
isAbstractUnixSocket = strings.HasPrefix(address, "@")
|
} else if na.IsFdNetwork() {
|
||||||
|
address = na.Host
|
||||||
} else {
|
} else {
|
||||||
address = na.JoinHostPort(portOffset)
|
address = na.JoinHostPort(portOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this is a unix socket, see if we already have it open,
|
|
||||||
// force socket permissions on it and return early
|
|
||||||
if socket, err := reuseUnixSocket(na.Network, address); socket != nil || err != nil {
|
|
||||||
if !isAbstractUnixSocket {
|
|
||||||
if err := os.Chmod(address, unixFileMode); err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return socket, err
|
|
||||||
}
|
|
||||||
|
|
||||||
lnKey := listenerKey(na.Network, address)
|
|
||||||
|
|
||||||
if strings.HasPrefix(na.Network, "ip") {
|
if strings.HasPrefix(na.Network, "ip") {
|
||||||
ln, err = config.ListenPacket(ctx, na.Network, address)
|
ln, err = config.ListenPacket(ctx, na.Network, address)
|
||||||
} else {
|
} else {
|
||||||
|
if na.IsUnixNetwork() {
|
||||||
|
// if this is a unix socket, see if we already have it open
|
||||||
|
ln, err = reuseUnixSocket(na.Network, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ln == nil && err == nil {
|
||||||
|
// otherwise, create a new listener
|
||||||
|
lnKey := listenerKey(na.Network, address)
|
||||||
ln, err = listenReusable(ctx, lnKey, na.Network, address, config)
|
ln, err = listenReusable(ctx, lnKey, na.Network, address, config)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ln == nil {
|
if ln == nil {
|
||||||
return nil, fmt.Errorf("unsupported network type: %s", na.Network)
|
return nil, fmt.Errorf("unsupported network type: %s", na.Network)
|
||||||
}
|
}
|
||||||
|
|
||||||
if IsUnixNetwork(na.Network) {
|
if IsUnixNetwork(na.Network) {
|
||||||
|
isAbstractUnixSocket := strings.HasPrefix(address, "@")
|
||||||
if !isAbstractUnixSocket {
|
if !isAbstractUnixSocket {
|
||||||
if err := os.Chmod(address, unixFileMode); err != nil {
|
err = os.Chmod(address, unixFileMode)
|
||||||
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err)
|
return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,18 +210,22 @@ func (na NetworkAddress) IsUnixNetwork() bool {
|
|||||||
return IsUnixNetwork(na.Network)
|
return IsUnixNetwork(na.Network)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsUnixNetwork returns true if na.Network is
|
||||||
|
// fd or fdgram.
|
||||||
|
func (na NetworkAddress) IsFdNetwork() bool {
|
||||||
|
return IsFdNetwork(na.Network)
|
||||||
|
}
|
||||||
|
|
||||||
// JoinHostPort is like net.JoinHostPort, but where the port
|
// JoinHostPort is like net.JoinHostPort, but where the port
|
||||||
// is StartPort + offset.
|
// is StartPort + offset.
|
||||||
func (na NetworkAddress) JoinHostPort(offset uint) string {
|
func (na NetworkAddress) JoinHostPort(offset uint) string {
|
||||||
if na.IsUnixNetwork() {
|
if na.IsUnixNetwork() || na.IsFdNetwork() {
|
||||||
return na.Host
|
return na.Host
|
||||||
}
|
}
|
||||||
return net.JoinHostPort(na.Host, strconv.Itoa(int(na.StartPort+offset)))
|
return net.JoinHostPort(na.Host, strconv.FormatUint(uint64(na.StartPort+offset), 10))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand returns one NetworkAddress for each port in the port range.
|
// Expand returns one NetworkAddress for each port in the port range.
|
||||||
//
|
|
||||||
// This is EXPERIMENTAL and subject to change or removal.
|
|
||||||
func (na NetworkAddress) Expand() []NetworkAddress {
|
func (na NetworkAddress) Expand() []NetworkAddress {
|
||||||
size := na.PortRangeSize()
|
size := na.PortRangeSize()
|
||||||
addrs := make([]NetworkAddress, size)
|
addrs := make([]NetworkAddress, size)
|
||||||
@@ -253,7 +256,7 @@ func (na NetworkAddress) PortRangeSize() uint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (na NetworkAddress) isLoopback() bool {
|
func (na NetworkAddress) isLoopback() bool {
|
||||||
if na.IsUnixNetwork() {
|
if na.IsUnixNetwork() || na.IsFdNetwork() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if na.Host == "localhost" {
|
if na.Host == "localhost" {
|
||||||
@@ -297,6 +300,11 @@ func IsUnixNetwork(netw string) bool {
|
|||||||
return strings.HasPrefix(netw, "unix")
|
return strings.HasPrefix(netw, "unix")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsFdNetwork returns true if the netw is a fd network.
|
||||||
|
func IsFdNetwork(netw string) bool {
|
||||||
|
return strings.HasPrefix(netw, "fd")
|
||||||
|
}
|
||||||
|
|
||||||
// ParseNetworkAddress parses addr into its individual
|
// ParseNetworkAddress parses addr into its individual
|
||||||
// components. The input string is expected to be of
|
// components. The input string is expected to be of
|
||||||
// the form "network/host:port-range" where any part is
|
// the form "network/host:port-range" where any part is
|
||||||
@@ -327,6 +335,12 @@ func ParseNetworkAddressWithDefaults(addr, defaultNetwork string, defaultPort ui
|
|||||||
Host: host,
|
Host: host,
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
if IsFdNetwork(network) {
|
||||||
|
return NetworkAddress{
|
||||||
|
Network: network,
|
||||||
|
Host: host,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
var start, end uint64
|
var start, end uint64
|
||||||
if port == "" {
|
if port == "" {
|
||||||
start = uint64(defaultPort)
|
start = uint64(defaultPort)
|
||||||
@@ -366,25 +380,28 @@ func SplitNetworkAddress(a string) (network, host, port string, err error) {
|
|||||||
if slashFound {
|
if slashFound {
|
||||||
network = strings.ToLower(strings.TrimSpace(beforeSlash))
|
network = strings.ToLower(strings.TrimSpace(beforeSlash))
|
||||||
a = afterSlash
|
a = afterSlash
|
||||||
}
|
if IsUnixNetwork(network) || IsFdNetwork(network) {
|
||||||
if IsUnixNetwork(network) {
|
|
||||||
host = a
|
host = a
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
host, port, err = net.SplitHostPort(a)
|
|
||||||
if err == nil || a == "" {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
host, port, err = net.SplitHostPort(a)
|
||||||
|
firstErr := err
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
// in general, if there was an error, it was likely "missing port",
|
// in general, if there was an error, it was likely "missing port",
|
||||||
// so try adding a bogus port to take advantage of standard library's
|
// so try removing square brackets around an IPv6 host, adding a bogus
|
||||||
// robust parser, then strip the artificial port before returning
|
// port to take advantage of standard library's robust parser, then
|
||||||
// (don't overwrite original error though; might still be relevant)
|
// strip the artificial port.
|
||||||
var err2 error
|
host, _, err = net.SplitHostPort(net.JoinHostPort(strings.Trim(a, "[]"), "0"))
|
||||||
host, port, err2 = net.SplitHostPort(a + ":0")
|
|
||||||
if err2 == nil {
|
|
||||||
err = nil
|
|
||||||
port = ""
|
port = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Join(firstErr, err)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,7 +415,7 @@ func JoinNetworkAddress(network, host, port string) string {
|
|||||||
if network != "" {
|
if network != "" {
|
||||||
a = network + "/"
|
a = network + "/"
|
||||||
}
|
}
|
||||||
if (host != "" && port == "") || IsUnixNetwork(network) {
|
if (host != "" && port == "") || IsUnixNetwork(network) || IsFdNetwork(network) {
|
||||||
a += host
|
a += host
|
||||||
} else if port != "" {
|
} else if port != "" {
|
||||||
a += net.JoinHostPort(host, port)
|
a += net.JoinHostPort(host, port)
|
||||||
@@ -406,9 +423,11 @@ func JoinNetworkAddress(network, host, port string) string {
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenQUIC returns a quic.EarlyListener suitable for use in a Caddy module.
|
// ListenQUIC returns a http3.QUICEarlyListener suitable for use in a Caddy module.
|
||||||
// The network will be transformed into a QUIC-compatible type (if unix, then
|
//
|
||||||
// unixgram will be used; otherwise, udp will be used).
|
// The network will be transformed into a QUIC-compatible type if the same address can be used with
|
||||||
|
// different networks. Currently this just means that for tcp, udp will be used with the same
|
||||||
|
// address instead.
|
||||||
//
|
//
|
||||||
// NOTE: This API is EXPERIMENTAL and may be changed or removed.
|
// NOTE: This API is EXPERIMENTAL and may be changed or removed.
|
||||||
func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, tlsConf *tls.Config) (http3.QUICEarlyListener, error) {
|
func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, tlsConf *tls.Config) (http3.QUICEarlyListener, error) {
|
||||||
@@ -443,7 +462,13 @@ func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config
|
|||||||
Conn: h3ln,
|
Conn: h3ln,
|
||||||
VerifySourceAddress: func(addr net.Addr) bool { return !limiter.Allow() },
|
VerifySourceAddress: func(addr net.Addr) bool { return !limiter.Allow() },
|
||||||
}
|
}
|
||||||
earlyLn, err := tr.ListenEarly(http3.ConfigureTLSConfig(quicTlsConfig), &quic.Config{Allow0RTT: true})
|
earlyLn, err := tr.ListenEarly(
|
||||||
|
http3.ConfigureTLSConfig(quicTlsConfig),
|
||||||
|
&quic.Config{
|
||||||
|
Allow0RTT: true,
|
||||||
|
Tracer: qlog.DefaultConnectionTracer,
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -616,7 +641,8 @@ func RegisterNetwork(network string, getListener ListenerFunc) {
|
|||||||
if network == "tcp" || network == "tcp4" || network == "tcp6" ||
|
if network == "tcp" || network == "tcp4" || network == "tcp6" ||
|
||||||
network == "udp" || network == "udp4" || network == "udp6" ||
|
network == "udp" || network == "udp4" || network == "udp6" ||
|
||||||
network == "unix" || network == "unixpacket" || network == "unixgram" ||
|
network == "unix" || network == "unixpacket" || network == "unixgram" ||
|
||||||
strings.HasPrefix("ip:", network) || strings.HasPrefix("ip4:", network) || strings.HasPrefix("ip6:", network) {
|
strings.HasPrefix("ip:", network) || strings.HasPrefix("ip4:", network) || strings.HasPrefix("ip6:", network) ||
|
||||||
|
network == "fd" || network == "fdgram" {
|
||||||
panic("network type " + network + " is reserved")
|
panic("network type " + network + " is reserved")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -632,11 +658,11 @@ var unixSocketsMu sync.Mutex
|
|||||||
// getListenerFromPlugin returns a listener on the given network and address
|
// getListenerFromPlugin returns a listener on the given network and address
|
||||||
// if a plugin has registered the network name. It may return (nil, nil) if
|
// if a plugin has registered the network name. It may return (nil, nil) if
|
||||||
// no plugin can provide a listener.
|
// no plugin can provide a listener.
|
||||||
func getListenerFromPlugin(ctx context.Context, network, addr string, config net.ListenConfig) (any, error) {
|
func getListenerFromPlugin(ctx context.Context, network, host, port string, portOffset uint, config net.ListenConfig) (any, error) {
|
||||||
// get listener from plugin if network type is registered
|
// get listener from plugin if network type is registered
|
||||||
if getListener, ok := networkTypes[network]; ok {
|
if getListener, ok := networkTypes[network]; ok {
|
||||||
Log().Debug("getting listener from plugin", zap.String("network", network))
|
Log().Debug("getting listener from plugin", zap.String("network", network))
|
||||||
return getListener(ctx, network, addr, config)
|
return getListener(ctx, network, host, port, portOffset, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -650,7 +676,7 @@ func listenerKey(network, addr string) string {
|
|||||||
// The listeners must be capable of overlapping: with Caddy, new configs are loaded
|
// The listeners must be capable of overlapping: with Caddy, new configs are loaded
|
||||||
// before old ones are unloaded, so listeners may overlap briefly if the configs
|
// before old ones are unloaded, so listeners may overlap briefly if the configs
|
||||||
// both need the same listener. EXPERIMENTAL and subject to change.
|
// both need the same listener. EXPERIMENTAL and subject to change.
|
||||||
type ListenerFunc func(ctx context.Context, network, addr string, cfg net.ListenConfig) (any, error)
|
type ListenerFunc func(ctx context.Context, network, host, portRange string, portOffset uint, cfg net.ListenConfig) (any, error)
|
||||||
|
|
||||||
var networkTypes = map[string]ListenerFunc{}
|
var networkTypes = map[string]ListenerFunc{}
|
||||||
|
|
||||||
|
|||||||
+7
-5
@@ -31,7 +31,7 @@ func TestSplitNetworkAddress(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
input: "",
|
input: "",
|
||||||
expectErr: true,
|
expectHost: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "foo",
|
input: "foo",
|
||||||
@@ -42,7 +42,7 @@ func TestSplitNetworkAddress(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "::",
|
input: "::",
|
||||||
expectErr: true,
|
expectHost: "::",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "[::]",
|
input: "[::]",
|
||||||
@@ -77,7 +77,7 @@ func TestSplitNetworkAddress(t *testing.T) {
|
|||||||
{
|
{
|
||||||
input: "udp/",
|
input: "udp/",
|
||||||
expectNetwork: "udp",
|
expectNetwork: "udp",
|
||||||
expectErr: true,
|
expectHost: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "unix//foo/bar",
|
input: "unix//foo/bar",
|
||||||
@@ -185,7 +185,8 @@ func TestParseNetworkAddress(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
input: "",
|
input: "",
|
||||||
expectErr: true,
|
expectAddr: NetworkAddress{
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: ":",
|
input: ":",
|
||||||
@@ -311,7 +312,8 @@ func TestParseNetworkAddressWithDefaults(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
input: "",
|
input: "",
|
||||||
expectErr: true,
|
expectAddr: NetworkAddress{
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: ":",
|
input: ":",
|
||||||
|
|||||||
+16
-7
@@ -4,30 +4,33 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/collectors"
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2/internal/metrics"
|
"github.com/caddyserver/caddy/v2/internal/metrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
// define and register the metrics used in this package.
|
// define and register the metrics used in this package.
|
||||||
func init() {
|
func init() {
|
||||||
prometheus.MustRegister(collectors.NewBuildInfoCollector())
|
|
||||||
|
|
||||||
const ns, sub = "caddy", "admin"
|
const ns, sub = "caddy", "admin"
|
||||||
|
adminMetrics.requestCount = prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||||
adminMetrics.requestCount = promauto.NewCounterVec(prometheus.CounterOpts{
|
|
||||||
Namespace: ns,
|
Namespace: ns,
|
||||||
Subsystem: sub,
|
Subsystem: sub,
|
||||||
Name: "http_requests_total",
|
Name: "http_requests_total",
|
||||||
Help: "Counter of requests made to the Admin API's HTTP endpoints.",
|
Help: "Counter of requests made to the Admin API's HTTP endpoints.",
|
||||||
}, []string{"handler", "path", "code", "method"})
|
}, []string{"handler", "path", "code", "method"})
|
||||||
adminMetrics.requestErrors = promauto.NewCounterVec(prometheus.CounterOpts{
|
adminMetrics.requestErrors = prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||||
Namespace: ns,
|
Namespace: ns,
|
||||||
Subsystem: sub,
|
Subsystem: sub,
|
||||||
Name: "http_request_errors_total",
|
Name: "http_request_errors_total",
|
||||||
Help: "Number of requests resulting in middleware errors.",
|
Help: "Number of requests resulting in middleware errors.",
|
||||||
}, []string{"handler", "path", "method"})
|
}, []string{"handler", "path", "method"})
|
||||||
|
globalMetrics.configSuccess = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "caddy_config_last_reload_successful",
|
||||||
|
Help: "Whether the last configuration reload attempt was successful.",
|
||||||
|
})
|
||||||
|
globalMetrics.configSuccessTime = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "caddy_config_last_reload_success_timestamp_seconds",
|
||||||
|
Help: "Timestamp of the last successful configuration reload.",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// adminMetrics is a collection of metrics that can be tracked for the admin API.
|
// adminMetrics is a collection of metrics that can be tracked for the admin API.
|
||||||
@@ -36,6 +39,12 @@ var adminMetrics = struct {
|
|||||||
requestErrors *prometheus.CounterVec
|
requestErrors *prometheus.CounterVec
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
|
// globalMetrics is a collection of metrics that can be tracked for Caddy global state
|
||||||
|
var globalMetrics = struct {
|
||||||
|
configSuccess prometheus.Gauge
|
||||||
|
configSuccessTime prometheus.Gauge
|
||||||
|
}{}
|
||||||
|
|
||||||
// Similar to promhttp.InstrumentHandlerCounter, but upper-cases method names
|
// Similar to promhttp.InstrumentHandlerCounter, but upper-cases method names
|
||||||
// instead of lower-casing them.
|
// instead of lower-casing them.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -124,7 +124,9 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||||||
app.subscriptions = make(map[string]map[caddy.ModuleID][]Handler)
|
app.subscriptions = make(map[string]map[caddy.ModuleID][]Handler)
|
||||||
|
|
||||||
for _, sub := range app.Subscriptions {
|
for _, sub := range app.Subscriptions {
|
||||||
if sub.HandlersRaw != nil {
|
if sub.HandlersRaw == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
handlersIface, err := ctx.LoadModule(sub, "HandlersRaw")
|
handlersIface, err := ctx.LoadModule(sub, "HandlersRaw")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading event subscriber modules: %v", err)
|
return fmt.Errorf("loading event subscriber modules: %v", err)
|
||||||
@@ -137,7 +139,6 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||||||
return fmt.Errorf("no handlers defined")
|
return fmt.Errorf("no handlers defined")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -261,7 +262,7 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) E
|
|||||||
return nil, false
|
return nil, false
|
||||||
})
|
})
|
||||||
|
|
||||||
logger = logger.With(zap.Any("data", e.Data))
|
logger = logger.WithLazy(zap.Any("data", e.Data))
|
||||||
|
|
||||||
logger.Debug("event")
|
logger.Debug("event")
|
||||||
|
|
||||||
|
|||||||
+168
-58
@@ -15,9 +15,11 @@
|
|||||||
package caddyhttp
|
package caddyhttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -141,6 +143,10 @@ type App struct {
|
|||||||
// affect functionality.
|
// affect functionality.
|
||||||
Servers map[string]*Server `json:"servers,omitempty"`
|
Servers map[string]*Server `json:"servers,omitempty"`
|
||||||
|
|
||||||
|
// If set, metrics observations will be enabled.
|
||||||
|
// This setting is EXPERIMENTAL and subject to change.
|
||||||
|
Metrics *Metrics `json:"metrics,omitempty"`
|
||||||
|
|
||||||
ctx caddy.Context
|
ctx caddy.Context
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
tlsApp *caddytls.TLS
|
tlsApp *caddytls.TLS
|
||||||
@@ -183,6 +189,10 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if app.Metrics != nil {
|
||||||
|
app.Metrics.init = sync.Once{}
|
||||||
|
app.Metrics.httpMetrics = &httpMetrics{}
|
||||||
|
}
|
||||||
// prepare each server
|
// prepare each server
|
||||||
oldContext := ctx.Context
|
oldContext := ctx.Context
|
||||||
for srvName, srv := range app.Servers {
|
for srvName, srv := range app.Servers {
|
||||||
@@ -195,6 +205,15 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||||||
srv.errorLogger = app.logger.Named("log.error")
|
srv.errorLogger = app.logger.Named("log.error")
|
||||||
srv.shutdownAtMu = new(sync.RWMutex)
|
srv.shutdownAtMu = new(sync.RWMutex)
|
||||||
|
|
||||||
|
if srv.Metrics != nil {
|
||||||
|
srv.logger.Warn("per-server 'metrics' is deprecated; use 'metrics' in the root 'http' app instead")
|
||||||
|
app.Metrics = cmp.Or(app.Metrics, &Metrics{
|
||||||
|
init: sync.Once{},
|
||||||
|
httpMetrics: &httpMetrics{},
|
||||||
|
})
|
||||||
|
app.Metrics.PerHost = app.Metrics.PerHost || srv.Metrics.PerHost
|
||||||
|
}
|
||||||
|
|
||||||
// only enable access logs if configured
|
// only enable access logs if configured
|
||||||
if srv.Logs != nil {
|
if srv.Logs != nil {
|
||||||
srv.accessLogger = app.logger.Named("log.access")
|
srv.accessLogger = app.logger.Named("log.access")
|
||||||
@@ -203,17 +222,75 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the Go standard library does not let us serve only HTTP/2 using
|
|
||||||
// http.Server; we would probably need to write our own server
|
|
||||||
if !srv.protocol("h1") && (srv.protocol("h2") || srv.protocol("h2c")) {
|
|
||||||
return fmt.Errorf("server %s: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no protocols configured explicitly, enable all except h2c
|
// if no protocols configured explicitly, enable all except h2c
|
||||||
if len(srv.Protocols) == 0 {
|
if len(srv.Protocols) == 0 {
|
||||||
srv.Protocols = []string{"h1", "h2", "h3"}
|
srv.Protocols = []string{"h1", "h2", "h3"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
srvProtocolsUnique := map[string]struct{}{}
|
||||||
|
for _, srvProtocol := range srv.Protocols {
|
||||||
|
srvProtocolsUnique[srvProtocol] = struct{}{}
|
||||||
|
}
|
||||||
|
_, h1ok := srvProtocolsUnique["h1"]
|
||||||
|
_, h2ok := srvProtocolsUnique["h2"]
|
||||||
|
_, h2cok := srvProtocolsUnique["h2c"]
|
||||||
|
|
||||||
|
// the Go standard library does not let us serve only HTTP/2 using
|
||||||
|
// http.Server; we would probably need to write our own server
|
||||||
|
if !h1ok && (h2ok || h2cok) {
|
||||||
|
return fmt.Errorf("server %s: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if srv.ListenProtocols != nil {
|
||||||
|
if len(srv.ListenProtocols) != len(srv.Listen) {
|
||||||
|
return fmt.Errorf("server %s: listener protocols count does not match address count: %d != %d",
|
||||||
|
srvName, len(srv.ListenProtocols), len(srv.Listen))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, lnProtocols := range srv.ListenProtocols {
|
||||||
|
if lnProtocols != nil {
|
||||||
|
// populate empty listen protocols with server protocols
|
||||||
|
lnProtocolsDefault := false
|
||||||
|
var lnProtocolsInclude []string
|
||||||
|
srvProtocolsInclude := maps.Clone(srvProtocolsUnique)
|
||||||
|
|
||||||
|
// keep existing listener protocols unless they are empty
|
||||||
|
for _, lnProtocol := range lnProtocols {
|
||||||
|
if lnProtocol == "" {
|
||||||
|
lnProtocolsDefault = true
|
||||||
|
} else {
|
||||||
|
lnProtocolsInclude = append(lnProtocolsInclude, lnProtocol)
|
||||||
|
delete(srvProtocolsInclude, lnProtocol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// append server protocols to listener protocols if any listener protocols were empty
|
||||||
|
if lnProtocolsDefault {
|
||||||
|
for _, srvProtocol := range srv.Protocols {
|
||||||
|
if _, ok := srvProtocolsInclude[srvProtocol]; ok {
|
||||||
|
lnProtocolsInclude = append(lnProtocolsInclude, srvProtocol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lnProtocolsIncludeUnique := map[string]struct{}{}
|
||||||
|
for _, lnProtocol := range lnProtocolsInclude {
|
||||||
|
lnProtocolsIncludeUnique[lnProtocol] = struct{}{}
|
||||||
|
}
|
||||||
|
_, h1ok := lnProtocolsIncludeUnique["h1"]
|
||||||
|
_, h2ok := lnProtocolsIncludeUnique["h2"]
|
||||||
|
_, h2cok := lnProtocolsIncludeUnique["h2c"]
|
||||||
|
|
||||||
|
// check if any listener protocols contain h2 or h2c without h1
|
||||||
|
if !h1ok && (h2ok || h2cok) {
|
||||||
|
return fmt.Errorf("server %s, listener %d: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.ListenProtocols[i] = lnProtocolsInclude
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if not explicitly configured by the user, disallow TLS
|
// if not explicitly configured by the user, disallow TLS
|
||||||
// client auth bypass (domain fronting) which could
|
// client auth bypass (domain fronting) which could
|
||||||
// otherwise be exploited by sending an unprotected SNI
|
// otherwise be exploited by sending an unprotected SNI
|
||||||
@@ -283,12 +360,11 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||||||
srv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...)
|
srv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pre-compile the primary handler chain, and be sure to wrap it in our
|
// pre-compile the primary handler chain, and be sure to wrap it in our
|
||||||
// route handler so that important security checks are done, etc.
|
// route handler so that important security checks are done, etc.
|
||||||
primaryRoute := emptyHandler
|
primaryRoute := emptyHandler
|
||||||
if srv.Routes != nil {
|
if srv.Routes != nil {
|
||||||
err := srv.Routes.ProvisionHandlers(ctx, srv.Metrics)
|
err := srv.Routes.ProvisionHandlers(ctx, app.Metrics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err)
|
return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err)
|
||||||
}
|
}
|
||||||
@@ -307,7 +383,7 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||||||
|
|
||||||
// provision the named routes (they get compiled at runtime)
|
// provision the named routes (they get compiled at runtime)
|
||||||
for name, route := range srv.NamedRoutes {
|
for name, route := range srv.NamedRoutes {
|
||||||
err := route.Provision(ctx, srv.Metrics)
|
err := route.Provision(ctx, app.Metrics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("server %s: setting up named route '%s' handlers: %v", name, srvName, err)
|
return fmt.Errorf("server %s: setting up named route '%s' handlers: %v", name, srvName, err)
|
||||||
}
|
}
|
||||||
@@ -325,6 +401,9 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||||||
if srv.IdleTimeout == 0 {
|
if srv.IdleTimeout == 0 {
|
||||||
srv.IdleTimeout = defaultIdleTimeout
|
srv.IdleTimeout = defaultIdleTimeout
|
||||||
}
|
}
|
||||||
|
if srv.ReadHeaderTimeout == 0 {
|
||||||
|
srv.ReadHeaderTimeout = defaultReadHeaderTimeout // see #6663
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ctx.Context = oldContext
|
ctx.Context = oldContext
|
||||||
return nil
|
return nil
|
||||||
@@ -344,7 +423,7 @@ func (app *App) Validate() error {
|
|||||||
// check that every address in the port range is unique to this server;
|
// check that every address in the port range is unique to this server;
|
||||||
// we do not use <= here because PortRangeSize() adds 1 to EndPort for us
|
// we do not use <= here because PortRangeSize() adds 1 to EndPort for us
|
||||||
for i := uint(0); i < listenAddr.PortRangeSize(); i++ {
|
for i := uint(0); i < listenAddr.PortRangeSize(); i++ {
|
||||||
addr := caddy.JoinNetworkAddress(listenAddr.Network, listenAddr.Host, strconv.Itoa(int(listenAddr.StartPort+i)))
|
addr := caddy.JoinNetworkAddress(listenAddr.Network, listenAddr.Host, strconv.FormatUint(uint64(listenAddr.StartPort+i), 10))
|
||||||
if sn, ok := lnAddrs[addr]; ok {
|
if sn, ok := lnAddrs[addr]; ok {
|
||||||
return fmt.Errorf("server %s: listener address repeated: %s (already claimed by server '%s')", srvName, addr, sn)
|
return fmt.Errorf("server %s: listener address repeated: %s (already claimed by server '%s')", srvName, addr, sn)
|
||||||
}
|
}
|
||||||
@@ -422,21 +501,44 @@ func (app *App) Start() error {
|
|||||||
srv.server.Handler = h2c.NewHandler(srv, h2server)
|
srv.server.Handler = h2c.NewHandler(srv, h2server)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, lnAddr := range srv.Listen {
|
for lnIndex, lnAddr := range srv.Listen {
|
||||||
listenAddr, err := caddy.ParseNetworkAddress(lnAddr)
|
listenAddr, err := caddy.ParseNetworkAddress(lnAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: parsing listen address '%s': %v", srvName, lnAddr, err)
|
return fmt.Errorf("%s: parsing listen address '%s': %v", srvName, lnAddr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.addresses = append(srv.addresses, listenAddr)
|
srv.addresses = append(srv.addresses, listenAddr)
|
||||||
|
|
||||||
|
protocols := srv.Protocols
|
||||||
|
if srv.ListenProtocols != nil && srv.ListenProtocols[lnIndex] != nil {
|
||||||
|
protocols = srv.ListenProtocols[lnIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
protocolsUnique := map[string]struct{}{}
|
||||||
|
for _, protocol := range protocols {
|
||||||
|
protocolsUnique[protocol] = struct{}{}
|
||||||
|
}
|
||||||
|
_, h1ok := protocolsUnique["h1"]
|
||||||
|
_, h2ok := protocolsUnique["h2"]
|
||||||
|
_, h2cok := protocolsUnique["h2c"]
|
||||||
|
_, h3ok := protocolsUnique["h3"]
|
||||||
|
|
||||||
for portOffset := uint(0); portOffset < listenAddr.PortRangeSize(); portOffset++ {
|
for portOffset := uint(0); portOffset < listenAddr.PortRangeSize(); portOffset++ {
|
||||||
// create the listener for this socket
|
|
||||||
hostport := listenAddr.JoinHostPort(portOffset)
|
hostport := listenAddr.JoinHostPort(portOffset)
|
||||||
|
|
||||||
|
// enable TLS if there is a policy and if this is not the HTTP port
|
||||||
|
useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort()
|
||||||
|
|
||||||
|
if h1ok || h2ok && useTLS || h2cok {
|
||||||
|
// create the listener for this socket
|
||||||
lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)})
|
lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("listening on %s: %v", listenAddr.At(portOffset), err)
|
return fmt.Errorf("listening on %s: %v", listenAddr.At(portOffset), err)
|
||||||
}
|
}
|
||||||
ln := lnAny.(net.Listener)
|
ln, ok := lnAny.(net.Listener)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("network '%s' cannot handle HTTP/1 or HTTP/2 connections", listenAddr.Network)
|
||||||
|
}
|
||||||
|
|
||||||
// wrap listener before TLS (up to the TLS placeholder wrapper)
|
// wrap listener before TLS (up to the TLS placeholder wrapper)
|
||||||
var lnWrapperIdx int
|
var lnWrapperIdx int
|
||||||
@@ -448,36 +550,9 @@ func (app *App) Start() error {
|
|||||||
ln = lnWrapper.WrapListener(ln)
|
ln = lnWrapper.WrapListener(ln)
|
||||||
}
|
}
|
||||||
|
|
||||||
// enable TLS if there is a policy and if this is not the HTTP port
|
|
||||||
useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort()
|
|
||||||
if useTLS {
|
if useTLS {
|
||||||
// create TLS listener - this enables and terminates TLS
|
// create TLS listener - this enables and terminates TLS
|
||||||
ln = tls.NewListener(ln, tlsCfg)
|
ln = tls.NewListener(ln, tlsCfg)
|
||||||
|
|
||||||
// enable HTTP/3 if configured
|
|
||||||
if srv.protocol("h3") {
|
|
||||||
// Can't serve HTTP/3 on the same socket as HTTP/1 and 2 because it uses
|
|
||||||
// a different transport mechanism... which is fine, but the OS doesn't
|
|
||||||
// differentiate between a SOCK_STREAM file and a SOCK_DGRAM file; they
|
|
||||||
// are still one file on the system. So even though "unixpacket" and
|
|
||||||
// "unixgram" are different network types just as "tcp" and "udp" are,
|
|
||||||
// the OS will not let us use the same file as both STREAM and DGRAM.
|
|
||||||
if len(srv.Protocols) > 1 && listenAddr.IsUnixNetwork() {
|
|
||||||
app.logger.Warn("HTTP/3 disabled because Unix can't multiplex STREAM and DGRAM on same socket",
|
|
||||||
zap.String("file", hostport))
|
|
||||||
for i := range srv.Protocols {
|
|
||||||
if srv.Protocols[i] == "h3" {
|
|
||||||
srv.Protocols = append(srv.Protocols[:i], srv.Protocols[i+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport))
|
|
||||||
if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// finish wrapping listener where we left off before TLS
|
// finish wrapping listener where we left off before TLS
|
||||||
@@ -486,7 +561,7 @@ func (app *App) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handle http2 if use tls listener wrapper
|
// handle http2 if use tls listener wrapper
|
||||||
if useTLS {
|
if h2ok {
|
||||||
http2lnWrapper := &http2Listener{
|
http2lnWrapper := &http2Listener{
|
||||||
Listener: ln,
|
Listener: ln,
|
||||||
server: srv.server,
|
server: srv.server,
|
||||||
@@ -498,7 +573,7 @@ func (app *App) Start() error {
|
|||||||
|
|
||||||
// if binding to port 0, the OS chooses a port for us;
|
// if binding to port 0, the OS chooses a port for us;
|
||||||
// but the user won't know the port unless we print it
|
// but the user won't know the port unless we print it
|
||||||
if !listenAddr.IsUnixNetwork() && listenAddr.StartPort == 0 && listenAddr.EndPort == 0 {
|
if !listenAddr.IsUnixNetwork() && !listenAddr.IsFdNetwork() && listenAddr.StartPort == 0 && listenAddr.EndPort == 0 {
|
||||||
app.logger.Info("port 0 listener",
|
app.logger.Info("port 0 listener",
|
||||||
zap.String("input_address", lnAddr),
|
zap.String("input_address", lnAddr),
|
||||||
zap.String("actual_address", ln.Addr().String()))
|
zap.String("actual_address", ln.Addr().String()))
|
||||||
@@ -512,11 +587,46 @@ func (app *App) Start() error {
|
|||||||
srv.listeners = append(srv.listeners, ln)
|
srv.listeners = append(srv.listeners, ln)
|
||||||
|
|
||||||
// enable HTTP/1 if configured
|
// enable HTTP/1 if configured
|
||||||
if srv.protocol("h1") {
|
if h1ok {
|
||||||
//nolint:errcheck
|
//nolint:errcheck
|
||||||
go srv.server.Serve(ln)
|
go srv.server.Serve(ln)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if h2ok && !useTLS {
|
||||||
|
// Can only serve h2 with TLS enabled
|
||||||
|
app.logger.Warn("HTTP/2 skipped because it requires TLS",
|
||||||
|
zap.String("network", listenAddr.Network),
|
||||||
|
zap.String("addr", hostport))
|
||||||
|
}
|
||||||
|
|
||||||
|
if h3ok {
|
||||||
|
// Can't serve HTTP/3 on the same socket as HTTP/1 and 2 because it uses
|
||||||
|
// a different transport mechanism... which is fine, but the OS doesn't
|
||||||
|
// differentiate between a SOCK_STREAM file and a SOCK_DGRAM file; they
|
||||||
|
// are still one file on the system. So even though "unixpacket" and
|
||||||
|
// "unixgram" are different network types just as "tcp" and "udp" are,
|
||||||
|
// the OS will not let us use the same file as both STREAM and DGRAM.
|
||||||
|
if listenAddr.IsUnixNetwork() {
|
||||||
|
app.logger.Warn("HTTP/3 disabled because Unix can't multiplex STREAM and DGRAM on same socket",
|
||||||
|
zap.String("file", hostport))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if useTLS {
|
||||||
|
// enable HTTP/3 if configured
|
||||||
|
app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport))
|
||||||
|
if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Can only serve h3 with TLS enabled
|
||||||
|
app.logger.Warn("HTTP/3 skipped because it requires TLS",
|
||||||
|
zap.String("network", listenAddr.Network),
|
||||||
|
zap.String("addr", hostport))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.logger.Info("server running",
|
srv.logger.Info("server running",
|
||||||
@@ -607,16 +717,7 @@ func (app *App) Stop() error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// First close h3server then close listeners unlike stdlib for several reasons:
|
if err := server.h3server.Shutdown(ctx); err != nil {
|
||||||
// 1, udp has only a single socket, once closed, no more data can be read and
|
|
||||||
// written. In contrast, closing tcp listeners won't affect established connections.
|
|
||||||
// This have something to do with graceful shutdown when upstream implements it.
|
|
||||||
// 2, h3server will only close listeners it's registered (quic listeners). Closing
|
|
||||||
// listener first and these listeners maybe unregistered thus won't be closed. caddy
|
|
||||||
// distinguishes quic-listener and underlying datagram sockets.
|
|
||||||
|
|
||||||
// TODO: CloseGracefully, once implemented upstream (see https://github.com/quic-go/quic-go/issues/2103)
|
|
||||||
if err := server.h3server.Close(); err != nil {
|
|
||||||
app.logger.Error("HTTP/3 server shutdown",
|
app.logger.Error("HTTP/3 server shutdown",
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Strings("addresses", server.Listen))
|
zap.Strings("addresses", server.Listen))
|
||||||
@@ -684,11 +785,20 @@ func (app *App) httpsPort() int {
|
|||||||
return app.HTTPSPort
|
return app.HTTPSPort
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultIdleTimeout is the default HTTP server timeout
|
const (
|
||||||
// for closing idle connections; useful to avoid resource
|
// defaultIdleTimeout is the default HTTP server timeout
|
||||||
// exhaustion behind hungry CDNs, for example (we've had
|
// for closing idle connections; useful to avoid resource
|
||||||
// several complaints without this).
|
// exhaustion behind hungry CDNs, for example (we've had
|
||||||
const defaultIdleTimeout = caddy.Duration(5 * time.Minute)
|
// several complaints without this).
|
||||||
|
defaultIdleTimeout = caddy.Duration(5 * time.Minute)
|
||||||
|
|
||||||
|
// defaultReadHeaderTimeout is the default timeout for
|
||||||
|
// reading HTTP headers from clients. Headers are generally
|
||||||
|
// small, often less than 1 KB, so it shouldn't take a
|
||||||
|
// long time even on legitimately slow connections or
|
||||||
|
// busy servers to read it.
|
||||||
|
defaultReadHeaderTimeout = caddy.Duration(time.Minute)
|
||||||
|
)
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package caddyhttp
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -64,17 +65,12 @@ type AutoHTTPSConfig struct {
|
|||||||
// enabled. To force automated certificate management
|
// enabled. To force automated certificate management
|
||||||
// regardless of loaded certificates, set this to true.
|
// regardless of loaded certificates, set this to true.
|
||||||
IgnoreLoadedCerts bool `json:"ignore_loaded_certificates,omitempty"`
|
IgnoreLoadedCerts bool `json:"ignore_loaded_certificates,omitempty"`
|
||||||
}
|
|
||||||
|
|
||||||
// Skipped returns true if name is in skipSlice, which
|
// If true, automatic HTTPS will prefer wildcard names
|
||||||
// should be either the Skip or SkipCerts field on ahc.
|
// and ignore non-wildcard names if both are available.
|
||||||
func (ahc AutoHTTPSConfig) Skipped(name string, skipSlice []string) bool {
|
// This allows for writing a config with top-level host
|
||||||
for _, n := range skipSlice {
|
// matchers without having those names produce certificates.
|
||||||
if name == n {
|
PreferWildcard bool `json:"prefer_wildcard,omitempty"`
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// automaticHTTPSPhase1 provisions all route matchers, determines
|
// automaticHTTPSPhase1 provisions all route matchers, determines
|
||||||
@@ -158,7 +154,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||||||
return fmt.Errorf("%s: route %d, matcher set %d, matcher %d, host matcher %d: %v",
|
return fmt.Errorf("%s: route %d, matcher set %d, matcher %d, host matcher %d: %v",
|
||||||
srvName, routeIdx, matcherSetIdx, matcherIdx, hostMatcherIdx, err)
|
srvName, routeIdx, matcherSetIdx, matcherIdx, hostMatcherIdx, err)
|
||||||
}
|
}
|
||||||
if !srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.Skip) {
|
if !slices.Contains(srv.AutoHTTPS.Skip, d) {
|
||||||
serverDomainSet[d] = struct{}{}
|
serverDomainSet[d] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,6 +163,27 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if srv.AutoHTTPS.PreferWildcard {
|
||||||
|
wildcards := make(map[string]struct{})
|
||||||
|
for d := range serverDomainSet {
|
||||||
|
if strings.HasPrefix(d, "*.") {
|
||||||
|
wildcards[d[2:]] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for d := range serverDomainSet {
|
||||||
|
if strings.HasPrefix(d, "*.") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
base := d
|
||||||
|
if idx := strings.Index(d, "."); idx != -1 {
|
||||||
|
base = d[idx+1:]
|
||||||
|
}
|
||||||
|
if _, ok := wildcards[base]; ok {
|
||||||
|
delete(serverDomainSet, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// nothing more to do here if there are no domains that qualify for
|
// nothing more to do here if there are no domains that qualify for
|
||||||
// automatic HTTPS and there are no explicit TLS connection policies:
|
// automatic HTTPS and there are no explicit TLS connection policies:
|
||||||
// if there is at least one domain but no TLS conn policy (F&&T), we'll
|
// if there is at least one domain but no TLS conn policy (F&&T), we'll
|
||||||
@@ -188,12 +205,13 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||||||
// for all the hostnames we found, filter them so we have
|
// for all the hostnames we found, filter them so we have
|
||||||
// a deduplicated list of names for which to obtain certs
|
// a deduplicated list of names for which to obtain certs
|
||||||
// (only if cert management not disabled for this server)
|
// (only if cert management not disabled for this server)
|
||||||
|
var echDomains []string
|
||||||
if srv.AutoHTTPS.DisableCerts {
|
if srv.AutoHTTPS.DisableCerts {
|
||||||
logger.Warn("skipping automated certificate management for server because it is disabled", zap.String("server_name", srvName))
|
logger.Warn("skipping automated certificate management for server because it is disabled", zap.String("server_name", srvName))
|
||||||
} else {
|
} else {
|
||||||
for d := range serverDomainSet {
|
for d := range serverDomainSet {
|
||||||
if certmagic.SubjectQualifiesForCert(d) &&
|
if certmagic.SubjectQualifiesForCert(d) &&
|
||||||
!srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.SkipCerts) {
|
!slices.Contains(srv.AutoHTTPS.SkipCerts, d) {
|
||||||
// if a certificate for this name is already loaded,
|
// if a certificate for this name is already loaded,
|
||||||
// don't obtain another one for it, unless we are
|
// don't obtain another one for it, unless we are
|
||||||
// supposed to ignore loaded certificates
|
// supposed to ignore loaded certificates
|
||||||
@@ -214,10 +232,14 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
uniqueDomainsForCerts[d] = struct{}{}
|
uniqueDomainsForCerts[d] = struct{}{}
|
||||||
|
echDomains = append(echDomains, d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// let the TLS server know we have some hostnames that could be protected behind ECH
|
||||||
|
app.tlsApp.RegisterServerNames(echDomains)
|
||||||
|
|
||||||
// tell the server to use TLS if it is not already doing so
|
// tell the server to use TLS if it is not already doing so
|
||||||
if srv.TLSConnPolicies == nil {
|
if srv.TLSConnPolicies == nil {
|
||||||
srv.TLSConnPolicies = caddytls.ConnectionPolicies{new(caddytls.ConnectionPolicy)}
|
srv.TLSConnPolicies = caddytls.ConnectionPolicies{new(caddytls.ConnectionPolicy)}
|
||||||
@@ -303,11 +325,21 @@ uniqueDomainsLoop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if no automation policy exists for the name yet, we
|
// if no automation policy exists for the name yet, we will associate it with an implicit one;
|
||||||
// will associate it with an implicit one
|
// we handle tailscale domains specially, and we also separate out identifiers that need the
|
||||||
|
// internal issuer (self-signed certs); certmagic does not consider public IP addresses to be
|
||||||
|
// disqualified for public certs, because there are public CAs that will issue certs for IPs.
|
||||||
|
// However, with auto-HTTPS, many times there is no issuer explicitly defined, and the default
|
||||||
|
// issuers do not (currently, as of 2024) issue IP certificates; so assign all IP subjects to
|
||||||
|
// the internal issuer when there are no explicit automation policies
|
||||||
|
shouldUseInternal := func(ident string) bool {
|
||||||
|
usingDefaultIssuersAndIsIP := certmagic.SubjectIsIP(ident) &&
|
||||||
|
(app.tlsApp == nil || app.tlsApp.Automation == nil || len(app.tlsApp.Automation.Policies) == 0)
|
||||||
|
return !certmagic.SubjectQualifiesForPublicCert(d) || usingDefaultIssuersAndIsIP
|
||||||
|
}
|
||||||
if isTailscaleDomain(d) {
|
if isTailscaleDomain(d) {
|
||||||
tailscale = append(tailscale, d)
|
tailscale = append(tailscale, d)
|
||||||
} else if !certmagic.SubjectQualifiesForPublicCert(d) {
|
} else if shouldUseInternal(d) {
|
||||||
internal = append(internal, d)
|
internal = append(internal, d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -742,7 +774,7 @@ func (app *App) automaticHTTPSPhase2() error {
|
|||||||
)
|
)
|
||||||
err := app.tlsApp.Manage(app.allCertDomains)
|
err := app.tlsApp.Manage(app.allCertDomains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("managing certificates for %v: %s", app.allCertDomains, err)
|
return fmt.Errorf("managing certificates for %d domains: %s", len(app.allCertDomains), err)
|
||||||
}
|
}
|
||||||
app.allCertDomains = nil // no longer needed; allow GC to deallocate
|
app.allCertDomains = nil // no longer needed; allow GC to deallocate
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
@@ -76,9 +77,9 @@ func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
|||||||
for provName, prov := range a.Providers {
|
for provName, prov := range a.Providers {
|
||||||
user, authed, err = prov.Authenticate(w, r)
|
user, authed, err = prov.Authenticate(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Error("auth provider returned error",
|
if c := a.logger.Check(zapcore.ErrorLevel, "auth provider returned error"); c != nil {
|
||||||
zap.String("provider", provName),
|
c.Write(zap.String("provider", provName), zap.Error(err))
|
||||||
zap.Error(err))
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if authed {
|
if authed {
|
||||||
|
|||||||
@@ -36,10 +36,26 @@ func init() {
|
|||||||
// RequestMatcher is a type that can match to a request.
|
// RequestMatcher is a type that can match to a request.
|
||||||
// A route matcher MUST NOT modify the request, with the
|
// A route matcher MUST NOT modify the request, with the
|
||||||
// only exception being its context.
|
// only exception being its context.
|
||||||
|
//
|
||||||
|
// Deprecated: Matchers should now implement RequestMatcherWithError.
|
||||||
|
// You may remove any interface guards for RequestMatcher
|
||||||
|
// but keep your Match() methods for backwards compatibility.
|
||||||
type RequestMatcher interface {
|
type RequestMatcher interface {
|
||||||
Match(*http.Request) bool
|
Match(*http.Request) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestMatcherWithError is like RequestMatcher but can return an error.
|
||||||
|
// An error during matching will abort the request middleware chain and
|
||||||
|
// invoke the error middleware chain.
|
||||||
|
//
|
||||||
|
// This will eventually replace RequestMatcher. Matcher modules
|
||||||
|
// should implement both interfaces, and once all modules have
|
||||||
|
// been updated to use RequestMatcherWithError, the RequestMatcher
|
||||||
|
// interface may eventually be dropped.
|
||||||
|
type RequestMatcherWithError interface {
|
||||||
|
MatchWithError(*http.Request) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
// Handler is like http.Handler except ServeHTTP may return an error.
|
// Handler is like http.Handler except ServeHTTP may return an error.
|
||||||
//
|
//
|
||||||
// If any handler encounters an error, it should be returned for proper
|
// If any handler encounters an error, it should be returned for proper
|
||||||
|
|||||||
+116
-30
@@ -126,6 +126,10 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error {
|
|||||||
// light (and possibly naïve) syntactic sugar
|
// light (and possibly naïve) syntactic sugar
|
||||||
m.expandedExpr = placeholderRegexp.ReplaceAllString(m.Expr, placeholderExpansion)
|
m.expandedExpr = placeholderRegexp.ReplaceAllString(m.Expr, placeholderExpansion)
|
||||||
|
|
||||||
|
// as a second pass, we'll strip the escape character from an escaped
|
||||||
|
// placeholder, so that it can be used as an input to other CEL functions
|
||||||
|
m.expandedExpr = escapedPlaceholderRegexp.ReplaceAllString(m.expandedExpr, escapedPlaceholderExpansion)
|
||||||
|
|
||||||
// our type adapter expands CEL's standard type support
|
// our type adapter expands CEL's standard type support
|
||||||
m.ta = celTypeAdapter{}
|
m.ta = celTypeAdapter{}
|
||||||
|
|
||||||
@@ -159,14 +163,17 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error {
|
|||||||
|
|
||||||
// create the CEL environment
|
// create the CEL environment
|
||||||
env, err := cel.NewEnv(
|
env, err := cel.NewEnv(
|
||||||
cel.Function(placeholderFuncName, cel.SingletonBinaryBinding(m.caddyPlaceholderFunc), cel.Overload(
|
cel.Function(CELPlaceholderFuncName, cel.SingletonBinaryBinding(m.caddyPlaceholderFunc), cel.Overload(
|
||||||
placeholderFuncName+"_httpRequest_string",
|
CELPlaceholderFuncName+"_httpRequest_string",
|
||||||
[]*cel.Type{httpRequestObjectType, cel.StringType},
|
[]*cel.Type{httpRequestObjectType, cel.StringType},
|
||||||
cel.AnyType,
|
cel.AnyType,
|
||||||
)),
|
)),
|
||||||
cel.Variable("request", httpRequestObjectType),
|
cel.Variable(CELRequestVarName, httpRequestObjectType),
|
||||||
cel.CustomTypeAdapter(m.ta),
|
cel.CustomTypeAdapter(m.ta),
|
||||||
ext.Strings(),
|
ext.Strings(),
|
||||||
|
ext.Bindings(),
|
||||||
|
ext.Lists(),
|
||||||
|
ext.Math(),
|
||||||
matcherLib,
|
matcherLib,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -195,17 +202,25 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error {
|
|||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchExpression) Match(r *http.Request) bool {
|
func (m MatchExpression) Match(r *http.Request) bool {
|
||||||
|
match, err := m.MatchWithError(r)
|
||||||
|
if err != nil {
|
||||||
|
SetVar(r.Context(), MatcherErrorVarKey, err)
|
||||||
|
}
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchWithError returns true if r matches m.
|
||||||
|
func (m MatchExpression) MatchWithError(r *http.Request) (bool, error) {
|
||||||
celReq := celHTTPRequest{r}
|
celReq := celHTTPRequest{r}
|
||||||
out, _, err := m.prg.Eval(celReq)
|
out, _, err := m.prg.Eval(celReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.log.Error("evaluating expression", zap.Error(err))
|
m.log.Error("evaluating expression", zap.Error(err))
|
||||||
SetVar(r.Context(), MatcherErrorVarKey, err)
|
return false, err
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
if outBool, ok := out.Value().(bool); ok {
|
if outBool, ok := out.Value().(bool); ok {
|
||||||
return outBool
|
return outBool, nil
|
||||||
}
|
}
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||||
@@ -247,7 +262,7 @@ func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
|
|||||||
return types.NewErr(
|
return types.NewErr(
|
||||||
"invalid request of type '%v' to %s(request, placeholderVarName)",
|
"invalid request of type '%v' to %s(request, placeholderVarName)",
|
||||||
lhs.Type(),
|
lhs.Type(),
|
||||||
placeholderFuncName,
|
CELPlaceholderFuncName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
phStr, ok := rhs.(types.String)
|
phStr, ok := rhs.(types.String)
|
||||||
@@ -255,7 +270,7 @@ func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
|
|||||||
return types.NewErr(
|
return types.NewErr(
|
||||||
"invalid placeholder variable name of type '%v' to %s(request, placeholderVarName)",
|
"invalid placeholder variable name of type '%v' to %s(request, placeholderVarName)",
|
||||||
rhs.Type(),
|
rhs.Type(),
|
||||||
placeholderFuncName,
|
CELPlaceholderFuncName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,7 +290,7 @@ var httpRequestCELType = cel.ObjectType("http.Request", traits.ReceiverType)
|
|||||||
type celHTTPRequest struct{ *http.Request }
|
type celHTTPRequest struct{ *http.Request }
|
||||||
|
|
||||||
func (cr celHTTPRequest) ResolveName(name string) (any, bool) {
|
func (cr celHTTPRequest) ResolveName(name string) (any, bool) {
|
||||||
if name == "request" {
|
if name == CELRequestVarName {
|
||||||
return cr, true
|
return cr, true
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
@@ -340,7 +355,7 @@ func (celTypeAdapter) NativeToValue(value any) ref.Val {
|
|||||||
case time.Time:
|
case time.Time:
|
||||||
return types.Timestamp{Time: v}
|
return types.Timestamp{Time: v}
|
||||||
case error:
|
case error:
|
||||||
types.NewErr(v.Error())
|
return types.WrapErr(v)
|
||||||
}
|
}
|
||||||
return types.DefaultTypeAdapter.NativeToValue(value)
|
return types.DefaultTypeAdapter.NativeToValue(value)
|
||||||
}
|
}
|
||||||
@@ -373,7 +388,7 @@ type CELLibraryProducer interface {
|
|||||||
// limited set of function signatures. For strong type validation you may need
|
// limited set of function signatures. For strong type validation you may need
|
||||||
// to provide a custom macro which does a more detailed analysis of the CEL
|
// to provide a custom macro which does a more detailed analysis of the CEL
|
||||||
// literal provided to the macro as an argument.
|
// literal provided to the macro as an argument.
|
||||||
func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fac CELMatcherFactory) (cel.Library, error) {
|
func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fac any) (cel.Library, error) {
|
||||||
requestType := cel.ObjectType("http.Request")
|
requestType := cel.ObjectType("http.Request")
|
||||||
var macro parser.Macro
|
var macro parser.Macro
|
||||||
switch len(matcherDataTypes) {
|
switch len(matcherDataTypes) {
|
||||||
@@ -417,7 +432,11 @@ func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CELMatcherFactory converts a constant CEL value into a RequestMatcher.
|
// CELMatcherFactory converts a constant CEL value into a RequestMatcher.
|
||||||
type CELMatcherFactory func(data ref.Val) (RequestMatcher, error)
|
// Deprecated: Use CELMatcherWithErrorFactory instead.
|
||||||
|
type CELMatcherFactory = func(data ref.Val) (RequestMatcher, error)
|
||||||
|
|
||||||
|
// CELMatcherWithErrorFactory converts a constant CEL value into a RequestMatcherWithError.
|
||||||
|
type CELMatcherWithErrorFactory = func(data ref.Val) (RequestMatcherWithError, error)
|
||||||
|
|
||||||
// matcherCELLibrary is a simplistic configurable cel.Library implementation.
|
// matcherCELLibrary is a simplistic configurable cel.Library implementation.
|
||||||
type matcherCELLibrary struct {
|
type matcherCELLibrary struct {
|
||||||
@@ -445,7 +464,7 @@ func (lib *matcherCELLibrary) ProgramOptions() []cel.ProgramOption {
|
|||||||
// that takes a single argument, and optimizes the implementation to precompile
|
// that takes a single argument, and optimizes the implementation to precompile
|
||||||
// the matcher and return a function that references the precompiled and
|
// the matcher and return a function that references the precompiled and
|
||||||
// provisioned matcher.
|
// provisioned matcher.
|
||||||
func CELMatcherDecorator(funcName string, fac CELMatcherFactory) interpreter.InterpretableDecorator {
|
func CELMatcherDecorator(funcName string, fac any) interpreter.InterpretableDecorator {
|
||||||
return func(i interpreter.Interpretable) (interpreter.Interpretable, error) {
|
return func(i interpreter.Interpretable) (interpreter.Interpretable, error) {
|
||||||
call, ok := i.(interpreter.InterpretableCall)
|
call, ok := i.(interpreter.InterpretableCall)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -457,15 +476,15 @@ func CELMatcherDecorator(funcName string, fac CELMatcherFactory) interpreter.Int
|
|||||||
callArgs := call.Args()
|
callArgs := call.Args()
|
||||||
reqAttr, ok := callArgs[0].(interpreter.InterpretableAttribute)
|
reqAttr, ok := callArgs[0].(interpreter.InterpretableAttribute)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("missing 'request' argument")
|
return nil, errors.New("missing 'req' argument")
|
||||||
}
|
}
|
||||||
nsAttr, ok := reqAttr.Attr().(interpreter.NamespacedAttribute)
|
nsAttr, ok := reqAttr.Attr().(interpreter.NamespacedAttribute)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("missing 'request' argument")
|
return nil, errors.New("missing 'req' argument")
|
||||||
}
|
}
|
||||||
varNames := nsAttr.CandidateVariableNames()
|
varNames := nsAttr.CandidateVariableNames()
|
||||||
if len(varNames) != 1 || len(varNames) == 1 && varNames[0] != "request" {
|
if len(varNames) != 1 || len(varNames) == 1 && varNames[0] != CELRequestVarName {
|
||||||
return nil, errors.New("missing 'request' argument")
|
return nil, errors.New("missing 'req' argument")
|
||||||
}
|
}
|
||||||
matcherData, ok := callArgs[1].(interpreter.InterpretableConst)
|
matcherData, ok := callArgs[1].(interpreter.InterpretableConst)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -474,7 +493,9 @@ func CELMatcherDecorator(funcName string, fac CELMatcherFactory) interpreter.Int
|
|||||||
// and matcher provisioning should be handled at dynamically.
|
// and matcher provisioning should be handled at dynamically.
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
matcher, err := fac(matcherData.Value())
|
|
||||||
|
if factory, ok := fac.(CELMatcherWithErrorFactory); ok {
|
||||||
|
matcher, err := factory(matcherData.Value())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -487,23 +508,78 @@ func CELMatcherDecorator(funcName string, fac CELMatcherFactory) interpreter.Int
|
|||||||
// If needed this call could be changed to convert the value
|
// If needed this call could be changed to convert the value
|
||||||
// to a *http.Request using CEL's ConvertToNative method.
|
// to a *http.Request using CEL's ConvertToNative method.
|
||||||
httpReq := celReq.Value().(celHTTPRequest)
|
httpReq := celReq.Value().(celHTTPRequest)
|
||||||
|
match, err := matcher.MatchWithError(httpReq.Request)
|
||||||
|
if err != nil {
|
||||||
|
return types.WrapErr(err)
|
||||||
|
}
|
||||||
|
return types.Bool(match)
|
||||||
|
},
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if factory, ok := fac.(CELMatcherFactory); ok {
|
||||||
|
matcher, err := factory(matcherData.Value())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return interpreter.NewCall(
|
||||||
|
i.ID(), funcName, funcName+"_opt",
|
||||||
|
[]interpreter.Interpretable{reqAttr},
|
||||||
|
func(args ...ref.Val) ref.Val {
|
||||||
|
// The request value, guaranteed to be of type celHTTPRequest
|
||||||
|
celReq := args[0]
|
||||||
|
// If needed this call could be changed to convert the value
|
||||||
|
// to a *http.Request using CEL's ConvertToNative method.
|
||||||
|
httpReq := celReq.Value().(celHTTPRequest)
|
||||||
|
if m, ok := matcher.(RequestMatcherWithError); ok {
|
||||||
|
match, err := m.MatchWithError(httpReq.Request)
|
||||||
|
if err != nil {
|
||||||
|
return types.WrapErr(err)
|
||||||
|
}
|
||||||
|
return types.Bool(match)
|
||||||
|
}
|
||||||
return types.Bool(matcher.Match(httpReq.Request))
|
return types.Bool(matcher.Match(httpReq.Request))
|
||||||
},
|
},
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("invalid matcher factory, must be CELMatcherFactory or CELMatcherWithErrorFactory: %T", fac)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CELMatcherRuntimeFunction creates a function binding for when the input to the matcher
|
// CELMatcherRuntimeFunction creates a function binding for when the input to the matcher
|
||||||
// is dynamically resolved rather than a set of static constant values.
|
// is dynamically resolved rather than a set of static constant values.
|
||||||
func CELMatcherRuntimeFunction(funcName string, fac CELMatcherFactory) functions.BinaryOp {
|
func CELMatcherRuntimeFunction(funcName string, fac any) functions.BinaryOp {
|
||||||
return func(celReq, matcherData ref.Val) ref.Val {
|
return func(celReq, matcherData ref.Val) ref.Val {
|
||||||
matcher, err := fac(matcherData)
|
if factory, ok := fac.(CELMatcherWithErrorFactory); ok {
|
||||||
|
matcher, err := factory(matcherData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.NewErr(err.Error())
|
return types.WrapErr(err)
|
||||||
}
|
}
|
||||||
httpReq := celReq.Value().(celHTTPRequest)
|
httpReq := celReq.Value().(celHTTPRequest)
|
||||||
|
match, err := matcher.MatchWithError(httpReq.Request)
|
||||||
|
if err != nil {
|
||||||
|
return types.WrapErr(err)
|
||||||
|
}
|
||||||
|
return types.Bool(match)
|
||||||
|
}
|
||||||
|
if factory, ok := fac.(CELMatcherFactory); ok {
|
||||||
|
matcher, err := factory(matcherData)
|
||||||
|
if err != nil {
|
||||||
|
return types.WrapErr(err)
|
||||||
|
}
|
||||||
|
httpReq := celReq.Value().(celHTTPRequest)
|
||||||
|
if m, ok := matcher.(RequestMatcherWithError); ok {
|
||||||
|
match, err := m.MatchWithError(httpReq.Request)
|
||||||
|
if err != nil {
|
||||||
|
return types.WrapErr(err)
|
||||||
|
}
|
||||||
|
return types.Bool(match)
|
||||||
|
}
|
||||||
return types.Bool(matcher.Match(httpReq.Request))
|
return types.Bool(matcher.Match(httpReq.Request))
|
||||||
}
|
}
|
||||||
|
return types.NewErr("CELMatcherRuntimeFunction invalid matcher factory: %T", fac)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// celMatcherStringListMacroExpander validates that the macro is called
|
// celMatcherStringListMacroExpander validates that the macro is called
|
||||||
@@ -524,7 +600,7 @@ func celMatcherStringListMacroExpander(funcName string) cel.MacroFactory {
|
|||||||
return nil, eh.NewError(arg.ID(), "matcher arguments must be string constants")
|
return nil, eh.NewError(arg.ID(), "matcher arguments must be string constants")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return eh.NewCall(funcName, eh.NewIdent("request"), eh.NewList(matchArgs...)), nil
|
return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), eh.NewList(matchArgs...)), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,7 +614,7 @@ func celMatcherStringMacroExpander(funcName string) parser.MacroExpander {
|
|||||||
return nil, eh.NewError(0, "matcher requires one argument")
|
return nil, eh.NewError(0, "matcher requires one argument")
|
||||||
}
|
}
|
||||||
if isCELStringExpr(args[0]) {
|
if isCELStringExpr(args[0]) {
|
||||||
return eh.NewCall(funcName, eh.NewIdent("request"), args[0]), nil
|
return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), args[0]), nil
|
||||||
}
|
}
|
||||||
return nil, eh.NewError(args[0].ID(), "matcher argument must be a string literal")
|
return nil, eh.NewError(args[0].ID(), "matcher argument must be a string literal")
|
||||||
}
|
}
|
||||||
@@ -572,7 +648,7 @@ func celMatcherJSONMacroExpander(funcName string) parser.MacroExpander {
|
|||||||
return nil, eh.NewError(entry.AsMapEntry().Value().ID(), "matcher map values must be string or list literals")
|
return nil, eh.NewError(entry.AsMapEntry().Value().ID(), "matcher map values must be string or list literals")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return eh.NewCall(funcName, eh.NewIdent("request"), arg), nil
|
return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), arg), nil
|
||||||
case ast.UnspecifiedExprKind, ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.SelectKind:
|
case ast.UnspecifiedExprKind, ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.SelectKind:
|
||||||
// appeasing the linter :)
|
// appeasing the linter :)
|
||||||
}
|
}
|
||||||
@@ -646,7 +722,7 @@ func isCELCaddyPlaceholderCall(e ast.Expr) bool {
|
|||||||
switch e.Kind() {
|
switch e.Kind() {
|
||||||
case ast.CallKind:
|
case ast.CallKind:
|
||||||
call := e.AsCall()
|
call := e.AsCall()
|
||||||
if call.FunctionName() == "caddyPlaceholder" {
|
if call.FunctionName() == CELPlaceholderFuncName {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case ast.UnspecifiedExprKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind:
|
case ast.UnspecifiedExprKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind:
|
||||||
@@ -701,8 +777,15 @@ func isCELStringListLiteral(e ast.Expr) bool {
|
|||||||
// expressions with a proper CEL function call; this is
|
// expressions with a proper CEL function call; this is
|
||||||
// just for syntactic sugar.
|
// just for syntactic sugar.
|
||||||
var (
|
var (
|
||||||
placeholderRegexp = regexp.MustCompile(`{([a-zA-Z][\w.-]+)}`)
|
// The placeholder may not be preceded by a backslash; the expansion
|
||||||
placeholderExpansion = `caddyPlaceholder(request, "${1}")`
|
// will include the preceding character if it is not a backslash.
|
||||||
|
placeholderRegexp = regexp.MustCompile(`([^\\]|^){([a-zA-Z][\w.-]+)}`)
|
||||||
|
placeholderExpansion = `${1}ph(req, "${2}")`
|
||||||
|
|
||||||
|
// As a second pass, we need to strip the escape character in front of
|
||||||
|
// the placeholder, if it exists.
|
||||||
|
escapedPlaceholderRegexp = regexp.MustCompile(`\\{([a-zA-Z][\w.-]+)}`)
|
||||||
|
escapedPlaceholderExpansion = `{${1}}`
|
||||||
|
|
||||||
CELTypeJSON = cel.MapType(cel.StringType, cel.DynType)
|
CELTypeJSON = cel.MapType(cel.StringType, cel.DynType)
|
||||||
)
|
)
|
||||||
@@ -710,14 +793,17 @@ var (
|
|||||||
var httpRequestObjectType = cel.ObjectType("http.Request")
|
var httpRequestObjectType = cel.ObjectType("http.Request")
|
||||||
|
|
||||||
// The name of the CEL function which accesses Replacer values.
|
// The name of the CEL function which accesses Replacer values.
|
||||||
const placeholderFuncName = "caddyPlaceholder"
|
const CELPlaceholderFuncName = "ph"
|
||||||
|
|
||||||
|
// The name of the CEL request variable.
|
||||||
|
const CELRequestVarName = "req"
|
||||||
|
|
||||||
const MatcherNameCtxKey = "matcher_name"
|
const MatcherNameCtxKey = "matcher_name"
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
_ caddy.Provisioner = (*MatchExpression)(nil)
|
_ caddy.Provisioner = (*MatchExpression)(nil)
|
||||||
_ RequestMatcher = (*MatchExpression)(nil)
|
_ RequestMatcherWithError = (*MatchExpression)(nil)
|
||||||
_ caddyfile.Unmarshaler = (*MatchExpression)(nil)
|
_ caddyfile.Unmarshaler = (*MatchExpression)(nil)
|
||||||
_ json.Marshaler = (*MatchExpression)(nil)
|
_ json.Marshaler = (*MatchExpression)(nil)
|
||||||
_ json.Unmarshaler = (*MatchExpression)(nil)
|
_ json.Unmarshaler = (*MatchExpression)(nil)
|
||||||
|
|||||||
@@ -70,12 +70,35 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
wantResult: true,
|
wantResult: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "header error (MatchHeader)",
|
name: "header matches an escaped placeholder value (MatchHeader)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `header({'Field': '\\\{foobar}'})`,
|
||||||
|
},
|
||||||
|
urlTarget: "https://example.com/foo",
|
||||||
|
httpHeader: &http.Header{"Field": []string{"{foobar}"}},
|
||||||
|
wantResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "header matches an placeholder replaced during the header matcher (MatchHeader)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `header({'Field': '\{http.request.uri.path}'})`,
|
||||||
|
},
|
||||||
|
urlTarget: "https://example.com/foo",
|
||||||
|
httpHeader: &http.Header{"Field": []string{"/foo"}},
|
||||||
|
wantResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "header error, invalid escape sequence (MatchHeader)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `header({'Field': '\\{foobar}'})`,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "header error, needs to be JSON syntax with field as key (MatchHeader)",
|
||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `header('foo')`,
|
Expr: `header('foo')`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com/foo",
|
|
||||||
httpHeader: &http.Header{"Field": []string{"foo", "bar"}},
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -110,8 +133,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `header_regexp('foo')`,
|
Expr: `header_regexp('foo')`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com/foo",
|
|
||||||
httpHeader: &http.Header{"Field": []string{"foo", "bar"}},
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -143,7 +164,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `host(80)`,
|
Expr: `host(80)`,
|
||||||
},
|
},
|
||||||
urlTarget: "http://localhost:80",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -169,8 +189,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `method()`,
|
Expr: `method()`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://foo.example.com",
|
|
||||||
httpMethod: "PUT",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -266,7 +284,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `protocol()`,
|
Expr: `protocol()`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -274,7 +291,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `protocol('grpc', 'https')`,
|
Expr: `protocol('grpc', 'https')`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -282,7 +298,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `protocol(true)`,
|
Expr: `protocol(true)`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -330,7 +345,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `query({1: "1"})`,
|
Expr: `query({1: "1"})`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com/foo",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -338,7 +352,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `query(Message{field: "1"})`,
|
Expr: `query(Message{field: "1"})`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com/foo",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -346,7 +359,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `query({"debug": 1})`,
|
Expr: `query({"debug": 1})`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com/foo/?debug=1",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -354,7 +366,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `query()`,
|
Expr: `query()`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com/foo/?debug=1",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -362,7 +373,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `remote_ip()`,
|
Expr: `remote_ip()`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com/foo",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -373,6 +383,67 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
urlTarget: "https://example.com/foo",
|
urlTarget: "https://example.com/foo",
|
||||||
wantResult: true,
|
wantResult: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "vars value (VarsMatcher)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `vars({'foo': 'bar'})`,
|
||||||
|
},
|
||||||
|
urlTarget: "https://example.com/foo",
|
||||||
|
wantResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vars matches placeholder, needs escape (VarsMatcher)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `vars({'\{http.request.uri.path}': '/foo'})`,
|
||||||
|
},
|
||||||
|
urlTarget: "https://example.com/foo",
|
||||||
|
wantResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vars error wrong syntax (VarsMatcher)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `vars('foo', 'bar')`,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vars error no args (VarsMatcher)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `vars()`,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vars_regexp value (MatchVarsRE)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `vars_regexp('foo', 'ba?r')`,
|
||||||
|
},
|
||||||
|
urlTarget: "https://example.com/foo",
|
||||||
|
wantResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vars_regexp value with name (MatchVarsRE)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `vars_regexp('name', 'foo', 'ba?r')`,
|
||||||
|
},
|
||||||
|
urlTarget: "https://example.com/foo",
|
||||||
|
wantResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vars_regexp matches placeholder, needs escape (MatchVarsRE)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `vars_regexp('\{http.request.uri.path}', '/fo?o')`,
|
||||||
|
},
|
||||||
|
urlTarget: "https://example.com/foo",
|
||||||
|
wantResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vars_regexp error no args (MatchVarsRE)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `vars_regexp()`,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -396,6 +467,9 @@ func TestMatchExpressionMatch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
repl := caddy.NewReplacer()
|
repl := caddy.NewReplacer()
|
||||||
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
||||||
|
ctx = context.WithValue(ctx, VarsCtxKey, map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
})
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
||||||
|
|
||||||
@@ -415,7 +489,11 @@ func TestMatchExpressionMatch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tc.expression.Match(req) != tc.wantResult {
|
matches, err := tc.expression.MatchWithError(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MatchExpression.Match() error = %v", err)
|
||||||
|
}
|
||||||
|
if matches != tc.wantResult {
|
||||||
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr)
|
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -436,6 +514,9 @@ func BenchmarkMatchExpressionMatch(b *testing.B) {
|
|||||||
}
|
}
|
||||||
repl := caddy.NewReplacer()
|
repl := caddy.NewReplacer()
|
||||||
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
||||||
|
ctx = context.WithValue(ctx, VarsCtxKey, map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
})
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
||||||
if tc.clientCertificate != nil {
|
if tc.clientCertificate != nil {
|
||||||
@@ -455,7 +536,7 @@ func BenchmarkMatchExpressionMatch(b *testing.B) {
|
|||||||
}
|
}
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
tc.expression.Match(req)
|
tc.expression.MatchWithError(req)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,21 +57,7 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||||||
d.Next() // consume directive name
|
d.Next() // consume directive name
|
||||||
|
|
||||||
prefer := []string{}
|
prefer := []string{}
|
||||||
for _, arg := range d.RemainingArgs() {
|
remainingArgs := d.RemainingArgs()
|
||||||
mod, err := caddy.GetModule("http.encoders." + arg)
|
|
||||||
if err != nil {
|
|
||||||
return d.Errf("finding encoder module '%s': %v", mod, err)
|
|
||||||
}
|
|
||||||
encoding, ok := mod.New().(Encoding)
|
|
||||||
if !ok {
|
|
||||||
return d.Errf("module %s is not an HTTP encoding", mod)
|
|
||||||
}
|
|
||||||
if enc.EncodingsRaw == nil {
|
|
||||||
enc.EncodingsRaw = make(caddy.ModuleMap)
|
|
||||||
}
|
|
||||||
enc.EncodingsRaw[arg] = caddyconfig.JSON(encoding, nil)
|
|
||||||
prefer = append(prefer, arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
responseMatchers := make(map[string]caddyhttp.ResponseMatcher)
|
responseMatchers := make(map[string]caddyhttp.ResponseMatcher)
|
||||||
for d.NextBlock(0) {
|
for d.NextBlock(0) {
|
||||||
@@ -111,6 +97,26 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(prefer) == 0 && len(remainingArgs) == 0 {
|
||||||
|
remainingArgs = []string{"zstd", "gzip"}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arg := range remainingArgs {
|
||||||
|
mod, err := caddy.GetModule("http.encoders." + arg)
|
||||||
|
if err != nil {
|
||||||
|
return d.Errf("finding encoder module '%s': %v", mod, err)
|
||||||
|
}
|
||||||
|
encoding, ok := mod.New().(Encoding)
|
||||||
|
if !ok {
|
||||||
|
return d.Errf("module %s is not an HTTP encoding", mod)
|
||||||
|
}
|
||||||
|
if enc.EncodingsRaw == nil {
|
||||||
|
enc.EncodingsRaw = make(caddy.ModuleMap)
|
||||||
|
}
|
||||||
|
enc.EncodingsRaw[arg] = caddyconfig.JSON(encoding, nil)
|
||||||
|
prefer = append(prefer, arg)
|
||||||
|
}
|
||||||
|
|
||||||
// use the order in which the encoders were defined.
|
// use the order in which the encoders were defined.
|
||||||
enc.Prefer = prefer
|
enc.Prefer = prefer
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -112,7 +113,8 @@ func (enc *Encode) Provision(ctx caddy.Context) error {
|
|||||||
"application/x-ttf*",
|
"application/x-ttf*",
|
||||||
"application/xhtml+xml*",
|
"application/xhtml+xml*",
|
||||||
"application/xml*",
|
"application/xml*",
|
||||||
"font/*",
|
"font/ttf*",
|
||||||
|
"font/otf*",
|
||||||
"image/svg+xml*",
|
"image/svg+xml*",
|
||||||
"image/vnd.microsoft.icon*",
|
"image/vnd.microsoft.icon*",
|
||||||
"image/x-icon*",
|
"image/x-icon*",
|
||||||
@@ -154,7 +156,7 @@ func (enc *Encode) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyh
|
|||||||
if _, ok := enc.writerPools[encName]; !ok {
|
if _, ok := enc.writerPools[encName]; !ok {
|
||||||
continue // encoding not offered
|
continue // encoding not offered
|
||||||
}
|
}
|
||||||
w = enc.openResponseWriter(encName, w)
|
w = enc.openResponseWriter(encName, w, r.Method == http.MethodConnect)
|
||||||
defer w.(*responseWriter).Close()
|
defer w.(*responseWriter).Close()
|
||||||
|
|
||||||
// to comply with RFC 9110 section 8.8.3(.3), we modify the Etag when encoding
|
// to comply with RFC 9110 section 8.8.3(.3), we modify the Etag when encoding
|
||||||
@@ -199,14 +201,14 @@ func (enc *Encode) addEncoding(e Encoding) error {
|
|||||||
// openResponseWriter creates a new response writer that may (or may not)
|
// openResponseWriter creates a new response writer that may (or may not)
|
||||||
// encode the response with encodingName. The returned response writer MUST
|
// encode the response with encodingName. The returned response writer MUST
|
||||||
// be closed after the handler completes.
|
// be closed after the handler completes.
|
||||||
func (enc *Encode) openResponseWriter(encodingName string, w http.ResponseWriter) *responseWriter {
|
func (enc *Encode) openResponseWriter(encodingName string, w http.ResponseWriter, isConnect bool) *responseWriter {
|
||||||
var rw responseWriter
|
var rw responseWriter
|
||||||
return enc.initResponseWriter(&rw, encodingName, w)
|
return enc.initResponseWriter(&rw, encodingName, w, isConnect)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initResponseWriter initializes the responseWriter instance
|
// initResponseWriter initializes the responseWriter instance
|
||||||
// allocated in openResponseWriter, enabling mid-stack inlining.
|
// allocated in openResponseWriter, enabling mid-stack inlining.
|
||||||
func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, wrappedRW http.ResponseWriter) *responseWriter {
|
func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, wrappedRW http.ResponseWriter, isConnect bool) *responseWriter {
|
||||||
if rww, ok := wrappedRW.(*caddyhttp.ResponseWriterWrapper); ok {
|
if rww, ok := wrappedRW.(*caddyhttp.ResponseWriterWrapper); ok {
|
||||||
rw.ResponseWriter = rww
|
rw.ResponseWriter = rww
|
||||||
} else {
|
} else {
|
||||||
@@ -214,6 +216,7 @@ func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, w
|
|||||||
}
|
}
|
||||||
rw.encodingName = encodingName
|
rw.encodingName = encodingName
|
||||||
rw.config = enc
|
rw.config = enc
|
||||||
|
rw.isConnect = isConnect
|
||||||
|
|
||||||
return rw
|
return rw
|
||||||
}
|
}
|
||||||
@@ -228,6 +231,7 @@ type responseWriter struct {
|
|||||||
config *Encode
|
config *Encode
|
||||||
statusCode int
|
statusCode int
|
||||||
wroteHeader bool
|
wroteHeader bool
|
||||||
|
isConnect bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteHeader stores the status to write when the time comes
|
// WriteHeader stores the status to write when the time comes
|
||||||
@@ -243,6 +247,14 @@ func (rw *responseWriter) WriteHeader(status int) {
|
|||||||
rw.Header().Add("Vary", "Accept-Encoding")
|
rw.Header().Add("Vary", "Accept-Encoding")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// write status immediately if status is 2xx and the request is CONNECT
|
||||||
|
// since it means the response is successful.
|
||||||
|
// see: https://github.com/caddyserver/caddy/issues/6733#issuecomment-2525058845
|
||||||
|
if rw.isConnect && 200 <= status && status <= 299 {
|
||||||
|
rw.ResponseWriter.WriteHeader(status)
|
||||||
|
rw.wroteHeader = true
|
||||||
|
}
|
||||||
|
|
||||||
// write status immediately when status code is informational
|
// write status immediately when status code is informational
|
||||||
// see: https://caddy.community/t/disappear-103-early-hints-response-with-encode-enable-caddy-v2-7-6/23081/5
|
// see: https://caddy.community/t/disappear-103-early-hints-response-with-encode-enable-caddy-v2-7-6/23081/5
|
||||||
if 100 <= status && status <= 199 {
|
if 100 <= status && status <= 199 {
|
||||||
@@ -258,6 +270,12 @@ func (enc *Encode) Match(rw *responseWriter) bool {
|
|||||||
// FlushError is an alternative Flush returning an error. It delays the actual Flush of the underlying
|
// FlushError is an alternative Flush returning an error. It delays the actual Flush of the underlying
|
||||||
// ResponseWriterWrapper until headers were written.
|
// ResponseWriterWrapper until headers were written.
|
||||||
func (rw *responseWriter) FlushError() error {
|
func (rw *responseWriter) FlushError() error {
|
||||||
|
// WriteHeader wasn't called and is a CONNECT request, treat it as a success.
|
||||||
|
// otherwise, wait until header is written.
|
||||||
|
if rw.isConnect && !rw.wroteHeader && rw.statusCode == 0 {
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
if !rw.wroteHeader {
|
if !rw.wroteHeader {
|
||||||
// flushing the underlying ResponseWriter will write header and status code,
|
// flushing the underlying ResponseWriter will write header and status code,
|
||||||
// but we need to delay that until we can determine if we must encode and
|
// but we need to delay that until we can determine if we must encode and
|
||||||
@@ -265,6 +283,14 @@ func (rw *responseWriter) FlushError() error {
|
|||||||
// to rw.Write (see bug in #4314)
|
// to rw.Write (see bug in #4314)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// also flushes the encoder, if any
|
||||||
|
// see: https://github.com/jjiang-stripe/caddy-slow-gzip
|
||||||
|
if rw.w != nil {
|
||||||
|
err := rw.w.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
//nolint:bodyclose
|
//nolint:bodyclose
|
||||||
return http.NewResponseController(rw.ResponseWriter).Flush()
|
return http.NewResponseController(rw.ResponseWriter).Flush()
|
||||||
}
|
}
|
||||||
@@ -278,6 +304,12 @@ func (rw *responseWriter) Write(p []byte) (int, error) {
|
|||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteHeader wasn't called and is a CONNECT request, treat it as a success.
|
||||||
|
// otherwise, determine if the response should be compressed.
|
||||||
|
if rw.isConnect && !rw.wroteHeader && rw.statusCode == 0 {
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
// sniff content-type and determine content-length
|
// sniff content-type and determine content-length
|
||||||
if !rw.wroteHeader && rw.config.MinLength > 0 {
|
if !rw.wroteHeader && rw.config.MinLength > 0 {
|
||||||
var gtMinLength bool
|
var gtMinLength bool
|
||||||
@@ -315,6 +347,49 @@ func (rw *responseWriter) Write(p []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used to mask ReadFrom method
|
||||||
|
type writerOnly struct {
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// copied from stdlib
|
||||||
|
const sniffLen = 512
|
||||||
|
|
||||||
|
// ReadFrom will try to use sendfile to copy from the reader to the response writer.
|
||||||
|
// It's only used if the response writer implements io.ReaderFrom and the data can't be compressed.
|
||||||
|
// It's based on stdlin http1.1 response writer implementation.
|
||||||
|
// https://github.com/golang/go/blob/f4e3ec3dbe3b8e04a058d266adf8e048bab563f2/src/net/http/server.go#L586
|
||||||
|
func (rw *responseWriter) ReadFrom(r io.Reader) (int64, error) {
|
||||||
|
rf, ok := rw.ResponseWriter.(io.ReaderFrom)
|
||||||
|
// sendfile can't be used anyway
|
||||||
|
if !ok {
|
||||||
|
// mask ReadFrom to avoid infinite recursion
|
||||||
|
return io.Copy(writerOnly{rw}, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ns int64
|
||||||
|
// try to sniff the content type and determine if the response should be compressed
|
||||||
|
if !rw.wroteHeader && rw.config.MinLength > 0 {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
buf [sniffLen]byte
|
||||||
|
)
|
||||||
|
// mask ReadFrom to let Write determine if the response should be compressed
|
||||||
|
ns, err = io.CopyBuffer(writerOnly{rw}, io.LimitReader(r, sniffLen), buf[:])
|
||||||
|
if err != nil || ns < sniffLen {
|
||||||
|
return ns, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the response will be compressed, no sendfile support
|
||||||
|
if rw.w != nil {
|
||||||
|
nr, err := io.Copy(rw.w, r)
|
||||||
|
return nr + ns, err
|
||||||
|
}
|
||||||
|
nr, err := rf.ReadFrom(r)
|
||||||
|
return nr + ns, err
|
||||||
|
}
|
||||||
|
|
||||||
// Close writes any remaining buffered response and
|
// Close writes any remaining buffered response and
|
||||||
// deallocates any active resources.
|
// deallocates any active resources.
|
||||||
func (rw *responseWriter) Close() error {
|
func (rw *responseWriter) Close() error {
|
||||||
@@ -432,12 +507,9 @@ func AcceptedEncodings(r *http.Request, preferredOrder []string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set server preference
|
// set server preference
|
||||||
prefOrder := -1
|
prefOrder := slices.Index(preferredOrder, encName)
|
||||||
for i, p := range preferredOrder {
|
if prefOrder > -1 {
|
||||||
if encName == p {
|
prefOrder = len(preferredOrder) - prefOrder
|
||||||
prefOrder = len(preferredOrder) - i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prefs = append(prefs, encodingPreference{
|
prefs = append(prefs, encodingPreference{
|
||||||
@@ -474,6 +546,7 @@ type encodingPreference struct {
|
|||||||
type Encoder interface {
|
type Encoder interface {
|
||||||
io.WriteCloser
|
io.WriteCloser
|
||||||
Reset(io.Writer)
|
Reset(io.Writer)
|
||||||
|
Flush() error // encoder by default buffers data to maximize compressing rate
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encoding is a type which can create encoders of its kind
|
// Encoding is a type which can create encoders of its kind
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
func BenchmarkOpenResponseWriter(b *testing.B) {
|
func BenchmarkOpenResponseWriter(b *testing.B) {
|
||||||
enc := new(Encode)
|
enc := new(Encode)
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
enc.openResponseWriter("test", nil)
|
enc.openResponseWriter("test", nil, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
@@ -52,14 +53,32 @@ var BrowseTemplate string
|
|||||||
type Browse struct {
|
type Browse struct {
|
||||||
// Filename of the template to use instead of the embedded browse template.
|
// Filename of the template to use instead of the embedded browse template.
|
||||||
TemplateFile string `json:"template_file,omitempty"`
|
TemplateFile string `json:"template_file,omitempty"`
|
||||||
|
|
||||||
// Determines whether or not targets of symlinks should be revealed.
|
// Determines whether or not targets of symlinks should be revealed.
|
||||||
RevealSymlinks bool `json:"reveal_symlinks,omitempty"`
|
RevealSymlinks bool `json:"reveal_symlinks,omitempty"`
|
||||||
|
|
||||||
|
// Override the default sort.
|
||||||
|
// It includes the following options:
|
||||||
|
// - sort_by: name(default), namedirfirst, size, time
|
||||||
|
// - order: asc(default), desc
|
||||||
|
// eg.:
|
||||||
|
// - `sort time desc` will sort by time in descending order
|
||||||
|
// - `sort size` will sort by size in ascending order
|
||||||
|
// The first option must be `sort_by` and the second option must be `order` (if exists).
|
||||||
|
SortOptions []string `json:"sort,omitempty"`
|
||||||
|
|
||||||
|
// FileLimit limits the number of up to n DirEntry values in directory order.
|
||||||
|
FileLimit int `json:"file_limit,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultDirEntryLimit = 10000
|
||||||
|
)
|
||||||
|
|
||||||
func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||||
fsrv.logger.Debug("browse enabled; listing directory contents",
|
if c := fsrv.logger.Check(zapcore.DebugLevel, "browse enabled; listing directory contents"); c != nil {
|
||||||
zap.String("path", dirPath),
|
c.Write(zap.String("path", dirPath), zap.String("root", root))
|
||||||
zap.String("root", root))
|
}
|
||||||
|
|
||||||
// Navigation on the client-side gets messed up if the
|
// Navigation on the client-side gets messed up if the
|
||||||
// URL doesn't end in a trailing slash because hrefs to
|
// URL doesn't end in a trailing slash because hrefs to
|
||||||
@@ -81,7 +100,9 @@ func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w ht
|
|||||||
origReq := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request)
|
origReq := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request)
|
||||||
if r.URL.Path == "" || path.Base(origReq.URL.Path) == path.Base(r.URL.Path) {
|
if r.URL.Path == "" || path.Base(origReq.URL.Path) == path.Base(r.URL.Path) {
|
||||||
if !strings.HasSuffix(origReq.URL.Path, "/") {
|
if !strings.HasSuffix(origReq.URL.Path, "/") {
|
||||||
fsrv.logger.Debug("redirecting to trailing slash to preserve hrefs", zap.String("request_path", r.URL.Path))
|
if c := fsrv.logger.Check(zapcore.DebugLevel, "redirecting to trailing slash to preserve hrefs"); c != nil {
|
||||||
|
c.Write(zap.String("request_path", r.URL.Path))
|
||||||
|
}
|
||||||
return redirect(w, r, origReq.URL.Path+"/")
|
return redirect(w, r, origReq.URL.Path+"/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,9 +130,9 @@ func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w ht
|
|||||||
|
|
||||||
// speed up browser/client experience and caching by supporting If-Modified-Since
|
// speed up browser/client experience and caching by supporting If-Modified-Since
|
||||||
if ifModSinceStr := r.Header.Get("If-Modified-Since"); ifModSinceStr != "" {
|
if ifModSinceStr := r.Header.Get("If-Modified-Since"); ifModSinceStr != "" {
|
||||||
ifModSince, err := time.ParseInLocation(http.TimeFormat, ifModSinceStr, time.Local)
|
// basically a copy of stdlib file server's handling of If-Modified-Since
|
||||||
lastModTrunc := listing.lastModified.Truncate(time.Second)
|
ifModSince, err := http.ParseTime(ifModSinceStr)
|
||||||
if err == nil && (lastModTrunc.Equal(ifModSince) || lastModTrunc.Before(ifModSince)) {
|
if err == nil && listing.lastModified.Truncate(time.Second).Compare(ifModSince) <= 0 {
|
||||||
w.WriteHeader(http.StatusNotModified)
|
w.WriteHeader(http.StatusNotModified)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -192,7 +213,16 @@ func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fsrv *FileServer) loadDirectoryContents(ctx context.Context, fileSystem fs.FS, dir fs.ReadDirFile, root, urlPath string, repl *caddy.Replacer) (*browseTemplateContext, error) {
|
func (fsrv *FileServer) loadDirectoryContents(ctx context.Context, fileSystem fs.FS, dir fs.ReadDirFile, root, urlPath string, repl *caddy.Replacer) (*browseTemplateContext, error) {
|
||||||
files, err := dir.ReadDir(10000) // TODO: this limit should probably be configurable
|
// modTime for the directory itself
|
||||||
|
stat, err := dir.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dirLimit := defaultDirEntryLimit
|
||||||
|
if fsrv.Browse.FileLimit != 0 {
|
||||||
|
dirLimit = fsrv.Browse.FileLimit
|
||||||
|
}
|
||||||
|
files, err := dir.ReadDir(dirLimit)
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -200,17 +230,40 @@ func (fsrv *FileServer) loadDirectoryContents(ctx context.Context, fileSystem fs
|
|||||||
// user can presumably browse "up" to parent folder if path is longer than "/"
|
// user can presumably browse "up" to parent folder if path is longer than "/"
|
||||||
canGoUp := len(urlPath) > 1
|
canGoUp := len(urlPath) > 1
|
||||||
|
|
||||||
return fsrv.directoryListing(ctx, fileSystem, files, canGoUp, root, urlPath, repl), nil
|
return fsrv.directoryListing(ctx, fileSystem, stat.ModTime(), files, canGoUp, root, urlPath, repl), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// browseApplyQueryParams applies query parameters to the listing.
|
// browseApplyQueryParams applies query parameters to the listing.
|
||||||
// It mutates the listing and may set cookies.
|
// It mutates the listing and may set cookies.
|
||||||
func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Request, listing *browseTemplateContext) {
|
func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Request, listing *browseTemplateContext) {
|
||||||
|
var orderParam, sortParam string
|
||||||
|
|
||||||
|
// The configs in Caddyfile have lower priority than Query params,
|
||||||
|
// so put it at first.
|
||||||
|
for idx, item := range fsrv.Browse.SortOptions {
|
||||||
|
// Only `sort` & `order`, 2 params are allowed
|
||||||
|
if idx >= 2 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch item {
|
||||||
|
case sortByName, sortByNameDirFirst, sortBySize, sortByTime:
|
||||||
|
sortParam = item
|
||||||
|
case sortOrderAsc, sortOrderDesc:
|
||||||
|
orderParam = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
layoutParam := r.URL.Query().Get("layout")
|
layoutParam := r.URL.Query().Get("layout")
|
||||||
sortParam := r.URL.Query().Get("sort")
|
|
||||||
orderParam := r.URL.Query().Get("order")
|
|
||||||
limitParam := r.URL.Query().Get("limit")
|
limitParam := r.URL.Query().Get("limit")
|
||||||
offsetParam := r.URL.Query().Get("offset")
|
offsetParam := r.URL.Query().Get("offset")
|
||||||
|
sortParamTmp := r.URL.Query().Get("sort")
|
||||||
|
if sortParamTmp != "" {
|
||||||
|
sortParam = sortParamTmp
|
||||||
|
}
|
||||||
|
orderParamTmp := r.URL.Query().Get("order")
|
||||||
|
if orderParamTmp != "" {
|
||||||
|
orderParam = orderParamTmp
|
||||||
|
}
|
||||||
|
|
||||||
switch layoutParam {
|
switch layoutParam {
|
||||||
case "list", "grid", "":
|
case "list", "grid", "":
|
||||||
@@ -233,11 +286,11 @@ func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Re
|
|||||||
// then figure out the order
|
// then figure out the order
|
||||||
switch orderParam {
|
switch orderParam {
|
||||||
case "":
|
case "":
|
||||||
orderParam = "asc"
|
orderParam = sortOrderAsc
|
||||||
if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
|
if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
|
||||||
orderParam = orderCookie.Value
|
orderParam = orderCookie.Value
|
||||||
}
|
}
|
||||||
case "asc", "desc":
|
case sortOrderAsc, sortOrderDesc:
|
||||||
http.SetCookie(w, &http.Cookie{Name: "order", Value: orderParam, Secure: r.TLS != nil})
|
http.SetCookie(w, &http.Cookie{Name: "order", Value: orderParam, Secure: r.TLS != nil})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
|
{{ $nonce := uuidv4 -}}
|
||||||
|
{{ $nonceAttribute := print "nonce=" (quote $nonce) -}}
|
||||||
|
{{ $csp := printf "default-src 'none'; img-src 'self'; object-src 'none'; base-uri 'none'; script-src 'nonce-%s'; style-src 'nonce-%s'; frame-ancestors 'self'; form-action 'self';" $nonce $nonce -}}
|
||||||
|
{{/* To disable the Content-Security-Policy, set this to false */}}{{ $enableCsp := true -}}
|
||||||
|
{{ if $enableCsp -}}
|
||||||
|
{{- .RespHeader.Set "Content-Security-Policy" $csp -}}
|
||||||
|
{{ end -}}
|
||||||
{{- define "icon"}}
|
{{- define "icon"}}
|
||||||
{{- if .IsDir}}
|
{{- if .IsDir}}
|
||||||
{{- if .IsSymlink}}
|
{{- if .IsSymlink}}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-folder-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-folder-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
<path d="M9 3a1 1 0 0 1 .608 .206l.1 .087l2.706 2.707h6.586a3 3 0 0 1 2.995 2.824l.005 .176v8a3 3 0 0 1 -2.824 2.995l-.176 .005h-14a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-11a3 3 0 0 1 2.824 -2.995l.176 -.005h4z" stroke-width="0" fill="currentColor"/>
|
<path d="M9 3a1 1 0 0 1 .608 .206l.1 .087l2.706 2.707h6.586a3 3 0 0 1 2.995 2.824l.005 .176v8a3 3 0 0 1 -2.824 2.995l-.176 .005h-14a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-11a3 3 0 0 1 2.824 -2.995l.176 -.005h4z" stroke-width="0" fill="currentColor"/>
|
||||||
<path fill="#000" d="M2.795 17.306c0-2.374 1.792-4.314 4.078-4.538v-1.104a.38.38 0 0 1 .651-.272l2.45 2.492a.132.132 0 0 1 0 .188l-2.45 2.492a.381.381 0 0 1-.651-.272V15.24c-1.889.297-3.436 1.39-3.817 3.26a2.809 2.809 0 0 1-.261-1.193Z" style="stroke-width:.127478"/>
|
<path fill="#000" d="M2.795 17.306c0-2.374 1.792-4.314 4.078-4.538v-1.104a.38.38 0 0 1 .651-.272l2.45 2.492a.132.132 0 0 1 0 .188l-2.45 2.492a.381.381 0 0 1-.651-.272V15.24c-1.889.297-3.436 1.39-3.817 3.26a2.809 2.809 0 0 1-.261-1.193Z" stroke-width=".127478"/>
|
||||||
</svg>
|
</svg>
|
||||||
{{- else}}
|
{{- else}}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-folder-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-folder-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
@@ -303,7 +310,7 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="color-scheme" content="light dark">
|
<meta name="color-scheme" content="light dark">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<style>
|
<style {{ $nonceAttribute }}>
|
||||||
* { padding: 0; margin: 0; box-sizing: border-box; }
|
* { padding: 0; margin: 0; box-sizing: border-box; }
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -342,6 +349,10 @@ svg,
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#layout-list, #layout-grid {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
@@ -768,10 +779,10 @@ footer {
|
|||||||
|
|
||||||
</style>
|
</style>
|
||||||
{{- if eq .Layout "grid"}}
|
{{- if eq .Layout "grid"}}
|
||||||
<style>.wrapper { max-width: none; } main { margin-top: 1px; }</style>
|
<style {{ $nonceAttribute }}>.wrapper { max-width: none; } main { margin-top: 1px; }</style>
|
||||||
{{- end}}
|
{{- end}}
|
||||||
</head>
|
</head>
|
||||||
<body onload="initPage()">
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="breadcrumbs">Folder Path</div>
|
<div class="breadcrumbs">Folder Path</div>
|
||||||
@@ -799,7 +810,7 @@ footer {
|
|||||||
</span>
|
</span>
|
||||||
{{- end}}
|
{{- end}}
|
||||||
</div>
|
</div>
|
||||||
<a href="javascript:queryParam('layout', '')" id="layout-list" class='layout{{if eq $.Layout "list" ""}}current{{end}}'>
|
<a id="layout-list" class='layout{{if eq $.Layout "list" ""}}current{{end}}'>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-list" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-list" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v2a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"/>
|
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v2a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"/>
|
||||||
@@ -807,7 +818,7 @@ footer {
|
|||||||
</svg>
|
</svg>
|
||||||
List
|
List
|
||||||
</a>
|
</a>
|
||||||
<a href="javascript:queryParam('layout', 'grid')" id="layout-grid" class='layout{{if eq $.Layout "grid"}}current{{end}}'>
|
<a id="layout-grid" class='layout{{if eq $.Layout "grid"}}current{{end}}'>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-grid" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-grid" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
<path d="M4 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"/>
|
<path d="M4 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"/>
|
||||||
@@ -886,7 +897,7 @@ footer {
|
|||||||
<path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0"/>
|
<path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0"/>
|
||||||
<path d="M21 21l-6 -6"/>
|
<path d="M21 21l-6 -6"/>
|
||||||
</svg>
|
</svg>
|
||||||
<input type="search" placeholder="Search" id="filter" onkeyup='filter()'>
|
<input type="search" placeholder="Search" id="filter">
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
@@ -980,7 +991,7 @@ footer {
|
|||||||
<div class="sizebar">
|
<div class="sizebar">
|
||||||
<div class="sizebar-bar"></div>
|
<div class="sizebar-bar"></div>
|
||||||
<div class="sizebar-text">
|
<div class="sizebar-text">
|
||||||
{{.HumanSize}}
|
{{if .IsSymlink}}↱ {{end}}{{.HumanSize}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -1000,70 +1011,70 @@ footer {
|
|||||||
<footer>
|
<footer>
|
||||||
Served with
|
Served with
|
||||||
<a rel="noopener noreferrer" href="https://caddyserver.com">
|
<a rel="noopener noreferrer" href="https://caddyserver.com">
|
||||||
<svg class="caddy-logo" viewBox="0 0 379 114" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;">
|
<svg class="caddy-logo" viewBox="0 0 379 114" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<g transform="matrix(1,0,0,1,-1982.99,-530.985)">
|
<g transform="matrix(1,0,0,1,-1982.99,-530.985)">
|
||||||
<g transform="matrix(1.16548,0,0,1.10195,1823.12,393.466)">
|
<g transform="matrix(1.16548,0,0,1.10195,1823.12,393.466)">
|
||||||
<g transform="matrix(1,0,0,1,0.233052,1.17986)">
|
<g transform="matrix(1,0,0,1,0.233052,1.17986)">
|
||||||
<g id="Icon" transform="matrix(0.858013,0,0,0.907485,-3224.99,-1435.83)">
|
<g id="Icon" transform="matrix(0.858013,0,0,0.907485,-3224.99,-1435.83)">
|
||||||
<g>
|
<g>
|
||||||
<g transform="matrix(-0.191794,-0.715786,0.715786,-0.191794,4329.14,4673.64)">
|
<g transform="matrix(-0.191794,-0.715786,0.715786,-0.191794,4329.14,4673.64)">
|
||||||
<path d="M3901.56,610.734C3893.53,610.261 3886.06,608.1 3879.2,604.877C3872.24,601.608 3866.04,597.093 3860.8,591.633C3858.71,589.457 3856.76,587.149 3854.97,584.709C3853.2,582.281 3851.57,579.733 3850.13,577.066C3845.89,569.224 3843.21,560.381 3842.89,550.868C3842.57,543.321 3843.64,536.055 3845.94,529.307C3848.37,522.203 3852.08,515.696 3856.83,510.049L3855.79,509.095C3850.39,514.54 3846.02,520.981 3842.9,528.125C3839.84,535.125 3838.03,542.781 3837.68,550.868C3837.34,561.391 3839.51,571.425 3843.79,580.306C3845.27,583.38 3847.03,586.304 3849.01,589.049C3851.01,591.806 3853.24,594.39 3855.69,596.742C3861.75,602.568 3869,607.19 3877.03,610.1C3884.66,612.867 3892.96,614.059 3901.56,613.552L3901.56,610.734Z" style="fill:rgb(0,144,221);"/>
|
<path d="M3901.56,610.734C3893.53,610.261 3886.06,608.1 3879.2,604.877C3872.24,601.608 3866.04,597.093 3860.8,591.633C3858.71,589.457 3856.76,587.149 3854.97,584.709C3853.2,582.281 3851.57,579.733 3850.13,577.066C3845.89,569.224 3843.21,560.381 3842.89,550.868C3842.57,543.321 3843.64,536.055 3845.94,529.307C3848.37,522.203 3852.08,515.696 3856.83,510.049L3855.79,509.095C3850.39,514.54 3846.02,520.981 3842.9,528.125C3839.84,535.125 3838.03,542.781 3837.68,550.868C3837.34,561.391 3839.51,571.425 3843.79,580.306C3845.27,583.38 3847.03,586.304 3849.01,589.049C3851.01,591.806 3853.24,594.39 3855.69,596.742C3861.75,602.568 3869,607.19 3877.03,610.1C3884.66,612.867 3892.96,614.059 3901.56,613.552L3901.56,610.734Z" fill="rgb(0,144,221)"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(-0.191794,-0.715786,0.715786,-0.191794,4329.14,4673.64)">
|
<g transform="matrix(-0.191794,-0.715786,0.715786,-0.191794,4329.14,4673.64)">
|
||||||
<path d="M3875.69,496.573C3879.62,494.538 3883.8,492.897 3888.2,491.786C3892.49,490.704 3896.96,490.124 3901.56,490.032C3903.82,490.13 3906.03,490.332 3908.21,490.688C3917.13,492.147 3925.19,495.814 3932.31,500.683C3936.13,503.294 3939.59,506.335 3942.81,509.619C3947.09,513.98 3950.89,518.816 3953.85,524.232C3958.2,532.197 3960.96,541.186 3961.32,550.868C3961.61,558.748 3960.46,566.345 3957.88,573.322C3956.09,578.169 3953.7,582.753 3950.66,586.838C3947.22,591.461 3942.96,595.427 3938.27,598.769C3933.66,602.055 3928.53,604.619 3923.09,606.478C3922.37,606.721 3921.6,606.805 3920.93,607.167C3920.42,607.448 3920.14,607.854 3919.69,608.224L3920.37,610.389C3920.98,610.432 3921.47,610.573 3922.07,610.474C3922.86,610.344 3923.55,609.883 3924.28,609.566C3931.99,606.216 3938.82,601.355 3944.57,595.428C3947.02,592.903 3949.25,590.174 3951.31,587.319C3953.59,584.168 3955.66,580.853 3957.43,577.348C3961.47,569.34 3964.01,560.422 3964.36,550.868C3964.74,540.511 3962.66,530.628 3958.48,521.868C3955.57,515.775 3951.72,510.163 3946.95,505.478C3943.37,501.962 3939.26,498.99 3934.84,496.562C3926.88,492.192 3917.87,489.76 3908.37,489.229C3906.12,489.104 3903.86,489.054 3901.56,489.154C3896.87,489.06 3892.3,489.519 3887.89,490.397C3883.3,491.309 3878.89,492.683 3874.71,494.525L3875.69,496.573Z" style="fill:rgb(0,144,221);"/>
|
<path d="M3875.69,496.573C3879.62,494.538 3883.8,492.897 3888.2,491.786C3892.49,490.704 3896.96,490.124 3901.56,490.032C3903.82,490.13 3906.03,490.332 3908.21,490.688C3917.13,492.147 3925.19,495.814 3932.31,500.683C3936.13,503.294 3939.59,506.335 3942.81,509.619C3947.09,513.98 3950.89,518.816 3953.85,524.232C3958.2,532.197 3960.96,541.186 3961.32,550.868C3961.61,558.748 3960.46,566.345 3957.88,573.322C3956.09,578.169 3953.7,582.753 3950.66,586.838C3947.22,591.461 3942.96,595.427 3938.27,598.769C3933.66,602.055 3928.53,604.619 3923.09,606.478C3922.37,606.721 3921.6,606.805 3920.93,607.167C3920.42,607.448 3920.14,607.854 3919.69,608.224L3920.37,610.389C3920.98,610.432 3921.47,610.573 3922.07,610.474C3922.86,610.344 3923.55,609.883 3924.28,609.566C3931.99,606.216 3938.82,601.355 3944.57,595.428C3947.02,592.903 3949.25,590.174 3951.31,587.319C3953.59,584.168 3955.66,580.853 3957.43,577.348C3961.47,569.34 3964.01,560.422 3964.36,550.868C3964.74,540.511 3962.66,530.628 3958.48,521.868C3955.57,515.775 3951.72,510.163 3946.95,505.478C3943.37,501.962 3939.26,498.99 3934.84,496.562C3926.88,492.192 3917.87,489.76 3908.37,489.229C3906.12,489.104 3903.86,489.054 3901.56,489.154C3896.87,489.06 3892.3,489.519 3887.89,490.397C3883.3,491.309 3878.89,492.683 3874.71,494.525L3875.69,496.573Z" fill="rgb(0,144,221)"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<g transform="matrix(-3.37109,-0.514565,0.514565,-3.37109,4078.07,1806.88)">
|
<g transform="matrix(-3.37109,-0.514565,0.514565,-3.37109,4078.07,1806.88)">
|
||||||
<path d="M22,12C22,10.903 21.097,10 20,10C19.421,10 18.897,10.251 18.53,10.649C18.202,11.006 18,11.481 18,12C18,13.097 18.903,14 20,14C21.097,14 22,13.097 22,12Z" style="fill:none;fill-rule:nonzero;stroke:rgb(0,144,221);stroke-width:1.05px;"/>
|
<path d="M22,12C22,10.903 21.097,10 20,10C19.421,10 18.897,10.251 18.53,10.649C18.202,11.006 18,11.481 18,12C18,13.097 18.903,14 20,14C21.097,14 22,13.097 22,12Z" fill="none" fill-rule="nonzero" stroke="rgb(0,144,221)" stroke-width="1.05px"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(-5.33921,-5.26159,-3.12106,-6.96393,4073.87,1861.55)">
|
<g transform="matrix(-5.33921,-5.26159,-3.12106,-6.96393,4073.87,1861.55)">
|
||||||
<path d="M10.315,5.333C10.315,5.333 9.748,5.921 9.03,6.673C7.768,7.995 6.054,9.805 6.054,9.805L6.237,9.86C6.237,9.86 8.045,8.077 9.36,6.771C10.107,6.028 10.689,5.444 10.689,5.444L10.315,5.333Z" style="fill:rgb(0,144,221);"/>
|
<path d="M10.315,5.333C10.315,5.333 9.748,5.921 9.03,6.673C7.768,7.995 6.054,9.805 6.054,9.805L6.237,9.86C6.237,9.86 8.045,8.077 9.36,6.771C10.107,6.028 10.689,5.444 10.689,5.444L10.315,5.333Z" fill="rgb(0,144,221)"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g id="Padlock" transform="matrix(3.11426,0,0,3.11426,3938.31,1737.25)">
|
<g id="Padlock" transform="matrix(3.11426,0,0,3.11426,3938.31,1737.25)">
|
||||||
<g>
|
<g>
|
||||||
<path d="M9.876,21L18.162,21C18.625,21 19,20.625 19,20.162L19,11.838C19,11.375 18.625,11 18.162,11L5.838,11C5.375,11 5,11.375 5,11.838L5,16.758" style="fill:none;stroke:rgb(34,182,56);stroke-width:1.89px;stroke-linecap:butt;stroke-linejoin:miter;"/>
|
<path d="M9.876,21L18.162,21C18.625,21 19,20.625 19,20.162L19,11.838C19,11.375 18.625,11 18.162,11L5.838,11C5.375,11 5,11.375 5,11.838L5,16.758" fill="none" stroke="rgb(34,182,56)" stroke-width="1.89px" stroke-linecap="butt" stroke-linejoin="miter"/>
|
||||||
<path d="M8,11L8,7C8,4.806 9.806,3 12,3C14.194,3 16,4.806 16,7L16,11" style="fill:none;fill-rule:nonzero;stroke:rgb(34,182,56);stroke-width:1.89px;"/>
|
<path d="M8,11L8,7C8,4.806 9.806,3 12,3C14.194,3 16,4.806 16,7L16,11" fill="none" fill-rule="nonzero" stroke="rgb(34,182,56)" stroke-width="1.89px"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<g transform="matrix(5.30977,0.697415,-0.697415,5.30977,3852.72,1727.97)">
|
<g transform="matrix(5.30977,0.697415,-0.697415,5.30977,3852.72,1727.97)">
|
||||||
<path d="M22,12C22,11.659 21.913,11.337 21.76,11.055C21.421,10.429 20.756,10 20,10C18.903,10 18,10.903 18,12C18,13.097 18.903,14 20,14C21.097,14 22,13.097 22,12Z" style="fill:none;fill-rule:nonzero;stroke:rgb(0,144,221);stroke-width:0.98px;"/>
|
<path d="M22,12C22,11.659 21.913,11.337 21.76,11.055C21.421,10.429 20.756,10 20,10C18.903,10 18,10.903 18,12C18,13.097 18.903,14 20,14C21.097,14 22,13.097 22,12Z" fill="none" fill-rule="nonzero" stroke="rgb(0,144,221)" stroke-width="0.98px"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(4.93114,2.49604,1.11018,5.44847,3921.41,1726.72)">
|
<g transform="matrix(4.93114,2.49604,1.11018,5.44847,3921.41,1726.72)">
|
||||||
<path d="M8.902,6.77C8.902,6.77 7.235,8.253 6.027,9.366C5.343,9.996 4.819,10.502 4.819,10.502L5.52,11.164C5.52,11.164 6.021,10.637 6.646,9.951C7.749,8.739 9.219,7.068 9.219,7.068L8.902,6.77Z" style="fill:rgb(0,144,221);"/>
|
<path d="M8.902,6.77C8.902,6.77 7.235,8.253 6.027,9.366C5.343,9.996 4.819,10.502 4.819,10.502L5.52,11.164C5.52,11.164 6.021,10.637 6.646,9.951C7.749,8.739 9.219,7.068 9.219,7.068L8.902,6.77Z" fill="rgb(0,144,221)"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g id="Text">
|
<g id="Text">
|
||||||
<g id="Wordmark" transform="matrix(1.32271,0,0,2.60848,-899.259,-791.691)">
|
<g id="Wordmark" transform="matrix(1.32271,0,0,2.60848,-899.259,-791.691)">
|
||||||
<g id="y" transform="matrix(0.50291,0,0,0.281607,905.533,304.987)">
|
<g id="y" transform="matrix(0.50291,0,0,0.281607,905.533,304.987)">
|
||||||
<path d="M192.152,286.875L202.629,268.64C187.804,270.106 183.397,265.779 180.143,263.391C176.888,261.004 174.362,257.99 172.563,254.347C170.765,250.705 169.866,246.691 169.866,242.305L169.866,208.107L183.21,208.107L183.21,242.213C183.21,245.188 183.896,247.822 185.268,250.116C186.64,252.41 188.465,254.197 190.743,255.475C193.022,256.754 195.501,257.393 198.182,257.393C200.894,257.393 203.393,256.75 205.68,255.463C207.966,254.177 209.799,252.391 211.178,250.105C212.558,247.818 213.248,245.188 213.248,242.213L213.248,208.107L226.545,208.107L226.545,242.305C226.545,246.707 225.378,258.46 218.079,268.64C215.735,271.909 207.835,286.875 207.835,286.875L192.152,286.875Z" style="fill:rgb(47,47,47);fill-rule:nonzero;"/>
|
<path d="M192.152,286.875L202.629,268.64C187.804,270.106 183.397,265.779 180.143,263.391C176.888,261.004 174.362,257.99 172.563,254.347C170.765,250.705 169.866,246.691 169.866,242.305L169.866,208.107L183.21,208.107L183.21,242.213C183.21,245.188 183.896,247.822 185.268,250.116C186.64,252.41 188.465,254.197 190.743,255.475C193.022,256.754 195.501,257.393 198.182,257.393C200.894,257.393 203.393,256.75 205.68,255.463C207.966,254.177 209.799,252.391 211.178,250.105C212.558,247.818 213.248,245.188 213.248,242.213L213.248,208.107L226.545,208.107L226.545,242.305C226.545,246.707 225.378,258.46 218.079,268.64C215.735,271.909 207.835,286.875 207.835,286.875L192.152,286.875Z" fill="rgb(47,47,47)" fill-rule="nonzero"/>
|
||||||
</g>
|
</g>
|
||||||
<g id="add" transform="matrix(0.525075,0,0,0.281607,801.871,304.987)">
|
<g id="add" transform="matrix(0.525075,0,0,0.281607,801.871,304.987)">
|
||||||
<g transform="matrix(116.242,0,0,116.242,161.846,267.39)">
|
<g transform="matrix(116.242,0,0,116.242,161.846,267.39)">
|
||||||
<path d="M0.276,0.012C0.227,0.012 0.186,0 0.15,-0.024C0.115,-0.048 0.088,-0.08 0.069,-0.12C0.05,-0.161 0.04,-0.205 0.04,-0.254C0.04,-0.305 0.051,-0.35 0.072,-0.39C0.094,-0.431 0.125,-0.463 0.165,-0.487C0.205,-0.51 0.254,-0.522 0.31,-0.522C0.366,-0.522 0.413,-0.51 0.452,-0.486C0.491,-0.463 0.521,-0.431 0.542,-0.39C0.562,-0.35 0.573,-0.305 0.573,-0.256L0.573,-0L0.458,-0L0.458,-0.095L0.456,-0.095C0.446,-0.076 0.433,-0.058 0.417,-0.042C0.401,-0.026 0.381,-0.013 0.358,-0.003C0.335,0.007 0.307,0.012 0.276,0.012ZM0.307,-0.086C0.337,-0.086 0.363,-0.093 0.386,-0.108C0.408,-0.123 0.426,-0.144 0.438,-0.17C0.45,-0.195 0.456,-0.224 0.456,-0.256C0.456,-0.288 0.45,-0.317 0.438,-0.342C0.426,-0.367 0.409,-0.387 0.387,-0.402C0.365,-0.417 0.338,-0.424 0.308,-0.424C0.276,-0.424 0.249,-0.417 0.226,-0.402C0.204,-0.387 0.186,-0.366 0.174,-0.341C0.162,-0.315 0.156,-0.287 0.156,-0.255C0.156,-0.224 0.162,-0.195 0.174,-0.169C0.186,-0.144 0.203,-0.123 0.226,-0.108C0.248,-0.093 0.275,-0.086 0.307,-0.086Z" style="fill:rgb(47,47,47);fill-rule:nonzero;"/>
|
<path d="M0.276,0.012C0.227,0.012 0.186,0 0.15,-0.024C0.115,-0.048 0.088,-0.08 0.069,-0.12C0.05,-0.161 0.04,-0.205 0.04,-0.254C0.04,-0.305 0.051,-0.35 0.072,-0.39C0.094,-0.431 0.125,-0.463 0.165,-0.487C0.205,-0.51 0.254,-0.522 0.31,-0.522C0.366,-0.522 0.413,-0.51 0.452,-0.486C0.491,-0.463 0.521,-0.431 0.542,-0.39C0.562,-0.35 0.573,-0.305 0.573,-0.256L0.573,-0L0.458,-0L0.458,-0.095L0.456,-0.095C0.446,-0.076 0.433,-0.058 0.417,-0.042C0.401,-0.026 0.381,-0.013 0.358,-0.003C0.335,0.007 0.307,0.012 0.276,0.012ZM0.307,-0.086C0.337,-0.086 0.363,-0.093 0.386,-0.108C0.408,-0.123 0.426,-0.144 0.438,-0.17C0.45,-0.195 0.456,-0.224 0.456,-0.256C0.456,-0.288 0.45,-0.317 0.438,-0.342C0.426,-0.367 0.409,-0.387 0.387,-0.402C0.365,-0.417 0.338,-0.424 0.308,-0.424C0.276,-0.424 0.249,-0.417 0.226,-0.402C0.204,-0.387 0.186,-0.366 0.174,-0.341C0.162,-0.315 0.156,-0.287 0.156,-0.255C0.156,-0.224 0.162,-0.195 0.174,-0.169C0.186,-0.144 0.203,-0.123 0.226,-0.108C0.248,-0.093 0.275,-0.086 0.307,-0.086Z" fill="rgb(47,47,47)" fill-rule="nonzero"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(116.242,0,0,116.242,226.592,267.39)">
|
<g transform="matrix(116.242,0,0,116.242,226.592,267.39)">
|
||||||
<path d="M0.306,0.012C0.265,0.012 0.229,0.006 0.196,-0.008C0.163,-0.021 0.135,-0.039 0.112,-0.064C0.089,-0.088 0.071,-0.117 0.059,-0.151C0.046,-0.185 0.04,-0.222 0.04,-0.263C0.04,-0.315 0.051,-0.36 0.072,-0.399C0.093,-0.437 0.122,-0.468 0.159,-0.489C0.196,-0.511 0.239,-0.522 0.287,-0.522C0.311,-0.522 0.333,-0.518 0.355,-0.511C0.377,-0.504 0.396,-0.493 0.413,-0.48C0.431,-0.466 0.445,-0.451 0.455,-0.433L0.456,-0.433L0.456,-0.73L0.571,-0.73L0.571,-0.261C0.571,-0.205 0.56,-0.156 0.537,-0.115C0.515,-0.074 0.484,-0.043 0.444,-0.021C0.405,0.001 0.358,0.012 0.306,0.012ZM0.306,-0.086C0.335,-0.086 0.361,-0.093 0.384,-0.107C0.406,-0.122 0.423,-0.141 0.436,-0.167C0.448,-0.192 0.455,-0.221 0.455,-0.255C0.455,-0.288 0.448,-0.317 0.436,-0.343C0.423,-0.368 0.406,-0.388 0.383,-0.402C0.361,-0.417 0.335,-0.424 0.305,-0.424C0.276,-0.424 0.251,-0.417 0.228,-0.402C0.206,-0.387 0.188,-0.368 0.175,-0.342C0.163,-0.317 0.156,-0.288 0.156,-0.255C0.156,-0.222 0.163,-0.193 0.175,-0.167C0.188,-0.142 0.206,-0.122 0.229,-0.108C0.251,-0.093 0.277,-0.086 0.306,-0.086Z" style="fill:rgb(47,47,47);fill-rule:nonzero;"/>
|
<path d="M0.306,0.012C0.265,0.012 0.229,0.006 0.196,-0.008C0.163,-0.021 0.135,-0.039 0.112,-0.064C0.089,-0.088 0.071,-0.117 0.059,-0.151C0.046,-0.185 0.04,-0.222 0.04,-0.263C0.04,-0.315 0.051,-0.36 0.072,-0.399C0.093,-0.437 0.122,-0.468 0.159,-0.489C0.196,-0.511 0.239,-0.522 0.287,-0.522C0.311,-0.522 0.333,-0.518 0.355,-0.511C0.377,-0.504 0.396,-0.493 0.413,-0.48C0.431,-0.466 0.445,-0.451 0.455,-0.433L0.456,-0.433L0.456,-0.73L0.571,-0.73L0.571,-0.261C0.571,-0.205 0.56,-0.156 0.537,-0.115C0.515,-0.074 0.484,-0.043 0.444,-0.021C0.405,0.001 0.358,0.012 0.306,0.012ZM0.306,-0.086C0.335,-0.086 0.361,-0.093 0.384,-0.107C0.406,-0.122 0.423,-0.141 0.436,-0.167C0.448,-0.192 0.455,-0.221 0.455,-0.255C0.455,-0.288 0.448,-0.317 0.436,-0.343C0.423,-0.368 0.406,-0.388 0.383,-0.402C0.361,-0.417 0.335,-0.424 0.305,-0.424C0.276,-0.424 0.251,-0.417 0.228,-0.402C0.206,-0.387 0.188,-0.368 0.175,-0.342C0.163,-0.317 0.156,-0.288 0.156,-0.255C0.156,-0.222 0.163,-0.193 0.175,-0.167C0.188,-0.142 0.206,-0.122 0.229,-0.108C0.251,-0.093 0.277,-0.086 0.306,-0.086Z" fill="rgb(47,47,47)" fill-rule="nonzero"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(116.242,0,0,116.242,290.293,267.39)">
|
<g transform="matrix(116.242,0,0,116.242,290.293,267.39)">
|
||||||
<path d="M0.306,0.012C0.265,0.012 0.229,0.006 0.196,-0.008C0.163,-0.021 0.135,-0.039 0.112,-0.064C0.089,-0.088 0.071,-0.117 0.059,-0.151C0.046,-0.185 0.04,-0.222 0.04,-0.263C0.04,-0.315 0.051,-0.36 0.072,-0.399C0.093,-0.437 0.122,-0.468 0.159,-0.489C0.196,-0.511 0.239,-0.522 0.287,-0.522C0.311,-0.522 0.333,-0.518 0.355,-0.511C0.377,-0.504 0.396,-0.493 0.413,-0.48C0.431,-0.466 0.445,-0.451 0.455,-0.433L0.456,-0.433L0.456,-0.73L0.571,-0.73L0.571,-0.261C0.571,-0.205 0.56,-0.156 0.537,-0.115C0.515,-0.074 0.484,-0.043 0.444,-0.021C0.405,0.001 0.358,0.012 0.306,0.012ZM0.306,-0.086C0.335,-0.086 0.361,-0.093 0.384,-0.107C0.406,-0.122 0.423,-0.141 0.436,-0.167C0.448,-0.192 0.455,-0.221 0.455,-0.255C0.455,-0.288 0.448,-0.317 0.436,-0.343C0.423,-0.368 0.406,-0.388 0.383,-0.402C0.361,-0.417 0.335,-0.424 0.305,-0.424C0.276,-0.424 0.251,-0.417 0.228,-0.402C0.206,-0.387 0.188,-0.368 0.175,-0.342C0.163,-0.317 0.156,-0.288 0.156,-0.255C0.156,-0.222 0.163,-0.193 0.175,-0.167C0.188,-0.142 0.206,-0.122 0.229,-0.108C0.251,-0.093 0.277,-0.086 0.306,-0.086Z" style="fill:rgb(47,47,47);fill-rule:nonzero;"/>
|
<path d="M0.306,0.012C0.265,0.012 0.229,0.006 0.196,-0.008C0.163,-0.021 0.135,-0.039 0.112,-0.064C0.089,-0.088 0.071,-0.117 0.059,-0.151C0.046,-0.185 0.04,-0.222 0.04,-0.263C0.04,-0.315 0.051,-0.36 0.072,-0.399C0.093,-0.437 0.122,-0.468 0.159,-0.489C0.196,-0.511 0.239,-0.522 0.287,-0.522C0.311,-0.522 0.333,-0.518 0.355,-0.511C0.377,-0.504 0.396,-0.493 0.413,-0.48C0.431,-0.466 0.445,-0.451 0.455,-0.433L0.456,-0.433L0.456,-0.73L0.571,-0.73L0.571,-0.261C0.571,-0.205 0.56,-0.156 0.537,-0.115C0.515,-0.074 0.484,-0.043 0.444,-0.021C0.405,0.001 0.358,0.012 0.306,0.012ZM0.306,-0.086C0.335,-0.086 0.361,-0.093 0.384,-0.107C0.406,-0.122 0.423,-0.141 0.436,-0.167C0.448,-0.192 0.455,-0.221 0.455,-0.255C0.455,-0.288 0.448,-0.317 0.436,-0.343C0.423,-0.368 0.406,-0.388 0.383,-0.402C0.361,-0.417 0.335,-0.424 0.305,-0.424C0.276,-0.424 0.251,-0.417 0.228,-0.402C0.206,-0.387 0.188,-0.368 0.175,-0.342C0.163,-0.317 0.156,-0.288 0.156,-0.255C0.156,-0.222 0.163,-0.193 0.175,-0.167C0.188,-0.142 0.206,-0.122 0.229,-0.108C0.251,-0.093 0.277,-0.086 0.306,-0.086Z" fill="rgb(47,47,47)" fill-rule="nonzero"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g id="c" transform="matrix(-0.0716462,0.31304,-0.583685,-0.0384251,1489.76,-444.051)">
|
<g id="c" transform="matrix(-0.0716462,0.31304,-0.583685,-0.0384251,1489.76,-444.051)">
|
||||||
<path d="M2668.11,700.4C2666.79,703.699 2666.12,707.216 2666.12,710.766C2666.12,726.268 2678.71,738.854 2694.21,738.854C2709.71,738.854 2722.3,726.268 2722.3,710.766C2722.3,704.111 2719.93,697.672 2715.63,692.597L2707.63,699.378C2710.33,702.559 2711.57,706.602 2711.81,710.766C2712.2,717.38 2706.61,724.52 2697.27,726.637C2683.9,728.581 2676.61,720.482 2676.61,710.766C2676.61,708.541 2677.03,706.336 2677.85,704.269L2668.11,700.4Z" style="fill:rgb(46,46,46);"/>
|
<path d="M2668.11,700.4C2666.79,703.699 2666.12,707.216 2666.12,710.766C2666.12,726.268 2678.71,738.854 2694.21,738.854C2709.71,738.854 2722.3,726.268 2722.3,710.766C2722.3,704.111 2719.93,697.672 2715.63,692.597L2707.63,699.378C2710.33,702.559 2711.57,706.602 2711.81,710.766C2712.2,717.38 2706.61,724.52 2697.27,726.637C2683.9,728.581 2676.61,720.482 2676.61,710.766C2676.61,708.541 2677.03,706.336 2677.85,704.269L2668.11,700.4Z" fill="rgb(46,46,46)"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g id="R" transform="matrix(0.426446,0,0,0.451034,-1192.44,-722.167)">
|
<g id="R" transform="matrix(0.426446,0,0,0.451034,-1192.44,-722.167)">
|
||||||
<g transform="matrix(1,0,0,1,-0.10786,0.450801)">
|
<g transform="matrix(1,0,0,1,-0.10786,0.450801)">
|
||||||
<g transform="matrix(12.1247,0,0,12.1247,3862.61,1929.9)">
|
<g transform="matrix(12.1247,0,0,12.1247,3862.61,1929.9)">
|
||||||
<path d="M0.073,-0L0.073,-0.7L0.383,-0.7C0.428,-0.7 0.469,-0.69 0.506,-0.67C0.543,-0.651 0.572,-0.623 0.594,-0.588C0.616,-0.553 0.627,-0.512 0.627,-0.465C0.627,-0.418 0.615,-0.377 0.592,-0.342C0.569,-0.306 0.539,-0.279 0.501,-0.259L0.57,-0.128C0.574,-0.12 0.579,-0.115 0.584,-0.111C0.59,-0.107 0.596,-0.106 0.605,-0.106L0.664,-0.106L0.664,-0L0.587,-0C0.56,-0 0.535,-0.007 0.514,-0.02C0.493,-0.034 0.476,-0.052 0.463,-0.075L0.381,-0.232C0.375,-0.231 0.368,-0.231 0.361,-0.231C0.354,-0.231 0.347,-0.231 0.34,-0.231L0.192,-0.231L0.192,-0L0.073,-0ZM0.192,-0.336L0.368,-0.336C0.394,-0.336 0.417,-0.341 0.438,-0.351C0.459,-0.361 0.476,-0.376 0.489,-0.396C0.501,-0.415 0.507,-0.438 0.507,-0.465C0.507,-0.492 0.501,-0.516 0.488,-0.535C0.475,-0.554 0.459,-0.569 0.438,-0.579C0.417,-0.59 0.394,-0.595 0.369,-0.595L0.192,-0.595L0.192,-0.336Z" style="fill:rgb(46,46,46);fill-rule:nonzero;"/>
|
<path d="M0.073,-0L0.073,-0.7L0.383,-0.7C0.428,-0.7 0.469,-0.69 0.506,-0.67C0.543,-0.651 0.572,-0.623 0.594,-0.588C0.616,-0.553 0.627,-0.512 0.627,-0.465C0.627,-0.418 0.615,-0.377 0.592,-0.342C0.569,-0.306 0.539,-0.279 0.501,-0.259L0.57,-0.128C0.574,-0.12 0.579,-0.115 0.584,-0.111C0.59,-0.107 0.596,-0.106 0.605,-0.106L0.664,-0.106L0.664,-0L0.587,-0C0.56,-0 0.535,-0.007 0.514,-0.02C0.493,-0.034 0.476,-0.052 0.463,-0.075L0.381,-0.232C0.375,-0.231 0.368,-0.231 0.361,-0.231C0.354,-0.231 0.347,-0.231 0.34,-0.231L0.192,-0.231L0.192,-0L0.073,-0ZM0.192,-0.336L0.368,-0.336C0.394,-0.336 0.417,-0.341 0.438,-0.351C0.459,-0.361 0.476,-0.376 0.489,-0.396C0.501,-0.415 0.507,-0.438 0.507,-0.465C0.507,-0.492 0.501,-0.516 0.488,-0.535C0.475,-0.554 0.459,-0.569 0.438,-0.579C0.417,-0.59 0.394,-0.595 0.369,-0.595L0.192,-0.595L0.192,-0.336Z" fill="rgb(46,46,46)" fill-rule="nonzero"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(1,0,0,1,0.278569,0.101881)">
|
<g transform="matrix(1,0,0,1,0.278569,0.101881)">
|
||||||
<circle cx="3866.43" cy="1926.14" r="8.923" style="fill:none;stroke:rgb(46,46,46);stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;"/>
|
<circle cx="3866.43" cy="1926.14" r="8.923" fill="none" stroke="rgb(46,46,46)" stroke-width="2px" stroke-linecap="butt" stroke-linejoin="miter"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
@@ -1074,7 +1085,7 @@ footer {
|
|||||||
</a>
|
</a>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script>
|
<script {{ $nonceAttribute }}>
|
||||||
const filterEl = document.getElementById('filter');
|
const filterEl = document.getElementById('filter');
|
||||||
filterEl?.focus({ preventScroll: true });
|
filterEl?.focus({ preventScroll: true });
|
||||||
|
|
||||||
@@ -1120,6 +1131,20 @@ footer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filterElem = document.getElementById("filter");
|
||||||
|
if (filterElem) {
|
||||||
|
filterElem.addEventListener("keyup", filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("layout-list").addEventListener("click", function() {
|
||||||
|
queryParam('layout', '');
|
||||||
|
});
|
||||||
|
document.getElementById("layout-grid").addEventListener("click", function() {
|
||||||
|
queryParam('layout', 'grid');
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("load", initPage);
|
||||||
|
|
||||||
function queryParam(k, v) {
|
function queryParam(k, v) {
|
||||||
const qs = new URLSearchParams(window.location.search);
|
const qs = new URLSearchParams(window.location.search);
|
||||||
if (!v) {
|
if (!v) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -28,12 +29,13 @@ import (
|
|||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS, entries []fs.DirEntry, canGoUp bool, root, urlPath string, repl *caddy.Replacer) *browseTemplateContext {
|
func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS, parentModTime time.Time, entries []fs.DirEntry, canGoUp bool, root, urlPath string, repl *caddy.Replacer) *browseTemplateContext {
|
||||||
filesToHide := fsrv.transformHidePaths(repl)
|
filesToHide := fsrv.transformHidePaths(repl)
|
||||||
|
|
||||||
name, _ := url.PathUnescape(urlPath)
|
name, _ := url.PathUnescape(urlPath)
|
||||||
@@ -42,6 +44,7 @@ func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS,
|
|||||||
Name: path.Base(name),
|
Name: path.Base(name),
|
||||||
Path: urlPath,
|
Path: urlPath,
|
||||||
CanGoUp: canGoUp,
|
CanGoUp: canGoUp,
|
||||||
|
lastModified: parentModTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
@@ -57,9 +60,9 @@ func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS,
|
|||||||
|
|
||||||
info, err := entry.Info()
|
info, err := entry.Info()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fsrv.logger.Error("could not get info about directory entry",
|
if c := fsrv.logger.Check(zapcore.ErrorLevel, "could not get info about directory entry"); c != nil {
|
||||||
zap.String("name", entry.Name()),
|
c.Write(zap.String("name", entry.Name()), zap.String("root", root))
|
||||||
zap.String("root", root))
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +83,13 @@ func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS,
|
|||||||
}
|
}
|
||||||
|
|
||||||
size := info.Size()
|
size := info.Size()
|
||||||
|
|
||||||
|
if !isDir {
|
||||||
|
// increase the total by the symlink's size, not the target's size,
|
||||||
|
// by incrementing before we follow the symlink
|
||||||
|
tplCtx.TotalFileSize += size
|
||||||
|
}
|
||||||
|
|
||||||
fileIsSymlink := isSymlink(info)
|
fileIsSymlink := isSymlink(info)
|
||||||
symlinkPath := ""
|
symlinkPath := ""
|
||||||
if fileIsSymlink {
|
if fileIsSymlink {
|
||||||
@@ -103,7 +113,8 @@ func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !isDir {
|
if !isDir {
|
||||||
tplCtx.TotalFileSize += size
|
// increase the total including the symlink target's size
|
||||||
|
tplCtx.TotalFileSizeFollowingSymlinks += size
|
||||||
}
|
}
|
||||||
|
|
||||||
u := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name
|
u := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name
|
||||||
@@ -121,6 +132,10 @@ func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this time is used for the Last-Modified header and comparing If-Modified-Since from client
|
||||||
|
// both are expected to be in UTC, so we convert to UTC here
|
||||||
|
// see: https://github.com/caddyserver/caddy/issues/6828
|
||||||
|
tplCtx.lastModified = tplCtx.lastModified.UTC()
|
||||||
return tplCtx
|
return tplCtx
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,9 +165,15 @@ type browseTemplateContext struct {
|
|||||||
// The number of files (items that aren't directories) in the listing.
|
// The number of files (items that aren't directories) in the listing.
|
||||||
NumFiles int `json:"num_files"`
|
NumFiles int `json:"num_files"`
|
||||||
|
|
||||||
// The total size of all files in the listing.
|
// The total size of all files in the listing. Only includes the
|
||||||
|
// size of the files themselves, not the size of symlink targets
|
||||||
|
// (i.e. the calculation of this value does not follow symlinks).
|
||||||
TotalFileSize int64 `json:"total_file_size"`
|
TotalFileSize int64 `json:"total_file_size"`
|
||||||
|
|
||||||
|
// The total size of all files in the listing, including the
|
||||||
|
// size of the files targeted by symlinks.
|
||||||
|
TotalFileSizeFollowingSymlinks int64 `json:"total_file_size_following_symlinks"`
|
||||||
|
|
||||||
// Sort column used
|
// Sort column used
|
||||||
Sort string `json:"sort,omitempty"`
|
Sort string `json:"sort,omitempty"`
|
||||||
|
|
||||||
@@ -266,12 +287,9 @@ type fileInfo struct {
|
|||||||
|
|
||||||
// HasExt returns true if the filename has any of the given suffixes, case-insensitive.
|
// HasExt returns true if the filename has any of the given suffixes, case-insensitive.
|
||||||
func (fi fileInfo) HasExt(exts ...string) bool {
|
func (fi fileInfo) HasExt(exts ...string) bool {
|
||||||
for _, ext := range exts {
|
return slices.ContainsFunc(exts, func(ext string) bool {
|
||||||
if strings.HasSuffix(strings.ToLower(fi.Name), strings.ToLower(ext)) {
|
return strings.HasSuffix(strings.ToLower(fi.Name), strings.ToLower(ext))
|
||||||
return true
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HumanSize returns the size of the file as a
|
// HumanSize returns the size of the file as a
|
||||||
@@ -288,6 +306,12 @@ func (btc browseTemplateContext) HumanTotalFileSize() string {
|
|||||||
return humanize.IBytes(uint64(btc.TotalFileSize))
|
return humanize.IBytes(uint64(btc.TotalFileSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HumanTotalFileSizeFollowingSymlinks is the same as HumanTotalFileSize
|
||||||
|
// except the returned value reflects the size of symlink targets.
|
||||||
|
func (btc browseTemplateContext) HumanTotalFileSizeFollowingSymlinks() string {
|
||||||
|
return humanize.IBytes(uint64(btc.TotalFileSizeFollowingSymlinks))
|
||||||
|
}
|
||||||
|
|
||||||
// HumanModTime returns the modified time of the file
|
// HumanModTime returns the modified time of the file
|
||||||
// as a human-readable string given by format.
|
// as a human-readable string given by format.
|
||||||
func (fi fileInfo) HumanModTime(format string) string {
|
func (fi fileInfo) HumanModTime(format string) string {
|
||||||
@@ -353,4 +377,7 @@ const (
|
|||||||
sortByNameDirFirst = "namedirfirst"
|
sortByNameDirFirst = "namedirfirst"
|
||||||
sortBySize = "size"
|
sortBySize = "size"
|
||||||
sortByTime = "time"
|
sortByTime = "time"
|
||||||
|
|
||||||
|
sortOrderAsc = "asc"
|
||||||
|
sortOrderDesc = "desc"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package fileserver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
@@ -78,7 +79,7 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||||||
return d.ArgErr()
|
return d.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
for d.NextBlock(0) {
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||||
switch d.Val() {
|
switch d.Val() {
|
||||||
case "fs":
|
case "fs":
|
||||||
if !d.NextArg() {
|
if !d.NextArg() {
|
||||||
@@ -119,15 +120,39 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||||||
return d.Err("Symlinks path reveal is already enabled")
|
return d.Err("Symlinks path reveal is already enabled")
|
||||||
}
|
}
|
||||||
fsrv.Browse.RevealSymlinks = true
|
fsrv.Browse.RevealSymlinks = true
|
||||||
|
case "sort":
|
||||||
|
for d.NextArg() {
|
||||||
|
dVal := d.Val()
|
||||||
|
switch dVal {
|
||||||
|
case sortByName, sortByNameDirFirst, sortBySize, sortByTime, sortOrderAsc, sortOrderDesc:
|
||||||
|
fsrv.Browse.SortOptions = append(fsrv.Browse.SortOptions, dVal)
|
||||||
|
default:
|
||||||
|
return d.Errf("unknown sort option '%s'", dVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "file_limit":
|
||||||
|
fileLimit := d.RemainingArgs()
|
||||||
|
if len(fileLimit) != 1 {
|
||||||
|
return d.Err("file_limit should have an integer value")
|
||||||
|
}
|
||||||
|
val, _ := strconv.Atoi(fileLimit[0])
|
||||||
|
if fsrv.Browse.FileLimit != 0 {
|
||||||
|
return d.Err("file_limit is already enabled")
|
||||||
|
}
|
||||||
|
fsrv.Browse.FileLimit = val
|
||||||
default:
|
default:
|
||||||
return d.Errf("unknown subdirective '%s'", d.Val())
|
return d.Errf("unknown subdirective '%s'", d.Val())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case "precompressed":
|
case "precompressed":
|
||||||
var order []string
|
fsrv.PrecompressedOrder = d.RemainingArgs()
|
||||||
for d.NextArg() {
|
if len(fsrv.PrecompressedOrder) == 0 {
|
||||||
modID := "http.precompressed." + d.Val()
|
fsrv.PrecompressedOrder = []string{"br", "zstd", "gzip"}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, format := range fsrv.PrecompressedOrder {
|
||||||
|
modID := "http.precompressed." + format
|
||||||
mod, err := caddy.GetModule(modID)
|
mod, err := caddy.GetModule(modID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return d.Errf("getting module named '%s': %v", modID, err)
|
return d.Errf("getting module named '%s': %v", modID, err)
|
||||||
@@ -140,10 +165,8 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||||||
if fsrv.PrecompressedRaw == nil {
|
if fsrv.PrecompressedRaw == nil {
|
||||||
fsrv.PrecompressedRaw = make(caddy.ModuleMap)
|
fsrv.PrecompressedRaw = make(caddy.ModuleMap)
|
||||||
}
|
}
|
||||||
fsrv.PrecompressedRaw[d.Val()] = caddyconfig.JSON(precompress, nil)
|
fsrv.PrecompressedRaw[format] = caddyconfig.JSON(precompress, nil)
|
||||||
order = append(order, d.Val())
|
|
||||||
}
|
}
|
||||||
fsrv.PrecompressedOrder = order
|
|
||||||
|
|
||||||
case "status":
|
case "status":
|
||||||
if !d.NextArg() {
|
if !d.NextArg() {
|
||||||
@@ -253,7 +276,7 @@ func parseTryFiles(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
|
|||||||
tryPolicy = h.Val()
|
tryPolicy = h.Val()
|
||||||
|
|
||||||
switch tryPolicy {
|
switch tryPolicy {
|
||||||
case tryPolicyFirstExist, tryPolicyLargestSize, tryPolicySmallestSize, tryPolicyMostRecentlyMod:
|
case tryPolicyFirstExist, tryPolicyFirstExistFallback, tryPolicyLargestSize, tryPolicySmallestSize, tryPolicyMostRecentlyMod:
|
||||||
default:
|
default:
|
||||||
return nil, h.Errf("unrecognized try policy: %s", tryPolicy)
|
return nil, h.Errf("unrecognized try policy: %s", tryPolicy)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ respond with a file listing.`,
|
|||||||
cmd.Flags().BoolP("templates", "t", false, "Enable template rendering")
|
cmd.Flags().BoolP("templates", "t", false, "Enable template rendering")
|
||||||
cmd.Flags().BoolP("access-log", "a", false, "Enable the access log")
|
cmd.Flags().BoolP("access-log", "a", false, "Enable the access log")
|
||||||
cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs")
|
cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs")
|
||||||
|
cmd.Flags().IntP("file-limit", "f", defaultDirEntryLimit, "Max directories to read")
|
||||||
cmd.Flags().BoolP("no-compress", "", false, "Disable Zstandard and Gzip compression")
|
cmd.Flags().BoolP("no-compress", "", false, "Disable Zstandard and Gzip compression")
|
||||||
cmd.Flags().StringSliceP("precompressed", "p", []string{}, "Specify precompression file extensions. Compression preference implied from flag order.")
|
cmd.Flags().StringSliceP("precompressed", "p", []string{}, "Specify precompression file extensions. Compression preference implied from flag order.")
|
||||||
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdFileServer)
|
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdFileServer)
|
||||||
@@ -91,6 +92,7 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
|
|||||||
browse := fs.Bool("browse")
|
browse := fs.Bool("browse")
|
||||||
templates := fs.Bool("templates")
|
templates := fs.Bool("templates")
|
||||||
accessLog := fs.Bool("access-log")
|
accessLog := fs.Bool("access-log")
|
||||||
|
fileLimit := fs.Int("file-limit")
|
||||||
debug := fs.Bool("debug")
|
debug := fs.Bool("debug")
|
||||||
revealSymlinks := fs.Bool("reveal-symlinks")
|
revealSymlinks := fs.Bool("reveal-symlinks")
|
||||||
compress := !fs.Bool("no-compress")
|
compress := !fs.Bool("no-compress")
|
||||||
@@ -151,7 +153,7 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if browse {
|
if browse {
|
||||||
handler.Browse = &Browse{RevealSymlinks: revealSymlinks}
|
handler.Browse = &Browse{RevealSymlinks: revealSymlinks, FileLimit: fileLimit}
|
||||||
}
|
}
|
||||||
|
|
||||||
handlers = append(handlers, caddyconfig.JSONModuleObject(handler, "handler", "file_server", nil))
|
handlers = append(handlers, caddyconfig.JSONModuleObject(handler, "handler", "file_server", nil))
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user