Compare commits

..

16 Commits

Author SHA1 Message Date
a 3f1ff118f8 noot 2024-06-18 23:48:21 -05:00
a 4d40619aa4 Merge branch 'caddytest-2' of github.com:elee1766/caddy into caddytest-2 2024-06-18 23:45:57 -05:00
a 3c591ecac9 noot 2024-06-18 23:45:54 -05:00
a 73854014d9 Merge branch 'master' into caddytest-2 2024-06-18 22:48:21 -05:00
a c0d9a2383e noot 2024-06-18 22:36:02 -05:00
a 7bc7e1680e noot 2024-06-18 21:57:46 -05:00
a edf4168c8e noot 2024-06-18 21:18:38 -05:00
a 926fb82f6b noot 2024-06-18 21:18:07 -05:00
a 841fe2544d noot 2024-06-18 21:17:51 -05:00
a b19feec6dc noot 2024-06-18 21:01:15 -05:00
a 41a4320fd3 noot 2024-06-18 20:14:51 -05:00
a b491fc5d6c noot 2024-06-18 20:11:56 -05:00
a 01cb878087 noot 2024-06-18 20:08:38 -05:00
a b98c89fbb6 noot 2024-06-18 19:46:43 -05:00
a 2619271a5c noot 2024-06-18 19:44:05 -05:00
a 93a1853022 noot 2024-06-18 18:16:33 -05:00
204 changed files with 3552 additions and 11142 deletions
+2 -2
View File
@@ -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.latest | ✔️ | | 2.x | ✔️ |
| 1.x | :x: | | 1.x | :x: |
| < 1.x | :x: | | < 1.x | :x: |
+13 -52
View File
@@ -12,10 +12,6 @@ on:
- master - master
- 2.* - 2.*
env:
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
jobs: jobs:
test: test:
strategy: strategy:
@@ -27,13 +23,17 @@ jobs:
- mac - mac
- windows - windows
go: go:
- '1.24' - '1.21'
- '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.24' - go: '1.21'
GO_SEMVER: '~1.24.1' GO_SEMVER: '~1.21.0'
- 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 nobadger,nomysql,nopgx -trimpath -ldflags="-w -s" -v go build -tags nobdger -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,nomysql,nopgx -v -coverprofile="cover-profile.out" -short -race ./... go test -tags nobadger -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,48 +143,25 @@ 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 == 'caddyserver/caddy' && github.actor != 'dependabot[bot]' if: github.event.pull_request.head.repo.full_name == github.repository && 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 $ssh_opts" --progress --delete --exclude '.git' . "$ssh_host":/var/tmp/"$short_sha" rsync -arz -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" --progress --delete --exclude '.git' . "$CI_USER"@ci-s390x.caddyserver.com:/var/tmp/"$short_sha"
ssh $ssh_opts -t "$ssh_host" bash <<EOF 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 ./..."
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 $ssh_opts "$ssh_host" "rm -rf /var/tmp/'$short_sha'" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "$CI_USER"@ci-s390x.caddyserver.com "rm -rf /var/tmp/'$short_sha'"
echo "Test exit code: $test_result" echo "Test exit code: $test_result"
exit $test_result exit $test_result
@@ -194,7 +171,6 @@ 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
@@ -203,18 +179,3 @@ 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 }}
+4 -8
View File
@@ -10,10 +10,6 @@ on:
- master - master
- 2.* - 2.*
env:
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
jobs: jobs:
build: build:
strategy: strategy:
@@ -31,13 +27,13 @@ jobs:
- 'darwin' - 'darwin'
- 'netbsd' - 'netbsd'
go: go:
- '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.24' - go: '1.22'
GO_SEMVER: '~1.24.1' GO_SEMVER: '~1.22.3'
runs-on: ubuntu-latest runs-on: ubuntu-latest
continue-on-error: true continue-on-error: true
@@ -70,4 +66,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,nomysql,nopgx -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null GOOS=$GOOS GOARCH=$GOARCH go build -tags nobadger -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
+3 -7
View File
@@ -13,10 +13,6 @@ 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:
@@ -47,13 +43,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.24' go-version: '~1.22.3'
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: latest version: v1.55
# 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
@@ -67,5 +63,5 @@ jobs:
- name: govulncheck - name: govulncheck
uses: golang/govulncheck-action@v1 uses: golang/govulncheck-action@v1
with: with:
go-version-input: '~1.24.1' go-version-input: '~1.22.3'
check-latest: true check-latest: true
+3 -11
View File
@@ -5,10 +5,6 @@ on:
tags: tags:
- 'v*.*.*' - 'v*.*.*'
env:
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
jobs: jobs:
release: release:
name: Release name: Release
@@ -17,13 +13,13 @@ jobs:
os: os:
- ubuntu-latest - ubuntu-latest
go: go:
- '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.24' - go: '1.22'
GO_SEMVER: '~1.24.1' GO_SEMVER: '~1.22.3'
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
@@ -108,10 +104,6 @@ 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
+5 -19
View File
@@ -1,9 +1,7 @@
linters-settings: linters-settings:
errcheck: errcheck:
exclude-functions: ignore: fmt:.*,go.uber.org/zap/zapcore:^Add.*
- fmt.* ignoretests: true
- (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.
@@ -35,6 +33,7 @@ linters:
- errcheck - errcheck
- errname - errname
- exhaustive - exhaustive
- exportloopref
- gci - gci
- gofmt - gofmt
- goimports - goimports
@@ -131,22 +130,18 @@ 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.
timeout: 5m deadline: 5m
issues-exit-code: 1 issues-exit-code: 1
tests: false tests: false
# output configuration options # output configuration options
output: output:
formats: format: 'colored-line-number'
- 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:
@@ -171,12 +166,3 @@ 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
+2 -9
View File
@@ -12,9 +12,6 @@ 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
@@ -34,6 +31,7 @@ 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:
@@ -83,8 +81,6 @@ builds:
- -s -w - -s -w
tags: tags:
- nobadger - nobadger
- nomysql
- nopgx
signs: signs:
- cmd: cosign - cmd: cosign
@@ -111,7 +107,7 @@ archives:
- id: default - id: default
format_overrides: format_overrides:
- goos: windows - goos: windows
formats: zip format: zip
name_template: >- name_template: >-
{{ .ProjectName }}_ {{ .ProjectName }}_
{{- .Version }}_ {{- .Version }}_
@@ -192,9 +188,6 @@ 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
+6 -7
View File
@@ -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://x.com/caddyserver" title="@caddyserver on Twitter"><img src="https://img.shields.io/twitter/follow/caddyserver" alt="@caddyserver on Twitter"></a> <a href="https://twitter.com/caddyserver" title="@caddyserver on Twitter"><img src="https://img.shields.io/badge/twitter-@caddyserver-55acee.svg" alt="@caddyserver on Twitter"></a>
<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,7 +67,6 @@
- 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
@@ -88,7 +87,7 @@ See [our online documentation](https://caddyserver.com/docs/install) for other i
Requirements: Requirements:
- [Go 1.24.0 or newer](https://golang.org/dl/) - [Go 1.21 or newer](https://golang.org/dl/)
### For development ### For development
@@ -132,7 +131,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 -tags=nobadger,nomysql,nopgx` 7. Compile: `go build`
@@ -177,7 +176,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) before help is needed. - 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.
- 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!
@@ -193,8 +192,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 X: [@caddyserver](https://x.com/caddyserver)_ - _Project on Twitter: [@caddyserver](https://twitter.com/caddyserver)_
- _Author on X: [@mholt6](https://x.com/mholt6)_ - _Author on Twitter: [@mholt6](https://twitter.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.
+39 -28
View File
@@ -34,7 +34,6 @@ import (
"os" "os"
"path" "path"
"regexp" "regexp"
"slices"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -214,15 +213,14 @@ 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, _ Context) adminHandler { func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool) adminHandler {
muxWrap := adminHandler{mux: http.NewServeMux()} muxWrap := adminHandler{mux: http.NewServeMux()}
// secure the local or remote endpoint respectively // secure the local or remote endpoint respectively
if remote { if remote {
muxWrap.remoteControl = admin.Remote muxWrap.remoteControl = admin.Remote
} else { } else {
// see comment in allowedOrigins() as to why we disable the host check for unix/fd networks muxWrap.enforceHost = !addr.isWildcardInterface()
muxWrap.enforceHost = !addr.isWildcardInterface() && !addr.IsUnixNetwork() && !addr.IsFdNetwork()
muxWrap.allowedOrigins = admin.allowedOrigins(addr) muxWrap.allowedOrigins = admin.allowedOrigins(addr)
muxWrap.enforceOrigin = admin.EnforceOrigin muxWrap.enforceOrigin = admin.EnforceOrigin
} }
@@ -271,6 +269,7 @@ func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, _ Co
// 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)
} }
@@ -311,6 +310,9 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
for _, o := range admin.Origins { for _, o := range admin.Origins {
uniqueOrigins[o] = struct{}{} uniqueOrigins[o] = struct{}{}
} }
if admin.Origins == nil {
if addr.isLoopback() {
if addr.IsUnixNetwork() {
// 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
@@ -328,26 +330,27 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
// bogus host; see https://superuser.com/a/925610.) If we disable Host/Origin // bogus host; see https://superuser.com/a/925610.) If we disable Host/Origin
// security checks, the infosec community assures me that it is secure to do // security checks, the infosec community assures me that it is secure to do
// so, because: // so, because:
//
// 1) Browsers do not allow access to unix sockets // 1) Browsers do not allow access to unix sockets
// 2) DNS is irrelevant to unix sockets // 2) DNS is irrelevant to unix sockets
// //
// If either of those two statements ever fail to hold true, it is not the // I am not quite ready to trust either of those external factors, so instead
// fault of Caddy. // of disabling Host/Origin checks, we now allow specific Host values when
// // accessing the admin endpoint over unix sockets. I definitely don't trust
// Thus, we do not fill out allowed origins and do not enforce Host // DNS (e.g. I don't trust 'localhost' to always resolve to the local host),
// requirements for unix sockets. Enforcing it leads to confusion and // and IP shouldn't even be used, but if it is for some reason, I think we can
// frustration, when UDS have their own permissions from the OS. // at least be reasonably assured that 127.0.0.1 and ::1 route to the local
// Enforcing host requirements here is effectively security theater, // machine, meaning that a hypothetical browser origin would have to be on the
// and a false sense of security. // local machine as well.
// uniqueOrigins[""] = struct{}{}
// See also the discussion in #6832. uniqueOrigins["127.0.0.1"] = struct{}{}
if admin.Origins == nil && !addr.IsUnixNetwork() && !addr.IsFdNetwork() { uniqueOrigins["::1"] = struct{}{}
if addr.isLoopback() { } else {
uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{} uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{} uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{} uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
} else { }
}
if !addr.IsUnixNetwork() {
uniqueOrigins[addr.JoinHostPort(0)] = struct{}{} uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
} }
} }
@@ -378,9 +381,7 @@ 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).
// Critically note that some elements and functionality of the context func replaceLocalAdminServer(cfg *Config) error {
// 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
@@ -422,7 +423,7 @@ func replaceLocalAdminServer(cfg *Config, ctx Context) error {
return err return err
} }
handler := cfg.Admin.newAdminHandler(addr, false, ctx) handler := cfg.Admin.newAdminHandler(addr, false)
ln, err := addr.Listen(context.TODO(), 0, net.ListenConfig{}) ln, err := addr.Listen(context.TODO(), 0, net.ListenConfig{})
if err != nil { if err != nil {
@@ -543,7 +544,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, ctx) handler := cfg.Admin.newAdminHandler(addr, true)
// 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
@@ -674,7 +675,13 @@ 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 || slices.Contains(accessPerm.Methods, r.Method) methodFound := accessPerm.Methods == nil
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,
@@ -870,9 +877,13 @@ 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 {
allowed := slices.ContainsFunc(h.allowedOrigins, func(u *url.URL) bool { var allowed bool
return r.Host == u.Host for _, allowedOrigin := range h.allowedOrigins {
}) if r.Host == allowedOrigin.Host {
allowed = true
break
}
}
if !allowed { if !allowed {
return APIError{ return APIError{
HTTPStatus: http.StatusForbidden, HTTPStatus: http.StatusForbidden,
@@ -1136,7 +1147,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 || (method != http.MethodPut && idx >= len(arr)) || idx > len(arr) { if idx < 0 || 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)
} }
} }
-731
View File
@@ -15,19 +15,12 @@
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(`{
@@ -210,727 +203,3 @@ 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) {
// see comment in allowedOrigins() as to why we do not fill out allowed origins for UDS
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{},
},
{
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 i, 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("%d: Expected %d origins but got %d", i, 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("%d: Origins mismatch.\nExpected: %v\nGot: %v", i, 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)
}
})
}
}
+10 -107
View File
@@ -20,6 +20,7 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"flag"
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
@@ -83,12 +84,11 @@ type Config struct {
apps map[string]App apps map[string]App
storage certmagic.Storage storage certmagic.Storage
eventEmitter eventEmitter
cancelFunc context.CancelFunc cancelFunc context.CancelFunc
// fileSystems is a dict of fileSystems that will later be loaded from and added to. // filesystems is a dict of filesystems that will later be loaded from and added to.
fileSystems FileSystems filesystems FileSystems
} }
// App is a thing that Caddy runs. // App is a thing that Caddy runs.
@@ -400,7 +400,6 @@ 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
} }
@@ -412,7 +411,6 @@ 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
} }
@@ -438,14 +436,8 @@ 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()
// TODO: This event is experimental and subject to change.
ctx.emitEvent("started", nil)
// 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.
@@ -480,7 +472,6 @@ 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;
@@ -507,14 +498,14 @@ 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, ctx) err = replaceLocalAdminServer(newCfg)
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)
} }
} }
// create the new filesystem map // create the new filesystem map
newCfg.fileSystems = &filesystems.FileSystemMap{} newCfg.filesystems = &filesystems.FilesystemMap{}
// prepare the new config for use // prepare the new config for use
newCfg.apps = make(map[string]App) newCfg.apps = make(map[string]App)
@@ -701,9 +692,6 @@ func unsyncedStop(ctx Context) {
return return
} }
// TODO: This event is experimental and subject to change.
ctx.emitEvent("stopping", nil)
// stop each app // stop each app
for name, a := range ctx.cfg.apps { for name, a := range ctx.cfg.apps {
err := a.Stop() err := a.Stop()
@@ -733,10 +721,8 @@ 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; only do it once // let the rest of the program know we're quitting
if !atomic.CompareAndSwapInt32(exiting, 0, 1) { atomic.StoreInt32(exiting, 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 {
@@ -793,7 +779,10 @@ func exitProcess(ctx context.Context, logger *zap.Logger) {
} else { } else {
logger.Error("unclean shutdown") logger.Error("unclean shutdown")
} }
// check if we are in test environment, and dont call exit if we are
if flag.Lookup("test.v") == nil && !strings.Contains(os.Args[0], ".test") {
os.Exit(exitCode) os.Exit(exitCode)
}
}() }()
if remoteAdminServer != nil { if remoteAdminServer != nil {
@@ -1046,92 +1035,6 @@ func Version() (simple, full string) {
return return
} }
// Event represents something that has happened or is happening.
// An Event value is not synchronized, so it should be copied if
// being used in goroutines.
//
// EXPERIMENTAL: Events are subject to change.
type Event struct {
// If non-nil, the event has been aborted, meaning
// propagation has stopped to other handlers and
// the code should stop what it was doing. Emitters
// may choose to use this as a signal to adjust their
// code path appropriately.
Aborted error
// The data associated with the event. Usually the
// original emitter will be the only one to set or
// change these values, but the field is exported
// so handlers can have full access if needed.
// However, this map is not synchronized, so
// handlers must not use this map directly in new
// goroutines; instead, copy the map to use it in a
// goroutine. Data may be nil.
Data map[string]any
id uuid.UUID
ts time.Time
name string
origin Module
}
// NewEvent creates a new event, but does not emit the event. To emit an
// event, call Emit() on the current instance of the caddyevents app insteaad.
//
// EXPERIMENTAL: Subject to change.
func NewEvent(ctx Context, name string, data map[string]any) (Event, error) {
id, err := uuid.NewRandom()
if err != nil {
return Event{}, fmt.Errorf("generating new event ID: %v", err)
}
name = strings.ToLower(name)
return Event{
Data: data,
id: id,
ts: time.Now(),
name: name,
origin: ctx.Module(),
}, nil
}
func (e Event) ID() uuid.UUID { return e.id }
func (e Event) Timestamp() time.Time { return e.ts }
func (e Event) Name() string { return e.name }
func (e Event) Origin() Module { return e.origin } // Returns the module that originated the event. May be nil, usually if caddy core emits the event.
// CloudEvent exports event e as a structure that, when
// serialized as JSON, is compatible with the
// CloudEvents spec.
func (e Event) CloudEvent() CloudEvent {
dataJSON, _ := json.Marshal(e.Data)
return CloudEvent{
ID: e.id.String(),
Source: e.origin.CaddyModule().String(),
SpecVersion: "1.0",
Type: e.name,
Time: e.ts,
DataContentType: "application/json",
Data: dataJSON,
}
}
// CloudEvent is a JSON-serializable structure that
// is compatible with the CloudEvents specification.
// See https://cloudevents.io.
// EXPERIMENTAL: Subject to change.
type CloudEvent struct {
ID string `json:"id"`
Source string `json:"source"`
SpecVersion string `json:"specversion"`
Type string `json:"type"`
Time time.Time `json:"time"`
DataContentType string `json:"datacontenttype,omitempty"`
Data json.RawMessage `json:"data,omitempty"`
}
// ErrEventAborted cancels an event.
var ErrEventAborted = errors.New("event aborted")
// ActiveContext returns the currently-active context. // ActiveContext returns the currently-active context.
// This function is experimental and might be changed // This function is experimental and might be changed
// or removed in the future. // or removed in the future.
+1 -1
View File
@@ -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.WrapErr(errors.New(msg)) return d.Errf(msg)
} }
// Errf is like Err, but for formatted error messages // Errf is like Err, but for formatted error messages
+1 -14
View File
@@ -62,7 +62,6 @@ func Format(input []byte) []byte {
heredocClosingMarker []rune heredocClosingMarker []rune
nesting int // indentation level nesting int // indentation level
withinBackquote bool
) )
write := func(ch rune) { write := func(ch rune) {
@@ -89,9 +88,6 @@ func Format(input []byte) []byte {
} }
panic(err) panic(err)
} }
if ch == '`' {
withinBackquote = !withinBackquote
}
// detect whether we have the start of a heredoc // detect whether we have the start of a heredoc
if !quoted && !(heredoc != heredocClosed || heredocEscaped) && if !quoted && !(heredoc != heredocClosed || heredocEscaped) &&
@@ -240,23 +236,14 @@ func Format(input []byte) []byte {
switch { switch {
case ch == '{': case ch == '{':
openBrace = true openBrace = true
openBraceWritten = false
openBraceSpace = spacePrior && !beginningOfLine openBraceSpace = spacePrior && !beginningOfLine
if openBraceSpace { if openBraceSpace {
write(' ') write(' ')
} }
openBraceWritten = false
if withinBackquote {
write('{')
openBraceWritten = true
continue
}
continue continue
case ch == '}' && (spacePrior || !openBrace): case ch == '}' && (spacePrior || !openBrace):
if withinBackquote {
write('}')
continue
}
if last != '\n' { if last != '\n' {
nextLine() nextLine()
} }
-10
View File
@@ -434,16 +434,6 @@ block2 {
} }
`, `,
}, },
{
description: "Preserve braces wrapped by backquotes",
input: "block {respond `All braces should remain: {{now | date \"2006\"}}`}",
expect: "block {respond `All braces should remain: {{now | date \"2006\"}}`}",
},
{
description: "Preserve braces wrapped by quotes",
input: "block {respond \"All braces should remain: {{now | date `2006`}}\"}",
expect: "block {respond \"All braces should remain: {{now | date `2006`}}\"}",
},
} { } {
// the formatter should output a trailing newline, // the formatter should output a trailing newline,
// even if the tests aren't written to expect that // even if the tests aren't written to expect that
+6 -2
View File
@@ -16,7 +16,6 @@ package caddyfile
import ( import (
"fmt" "fmt"
"slices"
) )
type adjacency map[string][]string type adjacency map[string][]string
@@ -92,7 +91,12 @@ func (i *importGraph) areConnected(from, to string) bool {
if !ok { if !ok {
return false return false
} }
return slices.Contains(al, to) for _, v := range al {
if v == to {
return true
}
}
return false
} }
func (i *importGraph) willCycle(from, to string) bool { func (i *importGraph) willCycle(from, to string) bool {
+2 -7
View File
@@ -264,14 +264,9 @@ 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()
@@ -423,7 +418,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 := caddy.FastAbs(p.Dispenser.File()) absFile, err := filepath.Abs(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)
} }
@@ -622,7 +617,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 := caddy.FastAbs(importFile) filename, err := filepath.Abs(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)
} }
+2 -6
View File
@@ -555,10 +555,6 @@ 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 {
@@ -618,8 +614,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: %v", t.Errorf("Test %d: Expected %d keys in block %d, got %d",
i, len(test.keys[j]), j, len(block.Keys), block.Keys) i, len(test.keys[j]), j, len(block.Keys))
continue continue
} }
for k, addr := range block.GetKeysText() { for k, addr := range block.GetKeysText() {
+96 -172
View File
@@ -31,7 +31,7 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
// mapAddressToProtocolToServerBlocks returns a map of listener address to list of server // mapAddressToServerBlocks 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,15 +77,10 @@ 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) mapAddressToProtocolToServerBlocks(originalServerBlocks []serverBlock, func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock,
options map[string]any, options map[string]any,
) (map[string]map[string][]serverBlock, error) { ) (map[string][]serverBlock, error) {
addrToProtocolToServerBlocks := map[string]map[string][]serverBlock{} sbmap := make(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
@@ -93,48 +88,27 @@ func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks []
// 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
addrToProtocolToKeyWithParsedKeys := map[string]map[string][]keyWithParsedKey{} addrToKeys := make(map[string][]caddyfile.Token)
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)
listeners, err := st.listenersForServerBlockAddress(sblock, parsedKey, options) addrs, err := st.listenerAddrsForServerBlockKey(sblock, key.Text, 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 its protocols and each listener address served with them // associate this key with each listener address it is served on
kwpk := keyWithParsedKey{key, parsedKey} for _, addr := range addrs {
for addr, protocols := range listeners { addrToKeys[addr] = append(addrToKeys[addr], key)
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(addrToProtocolToKeyWithParsedKeys)) addrs := make([]string, 0, len(addrToKeys))
for addr := range addrToProtocolToKeyWithParsedKeys { for k := range addrToKeys {
addrs = append(addrs, addr) addrs = append(addrs, k)
} }
sort.Strings(addrs) sort.Strings(addrs)
@@ -144,132 +118,85 @@ func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks []
// 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 {
protocolToKeyWithParsedKeys := addrToProtocolToKeyWithParsedKeys[addr] keys := addrToKeys[addr]
// parse keys so that we only have to do it once
prots := make([]string, 0, len(protocolToKeyWithParsedKeys)) parsedKeys := make([]Address, 0, len(keys))
for prot := range protocolToKeyWithParsedKeys { for _, key := range keys {
prots = append(prots, prot) addr, err := ParseAddress(key.Text)
if err != nil {
return nil, fmt.Errorf("parsing key '%s': %v", key.Text, err)
} }
sort.Strings(prots) parsedKeys = append(parsedKeys, addr.Normalize())
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,
parsedKeys: parsedKeys, keys: parsedKeys,
}) })
} }
} }
}
return addrToProtocolToServerBlocks, nil return sbmap, 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 protocols to lists of server blocks. Since multiple addresses // single listener addresses to lists of server blocks. Since multiple addresses may serve
// may serve multiple protocols to identical sites (server block contents), this function turns // identical sites (server block contents), this function turns a 1:many mapping into a
// a 1:many mapping into a many:many mapping. Server block contents (tokens) must be // many:many mapping. Server block contents (tokens) must be exactly identical so that
// exactly identical so that reflect.DeepEqual returns true in order for the addresses to be combined. // reflect.DeepEqual returns true in order for the addresses to be combined. Identical
// Identical entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each // 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(addrToProtocolToServerBlocks map[string]map[string][]serverBlock) []sbAddrAssociation { func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]serverBlock) []sbAddrAssociation {
sbaddrs := make([]sbAddrAssociation, 0, len(addrToProtocolToServerBlocks)) sbaddrs := make([]sbAddrAssociation, 0, len(addrToServerBlocks))
for addr, sblocks := range addrToServerBlocks {
addrs := make([]string, 0, len(addrToProtocolToServerBlocks)) // we start with knowing that at least this address
for addr := range addrToProtocolToServerBlocks { // maps to these server blocks
addrs = append(addrs, addr) a := sbAddrAssociation{
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 map of listener // server blocks and add them to our list of
// addresses and protocols, while removing them from // addresses, while removing them from the map
// the original map for otherAddr, otherSblocks := range addrToServerBlocks {
listeners := map[string]map[string]struct{}{} if addr == otherAddr {
continue
}
if reflect.DeepEqual(sblocks, otherSblocks) {
a.addresses = append(a.addresses, otherAddr)
delete(addrToServerBlocks, otherAddr)
}
}
sort.Strings(a.addresses)
for otherAddr, otherProtocolToServerBlocks := range addrToProtocolToServerBlocks { sbaddrs = append(sbaddrs, a)
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)
}
}
} }
addresses := make([]string, 0, len(listeners)) // sort them by their first address (we know there will always be at least one)
for lnAddr := range listeners { // to avoid problems with non-deterministic ordering (makes tests flaky)
addresses = append(addresses, lnAddr) sort.Slice(sbaddrs, func(i, j int) bool {
} 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
} }
// listenersForServerBlockAddress essentially converts the Caddyfile site addresses to a map from // listenerAddrsForServerBlockKey essentially converts the Caddyfile
// Caddy listener addresses and the protocols to serve them with to the parsed address for each server block. // site addresses to Caddy listener addresses for each server block.
func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Address, func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string,
options map[string]any, options map[string]any,
) (map[string]map[string]struct{}, error) { ) ([]string, 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")
@@ -303,58 +230,55 @@ func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Ad
// 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", addr.String()) return nil, fmt.Errorf("[%s] scheme and port violate convention", key)
} }
// the bind directive specifies hosts (and potentially network), and the protocols to serve them with, but is optional // the bind directive specifies hosts (and potentially network), but is optional
lnCfgVals := make([]addressesWithProtocols, 0, len(sblock.pile["bind"])) lnHosts := make([]string, 0, len(sblock.pile["bind"]))
for _, cfgVal := range sblock.pile["bind"] { for _, cfgVal := range sblock.pile["bind"] {
if val, ok := cfgVal.Value.(addressesWithProtocols); ok { lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
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 {
lnCfgVals = []addressesWithProtocols{{ lnHosts = []string{""}
addresses: []string{""},
protocols: nil,
}}
} }
} }
// use a map to prevent duplication // use a map to prevent duplication
listeners := map[string]map[string]struct{}{} listeners := make(map[string]struct{})
for _, lnCfgVal := range lnCfgVals { for _, lnHost := range lnHosts {
for _, lnAddr := range lnCfgVal.addresses { // normally we would simply append the port,
lnNetw, lnHost, _, err := caddy.SplitNetworkAddress(lnAddr) // but if lnHost is IPv6, we need to ensure it
if err != nil { // is enclosed in [ ]; net.JoinHostPort does
return nil, fmt.Errorf("splitting listener address: %v", err) // this for us, but lnHost might also have a
// network type in front (e.g. "tcp/") leading
// to "[tcp/::1]" which causes parsing failures
// later; what we need is "tcp/[::1]", so we have
// to split the network and host, then re-combine
network, host, ok := strings.Cut(lnHost, "/")
if !ok {
host = network
network = ""
} }
networkAddr, err := caddy.ParseNetworkAddress(caddy.JoinNetworkAddress(lnNetw, lnHost, lnPort)) host = strings.Trim(host, "[]") // IPv6
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)
} }
if _, ok := listeners[addr.String()]; !ok { listeners[addr.String()] = struct{}{}
listeners[networkAddr.String()] = map[string]struct{}{}
}
for _, protocol := range lnCfgVal.protocols {
listeners[networkAddr.String()][protocol] = struct{}{}
}
}
} }
return listeners, nil // now turn map into list
} listenersList := make([]string, 0, len(listeners))
for lnStr := range listeners {
listenersList = append(listenersList, lnStr)
}
sort.Strings(listenersList)
// addressesWithProtocols associates a list of listen addresses return listenersList, nil
// 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
+10 -90
View File
@@ -24,7 +24,7 @@ import (
"time" "time"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/mholt/acmez/v3/acme" "github.com/mholt/acmez/v2/acme"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
@@ -56,35 +56,15 @@ 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
var addresses, protocols []string return []ConfigValue{{Class: "bind", Value: h.RemainingArgs()}}, nil
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|force_automate]|[<cert_file> <key_file>] { // tls [<email>|internal]|[<cert_file> <key_file>] {
// protocols <min> [<max>] // protocols <min> [<max>]
// ciphers <cipher_suites...> // ciphers <cipher_suites...>
// curves <curves...> // curves <curves...>
@@ -99,7 +79,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> [...]] (required, though, if DNS is not configured as global option) // dns <provider_name> [...]
// propagation_delay <duration> // propagation_delay <duration>
// propagation_timeout <duration> // propagation_timeout <duration>
// resolvers <dns_servers...> // resolvers <dns_servers...>
@@ -107,7 +87,6 @@ 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> [...]
@@ -127,7 +106,6 @@ 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) {
@@ -135,10 +113,8 @@ 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', 'force_automate', or an email address") return nil, h.Err("single argument must either be 'internal' or an email address")
} else { } else {
acmeIssuer = &caddytls.ACMEIssuer{ acmeIssuer = &caddytls.ACMEIssuer{
Email: firstLine[0], Email: firstLine[0],
@@ -312,6 +288,10 @@ 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)
} }
@@ -321,19 +301,12 @@ 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()
@@ -576,15 +549,6 @@ 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
@@ -997,50 +961,6 @@ 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,20 +62,6 @@ 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{
+33 -12
View File
@@ -17,7 +17,6 @@ package httpcaddyfile
import ( import (
"encoding/json" "encoding/json"
"net" "net"
"slices"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@@ -101,6 +100,17 @@ 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
@@ -151,7 +161,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 slices.Contains(directiveOrder, dir) { if directiveIsOrdered(dir) {
panic("directive '" + dir + "' already ordered") panic("directive '" + dir + "' already ordered")
} }
@@ -162,7 +172,12 @@ 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 := slices.Contains(defaultDirectiveOrder, standardDir) foundStandardDir := false
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")
} }
@@ -518,7 +533,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
parsedKeys []Address keys []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
@@ -535,7 +550,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.parsedKeys { for _, addr := range sb.keys {
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
@@ -567,7 +582,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.parsedKeys { for _, addr := range sb.keys {
if addr.Host == "" { if addr.Host == "" {
continue continue
} }
@@ -588,17 +603,23 @@ 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 {
return slices.ContainsFunc(sb.parsedKeys, func(addr Address) bool { for _, addr := range sb.keys {
return addr.Host == "" if 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 {
return !slices.ContainsFunc(sb.parsedKeys, func(addr Address) bool { for _, addr := range sb.keys {
return addr.Scheme != "http" if addr.Scheme != "http" {
}) return false
}
}
return true
} }
// Positional are the supported modes for ordering directives. // Positional are the supported modes for ordering directives.
+1 -1
View File
@@ -78,7 +78,7 @@ func TestHostsFromKeys(t *testing.T) {
[]string{"example.com:2015"}, []string{"example.com:2015"},
}, },
} { } {
sb := serverBlock{parsedKeys: tc.keys} sb := serverBlock{keys: tc.keys}
// test in normal mode // test in normal mode
actual := sb.hostsFromKeys(false) actual := sb.hostsFromKeys(false)
+58 -193
View File
@@ -15,7 +15,6 @@
package httpcaddyfile package httpcaddyfile
import ( import (
"cmp"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net" "net"
@@ -172,7 +171,7 @@ func (st ServerType) Setup(
} }
// map // map
sbmap, err := st.mapAddressToProtocolToServerBlocks(originalServerBlocks, options) sbmap, err := st.mapAddressToServerBlocks(originalServerBlocks, options)
if err != nil { if err != nil {
return nil, warnings, err return nil, warnings, err
} }
@@ -187,25 +186,12 @@ 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,
} }
@@ -350,7 +336,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(defaultLog.Exclude) defaultLog.Exclude = slices.Compact[[]string, string](defaultLog.Exclude)
} }
} }
// we may have not actually added anything, so remove if empty // we may have not actually added anything, so remove if empty
@@ -416,20 +402,6 @@ 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
} }
@@ -548,8 +520,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 := []string{} autoHTTPS := "on"
if ah, ok := options["auto_https"].([]string); ok { if ah, ok := options["auto_https"].(string); ok {
autoHTTPS = ah autoHTTPS = ah
} }
@@ -564,74 +536,28 @@ func (st *ServerType) serversFromPairings(
if k == j { if k == j {
continue continue
} }
if slices.Contains(sblock2.block.GetKeysText(), key) { if sliceContains(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: addresses, Listen: p.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
for _, val := range autoHTTPS { if autoHTTPS != "on" {
switch val { srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
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
} }
} }
@@ -640,7 +566,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.parsedKeys { for _, addr := range sblock.keys {
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()))
} }
@@ -658,7 +584,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].parsedKeys { for _, addr := range p.serverBlocks[i].keys {
if strings.Contains(addr.Host, "*") || addr.Host == "" { if strings.Contains(addr.Host, "*") || addr.Host == "" {
iWildcardHost = true iWildcardHost = true
} }
@@ -669,7 +595,7 @@ func (st *ServerType) serversFromPairings(
iLongestPath = addr.Path iLongestPath = addr.Path
} }
} }
for _, addr := range p.serverBlocks[j].parsedKeys { for _, addr := range p.serverBlocks[j].keys {
if strings.Contains(addr.Host, "*") || addr.Host == "" { if strings.Contains(addr.Host, "*") || addr.Host == "" {
jWildcardHost = true jWildcardHost = true
} }
@@ -701,7 +627,7 @@ func (st *ServerType) serversFromPairings(
}) })
var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool
autoHTTPSWillAddConnPolicy := srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled autoHTTPSWillAddConnPolicy := autoHTTPS != "off"
// 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
@@ -747,14 +673,6 @@ 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
@@ -785,21 +703,15 @@ func (st *ServerType) serversFromPairings(
cp.FallbackSNI = fallbackSNI cp.FallbackSNI = fallbackSNI
} }
// only append this policy if it actually changes something, // only append this policy if it actually changes something
// or if the configuration explicitly automates certs for if !cp.SettingsEmpty() {
// these names (this is necessary to hoist a connection policy
// above one that may manually load a wildcard cert that would
// otherwise clobber the automated one; the code that appends
// policies that manually load certs comes later, so they're
// lower in the list)
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.parsedKeys { for _, addr := range sblock.keys {
// 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://"
@@ -808,7 +720,7 @@ func (st *ServerType) serversFromPairings(
if srv.AutoHTTPS == nil { if srv.AutoHTTPS == nil {
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig) srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
} }
if !slices.Contains(srv.AutoHTTPS.Skip, addr.Host) { if !sliceContains(srv.AutoHTTPS.Skip, addr.Host) {
srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, addr.Host) srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, addr.Host)
} }
} }
@@ -822,7 +734,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 && !slices.Contains(srv.AutoHTTPS.Skip, addr.Host)) (addr.Host != "" && srv.AutoHTTPS != nil && !sliceContains(srv.AutoHTTPS.Skip, addr.Host))
// we'll need to remember if the address qualifies for auto-HTTPS, so we // 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
@@ -830,7 +742,6 @@ func (st *ServerType) serversFromPairings(
(addr.Scheme != "http" && addr.Port != httpPort && hasTLSEnabled) { (addr.Scheme != "http" && addr.Port != httpPort && hasTLSEnabled) {
addressQualifiesForTLS = true addressQualifiesForTLS = true
} }
// 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 &&
@@ -962,10 +873,7 @@ 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{ srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{DefaultSNI: defaultSNI, FallbackSNI: fallbackSNI})
DefaultSNI: defaultSNI,
FallbackSNI: fallbackSNI,
})
} }
// tidy things up a bit // tidy things up a bit
@@ -978,7 +886,8 @@ func (st *ServerType) serversFromPairings(
servers[fmt.Sprintf("srv%d", i)] = srv servers[fmt.Sprintf("srv%d", i)] = srv
} }
if err := applyServerOptions(servers, options, warnings); err != nil { err := applyServerOptions(servers, options, warnings)
if err != nil {
return nil, fmt.Errorf("applying global server options: %v", err) return nil, fmt.Errorf("applying global server options: %v", err)
} }
@@ -1023,7 +932,7 @@ func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []serverBlock,
} }
for _, sblock := range serverBlocks { for _, sblock := range serverBlocks {
for _, addr := range sblock.parsedKeys { for _, addr := range sblock.keys {
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
@@ -1061,40 +970,11 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti
// 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(cps[i], cps[j]) { if reflect.DeepEqual(cps[i], cps[j]) {
cps = slices.Delete(cps, j, j+1) cps = append(cps[:j], cps[j+1:]...)
i-- i--
break break
} }
// as a special case, if there are adjacent TLS conn policies that are identical except
// by their matchers, and the matchers are specifically just ServerName ("sni") matchers
// (by far the most common), we can combine them into a single policy
if i == j-1 && len(cps[i].MatchersRaw) == 1 && len(cps[j].MatchersRaw) == 1 {
if iSNIMatcherJSON, ok := cps[i].MatchersRaw["sni"]; ok {
if jSNIMatcherJSON, ok := cps[j].MatchersRaw["sni"]; ok {
// position of policies and the matcher criteria check out; if settings are
// the same, then we can combine the policies; we have to unmarshal and
// remarshal the matchers though
if cps[i].SettingsEqual(*cps[j]) {
var iSNIMatcher caddytls.MatchServerName
if err := json.Unmarshal(iSNIMatcherJSON, &iSNIMatcher); err == nil {
var jSNIMatcher caddytls.MatchServerName
if err := json.Unmarshal(jSNIMatcherJSON, &jSNIMatcher); err == nil {
iSNIMatcher = append(iSNIMatcher, jSNIMatcher...)
cps[i].MatchersRaw["sni"], err = json.Marshal(iSNIMatcher)
if err != nil {
return nil, fmt.Errorf("recombining SNI matchers: %v", err)
}
cps = slices.Delete(cps, j, j+1)
i--
break
}
}
}
}
}
}
// if they have the same matcher, try to reconcile each field: either they must // if they have the same matcher, try to reconcile each field: either they must
// be identical, or we have to be able to combine them safely // be identical, or we have to be able to combine them safely
if reflect.DeepEqual(cps[i].MatchersRaw, cps[j].MatchersRaw) { if reflect.DeepEqual(cps[i].MatchersRaw, cps[j].MatchersRaw) {
@@ -1128,12 +1008,6 @@ 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 {
@@ -1174,9 +1048,6 @@ 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
} }
@@ -1190,19 +1061,18 @@ 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 !slices.Contains(cps[i].CertSelection.AnyTag, tag) { if !sliceContains(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)
} }
} }
} }
cps = slices.Delete(cps, j, j+1) cps = append(cps[:j], cps[j+1:]...)
i-- i--
break break
} }
} }
} }
return cps, nil return cps, nil
} }
@@ -1274,7 +1144,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 !slices.Contains(directiveOrder, val.directive) { if !directiveIsOrdered(val.directive) {
return nil, fmt.Errorf("directive '%s' is not an ordered HTTP handler, so it cannot be used here - try placing within a route block or using the order global option", val.directive) 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)
} }
} }
@@ -1452,7 +1322,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.parsedKeys { for _, addr := range sblock.keys {
// 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
@@ -1484,16 +1354,25 @@ 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 != "" && !slices.Contains(chosenMatcherPair.hostm, addr.Host) { if 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.RequestMatcherWithError var matcherSets []map[string]caddyhttp.RequestMatcher
for _, mp := range matcherPairs { for _, mp := range matcherPairs {
matcherSet := make(map[string]caddyhttp.RequestMatcherWithError) matcherSet := make(map[string]caddyhttp.RequestMatcher)
if len(mp.hostm) > 0 { if len(mp.hostm) > 0 {
matcherSet["host"] = mp.hostm matcherSet["host"] = mp.hostm
} }
@@ -1552,18 +1431,13 @@ 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 rm, ok := unm.(caddyhttp.RequestMatcherWithError); ok { if !ok {
matchers[definitionName][matcherName] = caddyconfig.JSON(rm, 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) return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
} }
matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
return nil
}
// 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
@@ -1606,7 +1480,7 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M
return nil return nil
} }
func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcherWithError) (caddy.ModuleMap, error) { func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (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)
@@ -1666,6 +1540,16 @@ 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.
@@ -1686,18 +1570,6 @@ 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.
@@ -1741,18 +1613,11 @@ 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 with protocols, and a list of server // addresses to a list of server blocks that are
// blocks that are served on those addresses. // served on those addresses.
type sbAddrAssociation struct { type sbAddrAssociation struct {
addressesWithProtocols []addressWithProtocols addresses []string
serverBlocks []serverBlock serverBlocks []serverBlock
} }
+81 -185
View File
@@ -15,17 +15,14 @@
package httpcaddyfile package httpcaddyfile
import ( import (
"slices"
"strconv" "strconv"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/libdns/libdns" "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"
"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"
) )
@@ -33,20 +30,19 @@ 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", parseOptDefaultBind) RegisterGlobalOption("default_bind", parseOptStringList)
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_check", parseStorageCheck) RegisterGlobalOption("storage_clean_interval", parseOptDuration)
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", parseOptDNS) RegisterGlobalOption("acme_dns", parseOptACMEDNS)
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)
@@ -56,15 +52,12 @@ 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 }
@@ -117,12 +110,17 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
} }
pos := Positional(d.Val()) pos := Positional(d.Val())
// if directive already had an order, drop it newOrder := directiveOrder
newOrder := slices.DeleteFunc(directiveOrder, func(d string) bool {
return d == dirName
})
// act on the positional; if it's First or Last, we're done right away // if directive exists, first remove it
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...)
@@ -131,7 +129,6 @@ 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() {
@@ -139,11 +136,8 @@ 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)
} }
@@ -157,17 +151,17 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
return nil, d.ArgErr() return nil, d.ArgErr()
} }
// get the position of the target directive // insert directive into proper position
targetIndex := slices.Index(newOrder, otherDir) for i, d := range newOrder {
if targetIndex == -1 { if d == otherDir {
return nil, d.Errf("directive '%s' not found", otherDir) if pos == Before {
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
@@ -193,40 +187,6 @@ 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()
@@ -241,6 +201,25 @@ 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
@@ -305,32 +284,13 @@ func parseOptSingleString(d *caddyfile.Dispenser, _ any) (any, error) {
return val, nil return val, nil
} }
func parseOptDefaultBind(d *caddyfile.Dispenser, _ any) (any, error) { func parseOptStringList(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name d.Next() // consume option name
val := d.RemainingArgs()
var addresses, protocols []string if len(val) == 0 {
addresses = d.RemainingArgs() return "", d.ArgErr()
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) {
@@ -415,10 +375,36 @@ 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":
return nil, d.Errf("the on_demand_tls 'interval' option is no longer supported, remove it from your config") if !d.NextArg() {
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":
return nil, d.Errf("the on_demand_tls 'burst' option is no longer supported, remove it from your config") if !d.NextArg() {
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())
@@ -447,44 +433,19 @@ 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
val := d.RemainingArgs() if !d.Next() {
if len(val) == 0 {
return "", d.ArgErr() return "", d.ArgErr()
} }
for _, v := range val { val := d.Val()
switch v { if d.Next() {
case "off": return "", d.ArgErr()
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)
} }
@@ -554,68 +515,3 @@ 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
}
+22 -17
View File
@@ -17,7 +17,6 @@ package httpcaddyfile
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"slices"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
@@ -181,7 +180,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 slices.Contains(serverOpts.Protocols, proto) { if sliceContains(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)
@@ -230,7 +229,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 slices.Contains(serverOpts.ClientIPHeaders, header) { if sliceContains(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)
@@ -240,16 +239,13 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
} }
case "metrics": case "metrics":
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.") if d.NextArg() {
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() {
@@ -292,15 +288,24 @@ 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
optsIndex := slices.IndexFunc(serverOpts, func(s serverOptions) bool { opts := func() *serverOptions {
return s.ListenerAddress == "" || slices.Contains(server.Listen, s.ListenerAddress) for _, entry := range serverOpts {
}) 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 optsIndex == -1 { if opts == nil {
continue continue
} }
opts := serverOpts[optsIndex]
// set all the options // set all the options
server.ListenerWrappersRaw = opts.ListenerWrappersRaw server.ListenerWrappersRaw = opts.ListenerWrappersRaw
+3 -11
View File
@@ -52,27 +52,19 @@ 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}",
+37 -144
View File
@@ -19,13 +19,12 @@ 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/v3/acme" "github.com/mholt/acmez/v2/acme"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig"
@@ -45,8 +44,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 := []string{} autoHTTPS := "on"
if ah, ok := options["auto_https"].([]string); ok { if ah, ok := options["auto_https"].(string); ok {
autoHTTPS = ah autoHTTPS = ah
} }
@@ -54,17 +53,14 @@ 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 !slices.Contains(autoHTTPS, "off") { if autoHTTPS != "off" {
for _, pair := range pairings { for _, pair := range pairings {
for _, sb := range pair.serverBlocks { for _, sb := range pair.serverBlocks {
for _, addr := range sb.parsedKeys { for _, addr := range sb.keys {
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.parsedKeys { for _, otherAddr := range sb.keys {
if otherAddr.Original == addr.Original { if otherAddr.Original == addr.Original {
continue continue
} }
@@ -77,6 +73,7 @@ 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
@@ -92,33 +89,9 @@ func (st ServerType) buildTLSApp(
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP) tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
} }
var wildcardHosts []string // collect all hosts that have a wildcard in them, and aren't HTTP
forcedAutomatedNames := make(map[string]struct{}) // explicitly configured to be automated, even if covered by a wildcard
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
var addresses []string if !listenersUseAnyPortOtherThan(p.addresses, httpPort) {
for _, addressWithProtocols := range p.addressesWithProtocols {
addresses = append(addresses, addressWithProtocols.address)
}
if !listenersUseAnyPortOtherThan(addresses, httpPort) {
continue continue
} }
@@ -135,12 +108,6 @@ 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
@@ -151,13 +118,6 @@ func (st ServerType) buildTLSApp(
ap.OnDemand = true ap.OnDemand = true
} }
// collect hosts that are forced to have certs automated for their specific name
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
@@ -221,8 +181,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 asserted, ok := cfgVal.Value.(addressesWithProtocols); ok && len(asserted.addresses) > 0 { if bindHosts, ok := cfgVal.Value.([]string); ok && len(bindHosts) > 0 {
bindHost = asserted.addresses[0] bindHost = bindHosts[0]
} }
acmeIssuer.Challenges.BindHost = bindHost acmeIssuer.Challenges.BindHost = bindHost
} }
@@ -250,21 +210,9 @@ 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 = hostsNotHTTP ap.SubjectsRaw = sblock.hostsFromKeysNotHTTP(httpPort)
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
@@ -303,7 +251,6 @@ 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)
} }
@@ -338,7 +285,7 @@ func (st ServerType) buildTLSApp(
combined = reflect.New(reflect.TypeOf(cl)).Elem() combined = reflect.New(reflect.TypeOf(cl)).Elem()
} }
clVal := reflect.ValueOf(cl) clVal := reflect.ValueOf(cl)
for i := range clVal.Len() { for i := 0; i < clVal.Len(); i++ {
combined = reflect.Append(combined, clVal.Index(i)) combined = reflect.Append(combined, clVal.Index(i))
} }
loadersByName[name] = combined.Interface().(caddytls.CertificateLoader) loadersByName[name] = combined.Interface().(caddytls.CertificateLoader)
@@ -357,42 +304,6 @@ 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 {
if cfg.PublicName != "" {
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 {
@@ -433,7 +344,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 !slices.Contains(autoHTTPS, "off") && !slices.Contains(autoHTTPS, "disable_certs") { if autoHTTPS != "off" && 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) {
@@ -441,13 +352,6 @@ 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)
} }
@@ -469,7 +373,7 @@ func (st ServerType) buildTLSApp(
globalPreferredChains := options["preferred_chains"] globalPreferredChains := options["preferred_chains"]
hasGlobalACMEDefaults := globalEmail != nil || globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS != nil || globalACMEEAB != nil || globalPreferredChains != nil hasGlobalACMEDefaults := globalEmail != nil || globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS != nil || globalACMEEAB != nil || globalPreferredChains != nil
if hasGlobalACMEDefaults { if hasGlobalACMEDefaults {
for i := range tlsApp.Automation.Policies { for i := 0; i < len(tlsApp.Automation.Policies); i++ {
ap := tlsApp.Automation.Policies[i] ap := tlsApp.Automation.Policies[i]
if len(ap.Issuers) == 0 && automationPolicyHasAllPublicNames(ap) { if len(ap.Issuers) == 0 && automationPolicyHasAllPublicNames(ap) {
// for public names, create default issuers which will later be filled in with configured global defaults // for public names, create default issuers which will later be filled in with configured global defaults
@@ -561,7 +465,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 && !slices.Contains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) { if globalACMECARoot != nil && !sliceContains(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) {
@@ -577,8 +481,7 @@ 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)
} }
// 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.HTTP == nil || acmeIssuer.Challenges.HTTP.AlternatePort == 0) {
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)
} }
@@ -587,7 +490,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.DNS == nil) && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.TLSALPN == nil || acmeIssuer.Challenges.TLSALPN.AlternatePort == 0) { if globalHTTPSPort != 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)
} }
@@ -677,7 +580,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 = slices.Delete(aps, i, i+1) aps = append(aps[:i], aps[i+1:]...)
i-- i--
} }
} }
@@ -694,7 +597,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 = slices.Delete(aps, j, j+1) aps = append(aps[:j], aps[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--
@@ -724,18 +627,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 = slices.Delete(aps, i, i+1) aps = append(aps[:i], aps[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 !slices.Contains(aps[i].SubjectsRaw, subj) { if !sliceContains(aps[i].SubjectsRaw, subj) {
aps[i].SubjectsRaw = append(aps[i].SubjectsRaw, subj) aps[i].SubjectsRaw = append(aps[i].SubjectsRaw, subj)
} }
} }
aps = slices.Delete(aps, j, j+1) aps = append(aps[:j], aps[j+1:]...)
j-- j--
} }
} }
@@ -755,9 +658,13 @@ func automationPolicyIsSubset(a, b *caddytls.AutomationPolicy) bool {
return false return false
} }
for _, aSubj := range a.SubjectsRaw { for _, aSubj := range a.SubjectsRaw {
inSuperset := slices.ContainsFunc(b.SubjectsRaw, func(bSubj string) bool { var inSuperset bool
return certmagic.MatchWildcard(aSubj, bSubj) for _, bSubj := range b.SubjectsRaw {
}) if certmagic.MatchWildcard(aSubj, bSubj) {
inSuperset = true
break
}
}
if !inSuperset { if !inSuperset {
return false return false
} }
@@ -802,28 +709,14 @@ 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 {
return !slices.ContainsFunc(ap.SubjectsRaw, func(i string) bool { for _, subj := range ap.SubjectsRaw {
return !subjectQualifiesForPublicCert(ap, i) || isTailscaleDomain(i) if !subjectQualifiesForPublicCert(ap, subj) || isTailscaleDomain(subj) {
}) 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
}
+1 -1
View File
@@ -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. If you don't have control // read just like the admin API's `/load` endpoint. Uf you don't have control
// over the HTTP server (but can still trust its response), you can override // 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 {
+140 -401
View File
@@ -1,40 +1,29 @@
package caddytest package caddytest
import ( import (
"bytes"
"context" "context"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"log" "log"
"net" "net"
"net/http" "net/http"
"net/http/cookiejar" "net/http/cookiejar"
"os" "os"
"path" "strconv"
"reflect"
"regexp"
"runtime"
"strings" "strings"
"testing" "sync/atomic"
"time" "time"
"github.com/aryann/difflib"
caddycmd "github.com/caddyserver/caddy/v2/cmd" caddycmd "github.com/caddyserver/caddy/v2/cmd"
"github.com/caddyserver/caddy/v2/caddyconfig"
// plug in Caddy modules here // plug in Caddy modules here
_ "github.com/caddyserver/caddy/v2/modules/standard" _ "github.com/caddyserver/caddy/v2/modules/standard"
) )
// Config store any configuration required to make the tests run // Defaults store any configuration required to make the tests run
type Config struct { type Defaults struct {
// Port we expect caddy to listening on
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
Certificates []string Certificates []string
// TestRequestTimeout is the time to wait for a http request to // TestRequestTimeout is the time to wait for a http request to
@@ -44,31 +33,32 @@ type Config struct {
} }
// Default testing values // Default testing values
var Default = Config{ var Default = Defaults{
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,
LoadRequestTimeout: 5 * time.Second, LoadRequestTimeout: 5 * time.Second,
} }
var (
matchKey = regexp.MustCompile(`(/[\w\d\.]+\.key)`)
matchCert = regexp.MustCompile(`(/[\w\d\.]+\.crt)`)
)
// Tester represents an instance of a test client. // Tester represents an instance of a test client.
type Tester struct { type Tester struct {
Client *http.Client Client *http.Client
adminPort int
portOne int
portTwo int
started atomic.Bool
configLoaded bool configLoaded bool
t testing.TB configFileName string
config Config envFileName string
} }
// NewTester will create a new testing client with an attached cookie jar // NewTester will create a new testing client with an attached cookie jar
func NewTester(t testing.TB) *Tester { func NewTester() (*Tester, error) {
jar, err := cookiejar.New(nil) jar, err := cookiejar.New(nil)
if err != nil { if err != nil {
t.Fatalf("failed to create cookiejar: %s", err) return nil, fmt.Errorf("failed to create cookiejar: %w", err)
} }
return &Tester{ return &Tester{
@@ -78,28 +68,7 @@ func NewTester(t testing.TB) *Tester {
Timeout: Default.TestRequestTimeout, Timeout: Default.TestRequestTimeout,
}, },
configLoaded: false, configLoaded: false,
t: t, }, nil
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 {
@@ -113,53 +82,73 @@ func timeElapsed(start time.Time, name string) {
log.Printf("%s took %s", name, elapsed) log.Printf("%s took %s", name, elapsed)
} }
// InitServer this will configure the server with a configurion of a specific // launch caddy will start the server
// type. The configType must be either "json" or the adapter type. func (tc *Tester) LaunchCaddy() error {
func (tc *Tester) InitServer(rawConfig string, configType string) { if !tc.started.CompareAndSwap(false, true) {
if err := tc.initServer(rawConfig, configType); err != nil { return fmt.Errorf("already launched caddy with this tester")
tc.t.Logf("failed to load config: %s", err)
tc.t.Fail()
} }
if err := tc.ensureConfigRunning(rawConfig, configType); err != nil { if err := tc.startServer(); err != nil {
tc.t.Logf("failed ensuring config is running: %s", err) return fmt.Errorf("failed to start server: %w", err)
tc.t.Fail()
} }
return nil
} }
// InitServer this will configure the server with a configurion of a specific func (tc *Tester) CleanupCaddy() error {
// type. The configType must be either "json" or the adapter type. // now shutdown the server, since the test is done.
func (tc *Tester) initServer(rawConfig string, configType string) error { defer func() {
if testing.Short() { // try to remove pthe tmp config file we created
tc.t.SkipNow() if tc.configFileName != "" {
os.Remove(tc.configFileName)
}
if tc.envFileName != "" {
os.Remove(tc.envFileName)
}
}()
resp, err := http.Post(fmt.Sprintf("http://localhost:%d/stop", tc.adminPort), "", nil)
if err != nil {
return fmt.Errorf("couldn't stop caddytest server: %w", err)
}
resp.Body.Close()
for retries := 0; retries < 10; retries++ {
if tc.isCaddyAdminRunning() != nil {
return nil return nil
} }
time.Sleep(100 * time.Millisecond)
err := validateTestPrerequisites(tc)
if err != nil {
tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err)
return nil
} }
tc.t.Cleanup(func() { return fmt.Errorf("timed out waiting for caddytest server to stop")
if tc.t.Failed() && tc.configLoaded { }
res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort))
if err != nil {
tc.t.Log("unable to read the current config")
return
}
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
var out bytes.Buffer func (tc *Tester) AdminPort() int {
_ = json.Indent(&out, body, "", " ") return tc.adminPort
tc.t.Logf("----------- failed with config -----------\n%s", out.String()) }
}
})
rawConfig = prependCaddyFilePath(rawConfig) func (tc *Tester) PortOne() int {
return tc.portOne
}
func (tc *Tester) PortTwo() int {
return tc.portTwo
}
func (tc *Tester) ReplaceTestingPlaceholders(x string) string {
x = strings.ReplaceAll(x, "{$TESTING_CADDY_ADMIN_BIND}", fmt.Sprintf("localhost:%d", tc.adminPort))
x = strings.ReplaceAll(x, "{$TESTING_CADDY_ADMIN_PORT}", fmt.Sprintf("%d", tc.adminPort))
x = strings.ReplaceAll(x, "{$TESTING_CADDY_PORT_ONE}", fmt.Sprintf("%d", tc.portOne))
x = strings.ReplaceAll(x, "{$TESTING_CADDY_PORT_TWO}", fmt.Sprintf("%d", tc.portTwo))
return x
}
// LoadConfig loads the config to the tester server and also ensures that the config was loaded
// it should not be run
func (tc *Tester) LoadConfig(rawConfig string, configType string) error {
if tc.adminPort == 0 {
return fmt.Errorf("load config called where startServer didnt succeed")
}
rawConfig = tc.ReplaceTestingPlaceholders(rawConfig)
// replace special testing placeholders so we can have our admin api be on a random port
// normalize JSON config // normalize JSON config
if configType == "json" { if configType == "json" {
tc.t.Logf("Before: %s", rawConfig)
var conf any var conf any
if err := json.Unmarshal([]byte(rawConfig), &conf); err != nil { if err := json.Unmarshal([]byte(rawConfig), &conf); err != nil {
return err return err
@@ -169,16 +158,14 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
return err return err
} }
rawConfig = string(c) rawConfig = string(c)
tc.t.Logf("After: %s", rawConfig)
} }
client := &http.Client{ client := &http.Client{
Timeout: tc.config.LoadRequestTimeout, Timeout: Default.LoadRequestTimeout,
} }
start := time.Now() start := time.Now()
req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", tc.config.AdminPort), strings.NewReader(rawConfig)) req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", tc.adminPort), strings.NewReader(rawConfig))
if err != nil { if err != nil {
tc.t.Errorf("failed to create request. %s", err) return fmt.Errorf("failed to create request. %w", err)
return err
} }
if configType == "json" { if configType == "json" {
@@ -189,16 +176,14 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
res, err := client.Do(req) res, err := client.Do(req)
if err != nil { if err != nil {
tc.t.Errorf("unable to contact caddy server. %s", err) return fmt.Errorf("unable to contact caddy server. %w", err)
return err
} }
timeElapsed(start, "caddytest: config load time") timeElapsed(start, "caddytest: config load time")
defer res.Body.Close() defer res.Body.Close()
body, err := io.ReadAll(res.Body) body, err := io.ReadAll(res.Body)
if err != nil { if err != nil {
tc.t.Errorf("unable to read response. %s", err) return fmt.Errorf("unable to read response. %w", err)
return err
} }
if res.StatusCode != 200 { if res.StatusCode != 200 {
@@ -206,133 +191,115 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
} }
tc.configLoaded = true tc.configLoaded = true
// if the config is not loaded at this point, it is a bug in caddy's config.Load
// the contract for config.Load states that the config must be loaded before it returns, and that it will
// error if the config fails to apply
return nil return nil
} }
func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error { func (tc *Tester) GetCurrentConfig(receiver any) error {
expectedBytes := []byte(prependCaddyFilePath(rawConfig)) client := &http.Client{
if configType != "json" { Timeout: Default.LoadRequestTimeout,
adapter := caddyconfig.GetAdapter(configType)
if adapter == nil {
return fmt.Errorf("adapter of config type is missing: %s", configType)
}
expectedBytes, _, _ = adapter.Adapt([]byte(rawConfig), nil)
} }
var expected any resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", tc.adminPort))
err := json.Unmarshal(expectedBytes, &expected)
if err != nil { if err != nil {
return err return err
} }
client := &http.Client{
Timeout: tc.config.LoadRequestTimeout,
}
fetchConfig := func(client *http.Client) any {
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort))
if err != nil {
return nil
}
defer resp.Body.Close() defer resp.Body.Close()
actualBytes, err := io.ReadAll(resp.Body) actualBytes, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil return err
} }
var actual any err = json.Unmarshal(actualBytes, receiver)
err = json.Unmarshal(actualBytes, &actual)
if err != nil { if err != nil {
return err
}
return nil return nil
}
return actual
}
for retries := 10; retries > 0; retries-- {
if reflect.DeepEqual(expected, fetchConfig(client)) {
return nil
}
time.Sleep(1 * time.Second)
}
tc.t.Errorf("POSTed configuration isn't active")
return errors.New("EnsureConfigRunning: POSTed configuration isn't active")
} }
const initConfig = `{ func getFreePort() (int, error) {
admin localhost:%d lr, err := net.Listen("tcp", "localhost:0")
if err != nil {
return 0, err
}
port := strings.Split(lr.Addr().String(), ":")
if len(port) < 2 {
return 0, fmt.Errorf("no port available")
}
i, err := strconv.Atoi(port[1])
if err != nil {
return 0, err
}
err = lr.Close()
if err != nil {
return 0, fmt.Errorf("failed to close listener: %w", err)
}
return i, nil
} }
`
// validateTestPrerequisites ensures the certificates are available in the // launches caddy, and then ensures the Caddy sub-process is running.
// designated path and Caddy sub-process is running. func (tc *Tester) startServer() error {
func validateTestPrerequisites(tc *Tester) error { if tc.isCaddyAdminRunning() == nil {
// check certificates are found return fmt.Errorf("caddy test admin port still in use")
for _, certName := range tc.config.Certificates {
if _, err := os.Stat(getIntegrationDir() + certName); errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("caddy integration test certificates (%s) not found", certName)
} }
a, err := getFreePort()
if err != nil {
return fmt.Errorf("could not find a open port to listen on: %w", err)
}
tc.adminPort = a
tc.portOne, err = getFreePort()
if err != nil {
return fmt.Errorf("could not find a open portOne: %w", err)
}
tc.portTwo, err = getFreePort()
if err != nil {
return fmt.Errorf("could not find a open portOne: %w", err)
} }
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
} }
tc.t.Cleanup(func() { tc.configFileName = f.Name()
os.Remove(f.Name())
}) initConfig := fmt.Sprintf(`{
if _, err := f.WriteString(fmt.Sprintf(initConfig, tc.config.AdminPort)); err != nil { admin localhost:%d
}`, a)
if _, err := f.WriteString(initConfig); err != nil {
return err return err
} }
}
// start inprocess caddy server // start inprocess caddy server
os.Args = []string{"caddy", "run", "--config", f.Name(), "--adapter", "caddyfile"}
go func() { go func() {
caddycmd.Main() _ = caddycmd.MainForTesting("run", "--config", tc.configFileName, "--adapter", "caddyfile")
}() }()
// wait for caddy admin api to start. it should happen quickly.
// wait for caddy to start serving the initial config for retries := 10; retries > 0 && tc.isCaddyAdminRunning() != nil; retries-- {
for retries := 10; retries > 0 && isCaddyAdminRunning(tc) != nil; retries-- { time.Sleep(100 * time.Millisecond)
time.Sleep(1 * time.Second)
}
} }
// one more time to return the error // one more time to return the error
return isCaddyAdminRunning(tc) return tc.isCaddyAdminRunning()
} }
func isCaddyAdminRunning(tc *Tester) error { func (tc *Tester) isCaddyAdminRunning() error {
// assert that caddy is running // assert that caddy is running
client := &http.Client{ client := &http.Client{
Timeout: tc.config.LoadRequestTimeout, Timeout: Default.LoadRequestTimeout,
} }
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort)) resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", tc.adminPort))
if err != nil { if err != nil {
return fmt.Errorf("caddy integration test caddy server not running. Expected to be listening on localhost:%d", tc.config.AdminPort) return fmt.Errorf("caddy integration test caddy server not running. Expected to be listening on localhost:%d", tc.adminPort)
} }
resp.Body.Close() resp.Body.Close()
return nil return nil
} }
func getIntegrationDir() string {
_, filename, _, ok := runtime.Caller(1)
if !ok {
panic("unable to determine the current file path")
}
return path.Dir(filename)
}
// use the convention to replace /[certificatename].[crt|key] with the full path
// this helps reduce the noise in test configurations and also allow this
// to run in any path
func prependCaddyFilePath(rawConfig string) string {
r := matchKey.ReplaceAllString(rawConfig, getIntegrationDir()+"$1")
r = matchCert.ReplaceAllString(r, getIntegrationDir()+"$1")
return r
}
// CreateTestingTransport creates a testing transport that forces call dialing connections to happen locally // CreateTestingTransport creates a testing transport that forces call dialing connections to happen locally
func CreateTestingTransport() *http.Transport { func CreateTestingTransport() *http.Transport {
dialer := net.Dialer{ dialer := net.Dialer{
@@ -359,231 +326,3 @@ func CreateTestingTransport() *http.Transport {
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec
} }
} }
// AssertLoadError will load a config and expect an error
func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) {
tc := NewTester(t)
err := tc.initServer(rawConfig, configType)
if !strings.Contains(err.Error(), expectedError) {
t.Errorf("expected error \"%s\" but got \"%s\"", expectedError, err.Error())
}
}
// AssertRedirect makes a request and asserts the redirection happens
func (tc *Tester) AssertRedirect(requestURI string, expectedToLocation string, expectedStatusCode int) *http.Response {
redirectPolicyFunc := func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
// using the existing client, we override the check redirect policy for this test
old := tc.Client.CheckRedirect
tc.Client.CheckRedirect = redirectPolicyFunc
defer func() { tc.Client.CheckRedirect = old }()
resp, err := tc.Client.Get(requestURI)
if err != nil {
tc.t.Errorf("failed to call server %s", err)
return nil
}
if expectedStatusCode != resp.StatusCode {
tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", requestURI, expectedStatusCode, resp.StatusCode)
}
loc, err := resp.Location()
if err != nil {
tc.t.Errorf("requesting \"%s\" expected location: \"%s\" but got error: %s", requestURI, expectedToLocation, err)
}
if loc == nil && expectedToLocation != "" {
tc.t.Errorf("requesting \"%s\" expected a Location header, but didn't get one", requestURI)
}
if loc != nil {
if expectedToLocation != loc.String() {
tc.t.Errorf("requesting \"%s\" expected location: \"%s\" but got \"%s\"", requestURI, expectedToLocation, loc.String())
}
}
return resp
}
// CompareAdapt adapts a config and then compares it against an expected result
func CompareAdapt(t testing.TB, filename, rawConfig string, adapterName string, expectedResponse string) bool {
cfgAdapter := caddyconfig.GetAdapter(adapterName)
if cfgAdapter == nil {
t.Logf("unrecognized config adapter '%s'", adapterName)
return false
}
options := make(map[string]any)
result, warnings, err := cfgAdapter.Adapt([]byte(rawConfig), options)
if err != nil {
t.Logf("adapting config using %s adapter: %v", adapterName, err)
return false
}
// prettify results to keep tests human-manageable
var prettyBuf bytes.Buffer
err = json.Indent(&prettyBuf, result, "", "\t")
if err != nil {
return false
}
result = prettyBuf.Bytes()
if len(warnings) > 0 {
for _, w := range warnings {
t.Logf("warning: %s:%d: %s: %s", filename, w.Line, w.Directive, w.Message)
}
}
diff := difflib.Diff(
strings.Split(expectedResponse, "\n"),
strings.Split(string(result), "\n"))
// scan for failure
failed := false
for _, d := range diff {
if d.Delta != difflib.Common {
failed = true
break
}
}
if failed {
for _, d := range diff {
switch d.Delta {
case difflib.Common:
fmt.Printf(" %s\n", d.Payload)
case difflib.LeftOnly:
fmt.Printf(" - %s\n", d.Payload)
case difflib.RightOnly:
fmt.Printf(" + %s\n", d.Payload)
}
}
return false
}
return true
}
// AssertAdapt adapts a config and then tests it against an expected result
func AssertAdapt(t testing.TB, rawConfig string, adapterName string, expectedResponse string) {
ok := CompareAdapt(t, "Caddyfile", rawConfig, adapterName, expectedResponse)
if !ok {
t.Fail()
}
}
// Generic request functions
func applyHeaders(t testing.TB, req *http.Request, requestHeaders []string) {
requestContentType := ""
for _, requestHeader := range requestHeaders {
arr := strings.SplitAfterN(requestHeader, ":", 2)
k := strings.TrimRight(arr[0], ":")
v := strings.TrimSpace(arr[1])
if k == "Content-Type" {
requestContentType = v
}
t.Logf("Request header: %s => %s", k, v)
req.Header.Set(k, v)
}
if requestContentType == "" {
t.Logf("Content-Type header not provided")
}
}
// AssertResponseCode will execute the request and verify the status code, returns a response for additional assertions
func (tc *Tester) AssertResponseCode(req *http.Request, expectedStatusCode int) *http.Response {
resp, err := tc.Client.Do(req)
if err != nil {
tc.t.Fatalf("failed to call server %s", err)
}
if expectedStatusCode != resp.StatusCode {
tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", req.URL.RequestURI(), expectedStatusCode, resp.StatusCode)
}
return resp
}
// AssertResponse request a URI and assert the status code and the body contains a string
func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expectedBody string) (*http.Response, string) {
resp := tc.AssertResponseCode(req, expectedStatusCode)
defer resp.Body.Close()
bytes, err := io.ReadAll(resp.Body)
if err != nil {
tc.t.Fatalf("unable to read the response body %s", err)
}
body := string(bytes)
if body != expectedBody {
tc.t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
}
return resp, body
}
// Verb specific test functions
// AssertGetResponse GET a URI and expect a statusCode and body text
func (tc *Tester) AssertGetResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
req, err := http.NewRequest("GET", requestURI, nil)
if err != nil {
tc.t.Fatalf("unable to create request %s", err)
}
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
// AssertDeleteResponse request a URI and expect a statusCode and body text
func (tc *Tester) AssertDeleteResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
req, err := http.NewRequest("DELETE", requestURI, nil)
if err != nil {
tc.t.Fatalf("unable to create request %s", err)
}
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
// AssertPostResponseBody POST to a URI and assert the response code and body
func (tc *Tester) AssertPostResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
req, err := http.NewRequest("POST", requestURI, requestBody)
if err != nil {
tc.t.Errorf("failed to create request %s", err)
return nil, ""
}
applyHeaders(tc.t, req, requestHeaders)
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
// AssertPutResponseBody PUT to a URI and assert the response code and body
func (tc *Tester) AssertPutResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
req, err := http.NewRequest("PUT", requestURI, requestBody)
if err != nil {
tc.t.Errorf("failed to create request %s", err)
return nil, ""
}
applyHeaders(tc.t, req, requestHeaders)
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
// AssertPatchResponseBody PATCH to a URI and assert the response code and body
func (tc *Tester) AssertPatchResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
req, err := http.NewRequest("PATCH", requestURI, requestBody)
if err != nil {
tc.t.Errorf("failed to create request %s", err)
return nil, ""
}
applyHeaders(tc.t, req, requestHeaders)
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
+116
View File
@@ -0,0 +1,116 @@
package caddytest
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"testing"
"github.com/aryann/difflib"
"github.com/stretchr/testify/require"
"github.com/caddyserver/caddy/v2/caddyconfig"
)
// AssertLoadError will load a config and expect an error
func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) {
tc, err := NewTester()
require.NoError(t, err)
err = tc.LaunchCaddy()
require.NoError(t, err)
err = tc.LoadConfig(rawConfig, configType)
if !strings.Contains(err.Error(), expectedError) {
t.Errorf("expected error \"%s\" but got \"%s\"", expectedError, err.Error())
}
_ = tc.CleanupCaddy()
}
// CompareAdapt adapts a config and then compares it against an expected result
func CompareAdapt(t testing.TB, filename, rawConfig string, adapterName string, expectedResponse string) bool {
cfgAdapter := caddyconfig.GetAdapter(adapterName)
if cfgAdapter == nil {
t.Logf("unrecognized config adapter '%s'", adapterName)
return false
}
options := make(map[string]any)
result, warnings, err := cfgAdapter.Adapt([]byte(rawConfig), options)
if err != nil {
t.Logf("adapting config using %s adapter: %v", adapterName, err)
return false
}
// prettify results to keep tests human-manageable
var prettyBuf bytes.Buffer
err = json.Indent(&prettyBuf, result, "", "\t")
if err != nil {
return false
}
result = prettyBuf.Bytes()
if len(warnings) > 0 {
for _, w := range warnings {
t.Logf("warning: %s:%d: %s: %s", filename, w.Line, w.Directive, w.Message)
}
}
diff := difflib.Diff(
strings.Split(expectedResponse, "\n"),
strings.Split(string(result), "\n"))
// scan for failure
failed := false
for _, d := range diff {
if d.Delta != difflib.Common {
failed = true
break
}
}
if failed {
for _, d := range diff {
switch d.Delta {
case difflib.Common:
fmt.Printf(" %s\n", d.Payload)
case difflib.LeftOnly:
fmt.Printf(" - %s\n", d.Payload)
case difflib.RightOnly:
fmt.Printf(" + %s\n", d.Payload)
}
}
return false
}
return true
}
// AssertAdapt adapts a config and then tests it against an expected result
func AssertAdapt(t testing.TB, rawConfig string, adapterName string, expectedResponse string) {
ok := CompareAdapt(t, "Caddyfile", rawConfig, adapterName, expectedResponse)
if !ok {
t.Fail()
}
}
// Generic request functions
func applyHeaders(t testing.TB, req *http.Request, requestHeaders []string) {
requestContentType := ""
for _, requestHeader := range requestHeaders {
arr := strings.SplitAfterN(requestHeader, ":", 2)
k := strings.TrimRight(arr[0], ":")
v := strings.TrimSpace(arr[1])
if k == "Content-Type" {
requestContentType = v
}
t.Logf("Request header: %s => %s", k, v)
req.Header.Set(k, v)
}
if requestContentType == "" {
t.Logf("Content-Type header not provided")
}
}
+11 -9
View File
@@ -1,13 +1,14 @@
package caddytest package caddytest
import ( import (
"fmt"
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
) )
func TestReplaceCertificatePaths(t *testing.T) { func TestReplaceCertificatePaths(t *testing.T) {
rawConfig := `a.caddy.localhost:9443 { rawConfig := `a.caddy.localhost:9443{
tls /caddy.localhost.crt /caddy.localhost.key { tls /caddy.localhost.crt /caddy.localhost.key {
} }
@@ -34,8 +35,8 @@ func TestReplaceCertificatePaths(t *testing.T) {
} }
func TestLoadUnorderedJSON(t *testing.T) { func TestLoadUnorderedJSON(t *testing.T) {
tester := NewTester(t) harness := StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"logging": { "logging": {
"logs": { "logs": {
@@ -68,7 +69,7 @@ func TestLoadUnorderedJSON(t *testing.T) {
} }
}, },
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"pki": { "pki": {
@@ -79,12 +80,13 @@ func TestLoadUnorderedJSON(t *testing.T) {
} }
}, },
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"servers": { "servers": {
"s_server": { "s_server": {
"listen": [ "listen": [
":9080" ":{$TESTING_CADDY_PORT_ONE}",
":{$TESTING_CADDY_PORT_TWO}"
], ],
"routes": [ "routes": [
{ {
@@ -119,10 +121,10 @@ func TestLoadUnorderedJSON(t *testing.T) {
} }
} }
`, "json") `, "json")
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080/", nil) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne()), nil)
if err != nil { if err != nil {
t.Fail() t.Fail()
return return
} }
tester.AssertResponseCode(req, 200) harness.AssertResponseCode(req, 200)
} }
+19 -26
View File
@@ -6,7 +6,6 @@ import (
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"log/slog"
"net" "net"
"net/http" "net/http"
"strings" "strings"
@@ -14,11 +13,10 @@ 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/v3" "github.com/mholt/acmez/v2"
"github.com/mholt/acmez/v3/acme" "github.com/mholt/acmez/v2/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
@@ -26,19 +24,13 @@ const acmeChallengePort = 9081
// Test the basic functionality of Caddy's ACME server // Test the basic functionality of Caddy's ACME server
func TestACMEServerWithDefaults(t *testing.T) { func TestACMEServerWithDefaults(t *testing.T) {
ctx := context.Background() ctx := context.Background()
logger, err := zap.NewDevelopment() harness := caddytest.StartHarness(t)
if err != nil { harness.LoadConfig(`
t.Error(err)
return
}
tester := caddytest.NewTester(t)
tester.InitServer(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
local_certs local_certs
} }
acme.localhost { acme.localhost {
@@ -46,11 +38,12 @@ func TestACMEServerWithDefaults(t *testing.T) {
} }
`, "caddyfile") `, "caddyfile")
logger := caddy.Log().Named("acmeserver")
client := acmez.Client{ client := acmez.Client{
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: fmt.Sprintf("https://acme.localhost:%d/acme/local/directory", harness.Tester().PortTwo()),
HTTPClient: tester.Client, HTTPClient: harness.Client(),
Logger: slog.New(zapslog.NewHandler(logger.Core())), Logger: logger,
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
@@ -99,13 +92,13 @@ func TestACMEServerWithMismatchedChallenges(t *testing.T) {
ctx := context.Background() ctx := context.Background()
logger := caddy.Log().Named("acmez") logger := caddy.Log().Named("acmez")
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
local_certs local_certs
} }
acme.localhost { acme.localhost {
@@ -117,9 +110,9 @@ func TestACMEServerWithMismatchedChallenges(t *testing.T) {
client := acmez.Client{ client := acmez.Client{
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: fmt.Sprintf("https://acme.localhost:%d/acme/local/directory", harness.Tester().PortTwo()),
HTTPClient: tester.Client, HTTPClient: harness.Client(),
Logger: slog.New(zapslog.NewHandler(logger.Core())), Logger: logger,
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
+33 -42
View File
@@ -5,52 +5,51 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
"log/slog" "fmt"
"strings" "strings"
"testing" "testing"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddytest" "github.com/caddyserver/caddy/v2/caddytest"
"github.com/mholt/acmez/v3" "github.com/mholt/acmez/v2"
"github.com/mholt/acmez/v3/acme" "github.com/mholt/acmez/v2/acme"
"go.uber.org/zap"
"go.uber.org/zap/exp/zapslog"
) )
func TestACMEServerDirectory(t *testing.T) { func TestACMEServerDirectory(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
local_certs local_certs
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
pki { pki {
ca local { ca local {
name "Caddy Local Authority" name "Caddy Local Authority"
} }
} }
} }
acme.localhost:9443 { acme.localhost:{$TESTING_CADDY_PORT_TWO} {
acme_server acme_server
} }
`, "caddyfile") `, "caddyfile")
tester.AssertGetResponse( harness.AssertGetResponse(
"https://acme.localhost:9443/acme/local/directory", fmt.Sprintf("https://acme.localhost:%d/acme/local/directory", harness.Tester().PortTwo()),
200, 200,
`{"newNonce":"https://acme.localhost:9443/acme/local/new-nonce","newAccount":"https://acme.localhost:9443/acme/local/new-account","newOrder":"https://acme.localhost:9443/acme/local/new-order","revokeCert":"https://acme.localhost:9443/acme/local/revoke-cert","keyChange":"https://acme.localhost:9443/acme/local/key-change"} fmt.Sprintf(`{"newNonce":"https://acme.localhost:%[1]d/acme/local/new-nonce","newAccount":"https://acme.localhost:%[1]d/acme/local/new-account","newOrder":"https://acme.localhost:%[1]d/acme/local/new-order","revokeCert":"https://acme.localhost:%[1]d/acme/local/revoke-cert","keyChange":"https://acme.localhost:%[1]d/acme/local/key-change"}
`) `, harness.Tester().PortTwo()))
} }
func TestACMEServerAllowPolicy(t *testing.T) { func TestACMEServerAllowPolicy(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
local_certs local_certs
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
pki { pki {
ca local { ca local {
name "Caddy Local Authority" name "Caddy Local Authority"
@@ -68,17 +67,13 @@ func TestACMEServerAllowPolicy(t *testing.T) {
`, "caddyfile") `, "caddyfile")
ctx := context.Background() ctx := context.Background()
logger, err := zap.NewDevelopment() logger := caddy.Log().Named("acmez")
if err != nil {
t.Error(err)
return
}
client := acmez.Client{ client := acmez.Client{
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: fmt.Sprintf("https://acme.localhost:%d/acme/local/directory", harness.Tester().PortTwo()),
HTTPClient: tester.Client, HTTPClient: harness.Client(),
Logger: slog.New(zapslog.NewHandler(logger.Core())), Logger: logger,
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
@@ -133,14 +128,14 @@ func TestACMEServerAllowPolicy(t *testing.T) {
} }
func TestACMEServerDenyPolicy(t *testing.T) { func TestACMEServerDenyPolicy(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
local_certs local_certs
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
pki { pki {
ca local { ca local {
name "Caddy Local Authority" name "Caddy Local Authority"
@@ -157,17 +152,13 @@ func TestACMEServerDenyPolicy(t *testing.T) {
`, "caddyfile") `, "caddyfile")
ctx := context.Background() ctx := context.Background()
logger, err := zap.NewDevelopment() logger := caddy.Log().Named("acmez")
if err != nil {
t.Error(err)
return
}
client := acmez.Client{ client := acmez.Client{
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: fmt.Sprintf("https://acme.localhost:%d/acme/local/directory", harness.Tester().PortTwo()),
HTTPClient: tester.Client, HTTPClient: harness.Client(),
Logger: slog.New(zapslog.NewHandler(logger.Core())), Logger: logger,
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
@@ -199,7 +190,7 @@ func TestACMEServerDenyPolicy(t *testing.T) {
_, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"deny.localhost"}) _, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"deny.localhost"})
if err == nil { if err == nil {
t.Errorf("obtaining certificate for 'deny.localhost' domain") t.Errorf("obtaining certificate for 'deny.localhost' domain")
} else if err != nil && !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") { } else if !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") {
t.Logf("unexpected error: %v", err) t.Logf("unexpected error: %v", err)
} }
} }
+47 -46
View File
@@ -1,6 +1,7 @@
package integration package integration
import ( import (
"fmt"
"net/http" "net/http"
"testing" "testing"
@@ -8,69 +9,69 @@ import (
) )
func TestAutoHTTPtoHTTPSRedirectsImplicitPort(t *testing.T) { func TestAutoHTTPtoHTTPSRedirectsImplicitPort(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
skip_install_trust skip_install_trust
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
} }
localhost localhost
respond "Yahaha! You found me!" respond "Yahaha! You found me!"
`, "caddyfile") `, "caddyfile")
tester.AssertRedirect("http://localhost:9080/", "https://localhost/", http.StatusPermanentRedirect) harness.AssertRedirect(fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne()), "https://localhost/", http.StatusPermanentRedirect)
} }
func TestAutoHTTPtoHTTPSRedirectsExplicitPortSameAsHTTPSPort(t *testing.T) { func TestAutoHTTPtoHTTPSRedirectsExplicitPortSameAsHTTPSPort(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
} }
localhost:9443 localhost:{$TESTING_CADDY_PORT_TWO}
respond "Yahaha! You found me!" respond "Yahaha! You found me!"
`, "caddyfile") `, "caddyfile")
tester.AssertRedirect("http://localhost:9080/", "https://localhost/", http.StatusPermanentRedirect) harness.AssertRedirect(fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne()), "https://localhost/", http.StatusPermanentRedirect)
} }
func TestAutoHTTPtoHTTPSRedirectsExplicitPortDifferentFromHTTPSPort(t *testing.T) { func TestAutoHTTPtoHTTPSRedirectsExplicitPortDifferentFromHTTPSPort(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
} }
localhost:1234 localhost:1234
respond "Yahaha! You found me!" respond "Yahaha! You found me!"
`, "caddyfile") `, "caddyfile")
tester.AssertRedirect("http://localhost:9080/", "https://localhost:1234/", http.StatusPermanentRedirect) harness.AssertRedirect(fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne()), "https://localhost:1234/", http.StatusPermanentRedirect)
} }
func TestAutoHTTPRedirectsWithHTTPListenerFirstInAddresses(t *testing.T) { func TestAutoHTTPRedirectsWithHTTPListenerFirstInAddresses(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"servers": { "servers": {
"ingress_server": { "ingress_server": {
"listen": [ "listen": [
":9080", ":{$TESTING_CADDY_PORT_ONE}",
":9443" ":{$TESTING_CADDY_PORT_TWO}"
], ],
"routes": [ "routes": [
{ {
@@ -94,52 +95,52 @@ func TestAutoHTTPRedirectsWithHTTPListenerFirstInAddresses(t *testing.T) {
} }
} }
`, "json") `, "json")
tester.AssertRedirect("http://localhost:9080/", "https://localhost/", http.StatusPermanentRedirect) harness.AssertRedirect(fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne()), "https://localhost/", http.StatusPermanentRedirect)
} }
func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAll(t *testing.T) { func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAll(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
local_certs local_certs
} }
http://:9080 { http://:{$TESTING_CADDY_PORT_ONE} {
respond "Foo" respond "Foo"
} }
http://baz.localhost:9080 { http://baz.localhost:{$TESTING_CADDY_PORT_ONE} {
respond "Baz" respond "Baz"
} }
bar.localhost { bar.localhost {
respond "Bar" respond "Bar"
} }
`, "caddyfile") `, "caddyfile")
tester.AssertRedirect("http://bar.localhost:9080/", "https://bar.localhost/", http.StatusPermanentRedirect) harness.AssertRedirect(fmt.Sprintf("http://bar.localhost:%d/", harness.Tester().PortOne()), "https://bar.localhost/", http.StatusPermanentRedirect)
tester.AssertGetResponse("http://foo.localhost:9080/", 200, "Foo") harness.AssertGetResponse(fmt.Sprintf("http://foo.localhost:%d/", harness.Tester().PortOne()), 200, "Foo")
tester.AssertGetResponse("http://baz.localhost:9080/", 200, "Baz") harness.AssertGetResponse(fmt.Sprintf("http://baz.localhost:%d/", harness.Tester().PortOne()), 200, "Baz")
} }
func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAllWithNoExplicitHTTPSite(t *testing.T) { func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAllWithNoExplicitHTTPSite(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
local_certs local_certs
} }
http://:9080 { http://:{$TESTING_CADDY_PORT_ONE} {
respond "Foo" respond "Foo"
} }
bar.localhost { bar.localhost {
respond "Bar" respond "Bar"
} }
`, "caddyfile") `, "caddyfile")
tester.AssertRedirect("http://bar.localhost:9080/", "https://bar.localhost/", http.StatusPermanentRedirect) harness.AssertRedirect(fmt.Sprintf("http://bar.localhost:%d/", harness.Tester().PortOne()), "https://bar.localhost/", http.StatusPermanentRedirect)
tester.AssertGetResponse("http://foo.localhost:9080/", 200, "Foo") harness.AssertGetResponse(fmt.Sprintf("http://foo.localhost:%d/", harness.Tester().PortOne()), 200, "Foo")
tester.AssertGetResponse("http://baz.localhost:9080/", 200, "Foo") harness.AssertGetResponse(fmt.Sprintf("http://baz.localhost:%d/", harness.Tester().PortOne()), 200, "Foo")
} }
@@ -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": {
@@ -1,142 +0,0 @@
{
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,8 +21,6 @@ encode {
zstd zstd
gzip 5 gzip 5
} }
encode
---------- ----------
{ {
"apps": { "apps": {
@@ -78,17 +76,6 @@ encode
"zstd", "zstd",
"gzip" "gzip"
] ]
},
{
"encodings": {
"gzip": {},
"zstd": {}
},
"handler": "encode",
"prefer": [
"zstd",
"gzip"
]
} }
] ]
} }
@@ -1,36 +0,0 @@
: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,10 +3,6 @@
file_server { file_server {
precompressed zstd br gzip precompressed zstd br gzip
} }
file_server {
precompressed
}
---------- ----------
{ {
"apps": { "apps": {
@@ -34,22 +30,6 @@ file_server {
"br", "br",
"gzip" "gzip"
] ]
},
{
"handler": "file_server",
"hide": [
"./Caddyfile"
],
"precompressed": {
"br": {},
"gzip": {},
"zstd": {}
},
"precompressed_order": [
"br",
"zstd",
"gzip"
]
} }
] ]
} }
@@ -1,39 +0,0 @@
: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/authz/forward-auth uri /api/verify?rd=https://authelia.example.com
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
} }
@@ -39,13 +39,6 @@ app.example.com {
] ]
}, },
"routes": [ "routes": [
{
"handle": [
{
"handler": "vars"
}
]
},
{ {
"handle": [ "handle": [
{ {
@@ -54,104 +47,19 @@ 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}": [
""
]
}
}
]
}
] ]
} }
] ]
@@ -172,7 +80,7 @@ app.example.com {
}, },
"rewrite": { "rewrite": {
"method": "GET", "method": "GET",
"uri": "/api/authz/forward-auth" "uri": "/api/verify?rd=https://authelia.example.com"
}, },
"upstreams": [ "upstreams": [
{ {
@@ -28,13 +28,6 @@ forward_auth localhost:9000 {
] ]
}, },
"routes": [ "routes": [
{
"handle": [
{
"handler": "vars"
}
]
},
{ {
"handle": [ "handle": [
{ {
@@ -43,131 +36,22 @@ forward_auth localhost:9000 {
"set": { "set": {
"1": [ "1": [
"{http.reverse_proxy.header.A}" "{http.reverse_proxy.header.A}"
]
}
}
}
], ],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.A}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"B": [
"{http.reverse_proxy.header.B}"
]
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.B}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"3": [ "3": [
"{http.reverse_proxy.header.C}" "{http.reverse_proxy.header.C}"
]
}
}
}
], ],
"match": [ "5": [
{ "{http.reverse_proxy.header.E}"
"not": [ ],
{ "B": [
"vars": { "{http.reverse_proxy.header.B}"
"{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,8 +9,6 @@
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
@@ -19,6 +17,8 @@
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,12 +72,14 @@
"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,6 +17,8 @@
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
@@ -87,6 +89,10 @@
"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,6 +16,8 @@
} }
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,6 +74,10 @@
"permission": { "permission": {
"endpoint": "https://example.com", "endpoint": "https://example.com",
"module": "http" "module": "http"
},
"rate_limit": {
"interval": 30000000000,
"burst": 20
} }
} }
} }
@@ -1,23 +0,0 @@
{
log {
sampling {
interval 300
first 50
thereafter 40
}
}
}
----------
{
"logging": {
"logs": {
"default": {
"sampling": {
"interval": 300,
"first": 50,
"thereafter": 40
}
}
}
}
}
@@ -12,14 +12,10 @@
@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
@@ -46,11 +42,6 @@
{ {
"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"
@@ -145,11 +136,6 @@
"Foo", "Foo",
"Bar" "Bar"
] ]
},
"require": {
"status_code": [
200
]
} }
} }
}, },
@@ -6,7 +6,6 @@ example.com {
</html> </html>
EOF 200 EOF 200
} }
---------- ----------
{ {
"apps": { "apps": {
@@ -1,45 +0,0 @@
: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,7 +27,6 @@ vars {
ghi 2.3 ghi 2.3
jkl "mn op" jkl "mn op"
} }
---------- ----------
{ {
"apps": { "apps": {
@@ -1,39 +0,0 @@
{
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
}
}
}
}
@@ -1,37 +0,0 @@
{
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 {orig_path}/{orig_?query} 308 redir @canonicalPath {http.request.orig_uri.path}/ 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 {file_match.relative} rewrite @indexFiles {http.matchers.file.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.prefixed_query}" "{http.request.orig_uri.path}/"
] ]
}, },
"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.prefixed_query}" "{http.request.orig_uri.path}/"
] ]
}, },
"status_code": 308 "status_code": 308
@@ -58,7 +58,6 @@
"{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.prefixed_query}" "{http.request.orig_uri.path}/"
] ]
}, },
"status_code": 308 "status_code": 308
@@ -73,8 +73,7 @@ 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.prefixed_query}" "{http.request.orig_uri.path}/"
] ]
}, },
"status_code": 308 "status_code": 308
@@ -59,7 +59,6 @@ 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.prefixed_query}" "{http.request.orig_uri.path}/"
] ]
}, },
"status_code": 308 "status_code": 308
@@ -1,95 +0,0 @@
:8884
php_fastcgi localhost:9000 {
# some php_fastcgi-specific subdirectives
split .php .php5
env VAR1 value1
env VAR2 value2
root /var/www
try_files {path} 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"
}
]
}
]
}
]
}
}
}
}
}
@@ -1,40 +0,0 @@
: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"
}
]
}
]
}
]
}
}
}
}
}
@@ -1,40 +0,0 @@
: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"
}
]
}
]
}
]
}
}
}
}
}
@@ -1,41 +0,0 @@
:8884
reverse_proxy 127.0.0.1:65535 {
transport http {
forward_proxy_url http://localhost:8080
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":8884"
],
"routes": [
{
"handle": [
{
"handler": "reverse_proxy",
"transport": {
"network_proxy": {
"from": "url",
"url": "http://localhost:8080"
},
"protocol": "http"
},
"upstreams": [
{
"dial": "127.0.0.1:65535"
}
]
}
]
}
]
}
}
}
}
}
@@ -1,40 +0,0 @@
:8884
reverse_proxy 127.0.0.1:65535 {
transport http {
network_proxy none
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":8884"
],
"routes": [
{
"handle": [
{
"handler": "reverse_proxy",
"transport": {
"network_proxy": {
"from": "none"
},
"protocol": "http"
},
"upstreams": [
{
"dial": "127.0.0.1:65535"
}
]
}
]
}
]
}
}
}
}
}
@@ -1,41 +0,0 @@
:8884
reverse_proxy 127.0.0.1:65535 {
transport http {
network_proxy url http://localhost:8080
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":8884"
],
"routes": [
{
"handle": [
{
"handler": "reverse_proxy",
"transport": {
"network_proxy": {
"from": "url",
"url": "http://localhost:8080"
},
"protocol": "http"
},
"upstreams": [
{
"dial": "127.0.0.1:65535"
}
]
}
]
}
]
}
}
}
}
}
@@ -1,57 +0,0 @@
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
}
]
}
}
}
}
}
@@ -1,174 +0,0 @@
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",
"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"
]
}
]
}
}
}
}
@@ -1,102 +0,0 @@
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"
]
}
]
}
}
}
}
@@ -1,157 +0,0 @@
*.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"
}
]
}
]
}
}
}
}
+161 -141
View File
@@ -1,6 +1,7 @@
package integration package integration
import ( import (
"fmt"
"net/http" "net/http"
"net/url" "net/url"
"testing" "testing"
@@ -10,16 +11,16 @@ import (
func TestRespond(t *testing.T) { func TestRespond(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
respond /version 200 { respond /version 200 {
body "hello from localhost" body "hello from localhost"
} }
@@ -27,23 +28,23 @@ func TestRespond(t *testing.T) {
`, "caddyfile") `, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost") harness.AssertGetResponse(fmt.Sprintf("http://localhost:%d/version", harness.Tester().PortOne()), 200, "hello from localhost")
} }
func TestRedirect(t *testing.T) { func TestRedirect(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
redir / http://localhost:9080/hello 301 redir / http://localhost:{$TESTING_CADDY_PORT_ONE}/hello 301
respond /hello 200 { respond /hello 200 {
body "hello from localhost" body "hello from localhost"
@@ -51,21 +52,22 @@ func TestRedirect(t *testing.T) {
} }
`, "caddyfile") `, "caddyfile")
target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
// act and assert // act and assert
tester.AssertRedirect("http://localhost:9080/", "http://localhost:9080/hello", 301) harness.AssertRedirect(target, target+"hello", 301)
// follow redirect // follow redirect
tester.AssertGetResponse("http://localhost:9080/", 200, "hello from localhost") harness.AssertGetResponse(target, 200, "hello from localhost")
} }
func TestDuplicateHosts(t *testing.T) { func TestDuplicateHosts(t *testing.T) {
// act and assert // act and assert
caddytest.AssertLoadError(t, caddytest.AssertLoadError(t,
` `
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
} }
`, `,
"caddyfile", "caddyfile",
@@ -80,18 +82,18 @@ func TestReadCookie(t *testing.T) {
} }
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.Client.Jar.SetCookies(localhost, []*http.Cookie{&cookie}) harness.Client().Jar.SetCookies(localhost, []*http.Cookie{&cookie})
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
templates { templates {
root testdata root testdata
} }
@@ -102,21 +104,22 @@ func TestReadCookie(t *testing.T) {
`, "caddyfile") `, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/cookie.html", 200, "<h2>Cookie.ClientName caddytest</h2>") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"cookie.html", 200, "<h2>Cookie.ClientName caddytest</h2>")
} }
func TestReplIndex(t *testing.T) { func TestReplIndex(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
templates { templates {
root testdata root testdata
} }
@@ -128,7 +131,8 @@ func TestReplIndex(t *testing.T) {
`, "caddyfile") `, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/", 200, "") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target, 200, "")
} }
func TestInvalidPrefix(t *testing.T) { func TestInvalidPrefix(t *testing.T) {
@@ -481,31 +485,32 @@ func TestValidPrefix(t *testing.T) {
} }
func TestUriReplace(t *testing.T) { func TestUriReplace(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
uri replace "\}" %7D uri replace "\}" %7D
uri replace "\{" %7B uri replace "\{" %7B
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?test={%20content%20}", 200, "test=%7B%20content%20%7D") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?test={%20content%20}", 200, "test=%7B%20content%20%7D")
} }
func TestUriOps(t *testing.T) { func TestUriOps(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
uri query +foo bar uri query +foo bar
uri query -baz uri query -baz
uri query taz test uri query taz test
@@ -514,7 +519,8 @@ func TestUriOps(t *testing.T) {
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar0&baz=buz&taz=nottest&changethis=val", 200, "changed=val&foo=bar0&foo=bar&key%3Dvalue=example&taz=test") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?foo=bar0&baz=buz&taz=nottest&changethis=val", 200, "changed=val&foo=bar0&foo=bar&key%3Dvalue=example&taz=test")
} }
// Tests the `http.request.local.port` placeholder. // Tests the `http.request.local.port` placeholder.
@@ -523,167 +529,176 @@ func TestUriOps(t *testing.T) {
// refer to 127.0.0.1 or ::1. // refer to 127.0.0.1 or ::1.
// TODO: Test each http version separately (especially http/3) // TODO: Test each http version separately (especially http/3)
func TestHttpRequestLocalPortPlaceholder(t *testing.T) { func TestHttpRequestLocalPortPlaceholder(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
respond "{http.request.local.port}"`, "caddyfile") respond "{http.request.local.port}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/", 200, "9080") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target, 200, fmt.Sprintf("%d", harness.Tester().PortOne()))
} }
func TestSetThenAddQueryParams(t *testing.T) { func TestSetThenAddQueryParams(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
uri query foo bar uri query foo bar
uri query +foo baz uri query +foo baz
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint", 200, "foo=bar&foo=baz") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint", 200, "foo=bar&foo=baz")
} }
func TestSetThenDeleteParams(t *testing.T) { func TestSetThenDeleteParams(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
uri query bar foo{query.foo} uri query bar foo{query.foo}
uri query -foo uri query -foo
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=foobar") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?foo=bar", 200, "bar=foobar")
} }
func TestRenameAndOtherOps(t *testing.T) { func TestRenameAndOtherOps(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
uri query foo>bar uri query foo>bar
uri query bar taz uri query bar taz
uri query +bar baz uri query +bar baz
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=taz&bar=baz") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?foo=bar", 200, "bar=taz&bar=baz")
} }
func TestReplaceOps(t *testing.T) { func TestReplaceOps(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
uri query foo bar baz uri query foo bar baz
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=baz") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?foo=bar", 200, "foo=baz")
} }
func TestReplaceWithReplacementPlaceholder(t *testing.T) { func TestReplaceWithReplacementPlaceholder(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
uri query foo bar {query.placeholder} uri query foo bar {query.placeholder}
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?placeholder=baz&foo=bar", 200, "foo=baz&placeholder=baz") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?placeholder=baz&foo=bar", 200, "foo=baz&placeholder=baz")
} }
func TestReplaceWithKeyPlaceholder(t *testing.T) { func TestReplaceWithKeyPlaceholder(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
uri query {query.placeholder} bar baz uri query {query.placeholder} bar baz
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?placeholder=foo&foo=bar", 200, "foo=baz&placeholder=foo") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?placeholder=foo&foo=bar", 200, "foo=baz&placeholder=foo")
} }
func TestPartialReplacement(t *testing.T) { func TestPartialReplacement(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
uri query foo ar az uri query foo ar az
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=baz") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?foo=bar", 200, "foo=baz")
} }
func TestNonExistingSearch(t *testing.T) { func TestNonExistingSearch(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
uri query foo var baz uri query foo var baz
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=bar") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?foo=bar", 200, "foo=bar")
} }
func TestReplaceAllOps(t *testing.T) { func TestReplaceAllOps(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
uri query * bar baz uri query * bar baz
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar&baz=bar", 200, "baz=baz&foo=baz") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?foo=bar&baz=bar", 200, "baz=baz&foo=baz")
} }
func TestUriOpsBlock(t *testing.T) { func TestUriOpsBlock(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
uri query { uri query {
+foo bar +foo bar
-baz -baz
@@ -691,16 +706,17 @@ func TestUriOpsBlock(t *testing.T) {
} }
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar0&baz=buz&taz=nottest", 200, "foo=bar0&foo=bar&taz=test") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?foo=bar0&baz=buz&taz=nottest", 200, "foo=bar0&foo=bar&taz=test")
} }
func TestHandleErrorSimpleCodes(t *testing.T) { func TestHandleErrorSimpleCodes(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(`{ harness.LoadConfig(`{
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
root * /srv root * /srv
error /private* "Unauthorized" 410 error /private* "Unauthorized" 410
error /hidden* "Not found" 404 error /hidden* "Not found" 404
@@ -710,17 +726,18 @@ func TestHandleErrorSimpleCodes(t *testing.T) {
} }
}`, "caddyfile") }`, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/private", 410, "404 or 410 error") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
tester.AssertGetResponse("http://localhost:9080/hidden", 404, "404 or 410 error") harness.AssertGetResponse(target+"private", 410, "404 or 410 error")
harness.AssertGetResponse(target+"hidden", 404, "404 or 410 error")
} }
func TestHandleErrorRange(t *testing.T) { func TestHandleErrorRange(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(`{ harness.LoadConfig(`{
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
root * /srv root * /srv
error /private* "Unauthorized" 410 error /private* "Unauthorized" 410
error /hidden* "Not found" 404 error /hidden* "Not found" 404
@@ -730,17 +747,18 @@ func TestHandleErrorRange(t *testing.T) {
} }
}`, "caddyfile") }`, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/private", 410, "Error in the [400 .. 499] range") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
tester.AssertGetResponse("http://localhost:9080/hidden", 404, "Error in the [400 .. 499] range") harness.AssertGetResponse(target+"private", 410, "Error in the [400 .. 499] range")
harness.AssertGetResponse(target+"hidden", 404, "Error in the [400 .. 499] range")
} }
func TestHandleErrorSort(t *testing.T) { func TestHandleErrorSort(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(`{ harness.LoadConfig(`{
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
root * /srv root * /srv
error /private* "Unauthorized" 410 error /private* "Unauthorized" 410
error /hidden* "Not found" 404 error /hidden* "Not found" 404
@@ -754,17 +772,18 @@ func TestHandleErrorSort(t *testing.T) {
} }
}`, "caddyfile") }`, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/internalerr", 500, "Fallback route: code outside the [400..499] range") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
tester.AssertGetResponse("http://localhost:9080/hidden", 404, "Error in the [400 .. 499] range") harness.AssertGetResponse(target+"internalerr", 500, "Fallback route: code outside the [400..499] range")
harness.AssertGetResponse(target+"hidden", 404, "Error in the [400 .. 499] range")
} }
func TestHandleErrorRangeAndCodes(t *testing.T) { func TestHandleErrorRangeAndCodes(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(`{ harness.LoadConfig(`{
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
root * /srv root * /srv
error /private* "Unauthorized" 410 error /private* "Unauthorized" 410
error /threehundred* "Moved Permanently" 301 error /threehundred* "Moved Permanently" 301
@@ -778,9 +797,10 @@ func TestHandleErrorRangeAndCodes(t *testing.T) {
} }
}`, "caddyfile") }`, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/internalerr", 500, "Error code is equal to 500 or in the [300..399] range") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
tester.AssertGetResponse("http://localhost:9080/threehundred", 301, "Error code is equal to 500 or in the [300..399] range") harness.AssertGetResponse(target+"internalerr", 500, "Error code is equal to 500 or in the [300..399] range")
tester.AssertGetResponse("http://localhost:9080/private", 410, "Error in the [400 .. 499] range") harness.AssertGetResponse(target+"threehundred", 301, "Error code is equal to 500 or in the [300..399] range")
harness.AssertGetResponse(target+"private", 410, "Error in the [400 .. 499] range")
} }
func TestInvalidSiteAddressesAsDirectives(t *testing.T) { func TestInvalidSiteAddressesAsDirectives(t *testing.T) {
+15 -14
View File
@@ -2,6 +2,7 @@ package integration
import ( import (
"bytes" "bytes"
"fmt"
"net/http" "net/http"
"testing" "testing"
@@ -9,36 +10,36 @@ import (
) )
func TestBrowse(t *testing.T) { func TestBrowse(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
http://localhost:9080 { http://localhost:{$TESTING_CADDY_PORT_ONE} {
file_server browse file_server browse
} }
`, "caddyfile") `, "caddyfile")
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080/", nil) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne()), nil)
if err != nil { if err != nil {
t.Fail() t.Fail()
return return
} }
tester.AssertResponseCode(req, 200) harness.AssertResponseCode(req, 200)
} }
func TestRespondWithJSON(t *testing.T) { func TestRespondWithJSON(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
localhost { localhost {
@@ -46,7 +47,7 @@ func TestRespondWithJSON(t *testing.T) {
} }
`, "caddyfile") `, "caddyfile")
res, _ := tester.AssertPostResponseBody("https://localhost:9443/", res, _ := harness.AssertPostResponseBody(fmt.Sprintf("https://localhost:%d/", harness.Tester().PortTwo()),
nil, nil,
bytes.NewBufferString(`{ bytes.NewBufferString(`{
"greeting": "Hello, world!" "greeting": "Hello, world!"
+9 -14
View File
@@ -1,40 +1,35 @@
package integration package integration
import ( import (
"fmt"
"testing" "testing"
"github.com/caddyserver/caddy/v2/caddytest" "github.com/caddyserver/caddy/v2/caddytest"
) )
func TestIntercept(t *testing.T) { func TestIntercept(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(`{ harness.LoadConfig(`{
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
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")
r, _ := tester.AssertGetResponse("http://localhost:9080/intercept", 503, "I'm a combined coffee/tea pot that is temporarily out of coffee") harness.AssertGetResponse(fmt.Sprintf("http://localhost:%d/intercept", harness.Tester().PortOne()), 503, "I'm a combined coffee/tea pot that is temporarily out of coffee")
if r.Header.Get("intercepted") != "ok" { harness.AssertGetResponse(fmt.Sprintf("http://localhost:%d/no-intercept", harness.Tester().PortOne()), 200, "I'm not a teapot")
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")
} }
@@ -7,21 +7,21 @@ import (
) )
func TestLeafCertLoaders(t *testing.T) { func TestLeafCertLoaders(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"grace_period": 1, "grace_period": 1,
"servers": { "servers": {
"srv0": { "srv0": {
"listen": [ "listen": [
":9443" ":{$TESTING_CADDY_PORT_TWO}"
], ],
"routes": [ "routes": [
{ {
+13 -13
View File
@@ -12,7 +12,7 @@ import (
"github.com/caddyserver/caddy/v2/caddytest" "github.com/caddyserver/caddy/v2/caddytest"
) )
func setupListenerWrapperTest(t *testing.T, handlerFunc http.HandlerFunc) *caddytest.Tester { func setupListenerWrapperTest(t *testing.T, handlerFunc http.HandlerFunc) *caddytest.TestHarness {
l, err := net.Listen("tcp", "127.0.0.1:0") l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil { if err != nil {
t.Fatalf("failed to listen: %s", err) t.Fatalf("failed to listen: %s", err)
@@ -28,15 +28,15 @@ func setupListenerWrapperTest(t *testing.T, handlerFunc http.HandlerFunc) *caddy
_ = srv.Close() _ = srv.Close()
_ = l.Close() _ = l.Close()
}) })
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(fmt.Sprintf(` harness.LoadConfig(fmt.Sprintf(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
local_certs local_certs
servers :9443 { servers :{$TESTING_CADDY_PORT_TWO} {
listener_wrappers { listener_wrappers {
http_redirect http_redirect
tls tls
@@ -47,7 +47,7 @@ func setupListenerWrapperTest(t *testing.T, handlerFunc http.HandlerFunc) *caddy
reverse_proxy %s reverse_proxy %s
} }
`, l.Addr().String()), "caddyfile") `, l.Addr().String()), "caddyfile")
return tester return harness
} }
func TestHTTPRedirectWrapperWithLargeUpload(t *testing.T) { func TestHTTPRedirectWrapperWithLargeUpload(t *testing.T) {
@@ -56,7 +56,7 @@ func TestHTTPRedirectWrapperWithLargeUpload(t *testing.T) {
body := make([]byte, uploadSize) body := make([]byte, uploadSize)
rand.New(rand.NewSource(0)).Read(body) rand.New(rand.NewSource(0)).Read(body)
tester := setupListenerWrapperTest(t, func(writer http.ResponseWriter, request *http.Request) { harness := setupListenerWrapperTest(t, func(writer http.ResponseWriter, request *http.Request) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
_, err := buf.ReadFrom(request.Body) _, err := buf.ReadFrom(request.Body)
if err != nil { if err != nil {
@@ -69,7 +69,7 @@ func TestHTTPRedirectWrapperWithLargeUpload(t *testing.T) {
writer.WriteHeader(http.StatusNoContent) writer.WriteHeader(http.StatusNoContent)
}) })
resp, err := tester.Client.Post("https://localhost:9443", "application/octet-stream", bytes.NewReader(body)) resp, err := harness.Client().Post(fmt.Sprintf("https://localhost:%d", harness.Tester().PortTwo()), "application/octet-stream", bytes.NewReader(body))
if err != nil { if err != nil {
t.Fatalf("failed to post: %s", err) t.Fatalf("failed to post: %s", err)
} }
@@ -80,14 +80,14 @@ func TestHTTPRedirectWrapperWithLargeUpload(t *testing.T) {
} }
func TestLargeHttpRequest(t *testing.T) { func TestLargeHttpRequest(t *testing.T) {
tester := setupListenerWrapperTest(t, func(writer http.ResponseWriter, request *http.Request) { harness := setupListenerWrapperTest(t, func(writer http.ResponseWriter, request *http.Request) {
t.Fatal("not supposed to handle a request") t.Fatal("not supposed to handle a request")
}) })
// We never read the body in any way, set an extra long header instead. // We never read the body in any way, set an extra long header instead.
req, _ := http.NewRequest("POST", "http://localhost:9443", nil) req, _ := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d", harness.Tester().PortTwo()), nil)
req.Header.Set("Long-Header", strings.Repeat("X", 1024*1024)) req.Header.Set("Long-Header", strings.Repeat("X", 1024*1024))
_, err := tester.Client.Do(req) _, err := harness.Client().Do(req)
if err == nil { if err == nil {
t.Fatal("not supposed to succeed") t.Fatal("not supposed to succeed")
} }
+26 -25
View File
@@ -2,6 +2,7 @@ package integration
import ( import (
"bytes" "bytes"
"fmt"
"testing" "testing"
"github.com/caddyserver/caddy/v2/caddytest" "github.com/caddyserver/caddy/v2/caddytest"
@@ -9,16 +10,16 @@ import (
func TestMap(t *testing.T) { func TestMap(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(`{ harness.LoadConfig(`{
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
map {http.request.method} {dest-1} {dest-2} { map {http.request.method} {dest-1} {dest-2} {
default unknown1 unknown2 default unknown1 unknown2
@@ -33,21 +34,21 @@ func TestMap(t *testing.T) {
`, "caddyfile") `, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost GET-called unknown2") harness.AssertGetResponse(fmt.Sprintf("http://localhost:%d/version", harness.Tester().PortOne()), 200, "hello from localhost GET-called unknown2")
tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called foobar") harness.AssertPostResponseBody(fmt.Sprintf("http://localhost:%d/version", harness.Tester().PortOne()), []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called foobar")
} }
func TestMapRespondWithDefault(t *testing.T) { func TestMapRespondWithDefault(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(`{ harness.LoadConfig(`{
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
map {http.request.method} {dest-name} { map {http.request.method} {dest-name} {
default unknown default unknown
@@ -61,17 +62,17 @@ func TestMapRespondWithDefault(t *testing.T) {
`, "caddyfile") `, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called") harness.AssertGetResponse(fmt.Sprintf("http://localhost:%d/version", harness.Tester().PortOne()), 200, "hello from localhost get-called")
tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost unknown") harness.AssertPostResponseBody(fmt.Sprintf("http://localhost:%d/version", harness.Tester().PortOne()), []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost unknown")
} }
func TestMapAsJSON(t *testing.T) { func TestMapAsJSON(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"pki": { "pki": {
@@ -82,12 +83,12 @@ func TestMapAsJSON(t *testing.T) {
} }
}, },
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"servers": { "servers": {
"srv0": { "srv0": {
"listen": [ "listen": [
":9080" ":{$TESTING_CADDY_PORT_ONE}"
], ],
"routes": [ "routes": [
{ {
@@ -145,7 +146,7 @@ func TestMapAsJSON(t *testing.T) {
} }
} }
}`, "json") }`, "json")
target := fmt.Sprintf("http://localhost:%d/version", harness.Tester().PortOne())
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called") harness.AssertGetResponse(target, 200, "hello from localhost get-called")
tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called") harness.AssertPostResponseBody(target, []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called")
} }
-61
View File
@@ -1,61 +0,0 @@
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)
+42 -40
View File
@@ -14,11 +14,11 @@ import (
) )
func TestSRVReverseProxy(t *testing.T) { func TestSRVReverseProxy(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"pki": { "pki": {
@@ -87,11 +87,11 @@ func TestDialWithPlaceholderUnix(t *testing.T) {
}) })
runtime.Gosched() // Allow other goroutines to run runtime.Gosched() // Allow other goroutines to run
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"pki": { "pki": {
@@ -135,15 +135,15 @@ func TestDialWithPlaceholderUnix(t *testing.T) {
return return
} }
req.Header.Set("X-Caddy-Upstream-Dial", socketName) req.Header.Set("X-Caddy-Upstream-Dial", socketName)
tester.AssertResponse(req, 200, "Hello, World!") harness.AssertResponse(req, 200, "Hello, World!")
} }
func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) { func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"pki": { "pki": {
@@ -186,7 +186,7 @@ func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
}, },
"srv1": { "srv1": {
"listen": [ "listen": [
":9080" ":{$TESTING_CADDY_PORT_ONE}"
], ],
"routes": [ "routes": [
{ {
@@ -223,21 +223,21 @@ func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
} }
`, "json") `, "json")
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%d", harness.Tester().PortOne()), nil)
if err != nil { if err != nil {
t.Fail() t.Fail()
return return
} }
req.Header.Set("X-Caddy-Upstream-Dial", "localhost:18080") req.Header.Set("X-Caddy-Upstream-Dial", "localhost:18080")
tester.AssertResponse(req, 200, "Hello, World!") harness.AssertResponse(req, 200, "Hello, World!")
} }
func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) { func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"pki": { "pki": {
@@ -280,7 +280,7 @@ func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
}, },
"srv1": { "srv1": {
"listen": [ "listen": [
":9080" ":{$TESTING_CADDY_PORT_ONE}"
], ],
"routes": [ "routes": [
{ {
@@ -317,23 +317,23 @@ func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
} }
`, "json") `, "json")
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%d", harness.Tester().PortOne()), nil)
if err != nil { if err != nil {
t.Fail() t.Fail()
return return
} }
req.Header.Set("X-Caddy-Upstream-Dial", "localhost") req.Header.Set("X-Caddy-Upstream-Dial", "localhost")
tester.AssertResponse(req, 200, "Hello, World!") harness.AssertResponse(req, 200, "Hello, World!")
} }
func TestReverseProxyHealthCheck(t *testing.T) { func TestReverseProxyHealthCheck(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
http://localhost:2020 { http://localhost:2020 {
@@ -342,7 +342,7 @@ func TestReverseProxyHealthCheck(t *testing.T) {
http://localhost:2021 { http://localhost:2021 {
respond "ok" respond "ok"
} }
http://localhost:9080 { http://localhost:{$TESTING_CADDY_PORT_ONE} {
reverse_proxy { reverse_proxy {
to localhost:2020 to localhost:2020
@@ -357,14 +357,15 @@ func TestReverseProxyHealthCheck(t *testing.T) {
`, "caddyfile") `, "caddyfile")
time.Sleep(100 * time.Millisecond) // TODO: for some reason this test seems particularly flaky, getting 503 when it should be 200, unless we wait time.Sleep(100 * time.Millisecond) // TODO: for some reason this test seems particularly flaky, getting 503 when it should be 200, unless we wait
tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target, 200, "Hello, World!")
} }
func TestReverseProxyHealthCheckUnixSocket(t *testing.T) { func TestReverseProxyHealthCheckUnixSocket(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
t.SkipNow() t.SkipNow()
} }
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
f, err := os.CreateTemp("", "*.sock") f, err := os.CreateTemp("", "*.sock")
if err != nil { if err != nil {
t.Errorf("failed to create TempFile: %s", err) t.Errorf("failed to create TempFile: %s", err)
@@ -395,15 +396,15 @@ func TestReverseProxyHealthCheckUnixSocket(t *testing.T) {
}) })
runtime.Gosched() // Allow other goroutines to run runtime.Gosched() // Allow other goroutines to run
tester.InitServer(fmt.Sprintf(` harness.LoadConfig(fmt.Sprintf(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
http://localhost:9080 { http://localhost:{$TESTING_CADDY_PORT_ONE} {
reverse_proxy { reverse_proxy {
to unix/%s to unix/%s
@@ -415,14 +416,15 @@ func TestReverseProxyHealthCheckUnixSocket(t *testing.T) {
} }
`, socketName), "caddyfile") `, socketName), "caddyfile")
tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target, 200, "Hello, World!")
} }
func TestReverseProxyHealthCheckUnixSocketWithoutPort(t *testing.T) { func TestReverseProxyHealthCheckUnixSocketWithoutPort(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
t.SkipNow() t.SkipNow()
} }
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
f, err := os.CreateTemp("", "*.sock") f, err := os.CreateTemp("", "*.sock")
if err != nil { if err != nil {
t.Errorf("failed to create TempFile: %s", err) t.Errorf("failed to create TempFile: %s", err)
@@ -453,15 +455,15 @@ func TestReverseProxyHealthCheckUnixSocketWithoutPort(t *testing.T) {
}) })
runtime.Gosched() // Allow other goroutines to run runtime.Gosched() // Allow other goroutines to run
tester.InitServer(fmt.Sprintf(` harness.LoadConfig(fmt.Sprintf(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
http://localhost:9080 { http://localhost:{$TESTING_CADDY_PORT_ONE} {
reverse_proxy { reverse_proxy {
to unix/%s to unix/%s
@@ -472,5 +474,5 @@ func TestReverseProxyHealthCheckUnixSocketWithoutPort(t *testing.T) {
} }
`, socketName), "caddyfile") `, socketName), "caddyfile")
tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!") harness.AssertGetResponse(fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne()), 200, "Hello, World!")
} }
+25 -21
View File
@@ -1,6 +1,7 @@
package integration package integration
import ( import (
"fmt"
"testing" "testing"
"github.com/caddyserver/caddy/v2/caddytest" "github.com/caddyserver/caddy/v2/caddytest"
@@ -8,20 +9,20 @@ import (
func TestDefaultSNI(t *testing.T) { func TestDefaultSNI(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(`{ harness.LoadConfig(`{
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"grace_period": 1, "grace_period": 1,
"servers": { "servers": {
"srv0": { "srv0": {
"listen": [ "listen": [
":9443" ":{$TESTING_CADDY_PORT_TWO}"
], ],
"routes": [ "routes": [
{ {
@@ -102,26 +103,27 @@ func TestDefaultSNI(t *testing.T) {
// act and assert // act and assert
// makes a request with no sni // makes a request with no sni
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a.caddy.localhost") target := fmt.Sprintf("https://127.0.0.1:%d/", harness.Tester().PortTwo())
harness.AssertGetResponse(target+"version", 200, "hello from a.caddy.localhost")
} }
func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) { func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"grace_period": 1, "grace_period": 1,
"servers": { "servers": {
"srv0": { "srv0": {
"listen": [ "listen": [
":9443" ":{$TESTING_CADDY_PORT_TWO}"
], ],
"routes": [ "routes": [
{ {
@@ -206,26 +208,27 @@ func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
// act and assert // act and assert
// makes a request with no sni // makes a request with no sni
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a") target := fmt.Sprintf("https://127.0.0.1:%d/", harness.Tester().PortTwo())
harness.AssertGetResponse(target+"version", 200, "hello from a")
} }
func TestDefaultSNIWithPortMappingOnly(t *testing.T) { func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"grace_period": 1, "grace_period": 1,
"servers": { "servers": {
"srv0": { "srv0": {
"listen": [ "listen": [
":9443" ":{$TESTING_CADDY_PORT_TWO}"
], ],
"routes": [ "routes": [
{ {
@@ -282,7 +285,8 @@ func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
// act and assert // act and assert
// makes a request with no sni // makes a request with no sni
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a.caddy.localhost") target := fmt.Sprintf("https://127.0.0.1:%d/", harness.Tester().PortTwo())
harness.AssertGetResponse(target+"version", 200, "hello from a.caddy.localhost")
} }
func TestHttpOnlyOnDomainWithSNI(t *testing.T) { func TestHttpOnlyOnDomainWithSNI(t *testing.T) {
+22 -29
View File
@@ -20,21 +20,21 @@ import (
// (see https://github.com/caddyserver/caddy/issues/3556 for use case) // (see https://github.com/caddyserver/caddy/issues/3556 for use case)
func TestH2ToH2CStream(t *testing.T) { func TestH2ToH2CStream(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"grace_period": 1, "grace_period": 1,
"servers": { "servers": {
"srv0": { "srv0": {
"listen": [ "listen": [
":9443" ":{$TESTING_CADDY_PORT_TWO}"
], ],
"routes": [ "routes": [
{ {
@@ -102,7 +102,7 @@ func TestH2ToH2CStream(t *testing.T) {
expectedBody := "some data to be echoed" expectedBody := "some data to be echoed"
// start the server // start the server
server := testH2ToH2CStreamServeH2C(t) server := testH2ToH2CStreamServeH2C(harness, t)
go server.ListenAndServe() go server.ListenAndServe()
defer func() { defer func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond) ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
@@ -116,7 +116,7 @@ func TestH2ToH2CStream(t *testing.T) {
Body: io.NopCloser(r), Body: io.NopCloser(r),
URL: &url.URL{ URL: &url.URL{
Scheme: "https", Scheme: "https",
Host: "127.0.0.1:9443", Host: fmt.Sprintf("127.0.0.1:%d", harness.Tester().PortTwo()),
Path: "/tov2ray", Path: "/tov2ray",
}, },
Proto: "HTTP/2", Proto: "HTTP/2",
@@ -127,7 +127,7 @@ func TestH2ToH2CStream(t *testing.T) {
// Disable any compression method from server. // Disable any compression method from server.
req.Header.Set("Accept-Encoding", "identity") req.Header.Set("Accept-Encoding", "identity")
resp := tester.AssertResponseCode(req, http.StatusOK) resp := harness.AssertResponseCode(req, http.StatusOK)
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return return
} }
@@ -149,7 +149,7 @@ func TestH2ToH2CStream(t *testing.T) {
} }
} }
func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server { func testH2ToH2CStreamServeH2C(harness *caddytest.TestHarness, t *testing.T) *http.Server {
h2s := &http2.Server{} h2s := &http2.Server{}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rstring, err := httputil.DumpRequest(r, false) rstring, err := httputil.DumpRequest(r, false)
@@ -163,7 +163,7 @@ func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server {
return return
} }
if r.Host != "127.0.0.1:9443" { if r.Host != fmt.Sprintf("127.0.0.1:%d", harness.Tester().PortTwo()) {
t.Errorf("r.Host doesn't match, %v!", r.Host) t.Errorf("r.Host doesn't match, %v!", r.Host)
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return
@@ -204,28 +204,21 @@ func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server {
// (see https://github.com/caddyserver/caddy/issues/3606 for use case) // (see https://github.com/caddyserver/caddy/issues/3606 for use case)
func TestH2ToH1ChunkedResponse(t *testing.T) { func TestH2ToH1ChunkedResponse(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
},
"logging": {
"logs": {
"default": {
"level": "DEBUG"
}
}
}, },
"apps": { "apps": {
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"grace_period": 1, "grace_period": 1,
"servers": { "servers": {
"srv0": { "srv0": {
"listen": [ "listen": [
":9443" ":{$TESTING_CADDY_PORT_TWO}"
], ],
"routes": [ "routes": [
{ {
@@ -312,7 +305,7 @@ func TestH2ToH1ChunkedResponse(t *testing.T) {
} }
// start the server // start the server
server := testH2ToH1ChunkedResponseServeH1(t) server := testH2ToH1ChunkedResponseServeH1(harness, t)
go server.ListenAndServe() go server.ListenAndServe()
defer func() { defer func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond) ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
@@ -326,7 +319,7 @@ func TestH2ToH1ChunkedResponse(t *testing.T) {
Body: io.NopCloser(r), Body: io.NopCloser(r),
URL: &url.URL{ URL: &url.URL{
Scheme: "https", Scheme: "https",
Host: "127.0.0.1:9443", Host: fmt.Sprintf("127.0.0.1:%d", harness.Tester().PortTwo()),
Path: "/tov2ray", Path: "/tov2ray",
}, },
Proto: "HTTP/2", Proto: "HTTP/2",
@@ -340,7 +333,7 @@ func TestH2ToH1ChunkedResponse(t *testing.T) {
fmt.Fprint(w, expectedBody) fmt.Fprint(w, expectedBody)
w.Close() w.Close()
}() }()
resp := tester.AssertResponseCode(req, http.StatusOK) resp := harness.AssertResponseCode(req, http.StatusOK)
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return return
} }
@@ -358,9 +351,9 @@ func TestH2ToH1ChunkedResponse(t *testing.T) {
} }
} }
func testH2ToH1ChunkedResponseServeH1(t *testing.T) *http.Server { func testH2ToH1ChunkedResponseServeH1(harness *caddytest.TestHarness, t *testing.T) *http.Server {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Host != "127.0.0.1:9443" { if r.Host != fmt.Sprintf("127.0.0.1:%d", harness.Tester().PortTwo()) {
t.Errorf("r.Host doesn't match, %v!", r.Host) t.Errorf("r.Host doesn't match, %v!", r.Host)
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return
@@ -1,2 +0,0 @@
foo
@@ -1 +0,0 @@
foo
+241
View File
@@ -0,0 +1,241 @@
package caddytest
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"path"
"regexp"
"runtime"
"testing"
"github.com/stretchr/testify/require"
)
// use the convention to replace /[certificatename].[crt|key] with the full path
// this helps reduce the noise in test configurations and also allow this
// to run in any path
func prependCaddyFilePath(rawConfig string) string {
r := matchKey.ReplaceAllString(rawConfig, getIntegrationDir()+"$1")
r = matchCert.ReplaceAllString(r, getIntegrationDir()+"$1")
return r
}
func getIntegrationDir() string {
_, filename, _, ok := runtime.Caller(1)
if !ok {
panic("unable to determine the current file path")
}
return path.Dir(filename)
}
var (
matchKey = regexp.MustCompile(`(/[\w\d\.]+\.key)`)
matchCert = regexp.MustCompile(`(/[\w\d\.]+\.crt)`)
)
type TestHarness struct {
t testing.TB
tester *Tester
}
// StartHarness creates and starts a test harness environment which spans the lifetime a single caddy instance
// This is used for the integration tests
func StartHarness(t *testing.T) *TestHarness {
if testing.Short() {
t.SkipNow()
return nil
}
o := &TestHarness{t: t}
o.init()
return o
}
func (tc *TestHarness) Tester() *Tester {
return tc.tester
}
func (tc *TestHarness) Client() *http.Client {
return tc.tester.Client
}
func (tc *TestHarness) LoadConfig(rawConfig, configType string) {
rawConfig = prependCaddyFilePath(rawConfig)
err := tc.tester.LoadConfig(rawConfig, configType)
require.NoError(tc.t, err)
}
func (tc *TestHarness) init() {
// start the server
tester, err := NewTester()
if err != nil {
tc.t.Errorf("Failed to create caddy tester: %s", err)
return
}
tc.tester = tester
err = tc.tester.LaunchCaddy()
if err != nil {
tc.t.Errorf("Failed to launch caddy server: %s", err)
tc.t.FailNow()
return
}
// cleanup
tc.t.Cleanup(func() {
func() {
if tc.t.Failed() {
res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", tc.tester.adminPort))
if err != nil {
tc.t.Log("unable to read the current config")
return
}
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
var out bytes.Buffer
_ = json.Indent(&out, body, "", " ")
tc.t.Logf("----------- failed with config -----------\n%s", out.String())
}
}()
// shutdown server after extracing the config
err = tc.tester.CleanupCaddy()
if err != nil {
tc.t.Errorf("failed to clean up caddy instance: %s", err)
tc.t.FailNow()
}
})
}
// AssertRedirect makes a request and asserts the redirection happens
func (tc *TestHarness) AssertRedirect(requestURI string, expectedToLocation string, expectedStatusCode int) *http.Response {
redirectPolicyFunc := func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
// using the existing client, we override the check redirect policy for this test
old := tc.tester.Client.CheckRedirect
tc.tester.Client.CheckRedirect = redirectPolicyFunc
defer func() { tc.tester.Client.CheckRedirect = old }()
resp, err := tc.tester.Client.Get(requestURI)
if err != nil {
tc.t.Errorf("failed to call server %s", err)
return nil
}
if expectedStatusCode != resp.StatusCode {
tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", requestURI, expectedStatusCode, resp.StatusCode)
}
loc, err := resp.Location()
if err != nil {
tc.t.Errorf("requesting \"%s\" expected location: \"%s\" but got error: %s", requestURI, expectedToLocation, err)
}
if loc == nil && expectedToLocation != "" {
tc.t.Errorf("requesting \"%s\" expected a Location header, but didn't get one", requestURI)
}
if loc != nil {
if expectedToLocation != loc.String() {
tc.t.Errorf("requesting \"%s\" expected location: \"%s\" but got \"%s\"", requestURI, expectedToLocation, loc.String())
}
}
return resp
}
// AssertResponseCode will execute the request and verify the status code, returns a response for additional assertions
func (tc *TestHarness) AssertResponseCode(req *http.Request, expectedStatusCode int) *http.Response {
resp, err := tc.tester.Client.Do(req)
if err != nil {
tc.t.Fatalf("failed to call server %s", err)
}
if expectedStatusCode != resp.StatusCode {
tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", req.URL.RequestURI(), expectedStatusCode, resp.StatusCode)
}
return resp
}
// AssertResponse request a URI and assert the status code and the body contains a string
func (tc *TestHarness) AssertResponse(req *http.Request, expectedStatusCode int, expectedBody string) (*http.Response, string) {
resp := tc.AssertResponseCode(req, expectedStatusCode)
defer resp.Body.Close()
bytes, err := io.ReadAll(resp.Body)
if err != nil {
tc.t.Fatalf("unable to read the response body %s", err)
}
body := string(bytes)
if body != expectedBody {
tc.t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
}
return resp, body
}
// Verb specific test functions
// AssertGetResponse GET a URI and expect a statusCode and body text
func (tc *TestHarness) AssertGetResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
req, err := http.NewRequest("GET", requestURI, nil)
if err != nil {
tc.t.Fatalf("unable to create request %s", err)
}
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
// AssertDeleteResponse request a URI and expect a statusCode and body text
func (tc *TestHarness) AssertDeleteResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
req, err := http.NewRequest("DELETE", requestURI, nil)
if err != nil {
tc.t.Fatalf("unable to create request %s", err)
}
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
// AssertPostResponseBody POST to a URI and assert the response code and body
func (tc *TestHarness) AssertPostResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
req, err := http.NewRequest("POST", requestURI, requestBody)
if err != nil {
tc.t.Errorf("failed to create request %s", err)
return nil, ""
}
applyHeaders(tc.t, req, requestHeaders)
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
// AssertPutResponseBody PUT to a URI and assert the response code and body
func (tc *TestHarness) AssertPutResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
req, err := http.NewRequest("PUT", requestURI, requestBody)
if err != nil {
tc.t.Errorf("failed to create request %s", err)
return nil, ""
}
applyHeaders(tc.t, req, requestHeaders)
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
// AssertPatchResponseBody PATCH to a URI and assert the response code and body
func (tc *TestHarness) AssertPatchResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
req, err := http.NewRequest("PATCH", requestURI, requestBody)
if err != nil {
tc.t.Errorf("failed to create request %s", err)
return nil, ""
}
applyHeaders(tc.t, req, requestHeaders)
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
+4 -4
View File
@@ -8,7 +8,7 @@ import (
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
) )
var defaultFactory = newRootCommandFactory(func() *cobra.Command { var defaultFactory = NewRootCommandFactory(func() *cobra.Command {
return &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.
@@ -108,9 +108,9 @@ 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) { defaultFactory.Use(func(cmd *cobra.Command) {
rootCmd.SetVersionTemplate("{{.Version}}\n") cmd.SetVersionTemplate("{{.Version}}\n")
rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n") cmd.SetHelpTemplate(cmd.HelpTemplate() + "\n" + fullDocsFooter + "\n")
}) })
} }
+5 -5
View File
@@ -4,22 +4,22 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
type rootCommandFactory struct { type RootCommandFactory struct {
constructor func() *cobra.Command constructor func() *cobra.Command
options []func(*cobra.Command) options []func(*cobra.Command)
} }
func newRootCommandFactory(fn func() *cobra.Command) *rootCommandFactory { func NewRootCommandFactory(fn func() *cobra.Command) *RootCommandFactory {
return &rootCommandFactory{ return &RootCommandFactory{
constructor: fn, constructor: fn,
} }
} }
func (f *rootCommandFactory) Use(fn func(cmd *cobra.Command)) { func (f *RootCommandFactory) Use(fn func(cmd *cobra.Command)) {
f.options = append(f.options, fn) f.options = append(f.options, fn)
} }
func (f *rootCommandFactory) Build() *cobra.Command { func (f *RootCommandFactory) Build() *cobra.Command {
o := f.constructor() o := f.constructor()
for _, v := range f.options { for _, v := range f.options {
v(o) v(o)
+22 -31
View File
@@ -20,6 +20,7 @@ import (
"crypto/rand" "crypto/rand"
"encoding/json" "encoding/json"
"errors" "errors"
"flag"
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
@@ -74,10 +75,6 @@ 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)
} }
@@ -171,10 +168,6 @@ 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")
@@ -200,18 +193,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
logger.Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath)) caddy.Log().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 == "" {
logger.Info("resuming from last configuration", caddy.Log().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!)
logger.Warn("--config and --resume flags were used together; ignoring --config and resuming from last configuration", caddy.Log().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))
} }
} }
@@ -229,7 +222,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 {
logger.Error("unable to write PID file", caddy.Log().Error("unable to write PID file",
zap.String("pidfile", pidfileFlag), zap.String("pidfile", pidfileFlag),
zap.Error(err)) zap.Error(err))
} }
@@ -240,7 +233,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)
} }
logger.Info("serving initial configuration") caddy.Log().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
@@ -265,6 +258,7 @@ func cmdRun(fl Flags) (int, error) {
// if enabled, reload config file automatically on changes // if enabled, reload config file automatically on changes
// (this better only be used in dev!) // (this better only be used in dev!)
// do not enable this during tests, it will cause leaks
if watchFlag { if watchFlag {
go watchConfigFile(configFile, configAdapterFlag) go watchConfigFile(configFile, configAdapterFlag)
} }
@@ -276,19 +270,23 @@ 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 {
logger.Warn("neither HOME nor USERPROFILE environment variables are set - please fix; some assets might be stored in ./caddy") caddy.Log().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 {
logger.Warn("$home environment variable is empty - please fix; some assets might be stored in ./caddy") caddy.Log().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 {
logger.Warn("$HOME environment variable is empty - please fix; some assets might be stored in ./caddy") caddy.Log().Warn("$HOME environment variable is empty - please fix; some assets might be stored in ./caddy")
} }
} }
if flag.Lookup("test.v") == nil || !strings.Contains(os.Args[0], ".test") {
select {} select {}
} else {
return caddy.ExitCodeSuccess, nil
}
} }
func cmdStop(fl Flags) (int, error) { func cmdStop(fl Flags) (int, error) {
@@ -564,15 +562,10 @@ 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)
configFlag := fl.String("config") if configFile == "" {
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" 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)
@@ -669,8 +662,6 @@ 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
@@ -678,13 +669,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() || parsedAddr.IsFdNetwork() { if parsedAddr.IsUnixNetwork() {
// 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 and socket fds. However, Go required a // with unix sockets. However, Go required a Host value so we
// Host value so we used a hack of a space character as the host // used a hack of a space character as the host (it would see
// (it would see the Host was non-empty, then trim the space later). // the Host was non-empty, then trim the space later). As of
// As of Go 1.20.6 (July 2023), this hack no longer works. See: // 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
@@ -725,7 +716,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*1024*2)) respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1024*10))
if err != nil { if err != nil {
return nil, fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err) return nil, fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
} }
+12 -13
View File
@@ -388,7 +388,6 @@ 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)
@@ -410,13 +409,12 @@ latest versions. EXPERIMENTAL: May be changed or removed.
RegisterCommand(Command{ RegisterCommand(Command{
Name: "add-package", Name: "add-package",
Usage: "<package[@version]...>", Usage: "<packages...>",
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, with an optional version specified (e.g., "package@version"). Retains added. Retains existing packages. Returns an error if the any of packages are
existing packages. Returns an error if any of the specified packages are already already included. EXPERIMENTAL: May be changed or removed.
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")
@@ -440,8 +438,7 @@ EXPERIMENTAL: May be changed or removed.
}, },
}) })
defaultFactory.Use(func(rootCmd *cobra.Command) { RegisterCommand(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",
@@ -462,7 +459,8 @@ argument of --directory. If the directory does not exist, it will be created.
if err := os.MkdirAll(dir, 0o755); err != nil { if err := os.MkdirAll(dir, 0o755); err != nil {
return caddy.ExitCodeFailedQuit, err return caddy.ExitCodeFailedQuit, err
} }
if err := doc.GenManTree(rootCmd, &doc.GenManHeader{ ccmd := defaultFactory.Build()
if err := doc.GenManTree(ccmd, &doc.GenManHeader{
Title: "Caddy", Title: "Caddy",
Section: "8", // https://en.wikipedia.org/wiki/Man_page#Manual_sections Section: "8", // https://en.wikipedia.org/wiki/Man_page#Manual_sections
}, dir); err != nil { }, dir); err != nil {
@@ -471,10 +469,11 @@ 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{ defaultFactory.Use(func(ccmd *cobra.Command) {
ccmd.AddCommand(&cobra.Command{
Use: "completion [bash|zsh|fish|powershell]", Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate completion script", Short: "Generate completion script",
Long: fmt.Sprintf(`To load completions: Long: fmt.Sprintf(`To load completions:
@@ -515,7 +514,7 @@ argument of --directory. If the directory does not exist, it will be created.
# To load completions for every new session, run: # To load completions for every new session, run:
PS> %[1]s completion powershell > %[1]s.ps1 PS> %[1]s completion powershell > %[1]s.ps1
# and source this file from your PowerShell profile. # and source this file from your PowerShell profile.
`, rootCmd.Root().Name()), `, defaultFactory.constructor().Name()),
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
@@ -567,8 +566,8 @@ 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) { defaultFactory.Use(func(ccmd *cobra.Command) {
rootCmd.AddCommand(caddyCmdToCobra(cmd)) ccmd.AddCommand(caddyCmdToCobra(cmd))
}) })
} }
+19 -29
View File
@@ -24,7 +24,6 @@ import (
"io" "io"
"io/fs" "io/fs"
"log" "log"
"log/slog"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
@@ -34,12 +33,10 @@ 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"
@@ -69,7 +66,13 @@ func Main() {
os.Exit(caddy.ExitCodeFailedStartup) os.Exit(caddy.ExitCodeFailedStartup)
} }
if err := defaultFactory.Build().Execute(); err != nil { undo, err := maxprocs.Set()
defer undo()
if err != nil {
caddy.Log().Warn("failed to set GOMAXPROCS", zap.Error(err))
}
rootCmd := defaultFactory.Build()
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)
@@ -78,6 +81,18 @@ func Main() {
} }
} }
// MainForTesting implements the main function of the caddy command, used internally for testing
func MainForTesting(args ...string) error {
// create a root command for testing which will not pollute the global namespace, and does not
// call os.Exit().
rootCmd := defaultFactory.Build()
rootCmd.SetArgs(args)
if err := rootCmd.Execute(); err != nil {
return err
}
return nil
}
// handlePingbackConn reads from conn and ensures it matches // handlePingbackConn reads from conn and ensures it matches
// the bytes in expect, or returns an error if it doesn't. // the bytes in expect, or returns an error if it doesn't.
func handlePingbackConn(conn net.Conn, expect []byte) error { func handlePingbackConn(conn net.Conn, expect []byte) error {
@@ -464,31 +479,6 @@ 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
+9 -49
View File
@@ -46,25 +46,6 @@ 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")
@@ -79,15 +60,10 @@ func cmdAddPackage(fl Flags) (int, error) {
} }
for _, arg := range fl.Args() { for _, arg := range fl.Args() {
module, version, err := splitModule(arg) if _, ok := pluginPkgs[arg]; ok {
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[module] = pluginPackage{Version: version, Path: module} pluginPkgs[arg] = struct{}{}
} }
return upgradeBuild(pluginPkgs, fl) return upgradeBuild(pluginPkgs, fl)
@@ -107,11 +83,7 @@ func cmdRemovePackage(fl Flags) (int, error) {
} }
for _, arg := range fl.Args() { for _, arg := range fl.Args() {
module, _, err := splitModule(arg) if _, ok := pluginPkgs[arg]; !ok {
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")
} }
@@ -121,7 +93,7 @@ func cmdRemovePackage(fl Flags) (int, error) {
return upgradeBuild(pluginPkgs, fl) return upgradeBuild(pluginPkgs, fl)
} }
func upgradeBuild(pluginPkgs map[string]pluginPackage, fl Flags) (int, error) { func upgradeBuild(pluginPkgs map[string]struct{}, fl Flags) (int, error) {
l := caddy.Log() l := caddy.Log()
thisExecPath, err := os.Executable() thisExecPath, err := os.Executable()
@@ -148,8 +120,8 @@ func upgradeBuild(pluginPkgs map[string]pluginPackage, fl Flags) (int, error) {
"os": {runtime.GOOS}, "os": {runtime.GOOS},
"arch": {runtime.GOARCH}, "arch": {runtime.GOARCH},
} }
for _, pkgInfo := range pluginPkgs { for pkg := range pluginPkgs {
qs.Add("p", pkgInfo.String()) qs.Add("p", pkg)
} }
// initiate the build // initiate the build
@@ -304,14 +276,14 @@ func downloadBuild(qs url.Values) (*http.Response, error) {
return resp, nil return resp, nil
} }
func getPluginPackages(modules []moduleInfo) (map[string]pluginPackage, error) { func getPluginPackages(modules []moduleInfo) (map[string]struct{}, error) {
pluginPkgs := make(map[string]pluginPackage) pluginPkgs := make(map[string]struct{})
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] = pluginPackage{Version: mod.goModule.Version, Path: mod.goModule.Path} pluginPkgs[mod.goModule.Path] = struct{}{}
} }
return pluginPkgs, nil return pluginPkgs, nil
} }
@@ -340,15 +312,3 @@ 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
}
-9
View File
@@ -21,7 +21,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"os" "os"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
@@ -191,20 +190,12 @@ 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
} }
+12 -64
View File
@@ -23,8 +23,6 @@ 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"
@@ -49,7 +47,6 @@ 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
@@ -61,7 +58,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, metricsRegistry: prometheus.NewPedanticRegistry()} newCtx := Context{moduleInstances: make(map[string][]Module), cfg: ctx.cfg}
c, cancel := context.WithCancel(ctx.Context) c, cancel := context.WithCancel(ctx.Context)
wrappedCancel := func() { wrappedCancel := func() {
cancel() cancel()
@@ -82,7 +79,6 @@ func NewContext(ctx Context) (Context, context.CancelFunc) {
} }
} }
newCtx.Context = c newCtx.Context = c
newCtx.initMetrics()
return newCtx, wrappedCancel return newCtx, wrappedCancel
} }
@@ -91,32 +87,14 @@ func (ctx *Context) OnCancel(f func()) {
ctx.cleanupFuncs = append(ctx.cleanupFuncs, f) ctx.cleanupFuncs = append(ctx.cleanupFuncs, f)
} }
// FileSystems returns a ref to the FilesystemMap. // Filesystems returns a ref to the FilesystemMap.
// EXPERIMENTAL: This API is subject to change. // EXPERIMENTAL: This API is subject to change.
func (ctx *Context) FileSystems() FileSystems { func (ctx *Context) Filesystems() FileSystems {
// if no config is loaded, we use a default filesystemmap, which includes the osfs // if no config is loaded, we use a default filesystemmap, which includes the osfs
if ctx.cfg == nil { if ctx.cfg == nil {
return &filesystems.FileSystemMap{} return &filesystems.FilesystemMap{}
} }
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.
@@ -277,14 +255,6 @@ func (ctx Context) LoadModule(structPointer any, fieldName string) (any, error)
return result, nil return result, nil
} }
// emitEvent is a small convenience method so the caddy core can emit events, if the event app is configured.
func (ctx Context) emitEvent(name string, data map[string]any) Event {
if ctx.cfg == nil || ctx.cfg.eventEmitter == nil {
return Event{}
}
return ctx.cfg.eventEmitter.Emit(ctx, name, data)
}
// loadModulesFromSomeMap loads modules from val, which must be a type of map[string]any. // loadModulesFromSomeMap loads modules from val, which must be a type of map[string]any.
// Depending on inlineModuleKey, it will be interpreted as either a ModuleMap (key is the module // Depending on inlineModuleKey, it will be interpreted as either a ModuleMap (key is the module
// name) or as a regular map (key is not the module name, and module name is defined inline). // name) or as a regular map (key is not the module name, and module name is defined inline).
@@ -393,17 +363,6 @@ func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error
return nil, fmt.Errorf("module value cannot be null") return nil, fmt.Errorf("module value cannot be null")
} }
// 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 {
@@ -437,14 +396,6 @@ func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error
ctx.moduleInstances[id] = append(ctx.moduleInstances[id], val) ctx.moduleInstances[id] = append(ctx.moduleInstances[id], val)
// if the loaded module happens to be an app that can emit events, store it so the
// core can have access to emit events without an import cycle
if ee, ok := val.(eventEmitter); ok {
if _, ok := ee.(App); ok {
ctx.cfg.eventEmitter = ee
}
}
return val, nil return val, nil
} }
@@ -498,6 +449,7 @@ 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
} }
@@ -583,8 +535,12 @@ 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(),
zapslog.WithName(string(mod.CaddyModule().ID)), return slog.New(zapslog.NewHandler(
ctx.cfg.Logging.Logger(mod).Core(),
&zapslog.HandlerOptions{
LoggerName: string(mod.CaddyModule().ID),
},
)) ))
} }
@@ -616,11 +572,3 @@ func (ctx *Context) WithValue(key, value any) Context {
exitFuncs: ctx.exitFuncs, exitFuncs: ctx.exitFuncs,
} }
} }
// eventEmitter is a small interface that inverts dependencies for
// the caddyevents package, so the core can emit events without an
// import cycle (i.e. the caddy package doesn't have to import
// the caddyevents package, which imports the caddy package).
type eventEmitter interface {
Emit(ctx Context, eventName string, data map[string]any) Event
}
-39
View File
@@ -1,39 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//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()
-27
View File
@@ -1,27 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package 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)
}
-14
View File
@@ -1,17 +1,3 @@
// 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"
+62 -64
View File
@@ -1,112 +1,110 @@
module github.com/caddyserver/caddy/v2 module github.com/caddyserver/caddy/v2
go 1.24 go 1.21.0
toolchain go1.22.2
require ( require (
github.com/BurntSushi/toml v1.4.0 github.com/BurntSushi/toml v1.3.2
github.com/KimMachineGun/automemlimit v0.7.1 github.com/Masterminds/sprig/v3 v3.2.3
github.com/Masterminds/sprig/v3 v3.3.0 github.com/alecthomas/chroma/v2 v2.13.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.23.0 github.com/caddyserver/certmagic v0.21.3
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.2.1 github.com/go-chi/chi/v5 v5.0.12
github.com/google/cel-go v0.24.1 github.com/google/cel-go v0.20.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/klauspost/compress v1.18.0 github.com/klauspost/compress v1.17.8
github.com/klauspost/cpuid/v2 v2.2.10 github.com/klauspost/cpuid/v2 v2.2.7
github.com/mholt/acmez/v3 v3.1.2 github.com/mholt/acmez/v2 v2.0.1
github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_golang v1.19.1
github.com/quic-go/quic-go v0.50.1 github.com/quic-go/quic-go v0.44.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.9.1 github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.6 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.9.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.8 github.com/yuin/goldmark v1.7.1
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.56.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 go.opentelemetry.io/contrib/propagators/autoprop v0.42.0
go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel v1.24.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0
go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/sdk v1.21.0
go.uber.org/automaxprocs v1.6.0 go.uber.org/automaxprocs v1.5.3
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
go.uber.org/zap/exp v0.3.0 go.uber.org/zap/exp v0.2.0
golang.org/x/crypto v0.36.0 golang.org/x/crypto v0.23.0
golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810 golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595
golang.org/x/net v0.38.0 golang.org/x/net v0.25.0
golang.org/x/sync v0.12.0 golang.org/x/sync v0.7.0
golang.org/x/term v0.30.0 golang.org/x/term v0.20.0
golang.org/x/time v0.11.0 golang.org/x/time v0.5.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.4 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // 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.22.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.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.5.1 // indirect github.com/quic-go/qpack v0.4.0 // 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.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect
go.opentelemetry.io/contrib/propagators/aws v1.17.0 // indirect go.opentelemetry.io/contrib/propagators/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.5.0 // indirect go.uber.org/mock v0.4.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-20241007155032-5fefd90f89a9 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // 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.3.0 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 github.com/cespare/xxhash/v2 v2.2.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.6 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // 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.2.0 // indirect github.com/dgraph-io/ristretto v0.1.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.4 // indirect github.com/dlclark/regexp2 v1.11.0 // 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.2 // indirect github.com/go-logr/logr v1.4.1 // 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.5.0 // indirect github.com/huandu/xstrings v1.3.3 // 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
@@ -116,43 +114,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 v1.0.0-beta.1 github.com/libdns/libdns v0.2.2 // indirect
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.63 // indirect github.com/miekg/dns v1.1.59 // 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.1-0.20240628150027-b718e7ce4964 github.com/pires/go-proxyproto v0.7.0
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.5.0 github.com/prometheus/client_model v0.5.0 // indirect
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.4.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/slackhq/nebula v1.6.1 // indirect github.com/slackhq/nebula v1.6.1 // indirect
github.com/spf13/cast v1.7.0 // indirect github.com/spf13/cast v1.4.1 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect
github.com/urfave/cli v1.22.14 // indirect github.com/urfave/cli v1.22.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.31.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.31.0 go.opentelemetry.io/otel/trace v1.24.0
go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // 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.24.0 // indirect golang.org/x/mod v0.17.0 // indirect
golang.org/x/sys v0.31.0 golang.org/x/sys v0.20.0
golang.org/x/text v0.23.0 // indirect golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.31.0 // indirect golang.org/x/tools v0.21.0 // indirect
google.golang.org/grpc v1.67.1 // indirect google.golang.org/grpc v1.63.2 // indirect
google.golang.org/protobuf v1.35.1 // indirect google.golang.org/protobuf v1.34.1 // indirect
howett.net/plist v1.0.0 // indirect howett.net/plist v1.0.0 // indirect
) )
+138 -277
View File
@@ -1,60 +1,43 @@
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.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
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.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
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.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/assert/v2 v2.6.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.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc= github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI=
github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio= github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk=
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=
@@ -88,21 +71,19 @@ 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/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI=
github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4=
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.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v4 v4.2.1/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.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
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=
@@ -112,21 +93,17 @@ 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.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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=
@@ -137,35 +114,27 @@ 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.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE= github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU= github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
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.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.0/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/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
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=
@@ -177,8 +146,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.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.1/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=
@@ -189,66 +158,55 @@ 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.24.1 h1:jsBCtxG8mM5wiUJDSGUqU0K7Mtr3w7Eyv00rw4DiZxI= github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
github.com/google/cel-go v0.24.1/go.mod h1:Hdf9TqOaTNSFQA1ybQaRqATVoK7m/zcf7IMhGXP5zI8= github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk=
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.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.3/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=
@@ -299,16 +257,14 @@ 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.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
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=
@@ -316,7 +272,6 @@ 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=
@@ -327,11 +282,9 @@ 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 v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ= github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/libdns/libdns v0.2.2/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=
@@ -344,38 +297,31 @@ 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/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc= github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k=
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
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.1-0.20240628150027-b718e7ce4964 h1:ct/vxNBgHpASQ4sT8NaBX9LtsEtluZqaUJydLG50U3E= github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY= github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
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=
@@ -383,25 +329,21 @@ 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.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q= github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0=
github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E= github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek=
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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
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=
@@ -413,35 +355,11 @@ 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=
@@ -463,22 +381,21 @@ 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.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
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.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
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.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/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=
@@ -496,41 +413,37 @@ 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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.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.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark v1.7.1/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.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
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.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= 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/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
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=
@@ -541,20 +454,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.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
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 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0=
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
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=
@@ -565,12 +478,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.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
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.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
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=
@@ -582,14 +495,10 @@ 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.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs=
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ=
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=
@@ -599,67 +508,44 @@ 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.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
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 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw=
golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810/go.mod h1:lxN5T34bK4Z/i6cMaU7frUU57VkDXFD4Kamfl/cp9oU= golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
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.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
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.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
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.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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=
@@ -668,6 +554,7 @@ 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=
@@ -675,41 +562,37 @@ 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.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
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.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
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=
@@ -720,41 +603,25 @@ 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.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
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-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= 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/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y=
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 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
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=
@@ -762,22 +629,16 @@ 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=
+16 -16
View File
@@ -7,10 +7,10 @@ import (
) )
const ( const (
DefaultFileSystemKey = "default" DefaultFilesystemKey = "default"
) )
var DefaultFileSystem = &wrapperFs{key: DefaultFileSystemKey, FS: OsFS{}} var DefaultFilesystem = &wrapperFs{key: DefaultFilesystemKey, FS: OsFS{}}
// wrapperFs exists so can easily add to wrapperFs down the line // wrapperFs exists so can easily add to wrapperFs down the line
type wrapperFs struct { type wrapperFs struct {
@@ -18,24 +18,24 @@ type wrapperFs struct {
fs.FS fs.FS
} }
// FileSystemMap stores a map of filesystems // FilesystemMap stores a map of filesystems
// the empty key will be overwritten to be the default key // the empty key will be overwritten to be the default key
// it includes a default filesystem, based off the os fs // it includes a default filesystem, based off the os fs
type FileSystemMap struct { type FilesystemMap struct {
m sync.Map m sync.Map
} }
// note that the first invocation of key cannot be called in a racy context. // note that the first invocation of key cannot be called in a racy context.
func (f *FileSystemMap) key(k string) string { func (f *FilesystemMap) key(k string) string {
if k == "" { if k == "" {
k = DefaultFileSystemKey k = DefaultFilesystemKey
} }
return k return k
} }
// Register will add the filesystem with key to later be retrieved // Register will add the filesystem with key to later be retrieved
// A call with a nil fs will call unregister, ensuring that a call to Default() will never be nil // A call with a nil fs will call unregister, ensuring that a call to Default() will never be nil
func (f *FileSystemMap) Register(k string, v fs.FS) { func (f *FilesystemMap) Register(k string, v fs.FS) {
k = f.key(k) k = f.key(k)
if v == nil { if v == nil {
f.Unregister(k) f.Unregister(k)
@@ -47,23 +47,23 @@ func (f *FileSystemMap) Register(k string, v fs.FS) {
// Unregister will remove the filesystem with key from the filesystem map // Unregister will remove the filesystem with key from the filesystem map
// if the key is the default key, it will set the default to the osFS instead of deleting it // if the key is the default key, it will set the default to the osFS instead of deleting it
// modules should call this on cleanup to be safe // modules should call this on cleanup to be safe
func (f *FileSystemMap) Unregister(k string) { func (f *FilesystemMap) Unregister(k string) {
k = f.key(k) k = f.key(k)
if k == DefaultFileSystemKey { if k == DefaultFilesystemKey {
f.m.Store(k, DefaultFileSystem) f.m.Store(k, DefaultFilesystem)
} else { } else {
f.m.Delete(k) f.m.Delete(k)
} }
} }
// Get will get a filesystem with a given key // Get will get a filesystem with a given key
func (f *FileSystemMap) Get(k string) (v fs.FS, ok bool) { func (f *FilesystemMap) Get(k string) (v fs.FS, ok bool) {
k = f.key(k) k = f.key(k)
c, ok := f.m.Load(strings.TrimSpace(k)) c, ok := f.m.Load(strings.TrimSpace(k))
if !ok { if !ok {
if k == DefaultFileSystemKey { if k == DefaultFilesystemKey {
f.m.Store(k, DefaultFileSystem) f.m.Store(k, DefaultFilesystem)
return DefaultFileSystem, true return DefaultFilesystem, true
} }
return nil, ok return nil, ok
} }
@@ -71,7 +71,7 @@ func (f *FileSystemMap) Get(k string) (v fs.FS, ok bool) {
} }
// Default will get the default filesystem in the filesystem map // Default will get the default filesystem in the filesystem map
func (f *FileSystemMap) Default() fs.FS { func (f *FilesystemMap) Default() fs.FS {
val, _ := f.Get(DefaultFileSystemKey) val, _ := f.Get(DefaultFilesystemKey)
return val return val
} }
-22
View File
@@ -1,22 +0,0 @@
package internal
import "fmt"
// MaxSizeSubjectsListForLog returns the keys in the map as a slice of maximum length
// maxToDisplay. It is useful for logging domains being managed, for example, since a
// map is typically needed for quick lookup, but a slice is needed for logging, and this
// can be quite a doozy since there may be a huge amount (hundreds of thousands).
func MaxSizeSubjectsListForLog(subjects map[string]struct{}, maxToDisplay int) []string {
numberOfNamesToDisplay := min(len(subjects), maxToDisplay)
domainsToDisplay := make([]string, 0, numberOfNamesToDisplay)
for domain := range subjects {
domainsToDisplay = append(domainsToDisplay, domain)
if len(domainsToDisplay) >= numberOfNamesToDisplay {
break
}
}
if len(subjects) > maxToDisplay {
domainsToDisplay = append(domainsToDisplay, fmt.Sprintf("(and %d more...)", len(subjects)-maxToDisplay))
}
return domainsToDisplay
}
-14
View File
@@ -1,14 +0,0 @@
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",
}
}
+7 -63
View File
@@ -18,11 +18,7 @@ package caddy
import ( import (
"context" "context"
"fmt"
"net" "net"
"os"
"slices"
"strconv"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -30,54 +26,15 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
func reuseUnixSocket(_, _ string) (any, error) { func reuseUnixSocket(network, addr 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) {
var socketFile *os.File switch network {
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) {
var ( pc, err := config.ListenPacket(ctx, network, address)
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
} }
@@ -87,18 +44,10 @@ 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) {
var ( ln, err := config.Listen(ctx, network, address)
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
} }
@@ -108,6 +57,7 @@ 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
@@ -310,9 +260,3 @@ 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
+31 -101
View File
@@ -22,14 +22,10 @@ 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"
@@ -38,9 +34,12 @@ 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. // have it open; if not, unlink it so we can have it. No-op if not a unix network.
// 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]
@@ -72,7 +71,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), socketKey, unixSocket.count} unixSockets[socketKey] = &unixConn{pc.(*net.UnixConn), addr, socketKey, unixSocket.count}
} }
return unixSockets[socketKey], nil return unixSockets[socketKey], nil
@@ -90,46 +89,7 @@ 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 {
@@ -140,57 +100,45 @@ func listenReusable(ctx context.Context, lnKey string, network, address string,
} }
return reusePort(network, address, c) return reusePort(network, address, c)
} }
}
datagram := slices.Contains([]string{"udp", "udp4", "udp6", "unixgram", "fdgram"}, network) // even though SO_REUSEPORT lets us bind the socket multiple times,
if datagram { // we still put it in the listenerPool so we can count how many
if fd { // configs are using this socket; necessary to ensure we can know
ln, err = net.FilePacketConn(socketFile) // whether to enforce shutdown delays, for example (see #5393).
} else { var ln io.Closer
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
if specificLn, ok := ln.(net.Listener); ok { switch specificLn := ln.(type) {
ln = deleteListener{specificLn, lnKey} case net.Listener:
} 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
@@ -222,18 +170,12 @@ 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()
if err == nil { _ = syscall.Unlink(addr)
_ = syscall.Unlink(name)
}
}() }()
} }
return uln.UnixListener.Close() return uln.UnixListener.Close()
@@ -241,6 +183,7 @@ 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
} }
@@ -248,18 +191,11 @@ 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()
if err == nil { _ = syscall.Unlink(uc.filename)
_ = syscall.Unlink(name)
}
}() }()
} }
return uc.UnixConn.Close() return uc.UnixConn.Close()
@@ -275,12 +211,6 @@ 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.
+50 -76
View File
@@ -31,7 +31,6 @@ 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"
@@ -58,9 +57,11 @@ 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
@@ -106,8 +107,7 @@ 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.)
// //
// 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,
// 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 //
// while an existing socket is unlinked. // TODO: Experimental API: subject to change or removal.
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.Host, na.port(), portOffset, config); ln != nil || err != nil { if ln, err := getListenerFromPlugin(ctx, na.Network, na.JoinHostPort(portOffset), config); ln != nil || err != nil {
return ln, err return ln, err
} }
@@ -153,49 +153,50 @@ 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
} }
} else if na.IsFdNetwork() { isAbstractUnixSocket = strings.HasPrefix(address, "@")
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 {
err = os.Chmod(address, unixFileMode) if err := os.Chmod(address, unixFileMode); err != nil {
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)
} }
} }
@@ -210,22 +211,18 @@ func (na NetworkAddress) IsUnixNetwork() bool {
return IsUnixNetwork(na.Network) return IsUnixNetwork(na.Network)
} }
// IsFdNetwork 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() || na.IsFdNetwork() { if na.IsUnixNetwork() {
return na.Host return na.Host
} }
return net.JoinHostPort(na.Host, strconv.FormatUint(uint64(na.StartPort+offset), 10)) return net.JoinHostPort(na.Host, strconv.Itoa(int(na.StartPort+offset)))
} }
// 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)
@@ -256,7 +253,7 @@ func (na NetworkAddress) PortRangeSize() uint {
} }
func (na NetworkAddress) isLoopback() bool { func (na NetworkAddress) isLoopback() bool {
if na.IsUnixNetwork() || na.IsFdNetwork() { if na.IsUnixNetwork() {
return true return true
} }
if na.Host == "localhost" { if na.Host == "localhost" {
@@ -300,11 +297,6 @@ 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
@@ -335,12 +327,6 @@ 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)
@@ -380,28 +366,25 @@ 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) host, port, err = net.SplitHostPort(a)
firstErr := err if err == nil || a == "" {
return
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 removing square brackets around an IPv6 host, adding a bogus // so try adding a bogus port to take advantage of standard library's
// port to take advantage of standard library's robust parser, then // robust parser, then strip the artificial port before returning
// strip the artificial port. // (don't overwrite original error though; might still be relevant)
host, _, err = net.SplitHostPort(net.JoinHostPort(strings.Trim(a, "[]"), "0")) var err2 error
host, port, err2 = net.SplitHostPort(a + ":0")
if err2 == nil {
err = nil
port = "" port = ""
} }
if err != nil {
err = errors.Join(firstErr, err)
}
return return
} }
@@ -415,7 +398,7 @@ func JoinNetworkAddress(network, host, port string) string {
if network != "" { if network != "" {
a = network + "/" a = network + "/"
} }
if (host != "" && port == "") || IsUnixNetwork(network) || IsFdNetwork(network) { if (host != "" && port == "") || IsUnixNetwork(network) {
a += host a += host
} else if port != "" { } else if port != "" {
a += net.JoinHostPort(host, port) a += net.JoinHostPort(host, port)
@@ -423,11 +406,9 @@ func JoinNetworkAddress(network, host, port string) string {
return a return a
} }
// ListenQUIC returns a http3.QUICEarlyListener suitable for use in a Caddy module. // ListenQUIC returns a quic.EarlyListener suitable for use in a Caddy module.
// // The network will be transformed into a QUIC-compatible type (if unix, then
// The network will be transformed into a QUIC-compatible type if the same address can be used with // unixgram will be used; otherwise, udp will be used).
// 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) {
@@ -462,13 +443,7 @@ 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( earlyLn, err := tr.ListenEarly(http3.ConfigureTLSConfig(quicTlsConfig), &quic.Config{Allow0RTT: true})
http3.ConfigureTLSConfig(quicTlsConfig),
&quic.Config{
Allow0RTT: true,
Tracer: qlog.DefaultConnectionTracer,
},
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -641,8 +616,7 @@ 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(network, "ip:") || strings.HasPrefix(network, "ip4:") || strings.HasPrefix(network, "ip6:") || 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")
} }
@@ -658,11 +632,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, host, port string, portOffset uint, config net.ListenConfig) (any, error) { func getListenerFromPlugin(ctx context.Context, network, addr string, 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, host, port, portOffset, config) return getListener(ctx, network, addr, config)
} }
return nil, nil return nil, nil
@@ -676,7 +650,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, host, portRange string, portOffset uint, cfg net.ListenConfig) (any, error) type ListenerFunc func(ctx context.Context, network, addr string, cfg net.ListenConfig) (any, error)
var networkTypes = map[string]ListenerFunc{} var networkTypes = map[string]ListenerFunc{}
+5 -7
View File
@@ -31,7 +31,7 @@ func TestSplitNetworkAddress(t *testing.T) {
}{ }{
{ {
input: "", input: "",
expectHost: "", expectErr: true,
}, },
{ {
input: "foo", input: "foo",
@@ -42,7 +42,7 @@ func TestSplitNetworkAddress(t *testing.T) {
}, },
{ {
input: "::", input: "::",
expectHost: "::", expectErr: true,
}, },
{ {
input: "[::]", input: "[::]",
@@ -77,7 +77,7 @@ func TestSplitNetworkAddress(t *testing.T) {
{ {
input: "udp/", input: "udp/",
expectNetwork: "udp", expectNetwork: "udp",
expectHost: "", expectErr: true,
}, },
{ {
input: "unix//foo/bar", input: "unix//foo/bar",
@@ -185,8 +185,7 @@ func TestParseNetworkAddress(t *testing.T) {
}{ }{
{ {
input: "", input: "",
expectAddr: NetworkAddress{ expectErr: true,
},
}, },
{ {
input: ":", input: ":",
@@ -312,8 +311,7 @@ func TestParseNetworkAddressWithDefaults(t *testing.T) {
}{ }{
{ {
input: "", input: "",
expectAddr: NetworkAddress{ expectErr: true,
},
}, },
{ {
input: ":", input: ":",

Some files were not shown because too many files have changed in this diff Show More